Créez vos propres types de variables !
Le langage C nous permet de faire quelque chose de très puissant : il nous permet de créer nos propres types de variables.
Des "types de variables personnalisés", nous allons en voir 2 sortes :
- Les structures : elles permettent de créer des variables composées de plusieurs sous-variables.
- Les énumérations : elles permettent de définir une liste de valeurs possibles pour une variable.
Créer de nouveaux types de variables devient indispensable quand l'on cherche à faire des programmes plus complexes. Par "plus complexe", je veux dire "autre chose que faire un Plus ou Moins"
Ce n'est (heureusement) pas bien compliqué à comprendre et à manipuler. Restez attentifs tout de même parce que nous réutiliserons les structures tout le temps à partir du prochain chapitre.
Il faut savoir que les bibliothèques définissent généralement leurs propres types. Vous ne tarderez donc pas à manipuler un type de variable "Fichier" ou encore, un peu plus tard, un type de variable "Fenetre", "Audio", "Clavier" etc
Une structure est
un assemblage de variables qui peuvent avoir différents types.
Contrairement aux tableaux qui vous obligent à utiliser le même type dans tout le tableau, vous pouvez créer une structure comportant des variables de type long, char, int, double à la fois
Les structures sont généralement
définies dans les fichiers .h, au même titre donc que les prototypes et les defines.
Voici un exemple de structure :
Code : C1
2
3
4
5
6
7 | struct NomDeVotreStructure
{
int variable1;
int variable2;
int autreVariable;
double nombreDecimal;
};
|
Une définition de structure commence par le mot-clé struct, suivi du nom de votre structure (par exemple "Fichier", ou encore "Ecran").
Après le nom de votre structure, vous ouvrez les accolades et les fermez plus loin, comme pour une fonction.
Attention, ici c'est particulier : vous DEVEZ mettre un point-virgule après l'accolade fermante. C'est obligatoire. Si vous ne le faites pas, la compilation plantera.
Et maintenant, que mettre entre les accolades ?
C'est simple, vous mettez les variables dont est composée votre structure. Une structure est généralement composée d'au moins 2 "sous-variables", sinon elle n'a pas trop d'intérêt
Comme vous le voyez, la création d'un type de variable personnalisé n'est pas bien complexe. Toutes les structures que vous verrez sont en fait des "assemblages" de variables de type de base, comme long, int, double etc... Il n'y a pas de miracle, un type "Fichier" n'est donc composé que de nombres de base
Exemple de structure
Des idées de structures, j'en ai des centaines en tête

Imaginons par exemple que vous vouliez créer une variable qui stocke les coordonnées d'un point à l'écran. Vous aurez très certainement besoin d'une structure comme cela lorsque vous ferez des jeux 2D dans la partie III, c'est donc l'occasion de s'avancer un peu là
Pour ceux chez qui le mot "géométrie" provoque des apparitions de boutons inexplicables sur tout le visage, voici un petit rappel fondamental sur la 2D :
Lorsqu'on travaille en 2D (2 dimensions), on a 2 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.
Etes-vous capables d'écrire une structure "Coordonnees" capable de stocker à la fois la valeur de l'abscisse (x) et celle de l'ordonnée (y) d'un point ?
Allons allons, ce n'est pas bien difficile
Code : C1
2
3
4
5 | struct Coordonnees
{
int x; // Abscisses
int y; // Ordonnées
};
|
Notre structure s'appelle Coordonnees et est composée de 2 variables : x et y, c'est-à-dire l'abscisse et 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. Et hop, on aurait une structure faite pour gérer des points en 3D dans l'espace
Tableaux dans une structure
Les structures peuvent contenir des tableaux. Ca 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 stockerait diverses informations sur une personne :
Code : C1
2
3
4
5
6
7
8
9 | struct Personne
{
char nom[100];
char prenom[100];
char adresse[1000];
int age;
int garcon; // Booléen : 1 = garçon, 0 = fille
};
|
Cette structure est composée de 5 sous-variables. Les 3 premières sont des chaînes, qui stockeront le nom, le prénom et l'adresse de la personne.
Les 2 dernières stockent l'âge et le sexe de la personne. Le sexe est un booléen, 1 = vrai = garçon, 0 = faux = fille.
Cette structure pourrait servir à créer un programme de carnet d'adresses. Bien entendu, vous pouvez rajouter des variables dans la structure pour la compléter si vous le voulez. Il n'y a pas de limite au nombre de variables dans une structure (enfin évitez d'en mettre une centaine ça va faire beaucoup quand même

).
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) :
Code : C | #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 appelée "point" de type Coordonnees
return 0;
}
|
Nous avons ainsi créé une variable "point" de type Coordonnees. Cette variable est automatiquement composée de 2 sous-variables : x et y (son abscisse et son ordonnées).
On est obligés de mettre le mot "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 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.
Le typedef
Retournons dans le fichier .h qui contient la définition de notre structure Coordonnees.
Nous allons ajouter une instruction appelée typedef qui sert à créer un alias de structure, c'est-à-dire à dire que telle chose est équivalente à écrire telle autre chose.
Nous allons ajouter une ligne commençant par typedef juste avant la définition de la structure :
Code : C | typedef struct Coordonnees Coordonnees;
struct Coordonnees
{
int x;
int y;
};
|
Cette ligne doit être découpée en 3 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 "Ecrire le mot
Coordonnees est désormais équivalent à écrire
struct 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 tout simplement :
Code : C | 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 le type Coordonnees. La plupart des programmeurs font comme cela. Ca leur évite d'avoir à écrire le mot "struct" partout, et vu que ce sont des feignasses ça les arrange

(je rigole hein les gars, tapez pas

)
Modifier 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 cela :
Code : C | 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 comme cela qu'on note une coordonnée en mathématiques

)
Pour accéder donc à chaque composante de la structure, vous devez écrire :
Code : C | variable.nomDeLaComposante
|
Le point fait la séparation entre la variable et la composante.
Si on prend la structure "Personne" de tout à l'heure et qu'on demande le nom et le prénom, on devra faire comme ça :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13 | 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;
}
|
Code : Console | 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 sexe, mais j'ai la flemme de le faire ici (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 etc

On peut donc faire :
Code : C | Personne joueur1, joueur2;
|
... et stocker ainsi les informations sur chaque joueur.
Mais le C, c'est plus fort encore ! On peut créer un tableau de Personne !
C'est facile à faire :
Code : C
Et ensuite, vous accédez par exemple au nom du joueur n°0 en tapant :
Code : C
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 2 fois le même code. Il suffit de parcourir le tableau joueur et de demander à chaque fois nom, prénom, adresse...
Exercice : créez ce tableau de type Personne et demandez les infos de chacun grâce à une boucle (qui se répète tant qu'il y a des joueurs). Faites un petit tableau de 2 joueurs pour commencer, mais si ça vous amuse vous pourrez agrandir la taille du tableau plus tard.
Affichez à la fin du programme les infos que vous avez recueillies sur chacun des joueurs
Ce code est facile à écrire, vous n'avez pas besoin de correction, vous êtes des grands maintenant
Initialiser une structure
Pour les structures comme pour les variables, tableaux et pointeurs, il est vivement conseillé de les initialiser dès leur création pour éviter qu'elles ne contiennent "n'importe quoi". En effet, je vous le rappelle, 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 vaut une valeur qui n'a aucun sens, comme -84570.
Pour rappel, voici comment on initialise :
- Une variable : on met sa valeur à 0 (ça c'est simple).
- Un pointeur : on met sa valeur à NULL. NULL est en fait un #define situé dans stdlib.h qui vaut généralement 0, mais on continue à utiliser NULL 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.
Pour les structures, ça va un peu ressembler à l'initialisation d'un tableau qu'on avait vue. En effet, on peut faire à la déclaration de la variable :
Code : C | Coordonnees point = {0, 0};
|
Cela mettra, dans l'ordre, point.x = 0 et point.y = 0.
Revenons à la structure Personne (qui contient des chaînes). Vous avez aussi le droit d'initialiser une chaîne en mettant juste
"" (rien entre les guillemets). Je ne vous ai pas parlé de cette possibilité dans le chapitre sur les chaînes je crois, mais il n'est jamais trop tard pour l'apprendre

On peut donc initialiser dans l'ordre nom, prenom, adresse, age et garcon comme ceci :
Code : C | Personne utilisateur = {"", "", "", 0, 0};
|
Toutefois, j'utilise assez peu cette technique personnellement. Je préfère envoyer par exemple ma variable point à une fonction initialiserCoordonnees qui se charge de faire les initialisations pour moi sur ma variable.
Toutefois, pour faire cela il faut envoyer un pointeur de ma variable. En effet si j'envoie juste ma variable, une copie en sera réalisée dans la fonction (comme pour une variable de base) et la fonction modifiera les valeurs de la copie et non celle de ma vraie variable. Revoyez le "fil rouge" du chapitre sur les pointeurs si vous avez un trou de mémoire à ce sujet
Il va donc falloir apprendre à utiliser des pointeurs sur des structures (hummm ça donne faim ça

).
Nous allons justement voir comment faire ça maintenant
Un pointeur de structure se crée de la même manière qu'un pointeur de int, de double ou de n'importe quelle autre type de base :
Code : C1 | 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 :
Code : C1 | Coordonnees *point = NULL;
|
Je fais d'ailleurs assez souvent comme cela, car si l'on veut définir plusieurs pointeurs sur la même ligne, on est obligés de mettre l'étoile devant chaque nom de pointeur :
Code : C1 | Coordonnees *point1 = NULL, *point2 = NULL;
|
Bon, mais ça pour le moment c'est du domaine des pointeurs, ça n'a pas de rapport direct avec les structures
Envoi de 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 juste créer une variable de type Coordonnees dans le main et envoyer son adresse à la fonction 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

).
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | 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 à 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, même "monPoint" ou "trucBidule"

).
Bien, et maintenant que nous sommes dans initialiserCoordonnees, nous allons initialiser chacune des valeurs une à une.
Il ne faut pas oublier de mettre une étoile devant le nom du pointeur pour accéder à la variable. Si vous ne le faites pas, vous risquez de modifier l'adresse, et ce n'est pas ce que nous voulons faire
Oui mais voilà, problème... On ne peut pas vraiment faire :
Code : C1
2
3
4
5 | void initialiserCoordonnees(Coordonnees* point)
{
*point.x = 0;
*point.y = 0;
}
|
C'aurait été trop facile bien entendu
Pourquoi on ne peut pas faire ça ? Parce que le point de séparation s'applique sur le mot "point" et non *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 mettre des parenthèses autour de *point. Comme cela, le point de séparation s'appliquera à *point et non juste à point :
Code : C1
2
3
4
5 | void initialiserCoordonnees(Coordonnees* point)
{
(*point).x = 0;
(*point).y = 0;
}
|
Voilà
Ce code fonctionne, vous pouvez tester. La variable de type Coordonnees a été transmise à la fonction qui a initialisé x et y à 0.
Un raccourci pratique et très utilisé
Vous allez voir qu'on manipulera très souvent des pointeurs de structures. Pour être franc, je dois même vous dire qu'en C on utilise plus souvent des pointeurs de structures que des structures tout court

Quand je vous disais que les pointeurs vous poursuivraient jusque dans votre tombe, je ne le disais pas en rigolant

Vous ne pouvez y échapper, c'est votre destinée.
Hum...
En fait je voulais vous parler d'un truc sérieux là

Comme les pointeurs de structures sont très utilisés, on sera souvent amenés à écrire ceci :
Code : C
Oui mais voilà, encore une fois les programmeurs trouvent ça trop long. Les parenthèses autour de *point, quelle plaie ! Alors, comme les programmeurs sont de grosses feignasses (comment ça je l'ai déjà dit ?!

), ils ont inventé le raccourci suivant :
Code : C
Ce raccourci consiste à former une flèche avec un tiret suivi d'un chevron ">".
Ecrire point->x est donc STRICTEMENT équivalent à écrire (*point).x
Ca éclaircira votre code vous verrez
N'oubliez pas qu'on ne peut utiliser la flèche que sur un pointeur !
Si vous travaillez directement sur la variable, vous devez utiliser le symbole "point" comme on l'a vu au début.
Reprenons notre fonction initialiserCoordonnees. Nous pouvons donc l'écrire comme ceci :
Code : C1
2
3
4
5 | void initialiserCoordonnees(Coordonnees* point)
{
point->x = 0;
point->y = 0;
}
|
Retenez bien ce raccourci de la flèche, nous allons le réutiliser et pas qu'une seule fois
Et surtout, surtout, ne confondez pas la flèche avec le symbole "point".
La flèche est réservée aux pointeurs, le "point" est réservé aux variables. Utilisez ce petit exemple pour vous en souvenir :
Code : C 1
2
3
4
5
6
7
8
9
10 | 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 met le 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.
Les énumérations sont une façon un peu différente de créer ses propres types de variables.
Une énumération ne contient pas de "sous-variables" comme c'était le cas pour les structures. C'est une liste de "valeurs possibles" pour une variable. Une énumération ne prend donc qu'une case en mémoire, et cette case peut prendre
une des valeurs que vous définissez (et une seule à la fois).
Voici un exemple d'énumération :
Code : C1
2
3
4
5 | 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.
Pour créer une énumération, on utilise le mot-clé enum. Notre énumération s'appelle ici "Volume". C'est un type de variable personnalisé qui peut prendre
une des 3 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 :
Code : C
Voilà qui est fait

Plus tard dans le programme, on pourra modifier la valeur du volume et la mettre soit à FAIBLE, soit à FORT.
Association de nombres aux valeurs
Vous avez remarqué que j'ai mis les valeurs possibles de l'énumération en majuscules. Ca devrait vous rappeler les constantes et les defines 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

Quand on a initialisé la variable
musique à MOYEN, on a donc en fait 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 vous vous en moquez

C'est le compilateur qui associe automatiquement un nombre à chaque valeur. Grâce à ça, vous n'avez plus qu'à faire :
Code : C1
2
3
4 | 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.
L'intérêt de tout ça ? C'est que du coup votre code est très lisible. En effet, 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").
Associer 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. Mais quel intérêt est-ce que ça peut bien avoir ?
Eh bien, 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 :
Code : C1
2
3
4
5 | 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 mettrait dans ce cas MUET à la valeur... 0 ! Bravo vous avez compris le truc
La possibilité d'utiliser des types de variables personnalisés est vraiment un atout majeur du langage C. Grâce à cela, les informations peuvent être regroupées entre elles, centralisées, traitées etc...
En résumé, nous avons vu :
- Les structures : elles permettent de créer des types de variables composés de plusieurs sous-variables.
- Les énumérations : elles permettent de créer des types de variables qui peuvent avoir une des valeurs définies dans l'énumération.
Nous ne tarderons pas à utiliser tout cela en pratique

D'ailleurs, nous commencerons même tout de suite car dans le prochain chapitre nous apprendrons à manipuler une structure de type Fichier
Quelques ajouts sur les structures
Avant de terminer ce chapitre, je tiens à faire quelques petits ajouts sur les structures pour vous montrer tout ce qu'on peut faire avec.
On peut vraiment tout mettre à l'intérieur d'une structure. Je vous ai dit qu'on pouvait mettre des types de base (int, long...) ainsi que des tableaux, mais sachez qu'il est aussi possible de mettre des pointeurs:
Code : C1
2
3
4
5
6 | struct MaStructure
{
int* monPointeur; // Pointeur sur int
int monBooleen;
char maChaine[10];
};
|
Plus fort encore, le C vous autorise à mettre une structure
dans une structure :
Code : C1
2
3
4
5
6 | struct MaStructure
{
Coordonnees element; // MaStructure contient une variable de type Coordonnees !
int monBooleen;
char maChaine[10];
};
|
Ca a l'air de compliquer un peu à priori, et pourtant c'est justement tout ce qui rend le langage
puissant.
Attention si vous faites ça : il faudra définir la structure Coordonnees avant MaStructure (plus haut dans le fichier), car sinon le compilateur ne la connaîtra pas au moment de lire MaStructure et il plantera en disant que le type "Coordonnees" n'existe pas.
Si vous avez une variable appelée "test" de type MaStructure, vous pouvez du coup accéder aux coordonnées de "element" comme ceci :
Code : C1
2 | test.element.x = 0;
test.element.y = 0;
|
Je vous laisse méditer sur ce dernier code source et imaginer les structures que vous allez pouvoir créer et imbriquer entre elles
Informations sur le tutoriel