Introduction aux structures
En C, la structure (struct) est un type défini par l’utilisateur qui permet de regrouper plusieurs variables (de types possiblement différents) sous un même nom.
C’est très utile pour représenter des objets ou des entités plus complexes.
Déclaration d’une structure
Pour déclarer une structure en langage C, on utilise le mot clé struct suivit d’une liste de champs fieldK avec où représente le nombre de champs associés à la structure. Les différents champs peuvent être de types différents.
De manière générale, on aura :
struct nomStructure{
typeF1 field1;
typeF2 field2;
...
typeFK fieldK;
};Ici on créé donc une structure nommée nomStructure de K champs.
Exemple :
On va créer une structure Etudiant permettant de stocker le nom, le prénom, l’âge et le domaine d’étude de l’étudiant.
struct Etudiant{
char nom[100];
char prenom[100];
int age;
char domaine[100];
};Il est possible de déclarer des variables de type struct Etudiant en même temps que la création.
Il suffit en fait de les spécifier juste après l’accolade fermante de la structure, comme ceci :
struct Etudiant{
char nom[100];
char prenom[100];
int age;
char domaine[100];
}etu1, etu2, etu3;Ainsi les variables etu1, etu2 et etu3 sont de type struct Etudiantet sont directement utilisable.
Initialisation d’une structure
Lors de la création d’une structure, on peut aussi initialiser ces dernières, c’est à dire donner une valeur à chacun des champs (ou pas) un à un.
On passera alors par l’instruction de la forme :
nomVarStruct = { valeurField1, valeurField2, ...};Sur notre structure de base Etudiant on peut initialiser les trois en même temps :
struct Etudiant{
char nom[100];
char prenom[100];
int age;
char domaine[100];
}etu1 = { "Leroux", "Antoine", 18, "Mathématiques" },
etu2 = { "Morin", "Mylène", 24, "Économie/Gestion" },
etu3 = { "Armand", "Quentin", 21, "Informatique Quantique" };Ainsi, dans notre code chacun des champs associés à chaque structure est associé à une valeur, on peut le voir comme une sorte de dictionnaire :
etu1 :
Nom = Leroux
Prenom = Antoine
Age = 18
Domaine = Mathématiques
etu2 :
Nom : Morin
Prénom : Mylène
Age : 24
Domaine : Économie/Gestion
etu3 :
Nom : Armand
Prénom : Quentin
Age : 21
Domaine : Informatique Quantique
Remarque
On est pas obligé d’initialiser chaque champ associé à une structure.
Regardons ce qu’il se passe dans ces cas là :
Exemple :
Dans notre exemple, on reprend un nouvel étudiant, on ne lui donne pas de nom ni d’age.
struct Etudiant {
char nom[100];
char prenom[100];
int age;
char domaine[100];
} inconnu = {.prenom = "Brice", .domaine = "Industrie"};Les autres variables age et nom sont quant à elle mises à .
On peut aussi déclarer et initialiser un instance de la structure en dehors de sa création, dans la fonction d’entrée main par exemple dans ce cas on utilise la syntaxe suivante :
struct nomStructure nomVariable = { ... };Si on reprend le dernier étudiant avec toutes les informations etu3 cela nous donnerais :
struct Etudiant etu3 = { "Adam", "Quentin", 21, "Informatique Quantique" };Dans le cas où vous n’initialisez pas toutes les champs associés à la structure, on reprend la syntaxe .champ = ... vue juste au dessus :
struct Etudiant inconnu = { .prenom = "Brice", .domaine = "Industrie" };Accès aux valeurs des champs
Accès direct
Pour accéder de manière directe aux valeurs des champs associés à une structure en C, on utilise l’opérateur .champ utilisé précédemment, ce dernier permettait de donner une valeur à certain champs lors de l’initialisation de ceux-ci. On s’en ressert ici pour accéder à leur valeur :
varStructure.champ
Exemple :
Affichage correct des informations concernant l’étudiant etu1.
#include <stdio.h>
struct Etudiant {
char nom[100];
char prenom[100];
int age;
char domaine[100];
};
int main(){
struct Etudiant etu1 = { "Leroux", "Antoine", 18, "Mathématiques" };
// Affichage des informations concernant Antoine
printf("Informations : %s %s\n", etu1.nom, etu1.prenom);
printf("|- Age : %d\n|- Domaine : %s\n", etu1.age, etu1.domaine);
return 0;
}Informations : Leroux Antoine
|- Age : 18
|- Domaine : Mathématiques
Accès via pointeur
Pour pouvoir accéder à une structure grâce à un pointeur, commençons déjà par relier la structure à notre pointeur. Vous allez voir, cela ne change rien à la manière de relier un pointeur et un entier.
int main(){
struct Etudiant etu1 = { "Leroux", "Antoine", 18, "Mathématiques" };
struct Etudiant* ptr = &etu1;
return 0;
}Rien de bien nouveau, on passe l’adresse mémoire au pointeur de type struct Etudiant.
Là ou ça changer c’est pour accéder à la valeur d’un champ via pointeur, à la place d’utiliser . on utilise -> le reste est exactement pareil :
int main(){
struct Etudiant etu1 = { "Leroux", "Antoine", 18, "Mathématiques" };
struct Etudiant* ptr = &etu1;
// Affichage des informations concernant Antoine
printf("Informations : %s %s\n", ptr->nom, ptr->prenom);
printf("|- Age : %d\n|- Domaine : %s\n", ptr->age, ptr->domaine);
return 0;
}Informations : Leroux Antoine
|- Age : 18
|- Domaine : Mathématiques
Modification d’un champ
Il est possible de modifier la valeur attribuée aux différents champs d’une instance de la structure. Dans ce cas là, on distingue aussi deux méthodes directe et indirecte via pointeur.
- Directe : On utilise l’opérateur
.tel queinstance.filed = ...; - Indirecte : On utilise l’opérateur
->tel queptr->field = ...;
Exemple :
Ici on modifie l’instance etu2 de la structure des deux manières.
int main(){
struct Etudiant etu2 = { "Morin", "Mylène", 24, "Économie/Gestion" };
// Modification directe
etu2.nom = "Dorey";
etu2.prenom = "Ewen";
// Modification via pointeur
struct Etudiant* ptr = &etu2;
ptr->domaine = "Météorologie";
ptr->age = 22;
// Affichage des informations
printf("Informations : %s %s\n", etu2.nom, ptr->prenom);
printf("|- Age : %d\n|- Domaine : %s\n", ptr->age, etu2.domaine);
return 0;
}ERROR!
/tmp/2oSE7IvEf7/main.c: In function 'main':
/tmp/2oSE7IvEf7/main.c:14:18: error: assignment to expression with array type
14 | etu2.nom = "Dorey";
| ^
ERROR!
/tmp/2oSE7IvEf7/main.c:15:21: error: assignment to expression with array type
15 | etu2.prenom = "Ewen";
| ^
/tmp/2oSE7IvEf7/main.c:19:22: error: assignment to expression with array type
19 | ptr->domaine = "Météorologie";
| ^Et oui, vous ne vous attendiez pas à celle ci !
En fait c’est plutôt logique, on avait vu pendant le cours 07 - Chaînes de caractères que lorsqu’un tableau de caractères est initialisé, il ne peut pas recevoir une nouvelle affectation. Ce qui implique les trois erreurs ici.
En fait, on utilise strcopy.
#include <string.h>
int main(){
struct Etudiant etu2 = { "Morin", "Mylène", 24, "Économie/Gestion" };
// Modification directe
strcpy(etu2.nom, "Dorey");
strcpy(etu2.prenom, "Ewen");
// Modification via pointeur
struct Etudiant* ptr = &etu2;
strcpy(ptr->domaine, "Météorologie");
ptr->age = 22;
// Affichage des informations
printf("Informations : %s %s\n", etu2.nom, ptr->prenom);
printf("|- Age : %d\n|- Domaine : %s\n", ptr->age, etu2.domaine);
return 0;
}Informations : Dorey Ewen
|- Age : 22
|- Domaine : Météorologie
Structures imbriquées
En considérant toujours notre structure Etudiant, on souhaite stocker maintenant l’adresse de ces derniers on va alors devoir ajouter un champ. Sauf que pour l’adresse on décide de le stocker sous forme de structure Adresse, celle ci contient le numéro de rue numero, le nom de la rue rue, le code postal code et le nom de la ville ville.
On donne alors la structure Adresse comme suit :
struct Adresse{
int numéro;
char rue[300];
int code;
char ville[100];
};Maintenant il faut ajouter le champ adresse à notre étudiant :
struct Etudiant {
char nom[100];
char prenom[100];
int age;
char domaine[100];
struct Adresse adresse;
};En fait, pour donner l’adresse à l’étudiant, il faudra donner un emplacement mémoire vers l’instance d’adresse. Rien de trop chiant, il suffit de définir et d’initialiser une instance de la structure et de la donner à étudiant :
struct Etudiant etu2 = { "Morin", "Mylène", 24, "Économie/Gestion" };
struct Adresse adr = { 25, "Rue Philippe Lebon", 76600, "Le Havre" };
// Affecter l'adresse à l'étudiant
etu2.adresse = adr;Pour accéder aux champs de l’adresse de l’étudiant, on procède avec les opérateurs . ou -> selon la méthode d’accès sauf qu’ici on le fait sur deux niveaux : une fois pour accéder au champ adresse une seconde fois pour accéder à un des champs associés à adresse.
Dans notre cas par exemple, pour accéder au nom de la rue, on utilisera la syntaxe ;
char nomRue[300] = etu2.adresse.rue;Modifions l’affichage de l’étudiant pour afficher maintenant son adresse proprement et simplement.
#include <stdio.h>
#include <string.h>
struct Adresse{
int numero;
char rue[300];
int code;
char ville[100];
};
struct Etudiant {
char nom[100];
char prenom[100];
int age;
char domaine[100];
struct Adresse adresse;
};
int main(){
struct Etudiant etu2 = { "Morin", "Mylène", 24, "Économie/Gestion" };
struct Adresse adr = { 25, "Rue Philippe Lebon", 76600, "Le Havre" };
etu2.adresse = adr;
// Affichage des informations
printf("Informations : %s %s\n", etu2.nom, etu2.prenom);
printf("|- Age : %d\n|- Domaine : %s\n", etu2.age, etu2.domaine);
printf("|- Adresse : %d %s, %d - %s", etu2.adresse.numero,
etu2.adresse.rue,
etu2.adresse.code,
etu2.adresse.ville);
return 0;
}Informations : Morin Mylène
|- Age : 24
|- Domaine : Économie/Gestion
|- Adresse : 25 Rue Philippe Lebon, 76600 - Le Havre
On peut pas utiliser un pointeur vers la structure adresse de l’étudiant pour que ça aille plus vite ?
Évidemment, regardez :
int main(){
struct Etudiant etu2 = { "Morin", "Mylène", 24, "Économie/Gestion" };
struct Adresse adr = { 25, "Rue Philippe Lebon", 76600, "Le Havre" };
etu2.adresse = adr;
// Pointeur vers l'adresse de l'étudiant
struct Adresse *ptr = &etu2.adresse;
// Modification d'une info de l'adresse :
ptr->code = 78540;
// Affichage des informations
printf("Informations : %s %s\n", etu2.nom, etu2.prenom);
printf("|- Age : %d\n|- Domaine : %s\n", etu2.age, etu2.domaine);
printf("|- Adresse : %d %s, %d - %s", ptr->numero, ptr->rue, ptr->code, ptr->ville);
return 0;
}Informations : Morin Mylène
|- Age : 24
|- Domaine : Économie/Gestion
|- Adresse : 25 Rue Philippe Lebon, 78540 - Le Havre
En résumé ici il y a deux manières d’accéder aux informations :
int codeDirect = etu2.adresse.code;
int codePointeur = ptr->code;De plus si vous modifiez la valeur d’un champ, cela affectera les deux méthodes car elles utilisent en faire exactement la même instance de la structure.
Tableaux de structures
Les tableaux jouent eux aussi un rôle important. Cette fois au lieu de stocker un caractère, ou un simple entier, chaque emplacement du tableau permettra de stocker une structure entière.
La définition et la déclaration fonctionne de la même manière que si on manipulait des entiers.
On reprend la structure struct Etudiant ainsi que la structure Adresse .
struct Adresse{
int numero;
char rue[300];
int code;
char ville[100];
};
struct Etudiant {
char nom[100];
char prenom[100];
int age;
char domaine[100];
// struct Adresse adresse; >>> utilisation après
};- Création d’un tableau initialement vide de
nétudiants :
struct Etudiant groupe[n];- Initialisation directe du tableau On donne manuellement chaque éléments du tableau.
struct Etudiant groupe[2] = {
{ "Leroux", "Antoine", 18, "Mathématiques" },
{ "Morin", "Mylène", 24, "Économie/Gestion" }
};
// Ou alors on déclare et initialise les étudiants et après création du tableau
struct Etudiant etu1 = { "Leroux", "Antoine", 18, "Mathématiques" };
struct Etudiant etu2 = { "Morin", "Mylène", 24, "Économie/Gestion" };
struct Etudiant groupe[2] = {etu1, etu2 };- Initialisation post déclaration du tableau
On peut créer un tableau sans l’initialiser avec la syntaxe suivante :
struct Etudiant groupe[2];Dans ce cas, les éléments du tableau sont des Valeurs Indéterminées. Les éléments du tableau ne sont pas des struct Etudiant !
Si vous souhaitez créer un tableau de struct Etudiant directement utilisable mais “sans créer les étudiants” on peut le faire en initialisant le tableau à { 0 }, ainsi chaque élément de ce dernier sera de type struct Etudiant sauf que chaque champs de chaque élément sera mis à 0 si c’est un entier "" pour les chaînes de caractères.
struct Etudiant groupe[5] = { 0 };En faisant ceci en fait chaque étudiant possèdera ces propres informations, toutes seront de la forme suivante :
Etudiant i
|- Nom : ""
|- Prénom : ""
|- Age : 0
|- Domaine : ""
L’affichage ci-dessus est choisit de manière arbitraire, il permet simplement de montrer qu’en utilisant la syntaxe du dessus, chaque éléments du tableau sera de type struct Etudiant et que chaque champs de chaque élément sera initialisé à 0.
Remarque
Si vous déclarez le tableau en global ou avec
staticalors ils seront automatiquement initialisés à0même si on a pas mis{ 0 }.
- Accès aux champs des éléments du tableau
L’accès aux éléments du tableau se fait de la même manière que sur les tableaux de types simples. On y accède via un indice noté généralement
ipuis on utilise la syntaxe.fieldpour accéder au champfield.
tab[i].field
- Parcours et affichage des données de chaque étudiant
On utilise une boucle
for(ouwhile) en accédant à chaque éléments un à un puis on affiche les informations de chaque étudiant du groupe.
Exemple :
#include <stdio.h>
struct Etudiant {
char nom[100];
char prenom[100];
int age;
char domaine[100];
};
int main() {
// Création de chaque étudiant
struct Etudiant etu1 = { "Leroux", "Antoine", 18, "Mathématiques" };
struct Etudiant etu2 = { "Morin", "Mylène", 24, "Économie/Gestion" };
struct Etudiant etu3 = { "Adam", "Quentin", 21, "Informatique Quantique" };
struct Etudiant etu4 = { "Ormont", "Hortence", 17, "Nucléaire" };
// Création et initialisation du tableau
struct Etudiant groupe[4] = { etu1, etu2, etu3, etu4 };
// Parcours et affichage
for(int i = 0; i<4; i++){
struct Etudiant etudiant = groupe[i];
printf("Informations : %s %s\n", etudiant.nom, etudiant.prenom);
printf("|- Age : %d\n|- Domaine : %s\n\n", etudiant.age, etudiant.domaine);
}
/*
Via pointeur :
for(int i = 0; i<4; i++){
struct Etudiant *ptr = &groupe[i];
printf("Informations : %s %s\n", ptr->nom, ptr->prenom);
printf("|- Age : %d\n|- Domaine : %s\n\n", ptr->age, ptr->domaine);
}
*/
return 0;
}Informations : Leroux Antoine
|- Age : 18
|- Domaine : Mathématiques
Informations : Morin Mylène
|- Age : 24
|- Domaine : Économie/Gestion
Informations : Adam Quentin
|- Age : 21
|- Domaine : Informatique Quantique
Informations : Ormont Hortence
|- Age : 17
|- Domaine : Nucléaire
Astuce
Pensez à faire l’affichage des informations de l’étudiant dans une fonction, c’est bien plus lisible par la suite…
void afficherInfoEtu(struct Etudiant *etu){
printf("Informations : %s %s\n", etu->nom, etu->prenom);
printf("|- Age : %d\n|- Domaine : %s\n\n", etu->age, etu->domaine);
}
// Appel dans la boucle
afficherInfoEtu(ptr);Typedef et alias
En C, le mot-clé typedef sert à créer un alias de type : c’est-à-dire donner un autre nom (souvent plus simple) à un type déjà existant.
C’est très utilisé avec les struct.
Alias de type simple
Pour créer un alias de type en utilisant typedef on utilise la syntaxe générale suivante :
typedef ancien_nom nouveau_nom;Exemple :
En python une chaîne de caractères est de type string, à la place de char[] ou char* en C alors vous décidez de renommez l’un en string.
typedef char* string;
string chaine = "Informatique";
printf("%s", chaine);Informatique
Alias de structures
Vous n’avez jamais pensé au fait qu’en utilisant les structures depuis le début de ce cours on est obligé d’écrire struct nomStruct c’est un peu long à écrire. En plus en informatique, on est partisan du moindre effort. Ainsi en utilisant typedef, on passe de ce code :
struct Etudiant {
char nom[100];
char prenom[100];
int age;
char domaine[100];
};
struct Etudiant etu1;à ce code là :
typedef struct{
char nom[100];
char prenom[100];
int age;
char domaine[100];
} Etudiant;
Etudiant etu1;L’utilisation d’un alias permet de zapper le mot clé struct comme ça on a un mot en moins à écrire plutôt sympas non ?
Structures et fonctions
Passage par valeur
On donne juste le nom de la structure en paramètre pour l’utiliser correctement :
void afficherAgeEtu(struct Etudiant etu){
printf("Age : %d\n", etu.age);
}
struct Etudiant etu = { "Leroux", "Antoine", 18, "Mathématiques" };
// Appel : afficherAgeEtu(etu);Remarque
Pour rappel, passer par valeur, passera en fait une copie de la structure. Donc si la fonction modifie la structure pendant l’exécution. Seule la copie sera modifiée à l’intérieure de la fonction, hors de celle ci la structure originale sera intacte, il faut utiliser les pointeurs.
Passage par adresse
On passe l’adresse &etu, donc la fonction peut modifier directement l’original.
void modifierAgeEtu(struct Etudiant* etu, int nvAge){
etu->age=nvAge;
}
struct Etudiant etu = { "Leroux", "Antoine", 18, "Mathématiques" };
// Appel : modifierAgeEtu(&etu);Ici, puisqu’on passe l’adresse de la structure la modification de l’âge se fera sur l’original puisqu’on modifie la champs de la structure étant à dresse mémoire donnée en paramètre.
Retourner une structure
Une fonction en C peut retourner une copie d’une structure, cela est plus sûr car la structure que la fonction renvoie est recopiée (on ne lui donne pas un pointeur).
struct Etudiant creerEtudiant(...);Je ne remplis pas les paramètres vous comprendrez plus tard…
Suivant
- Cours suivant : 11 - Les fichiers