Nous avons manipulé dans les exemples précédents des variables qui
ont toutes des types prédéfinis :
int, unsigned int, float, double, std::string
, etc.
Il est possible en C/C++ de regrouper plusieurs variables en une seule pour définir de nouveaux types structurés.
Prenons tout de suite un exemple. Mettons que vous vouliez écrire un programme qui gère l’organisation d’une grosse soirée. Vous avez besoin de savoir qui vient, l’argent qui a été avancé, qui dort sur place, etc.
Vous pouvez regrouper pour chaque personne toutes ces infos dans une seule variable de type structuré:
// declaration du nouveau type
struct personne {
std::string nom;
double avance;
bool resteDormir;
};
Dans cet exemple, le mot personne
désigne maintenant ce
nouveau type, et il peut être utilisé pour créer des variables de la
même manière que n’importe quel autre type.
Revenons sur la syntaxe:
// declaration du nouveau type
struct personne {
std::string nom;
double avance;
bool resteDormir;
};
struct
dit qu’on va définir un nouveau
type.struct
.struct
contient un std::vector
, on ne peut pas préciser sa taille
dans le bloc de déclaration. La déclaration n’est qu’une «liste
d’ingrédients» que le compilateur utilise pour savoir quelle place
mémoire accorder à ce type.nom
, avance
et
resteDormir
sont des identifiants des variables stockées
dans chaque variable de type personne
. On les appelle les
champs du type structuré personne
.Revenons sur la syntaxe:
personne p1; // une variable de type personne, nommée p1
std::vector<personne> invites; // un vecteur de variables de type personne
personne
de
struct
ou type
, comme je le vois souvent.Pour lire ou modifier les valeurs des champs d’une variable de type
structuré, on accède aux champs avec l’opérateur point “.
”
:
personne p1; // une variable de type personne, nommée p1
p1.nom = "Rosa Parks";
p1.avance = +100.;
p1.resteDormir = false;
std::vector<personne> invites(10); // un vecteur de 10 personnes
// bilan des sous:
// ...
double solde = 0.e0;
for (unsigned int i=0u;i<invites.size();i++)
solde += invites[i].avance;
// invites[i] est une variable de type personne
// on accede donc au champ "avance" de la ieme personne
Nous voulons maintenant afficher le contenu d’une
struct
:
Cette instruction ne va pas fonctionner. L’opérateur
“<<
” a un comportement qui est défini pour les types
usuels, mais pas pour des types qu’on a créé de toutes pièces.
Il est donc nécessaire de surcharger cet opérateur.
C’est d’ailleurs le cas pour n’importe quel opérateur. Il ne faut pas croire le compilateur assez intelligent pour deviner ce que vous voulez qu’il fasse de votre nouveau type.
La surcharge de l’opérateur <<
pour l’affichage se
fait comme ça:
std::ostream& operator<<(std::ostream& os, personne& p) {
// Ici je peux personnaliser l'affichage
os << "Nom de l'invite: " << p.nom << std::endl;
os << "Solde: " << p.avance << " euros" << std::endl;
os << "Reste la nuit " << p.resteDormir << std::endl;
return os;
}
Observez les arguments et le type de retour. C’est une référence vers
un objet de type std::ostream
. Ça tombe bien,
std::cout
et std::cerr
sont des objets de ce
type. Il est impératif de retourner la référence passée en argument.
En effet, regardons ce qu’il se passe quand on écrit
Ici, deux opérations d’affichage sont effectuées. D’abord celle de
invites[i]
, ensuite celle pour le retour à la ligne. On
pourrait écrire
Quand le compilateur lit la ligne complète, il commence par évaluer de gauche à droite (les opérateurs étant les mêmes, il n’y a pas de priorité). Il effectue donc
qu’on peut aussi écrire
On voit bien que la première opération doit retourner
std::cout
pour que l’affichage du retour à la ligne se
fasse bien dans std::cout
.
Ce principe de retour du flux de sortie passé en argument est nécessaire pour n’importe quel type.