En C, lorsque l'on cherche à faire des programmes plus complexes, on peut créer nos propres types de variables :
Les structures.
Et les énumérations.
Et bonne nouvelle : c'est plutôt simple à comprendre et à manipuler !
Définissez une structure
Pour définir une structure, il suffit de :
commencer par taper
struct
;puis écrire le nom de la structure ;
ouvrir ensuite les accolades et les fermer plus loin, comme pour une fonction ;
placer entre les accolades les variables dont est composée votre structure. Une structure est généralement composée d'au moins deux "sous-variables ", sinon elle n'a pas trop d'intérêt. Toutes les structures que vous verrez sont en fait des "assemblages" de variables de type de base, comme
long
,int
,double
, etc.
Voici donc à quoi ressemble une structure :
struct NomDeVotreStructure
{
int variable1;
int variable2;
int autreVariable;
double nombreDecimal;
};
Imaginons par exemple que vous vouliez créer une variable qui stocke les coordonnées d'un point à l'écran.
Lorsqu'on travaille en 2D (2 dimensions), on a deux axes : l'axe des abscisses (de gauche à droite) et l'axe des ordonnées (de bas en haut). On a l'habitude d'exprimer les abscisses par une variable appelée x
, et les ordonnées par y
.
Voyons ce que ça donne :
struct Coordonnees
{
int x; // Abscisses
int y; // Ordonnées
};
Notre structure s'appelle Coordonnees
et est composée de deux variables x
et y
, c'est-à-dire de l'abscisse et de l'ordonnée.
Si on le voulait, on pourrait facilement faire une structure Coordonnees
pour de la 3D : il suffirait d'ajouter une troisième variable (par exemple z
) qui indiquerait la hauteur.
Placez des tableaux dans une structure
Les structures peuvent contenir des tableaux. Ça tombe bien, on va pouvoir ainsi placer des tableaux de char
(chaînes de caractères) sans problème !
Allez, imaginons une structure Personne
qui stocke diverses informations sur une personne :
struct Personne
{
char nom[100];
char prenom[100];
char adresse[1000];
int age;
int etudiant; // Booléen : 1 = etudiant, 0 = non etudiant
};
Cette structure est composée de cinq sous-variables. Les trois premières sont des chaînes qui stockeront le nom, le prénom et l'adresse de la personne. Les deux dernières stockent l'âge et le statut étudiant de la personne (c'est un booléen, 1 = vrai = étudiant, 0 = faux = pas étudiant).
Utilisez une structure dans une fonction
Maintenant que notre structure est définie dans le .h
, on va pouvoir l'utiliser dans une fonction de notre fichier .c
.
Voici comment créer une variable de type Coordonnees
(la structure qu'on a définie plus haut) :
#include "main.h" // Inclusion du .h qui contient les prototypes et structures
int main(int argc, char *argv[])
{
struct Coordonnees point; // Création d'une variable "point" de type Coordonnees
return 0;
}
Nous avons ainsi créé une variable point
de type Coordonnees
. Cette variable est automatiquement composée de deux sous-variables : x
et y
(son abscisse et son ordonnée).
Faut-il obligatoirement écrire le mot-clé struct
lors de la définition de la variable ?
Oui, cela permet à l'ordinateur de différencier un type de base (comme int
) d'un type personnalisé, comme Coordonnees
.
Toutefois, les programmeurs trouvent souvent un peu lourd de mettre le mot struct
à chaque définition de variable personnalisée. Pour régler ce problème, ils ont inventé une instruction spéciale : le typedef
.
Créez un alias de structure avec l'instruction typedef
Retournons dans le fichier .h
qui contient la définition de notre structure de typeCoordonnees
. Nous allons ajouter une instruction appelée typedef
pour créer un alias de structure et dire qu'écrire telle chose équivaut à écrire telle autre chose.
Ajoutons une ligne commençant par typedef
juste avant la définition de la structure :
typedef struct Coordonnees Coordonnees;
struct Coordonnees
{
int x;
int y;
};
Cette ligne doit être découpée en trois morceaux (non, je n'ai pas bégayé le mot Coordonnees
) :
typedef
: indique que nous allons créer un alias de structure ;struct Coordonnees
: c'est le nom de la structure dont vous allez créer un alias (c'est-à-dire un "équivalent") ;Coordonnees
: c'est le nom de l'équivalent.
En clair, cette ligne dit :
"Écrire le mot
Coordonnees
est désormais équivalent à écrirestruct Coordonnees
".
En faisant cela, vous n'aurez plus besoin de mettre le mot struct
à chaque définition de variable de type Coordonnees
. On peut donc retourner dans notre main
et écrire :
int main(int argc, char *argv[])
{
Coordonnees point; // L'ordinateur comprend qu'il s'agit de "struct Coordonnees" grâce au typedef
return 0;
}
Je vous recommande de faire un typedef
comme je l'ai fait ici pour Coordonnees
. Ça évite d'avoir à écrire le mot struct
partout. Un bon programmeur est un programmeur fainéant ! Il en écrit le moins possible.
Modifiez les composantes de la structure
Maintenant que notre variable point
est créée, nous voulons modifier ses coordonnées.
Comment accéder au x
et au y
de point
? Comme ceci :
int main(int argc, char *argv[])
{
Coordonnees point;
point.x = 10;
point.y = 20;
return 0;
}
On a ainsi modifié la valeur de point
, en lui donnant une abscisse de 10 et une ordonnée de 20. Notre point se situe désormais à la position (10 ; 20) ; c'est la notation mathématique d'une coordonnée.
Pour accéder à chaque composante de la structure, vous devez donc écrire :
variable.nomDeLaComposante
Si on prend la structure Personne
que nous avons vue tout à l'heure, et qu'on demande le nom et le prénom, on devra faire comme ça :
int main(int argc, char *argv[])
{
Personne utilisateur;
printf("Quel est votre nom ? ");
scanf("%s", utilisateur.nom);
printf("Votre prenom ? ");
scanf("%s", utilisateur.prenom);
printf("Vous vous appelez %s %s", utilisateur.prenom, utilisateur.nom);
return 0;
}
Quel est votre nom ? Dupont Votre prenom ? Jean Vous vous appelez Jean Dupont
On envoie la variable utilisateur.nom
à scanf
qui écrira directement dans notre variable utilisateur
. On fait de même pour prenom
, et on pourrait aussi le faire pour l'adresse, l'âge et le statut étudiant, mais je n'ai guère envie de me répéter (je dois être programmeur, c'est pour ça).
Vous auriez pu faire la même chose sans connaître les structures, en créant juste une variable nom
et une autre, prenom
. Mais l'intérêt ici est que vous pouvez créer une autre variable de type Personne
qui aura aussi son propre nom, son propre prénom, etc.
On peut donc faire :
Personne joueur1, joueur2;
… et stocker ainsi les informations sur chaque joueur. Chaque joueur a son propre nom, son propre prénom, etc.
On peut même faire encore mieux : on peut créer un tableau de Personne
:
Personne joueurs[2];
Et ensuite, vous accédez par exemple au nom du joueur n° 0 en tapant :
joueurs[0].nom
L'avantage d'utiliser un tableau ici, c'est que vous pouvez faire une boucle pour demander les infos du joueur 1 et du joueur 2, sans avoir à répéter deux fois le même code. Il suffit de parcourir le tableau joueur
et de demander à chaque fois nom, prénom, adresse…
Initialisez une structure
En effet, une variable qui est créée prend la valeur de ce qui se trouve en mémoire là où elle a été placée. Parfois cette valeur est 0, parfois c'est un résidu d'un autre programme qui est passé par là avant vous, et la variable a alors une valeur qui n'a aucun sens, comme -84570.
Pour rappel, voici comment on initialise :
une variable : on met sa valeur à 0 (cas le plus simple) ;
un pointeur : on met sa valeur à
NULL
.NULL
est en fait un#define
situé dansstdlib.h
qui vaut généralement 0, mais on continue à utiliserNULL
par convention sur les pointeurs pour bien voir qu'il s'agit de pointeurs et non de variables ordinaires ;un tableau : on met chacune de ses valeurs à 0.
En effet, on peut faire à la déclaration de la variable :
Coordonnees point = {0, 0};
Cela définira, dans l'ordre :
point.x = 0
.point.y = 0
.
Pour envoyer ma variable point
à une fonction initialiserCoordonnees
par exemple, qui se charge de faire les initialisations sur ma variable, je peux envoyer un pointeur de ma variable.
Reprenons l’exemple de la structure Personne
et voyons comment créer et utiliser la structure dans la vidéo suivante :
Utilisez un pointeur sur une structure
Un pointeur de structure se crée de la même manière qu'un pointeur de int
, de double
ou de n'importe quel autre type de base :
Coordonnees* point = NULL;
On a ainsi un pointeur de Coordonnees
appelé point
.
Comme un rappel ne fera de mal à personne, je tiens à vous répéter que l'on aurait aussi pu mettre l'étoile devant le nom du pointeur, cela revient exactement au même :
Coordonnees *point = NULL;
C'est même mieux car pour définir plusieurs pointeurs sur la même ligne, nous sommes obligés de placer l'étoile devant chaque nom de pointeur :
Coordonnees *point1 = NULL, *point2 = NULL;
Envoyez la structure à une fonction
Ce qui nous intéresse ici, c'est de savoir comment envoyer un pointeur de structure à une fonction pour que celle-ci puisse modifier le contenu de la variable.
On va faire ceci pour cet exemple : on va simplement créer une variable de type Coordonnees
dans le main
et envoyer son adresse à initialiserCoordonnees
. Cette fonction aura pour rôle de mettre tous les éléments de la structure à 0.
Notre fonction initialiserCoordonnees
va prendre un paramètre : un pointeur sur une structure de type Coordonnees
(un Coordonnees*
, donc).
int main(int argc, char *argv[])
{
Coordonnees monPoint;
initialiserCoordonnees(&monPoint);
return 0;
}
void initialiserCoordonnees(Coordonnees* point)
{
// Initialisation de chacun des membres de la structure ici
}
Ma variable monPoint
est donc créée dans le main
.
On envoie son adresse à la fonction initialiserCoordonnees
qui récupère cette variable sous la forme d'un pointeur appelé point
(on aurait d'ailleurs pu l'appeler n'importe comment dans la fonction, cela n'aurait pas eu d'incidence).
Maintenant que nous sommes dans initialiserCoordonnees
, nous allons initialiser chacune des valeurs une à une.
Oui mais voilà, problème… On ne peut pas vraiment faire :
void initialiserCoordonnees(Coordonnees* point)
{
*point.x = 0;
*point.y = 0;
}
Pourquoi on ne peut pas faire ça ?
Parce que le point de séparation s'applique sur le mot point
et non sur *point
en entier. Or, nous ce qu'on veut, c'est accéder à *point
pour en modifier la valeur.
Pour régler le problème, il faut placer des parenthèses autour de *point
; le point de séparation s'appliquera à *point
et non juste à point
:
void initialiserCoordonnees(Coordonnees* point)
{
(*point).x = 0;
(*point).y = 0;
}
La variable de type Coordonnees
a été transmise à la fonction qui a initialisé x
et y
à 0.
Utilisez ce raccourci
Vous allez voir qu'on manipulera très souvent des pointeurs de structures. Pour être franc, je dois même vous avouer qu'en C, on utilise plus souvent des pointeurs de structures que des structures tout court.
Comme les pointeurs de structures sont très utilisés, on sera souvent amené à écrire ceci :
(*point).x = 0;
Oui mais voilà, encore une fois les programmeurs trouvent ça trop long. Les parenthèses autour de *point
, quelle plaie ! Alors, ils ont inventé le raccourci suivant :
point->x = 0;
Utilisez ce petit exemple pour vous en souvenir :
int main(int argc, char *argv[])
{
Coordonnees monPoint;
Coordonnees *pointeur = &monPoint;
monPoint.x = 10; // On travaille sur une variable, on utilise le "point"
pointeur->x = 10; // On travaille sur un pointeur, on utilise la flèche
return 0;
}
On modifie la valeur du x à 10 de deux manières différentes, ici : la première fois en travaillant directement sur la variable, la seconde fois en passant par le pointeur.
Reprenons maintenant notre fonction initialiserCoordonnees
; nous pouvons alors l'écrire comme ceci :
void initialiserCoordonnees(Coordonnees* point)
{
point->x = 0;
point->y = 0;
}
Utilisez des énumérations
Les énumérations constituent une façon un peu différente de créer ses propres types de variables.
Voici un exemple d'énumération :
typedef enum Volume Volume;
enum Volume
{
FAIBLE, MOYEN, FORT
};
Vous noterez qu'on utilise un typedef
là aussi, comme on l'a fait jusqu'ici.
Notre énumération s'appelle ici Volume
. C'est un type de variable personnalisé qui peut prendre une des trois valeurs qu'on a indiquées : soit FAIBLE
, soit MOYEN
, soit FORT
.
On va pouvoir créer une variable de type Volume
, par exemple musique
, qui stockera le volume actuel de la musique.
On peut par exemple initialiser la musique au volume MOYEN
:
Volume musique = MOYEN;
Voilà qui est fait. Plus tard dans le programme, on pourra modifier la valeur du volume et la mettre soit à FAIBLE
, soit à FORT
.
Associez des nombres aux valeurs
Vous avez remarqué que j'ai écrit les valeurs possibles de l'énumération en majuscules. Cela devrait vous rappeler les constantes et les define
, non ?
En effet, c'est assez similaire, mais ce n'est pourtant pas exactement la même chose. Le compilateur associe automatiquement un nombre à chacune des valeurs possibles de l'énumération.
Dans le cas de notre énumération Volume
, FAIBLE
vaut 0, MOYEN
vaut 1 et FORT
vaut 2. L'association est automatique et commence à 0.
Contrairement au #define
, c'est le compilateur qui associe MOYEN
à 1 par exemple, et non le préprocesseur. Au bout du compte, ça revient un peu au même. En fait, quand on a initialisé la variable musique
à MOYEN
, on a donc mis la case en mémoire à la valeur 1.
En pratique, est-ce utile de savoir que MOYEN
vaut 1, FORT
vaut 2, etc. ?
Non. En général ça nous est égal. C'est le compilateur qui associe automatiquement un nombre à chaque valeur. Grâce à ça, vous n'avez plus qu'à écrire :
if (musique == MOYEN)
{
// Jouer la musique au volume moyen
}
Peu importe la valeur de MOYEN
, vous laissez le compilateur se charger de gérer les nombres.
Du coup, votre code est très lisible : tout le monde peut facilement lire le if
précédent (on comprend bien que la condition signifie "Si la musique est au volume moyen").
Associez une valeur précise
Pour le moment, c'est le compilateur qui décide d'associer le nombre 0 à la première valeur, puis 1, 2, 3 dans l'ordre. Il est possible de demander d'associer une valeur précise à chaque élément de l'énumération.
Quel intérêt est-ce que ça peut bien avoir ?
Supposons que sur votre ordinateur, le volume soit géré entre 0 et 100 (0 = pas de son, 100 = 100 % du son). Il est alors pratique d'associer une valeur précise à chaque élément :
typedef enum Volume Volume;
enum Volume
{
FAIBLE = 10, MOYEN = 50, FORT = 100
};
Ici, le volume FAIBLE
correspondra à 10 % de volume, le volume MOYEN
à 50 %, etc.
On pourrait facilement ajouter de nouvelles valeurs possibles comme MUET
. On associerait dans ce cas MUET
à la valeur… 0 ! Vous avez compris.
Dans le cas suivant, l’élément FORT est égal à 51 car la valeur précédente est égale à 50 :
typedef enum Volume Volume;
enum Volume
{
FAIBLE = 10, MOYEN = 50, FORT
};
En résumé
Une structure est un type de variable personnalisé que vous pouvez créer et utiliser dans vos programmes. C'est à vous de la définir, contrairement aux types de base tels que
int
etdouble
que l'on retrouve dans tous les programmes.Une structure est composée de "sous-variables" qui sont en général des variables de type de base comme
int
etdouble
, mais aussi des tableaux.On accède à un des composants de la structure en séparant le nom de la variable et la composante d'un point :
joueur.prenom
.Si on manipule un pointeur de structure et qu'on veut accéder à une des composantes, on utilise une flèche à la place du point :
pointeurJoueur->prenom
.Une énumération est un type de variable personnalisé qui peut seulement prendre une des valeurs prédéfinies :
FAIBLE
,MOYEN
ouFORT
, par exemple.
Découvrons maintenant comment lire et écrire dans des fichiers. C’est parti !