Remédiation en C++

C++: le type struct

Université de Bordeaux – Licence Ingénierie Mathématique

Les types structurés

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.

personne p1; // une variable de type personne, nommée p1
std::vector<personne> invites; // un vecteur de variables de type personne

Les types structurés

Revenons sur la syntaxe:

// declaration du nouveau type
struct personne {
  std::string nom;
  double avance;
  bool resteDormir;
};
  • Le mot-clé struct dit qu’on va définir un nouveau type.
  • Notez qu’à la fin de la déclaration, il y a un point-virgule.
  • Comme pour les fonctions, les types structurés doivent être déclarés avant toute utilisation. La déclaration se fait en dehors de toute fonction.
  • Vous pouvez placer autant d’éléments que nécessaires dans une struct.
  • Les valeurs indiquées à la déclaration ne sont pas initialisées. En particulier, si la 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.
  • Les mots 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.

Les types structurés

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
  • Il n’est pas nécessaire de précéder le mot personne de struct ou type, comme je le vois souvent.
  • Quand on crée une nouvelle variable de type structuré, ses champs ne sont pas initialisés.
  • Comme pour les autres variables, vous pouvez créer des références vers une variable de type structuré:
personne& p = p1; // reference vers la personne p1

Les types structurés

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

Les types structurés

Nous voulons maintenant afficher le contenu d’une struct:

std::cout << invites[0] << std::endl;

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.

Les types structurés

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

std::cout << invites[i] << std::endl;

Les types structurés

std::cout << invites[i] << std::endl;

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

std::cout << invites[i];
std::cout << std::endl;

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

operator<<(std::cout,invites[i]) << std::endl;

qu’on peut aussi écrire

operator<<( operator<<(std::cout,invites[i]) , std::endl);

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.

Retour au cours Moodle