Introduction à la programmation C# · Module 7, leçon 4 sur 4
Regrouper des données : struct
Créer son propre type pour regrouper les données d'une même chose : déclarer une structure, créer des fiches, en faire des tableaux, et les passer à des méthodes.
À la fin de la leçon, vous remplacez des tableaux parallèles par un tableau de structures, que vous triez, parcourez et passez à des méthodes.
- L'essentiel
- Exercices
- Solutions
- Je fais le point
Ce que vous allez faire
- Déclarer une structure et ses champs.
- Créer une fiche et lire ou modifier ses champs.
- Remplacer des tableaux parallèles par un tableau de structures.
- Passer une structure à une méthode et en renvoyer une.
Vous aurez réussi si…
- chaque champ utilisé hors de la structure est déclaré public ;
- votre structure est déclarée à la toute fin du fichier ;
- vous savez qu'une structure, comme un int, est copiée à l'affectation et au passage en paramètre.
L'essentiel
Depuis la leçon 6.1, un sentier de randonnée se décrit avec des tableaux parallèles :
string[] noms = { "Lac des Castors", "Mont Pointu", "Ruisseau Vert" }; double[] longueurs = { 3.2, 7.8, 5.5 }; int[] deniveles = { 60, 540, 280 };
Ça fonctionne, mais rien dans le code ne dit que noms[1], longueurs[1] et deniveles[1] décrivent le même sentier. À chaque tri, il faut échanger dans les trois tableaux (leçon 6.5) ; un oubli, et les données se mélangent. Une structure crée un nouveau type qui regroupe ces données en une seule fiche.
Déclarer une structure
Sentier s = new Sentier { Nom = "Mont Pointu", LongueurKm = 7.8, Denivele = 540 }; Console.WriteLine($"{s.Nom} : {s.LongueurKm} km, {s.Denivele} m de dénivelé"); s.Denivele = 545; // on modifie un seul champ Console.WriteLine(s.Denivele); // ---------- Types ---------- struct Sentier { public string Nom; public double LongueurKm; public int Denivele; }
| Élément | Rôle |
|---|---|
struct Sentier { … } | Déclare un nouveau type, nommé en PascalCase comme une méthode |
public double LongueurKm; | Un champ : une variable qui fait partie de chaque fiche. public le rend utilisable hors de la structure |
new Sentier { Nom = …, … } | Crée une fiche et donne une valeur à ses champs |
s.Denivele | Lit ou modifie un champ, avec un point, comme texte.Length |
Dans un Program.cs comme ceux du cours, les structures se déclarent à la toute fin du fichier, après les méthodes. Les champs non initialisés par new valent 0, false ou null, comme les cases d'un nouveau tableau.
Un tableau de structures
Un tableau peut contenir des structures. Le tri de la leçon 6.5 y gagne beaucoup : un seul échange déplace la fiche entière, avec tous ses champs.
Sentier[] sentiers =
{
new Sentier { Nom = "Lac des Castors", LongueurKm = 3.2, Denivele = 60 },
new Sentier { Nom = "Mont Pointu", LongueurKm = 7.8, Denivele = 540 },
new Sentier { Nom = "Ruisseau Vert", LongueurKm = 5.5, Denivele = 280 },
new Sentier { Nom = "Crête des Pins", LongueurKm = 11.4, Denivele = 720 },
new Sentier { Nom = "Boucle du Moulin", LongueurKm = 2.6, Denivele = 35 }
};
// Tri par longueur croissante : un seul échange déplace toute la fiche
for (int passe = 0; passe < sentiers.Length - 1; passe++)
{
for (int j = 0; j < sentiers.Length - 1 - passe; j++)
{
if (sentiers[j].LongueurKm > sentiers[j + 1].LongueurKm)
{
Sentier temporaire = sentiers[j];
sentiers[j] = sentiers[j + 1];
sentiers[j + 1] = temporaire;
}
}
}
double totalKm = 0;
foreach (Sentier s in sentiers)
{
AfficherSentier(s);
totalKm += s.LongueurKm;
}
Console.WriteLine($"Réseau : {totalKm} km");
// ---------- Méthodes ----------
static void AfficherSentier(Sentier s)
{
Console.WriteLine($"{s.Nom} : {s.LongueurKm} km, {s.Denivele} m ({ObtenirDifficulte(s)})");
}
static string ObtenirDifficulte(Sentier s)
{
if (s.Denivele < 200)
{
return "facile";
}
if (s.Denivele < 500)
{
return "intermédiaire";
}
return "difficile";
}
// ---------- Types ----------
struct Sentier
{
public string Nom;
public double LongueurKm;
public int Denivele;
}
Boucle du Moulin : 2,6 km, 35 m (facile) Lac des Castors : 3,2 km, 60 m (facile) Ruisseau Vert : 5,5 km, 280 m (intermédiaire) Mont Pointu : 7,8 km, 540 m (difficile) Crête des Pins : 11,4 km, 720 m (difficile) Réseau : 30,5 km
La comparaison lit un champ, sentiers[j].LongueurKm, mais l'échange porte sur la fiche entière, avec une variable temporaire de type Sentier. Les méthodes reçoivent un seul paramètre, Sentier s, au lieu de trois.
Une structure est copiée
Une structure se comporte comme un int : l'affectation et le passage en paramètre font une copie de toute la fiche (leçon 7.2).
Sentier a = new Sentier { Nom = "Ruisseau Vert", LongueurKm = 5.5, Denivele = 180 }; Sentier b = a; // b est une copie complète de a b.LongueurKm = 6.0; Console.WriteLine($"{a.LongueurKm} {b.LongueurKm}"); // 5,5 6 Allonger(a); Console.WriteLine(a.LongueurKm); // 5,5 : la méthode a reçu une copie a = AllongerEtRenvoyer(a, 1.5); Console.WriteLine(a.LongueurKm); // 7 static void Allonger(Sentier s) { s.LongueurKm += 1; } static Sentier AllongerEtRenvoyer(Sentier s, double km) { s.LongueurKm += km; return s; } struct Sentier { public string Nom; public double LongueurKm; public int Denivele; }
Pour qu'une méthode change une fiche, elle la renvoie modifiée (return), ou la reçoit avec ref. La même règle explique deux refus du compilateur : on ne peut pas modifier un champ d'une fiche lue dans une List, ni la variable d'un foreach, puisque ce sont des copies.
C# offre aussi les classes, qui ressemblent aux structures mais ne sont pas copiées. Elles sont au cœur de la programmation orientée objet, le sujet d'un prochain cours. Pour regrouper quelques données simples, la structure suffit.
- Oublier public : un champ sans
publicest inaccessible hors de la structure (CS0122, « … est inaccessible en raison de son niveau de protection »). - Déclarer la structure avant le programme principal : CS8803 (« Les instructions de niveau supérieur doivent précéder les déclarations d'espace de noms et de type »).
- Modifier un champ dans une liste :
panier[0].Prix = 2.99;donne CS1612. On sort la fiche dans une variable, on la modifie, puis on la remet dans la liste. Dans un tableau, en revanche,tableau[0].Prix = 2.99;fonctionne. - Croire qu'une méthode modifie la fiche reçue : sans
refnireturn, elle modifie une copie, sans aucun message.
Exercices
GuidéExercice 1 : Prévoir les résultats
Point a = new Point { X = 1, Y = 2 }; Point b = a; b.X = 10; Console.WriteLine($"{a.X} {b.X}"); Deplacer(a); Console.WriteLine(a.X); DeplacerRef(ref a); Console.WriteLine(a.X); Point[] points = new Point[2]; points[1].Y = 7; Console.WriteLine($"{points[0].Y} {points[1].Y}"); static void Deplacer(Point p) { p.X += 5; } static void DeplacerRef(ref Point p) { p.X += 5; } struct Point { public int X; public int Y; }
Prévoyez les quatre lignes affichées, puis vérifiez en exécutant.
Vous avez réussi si vos quatre prévisions sont justes, et si vous expliquez pourquoi la deuxième ligne affiche 1 et la troisième 6.
GuidéExercice 2 : Les colis
Règle : « L'envoi d'un colis coûte 5 , plus 2 par kilogramme. »
Déclarez une structure Colis (destination, poids en kg). Créez un tableau de trois colis : Sherbrooke 1,5 kg, Rimouski 4 kg et Gatineau 0,8 kg. Affichez les frais de chacun, puis le total.
Vous avez réussi si les frais sont 8,00 , 13,00 et 6,60 , pour un total de 27,60 , et si les tarifs sont des constantes.
Semi-guidéExercice 3 : Des méthodes pour un rectangle
Déclarez une structure Rectangle (largeur, hauteur) et trois méthodes : CreerRectangle(double largeur, double hauteur), qui renvoie un Rectangle ; CalculerAire(Rectangle r) ; EstCarre(Rectangle r), qui renvoie un bool.
Vous avez réussi si un terrain de 30 × 20 donne 600 m² et n'est pas carré, et une case de 4 × 4 donne 16 m² et est carrée.
AutonomeExercice 4 : Le panier
Ce programme doit mettre le cahier en solde à 2,99 , ajouter un crayon, puis afficher le panier. Il ne compile pas et, une fois compilé, il reste une erreur de logique.
List<Article> panier = new List<Article> { new Article { Nom = "Cahier", Prix = 3.50, Quantite = 4 }, new Article { Nom = "Crayon", Prix = 1.25, Quantite = 10 } }; // Le cahier est en solde panier[0].Prix = 2.99; // On ajoute un crayon AjouterUn(panier[1]); foreach (Article a in panier) { Console.WriteLine($"{a.Nom} : {a.Quantite} × {a.Prix:C} = {a.Quantite * a.Prix:C}"); } static void AjouterUn(Article a) { a.Quantite = a.Quantite + 1; } struct Article { public string Nom; public double Prix; int Quantite; }
Corrigez les deux erreurs de compilation, puis l'erreur de logique.
Vous avez réussi si le programme affiche « Cahier : 4 × 2,99 = 11,96 » et « Crayon : 11 × 1,25 = 13,75 ».
DéfiExercice 5 : Fini les tableaux parallèles
À la leçon 6.5, le classement de Léa, Samuel, Inès, Hugo et Maya (notes 72,5, 88, 64, 91 et 79) demandait deux échanges par inversion. Réécrivez-le avec une structure Etudiant et un tableau de structures, triez en ordre décroissant de note, puis ajoutez une méthode CalculerMoyenne(Etudiant[] etudiants).
Vous avez réussi si le classement est Hugo, Samuel, Maya, Léa, Inès, si la moyenne est 78,9, et si chaque inversion ne fait qu'un seul échange.
Solutions
Exercice 1 : Prévoir les résultats
1 10 ; 1 ; 6 ; 0 7.
b = a copie la fiche : changer b.X ne change pas a.X. Deplacer reçoit une copie, donc a.X reste 1. DeplacerRef reçoit a lui-même avec ref : a.X passe à 6. Dans un tableau, points[1].Y = 7 modifie directement la case ; la case 0 garde la valeur par défaut, 0.
Exercice 2 : Les colis
const double FraisBase = 5.00; const double FraisParKg = 2.00; Colis[] colis = { new Colis { Destination = "Sherbrooke", PoidsKg = 1.5 }, new Colis { Destination = "Rimouski", PoidsKg = 4.0 }, new Colis { Destination = "Gatineau", PoidsKg = 0.8 } }; double total = 0; for (int i = 0; i < colis.Length; i++) { double frais = FraisBase + FraisParKg * colis[i].PoidsKg; Console.WriteLine($"{colis[i].Destination} ({colis[i].PoidsKg} kg) : {frais:C}"); total += frais; } Console.WriteLine($"Total : {total:C}"); struct Colis { public string Destination; public double PoidsKg; }
Exercice 3 : Des méthodes pour un rectangle
Rectangle terrain = CreerRectangle(30, 20); Rectangle case1 = CreerRectangle(4, 4); Console.WriteLine($"Terrain : {CalculerAire(terrain)} m², carré : {EstCarre(terrain)}"); Console.WriteLine($"Case : {CalculerAire(case1)} m², carré : {EstCarre(case1)}"); static Rectangle CreerRectangle(double largeur, double hauteur) { return new Rectangle { Largeur = largeur, Hauteur = hauteur }; } static double CalculerAire(Rectangle r) { return r.Largeur * r.Hauteur; } static bool EstCarre(Rectangle r) { return r.Largeur == r.Hauteur; } struct Rectangle { public double Largeur; public double Hauteur; }
CreerRectangle renvoie une structure : une méthode peut renvoyer n'importe quel type, y compris le vôtre. Les deux autres méthodes reçoivent un seul paramètre au lieu de deux : la structure voyage comme un tout.
Exercice 4 : Le panier
- CS0122 : le champ
Quantiten'est paspublic. - CS1612 :
panier[0].Prix = 2.99;modifie une copie lue dans la liste. On sort la fiche, on la modifie, on la remet. - La logique :
AjouterUnmodifie une copie ; le crayon reste à 10. La méthode renvoie maintenant la fiche modifiée, que l'appel remet dans la liste.
List<Article> panier = new List<Article> { new Article { Nom = "Cahier", Prix = 3.50, Quantite = 4 }, new Article { Nom = "Crayon", Prix = 1.25, Quantite = 10 } }; // Le cahier est en solde : on sort la fiche, on la modifie, on la remet Article cahier = panier[0]; cahier.Prix = 2.99; panier[0] = cahier; // On ajoute un crayon : la méthode renvoie la fiche modifiée panier[1] = AjouterUn(panier[1]); foreach (Article a in panier) { Console.WriteLine($"{a.Nom} : {a.Quantite} × {a.Prix:C} = {a.Quantite * a.Prix:C}"); } static Article AjouterUn(Article a) { a.Quantite = a.Quantite + 1; return a; } struct Article { public string Nom; public double Prix; public int Quantite; }
Exercice 5 : Fini les tableaux parallèles
Etudiant[] groupe =
{
new Etudiant { Nom = "Léa", Note = 72.5 },
new Etudiant { Nom = "Samuel", Note = 88 },
new Etudiant { Nom = "Inès", Note = 64 },
new Etudiant { Nom = "Hugo", Note = 91 },
new Etudiant { Nom = "Maya", Note = 79 }
};
// Tri décroissant : un seul échange par inversion, au lieu de deux
for (int passe = 0; passe < groupe.Length - 1; passe++)
{
for (int j = 0; j < groupe.Length - 1 - passe; j++)
{
if (groupe[j].Note < groupe[j + 1].Note)
{
Etudiant temporaire = groupe[j];
groupe[j] = groupe[j + 1];
groupe[j + 1] = temporaire;
}
}
}
for (int i = 0; i < groupe.Length; i++)
{
Console.WriteLine($"{i + 1}. {groupe[i].Nom} : {groupe[i].Note}");
}
Console.WriteLine($"Moyenne : {CalculerMoyenne(groupe):F1}");
static double CalculerMoyenne(Etudiant[] etudiants)
{
double somme = 0;
foreach (Etudiant e in etudiants)
{
somme += e.Note;
}
return somme / etudiants.Length;
}
struct Etudiant
{
public string Nom;
public double Note;
}
La variable temporaire est de type Etudiant : l'échange déplace le nom et la note ensemble, et il devient impossible de les désynchroniser. CalculerMoyenne parcourt le tableau avec foreach, en lecture seule, et lit le champ Note de chaque fiche.
Je fais le point
- Je sais déclarer une structure avec des champs public, à la fin du fichier.
- Je sais créer une fiche avec new et lire ou modifier ses champs avec un point.
- Je sais remplacer des tableaux parallèles par un tableau de structures, et le trier.
- Je sais passer une structure à une méthode et en renvoyer une.
- Je sais qu'une structure est copiée, et comment modifier une fiche dans une liste.
