Les structures et autres types personnalisés
===
###### tags: `algo` `bourg` `c++` `iut` `cm`
Le cours en vidéo:
{%youtube KYDSwvPWrrM %}
## Le renommage de type: la commande typedef
Pour des raisons de lisibilité du code, il est possible de définir des raccourcis vers de nouveaux types avec la commande ``typedef``.
La syntaxe:
````C++
typedef <un_type_existant> <nouveau_type>;
````
permet de faire ça.
Exemple: vous voulez renommer le type ``int`` en ``entier``. Cette déclaration de type peut s'effectuer de la façon suivante:
````C++
typedef int entier;
````
Cette déclaration doit avoir lieu dans l'entête de votre fichier (.h ou .cpp).
Ainsi, vous pourrez dans la suite de votre code déclarer une variable entière de la façon suivante:
````C++
entier i=3;
````
Pourquoi utiliser ce renommage ? Parce que cela permet de donner plus de lisibilité à votre code. Dans la mesure du possible, on préfère attacher une sémantique (un sens) au type de données. Par exemple, le type booléen est un type entier avec seulement deux valeurs. On pourrait se demande pourquoi il existe et simplement utiliser des ``int`` avec $0$ ou $1$ partout. Or je pense que vous avez compris en quoi le fait d'utiliser le type ``bool`` rend plus clair votre programme.
Par ailleurs, on peut utiliser ``typedef`` pour faire des raccourcis vers des types
qui représentent des tableaux. Ce genre d'alias est très pratique car il simplifie la notation avec les crochets. Attention toutefois à la syntaxe:
````C++
typedef char MaChaine[21];
typedef int Grille[8][8];
````
Dans ce cas là, ``MaChaine`` est un nouveau type qui représente des chaines de
$21$ caractères maximum, et ``Grille`` un type correspondant à un tableau 2D $8\times 8$. On peut donc déclarer et utiliser des variables comme ceci:
````C++
MaChaine nom, prenom; /*MaChaine est un type, nom et prenom des variables de ce type*/
strcpy(nom,"Durand");
Grille g;/*Grille est un type, g une variable de ce type*/
g[0][0]=7;
````
## Définir des types complexes: les structures
En langage C/C++, une *structure* permet d'associer sous un même nom un ensemble de valeurs pouvant être de types différents. Typiquement, cela vous permet de fabriquer des types personnalisés, et ainsi de ne pas vous cantonner aux types primitifs (à savoir int, float, char, bool...). Par exemple, si vous travaillez sur une liste de contacts pour un réseau social, vous aurez envie de créer un nouveau type ``Contact`` qui permettra à la fois de stocker le nom de la personne, son prénom, son email, ses préférences...
Chaque élément de la structure sera appelée un *champ* ou un *membre*. La déclaration d'un nouveau type structure se fait dans l'entête de votre fichier (juste après les ``#include``, ou dans vos fichiers .h).
### Déclaration d'un type structure
La syntaxe est la suivante:
````C++
struct <Nom_structure>
{
type membre1;
type membre2;
...
};
````
Ensuite, si je veux utiliser cette structure pour déclarer de nouvelles variables, je procède comme ceci:
````C++
Nom_structure a, b;
````
Par exemple, je souhaite définir une structure pour gérer les dates:
````C++
struct Date
{
int jour;
int mois;
int annee;
};
````
Que l'on utilise ensuite comme ceci pour déclarer deux variables de type ``Date``:
````C++
Date d1, d2;
````
:::warning
Dans l'exemple ci dessus, le mot ``Date`` est un nouveau **type**, pas une variable. Les variables sont ``d1`` et ``d2``. De manière générale, on choisira pour ces nouveaux types des noms qui commencent par une majuscule (et on gardera les minuscules pour les noms de variables).
:::
### Structures imbriquées
Notez qu'il est possible d'imbriquer des structures les unes dans les autres, par exemple:
````C++
struct Client
{
char nom[30];
char adresse[60];
Date date_naiss;
Date date_ouv_cpte;
};
````
### Accès aux champs d'une structure
Vous l'aurez compris, l'intérêt d'une structure est d'avoir une seule variable pour décrire plusieurs caractéristiques. Pour accéder à chacune de ces caractéristiques (les champs), on utilise l'opérateur **.**
````C++
Client c1,c2;
strcpy(c1.nom , "DUPONT" ); //strcpy est obligatoire pour
//initialiser la chaine ici
//car c1.nom="DUPONT" serait LE MAL
strcpy(c1.adresse , "7 Avenue des Lilas 01000 BOURG");
c1.date_naiss.jour = 14; /* affectation membre après membre */
c1.date_naiss.mois = 01;
c1.date_naiss.annee = 1975;
c2.date_naiss = {25,9,78}; /* affectation en bloc autorisée*/
````
:::warning
Evidemment l'opérateur **.** s'utilise derrière une **variable** de type struct. Une commande du genre ``Client.nom`` n'aurait pas de sens car ``Client`` n'est pas une variable mais un nom de type.
:::
### Tableaux dans des structures
Comme on l'a vu précédemment avec des chaines de caractères, on peut mettre des tableaux dans des structures:
````C++
struct Etablissement
{
char nom[30];
float reductions[3];
int ref;
};
````
Mais il faut faire attention à l'initialisation. On peut considérer le membre comme
une variable normale, donc quelque chose comme ça:
````C++
Etablissement univ;
univ.nom = "UCB Lyon 1"; //interdit
````
c'est toujours LE MAL.

En effet, rappelez-vous: ``nom`` est une chaine de caractères et on ne peut pas affecter une chaine (donc un tableau) avec le symbole ``=`` (à part dans le cas d'une déclaration-intialisation, ce qui n'est pas le cas ici).
Par contre on pourra faire:
````C++=
Etablissement univ;
strcpy(univ.nom,"UCB Lyon 1");
univ.reductions [0] = 5.0;
univ.reductions [1] = 18.6;
univ.reductions [2] = 20.0;
univ.ref = 1820001;
````
Ici en ligne 2, le champ ``nom`` est initialisé via un appel à ``strcpy``, ce qui est la stratégie privilégiée pour les chaines de caractères. En ce qui concerne le tableaux de réels ``reductions``, bien entendu la fonction ``strcpy`` n'est pas disponible (rappel: elle est valable seulement pour les chaines de caractères). On n'a pas d'autre choix que de passer par une initialisation case après case (lignes 3 à 5).
### Passage de paramètre
Comme les types primitifs, les structures peuvent être passées en paramètre de fonction. Il s'agit d'un passage par valeur.
Par conséquent, dès que possible, préférez les passages par référence pour les paramètres de type structure (même si les paramètres ne sont pas modifiés par la fonction).
### Fonction retournant une structure
Le type de retour d'une fonction peut être une classe. C'est un moyen simple
de retourner plusieurs valeurs en même temps. Exemple d'entête:
````Java
Date aujourdhui();
````
est une fonction qui renvoie une valeur de type ``Date`` et qui ne prend aucun
paramètre.
Cela nécessite de faire de l'allocation dynamique mais est très pratique. En outre, on peut renvoyer la valeur *NULL* en cas d'échec. Vous comprendrez mieux cela une fois que les chapitres sur les pointeurs et la gestion de la mémoire auront été abordés.
### Tableaux de structures
Il est souvent utile de considérer plusieurs structures. Une façon de les organiser
est de les mettre dans un tableau:
````C++
const int TAILLE=35;
struct Motcle /*on définit le type structure Motcle*/
{
char mot[20];
int cpt;
};
Motcle tabcle[TAILLE]; //déclaration d'un tableau de 35 structures de type Motcle
````
Si l'on veut proposer une déclaration-initialisation du tableau, on peut le faire facilement de la manière suivante:
````C++
Motcle tabcle[] = {
"break", 0,
"case",0,
"char", 0,
"continue",0,
"if", 0,
"for",0,
"return", 0,
"void",0,
"while", 0,
};
````
Dans ce cas, le nombre d'éléments du tableau sera automatiquement calculé par le compilateur. Ensuite, on accède aux membres des éléments du tableau de la façon suivante:
````C++
char mot_lu[50];
cin >> mot_lu;
for (i=0;i<9;i++){
if (strcmp (tabcle[i].mot, mot_lu) == 0)
cout << "Le mot tapé est présent ! " << endl;
}
````
:::warning
Soyez vigilants sur les types des variables lors de l'utilisation de tableau de structures. Par exemple, si vous voulez afficher le tableau de structures ci-dessus, l'écriture d'une boucle comme celle-ci:
````C++
for (int i=0;i<TAILLE;i++)
cout << tabcle[i] << endl; // pas valide
````
doit provoquer cette réaction chez vous:

En effet, ``tabcle[i]`` est de type ``Motcle``, et votre compilateur ne sait pas comment se comporter lorsqu'il doit faire un ``cout`` d'une variable de type ``struct``. Rappelez-vous, après un ``cout``, il faut forcément une variable de type primitif ou bien une chaine de caractères. Par conséquent, si on veut afficher tous les mots clés du tableau, il faut écrire:
````C++
for (int i=0;i<TAILLE;i++)
cout << tabcle[i].mot << " " << tabcle[i].cpt << endl; // valide
````
:::
### Opérations sur les structures
Les seules opérations autorisées sur les structures sont:
- l'affectation en la considérant comme un tout
- l'accès à ses membres grâce à l'opérateur ``.``
En ce qui concerne l'affection, il faut être prudent car l'opérateur ``=`` peut être source d'erreur pour les structures. Imaginons en effet deux variables ``s1`` et ``s2`` de type structure identique. L'instruction ````s1=s2```` est acceptée par le compilateur (ce qui n'était pas le cas pour les tableaux). Dans ce cas, C++ effectue une copie membre à membre des champs de ``s2`` dans ceux de ``s1``. Cela ne pose aucun problème si les champs sont de types primitifs (par exemple la structure ``Date`` qui ne contient que des entiers). En revanche, si certains champs sont eux mêmes des tableaux ou bien d'autres structures comportant des tableaux, alors l'affectation est source de plantage à l'exécution. Il vous faudra alors éviter d'utiliser l'opérateur ``=``, et coder vous-même les instructions d'affecation.
:::warning
**Comparer deux variables de type struct avec ==**
On ne peut pas comparer deux structures avec l'opérateur ``==``. Il faudra donc implémenter des fonctions spéciales pour ça. Par exemple:
````C++
struct Colis{
char nom[20];
int hauteur;
int largeur;
int longueur;
};
bool egalite_colis_taille(Colis& c1, Colis& c2) {
return ( (c1.hauteur == c2.hauteur) &&
(c1.largeur == c2.largeur) &&
(c1.longueur == c2.longueur) );
}
````
Dans l'exemple ci-dessus, on ne compare pas tous les membres de la structure, car le nom nous importe peu. Mais, cela pourrait être différemment bien entendu.
:::
## Les types énumérés
Une énumération est un type défini par une liste de valeurs entières constantes (commençant à $0$), auxquelles on attribue des noms symboliques qu'on appelle *mnémoniques*. Elle est définie grâce au mot-clé ``enum``.
````C++
enum MonEnumeration {NOM1, NOM2, NOM3, ... };
````
Par exemple:
````C++
enum Logique {NON, OUI};
````
Dans ce cas là, ``NON`` vaudra $0$ et ``OUI`` vaudra $1$. Dans les faits, on va surtout utiliser les noms des mnémoniques (et pas les valeurs entières) pour les variables de ce type, car elles permettent de rendre votre code plus lisible.
Pour l'utiliser dans votre code on écrira donc ensuite si l'on veut déclarer une variables de ce type:
````C++
Logique rep;//rep est une variable de type Logique
if (rep == NON){
...
}
````
Les mnémoniques qui définissent le type énuméré prennent des valeurs entières
commençant à $0$ par défaut et incrémentées automatiquement. Pour commencer à une autre valeur, on peut utiliser la syntaxe suivante:
````C++
enum Mois {JAN=1, FEV, MAR, AVR, MAI, JUIN,
JUIL, AOU, SEPT, OCT, NOV, DEC};
````
Ainsi, *JAN* vaut $1$, *FEV* vaut $2$, etc.
Les variables énumérées s'utilisent exactement comme des variables de type entier:
````C++
Mois mm,pm;
mm = NOV;
if (mm == NOV){ /* ou mm == 11 */
cout << "Novembre ! \n";
pm = mm;
pm = DEC;
cout << pm ; /*affiche 12, et non DEC ! */
}
````
On peut aussi indiquer une valeur explicitement pour chaque mnémonique:
````C++
enum Erreurs
{
PasErreur = 0,
ErreurVersion = 101,
ErreurMemoire = 102,
ErreurEcriture = 103,
ErreurLimite = 110,
ErreurNbParam = 111,
};
````
C'est très pratique pour faire de la gestion d'erreur.
### Affichage de la valeur d'un mnémonique
On ne peut pas afficher le nom d'un mnémonique par l'intermédiaire de ``cout`` directement. Autrement dit:
````C++
enum Couleur {NOIR, BLANC};
Couleur coul=NOIR;
cout << coul ; /*affiche 0, et non NOIR*/
````
affiche $0$ car pour lui c'est un entier. Pour pouvoir afficher le nom d'un mnémonique, il faut définir explicitement un tableau de chaines de caractères contenant les noms des mnémoniques. Par exemple:
````C++
char labels[][6] = {"NOIR", "BLANC"};
cout << labels[NOIR]; //affichera NOIR
````
affichera bien *NOIR*.