Big Tuto : Apprenez le C++

Chapitre 14 : Types de données complexes (2)

 

Tutoriel présenté par : Robert Gillard (Gondulzac)
Date de publication : 15 août 2016
Date de révision : -

 

     Préliminaires

 

   Dans le chapitre précédent, nous avons introduit les types de données complexes en commençant par l'implémentation de structures destinées à la création de bases de données. Nous avons vu l'intégration de données membres dans une structure, la création d'objets à partir d'une structure et nous avons créé des fonctions permettant d'ajouter des données ou de relire celles-ci dans les différents champs que comportent ces objets.

   Dans ce chapitre nous allons intégrer des fonctions membres (méthodes) dans une structure. L'utilisation de ces fonctions membres dans les structures va nous permettre d'accéder aux éléments des données sans avoir besoin d'utiliser du code qui n'appartient pas à ces structures. wink

   Avec la création d'objets par l'intermédiaire de structures, nous entrons petit-à-petit dans le domaine de la Programmation Orientée Objets ou POO qui sera plus largement expliquée dès l'implémentation des classes C++.  smiley

 

Retrouvez les projets complets de ce chapitre :

  

 

 

   1 – Structures (suite)
      1.1 – Ajout de fonctions membres dans une structure

 

   Pour illustrer notre propos sur l'intégration de fonctions membres dans une structure, nous allons de nouveau nous pencher sur le stock d'armes de notre marchand Ethiolas et voir ce qu'il a à nous proposer en matière d'armes dites tranchantes. cheeky

   Nous allons créer une structure ''armes_tranchantes'' qui pourra initialiser des champs représentant des épées courtes, épées longues, épées à deux mains, cimeterres, katanas, ou tout autre arme de ce type que vous pouvez trouver dans un rpg. wink

   Les divers champs doivent bien entendu être compatibles avec le type d'arme à créer. En effet, nous ne pouvons pas ici créer un champ dénommé ''portee'' qui représenterait la distance possible que pourrait atteindre une flèche alors que les armes que nous voulons utiliser sont des armes de corps-à-corps. frown

   Nous créons donc une structure dénommée ''armes_tranchantes'' à laquelle nous intégrons quelques champs utiles :

 

struct arme_tranchante
{
string nom;
double prix_achat;
double prix_revente;
uint degats;
};

 

   Dans cette structure nous avons ajouté un champ ''degats'' qui représentera les dégats occasionnés par une arme. C'est également important de connaître les dégats causés par la petite merveille que nous allons acheter, autant en avoir pour son argent, non ? cheeky

   Nous allons maintenant compléter la définition de notre structure en lui ajoutant deux fonctions membres, les fonctions ''ajouter_arme()'' et la fonction lire_arme(). La structure devient alors :

 

struct arme_tranchante
{
//Données membres
string nom;
double prix_achat;
double prix_revente;
uint degats;
 
//Fonctions membres
void Ajouter_arme();
void Lire_arme();
};

 

   L'implémentation des fonctions se fait en écrivant en premier le type de retour de la fonction, suivi du nom de la structure, de l'opérateur de résolution de portée et du nom de la fonction, soit :

 

void arme_tranchante::Ajouter_arme()
{
cout << "Nom : ";
getline(cin, nom);
cout << "Prix d'achat : ";
cin >> prix_achat;
cout << "Prix de revente : ";
cin >> prix_revente;
cout << "Degats : ";
cin >> degats;
cin.ignore();
}

 

   Et pour la fonction Lire_arme() :

 

void arme_tranchante::Lire_arme()
{
cout << "Nom : " << nom << endl;
cout << "Prix d'achat : " << prix_achat << endl;
cout << "Prix de revente : " << prix_revente << endl;
cout << "Degats : " << degats << endl;
}

 

   L'opérateur de résolution de portée nous indique que les fonctions Ajouter_arme() et Lire_arme() font toutes deux partie de la structure ''arme_tranchante''.

   Et enfin dans la fonction main() nous créerons par exemple un objet ''monArme'' et un tableau d'objets ''monTableauArmes''

 

int main()
{
//Création d'un objet de type arme_tranchante et un tableau d'objets
//Un tableau de 2 armes pour ne pas avoir trop de saisie à effectuer
const uint nb_armes(2);
arme_tranchante monArme;
arme_tranchante monTableauArmes[nb_armes];
 
//Appel des fonctions membres
cout << endl;
cout << "Donnes membres de l'objet 'monArme'" << endl << endl;
monArme.Ajouter_arme();
cout << endl;
cout << "Lecture des membres de l'objet 'monArme'" << endl << endl;
monArme.Lire_arme();
 
cout << endl;
cout << "Saisissez les champs suivants pour remplir les objets 'monTableauArmes'" << endl << << endl;
 
for (uint i = 0; i != nb_armes; ++i)
monTableauArmes[i].Ajouter_arme();
 
cout << endl;
cout << "Lecture des elements du tableau 'monTableauArmes'" << endl << endl;
 
for (uint i = 0; i != nb_armes; ++i)
monTableauArmes[i].Lire_arme();
 
_getch();
return 0;
}

 

   Ce qui nous donne dans la console :

 

 

Remarque : Nous aurions pu définir notre structure et les deux fonctions membres dans un fichier header mais nous attendrons les premiers chapitres se référant aux classes de C++ pour l'implémentation de plusieurs fichiers dans un projet. En attendant, nous continuerons à coder chaque projet dans un seul fichier .cpp.

 

   Dans ce projet, nous avons créé nos objets sur la pile mais rien ne nous aurait empêché, comme nous l'avons vu dans le précédant chapitre de créer ces objets dans le ''tas''. En effet, que nous utilisions des fonctions membres ou non, nous pouvons toujours créer des objets dans le tas.

   Par exemple, dans le cas où nous aurions réservé de la mémoire dans le tas avec new, renvoyant un pointeur sur les objets :

const uint nb_armes(2);
arme_tranchante *monArme = new arme_tranchante;
arme_tranchante *monTableauArmes = new arme_tranchante[nb_armes];

 

   L'appel aux fonctions membres se faisant alors à l'aide de l'opérateur flèche.

cout << "Donnes membres de l'objet 'monArme'" << endl << endl;;
monArme->Ajouter_arme();
cout << endl;
cout << "Lecture des membres de l'objet 'monArme'" << endl << endl;
monArme->Lire_arme();

 

   Sans oublier la libération de la mémoire :

delete monArme;
delete[2]monTableauArmes;

 

  Soit dans le cas où nous aurions réservé de la mémoire dans le tas avec new renvoyant une référence sur les objets :

const uint nb_armes(2);
arme_tranchante &monArme = *new arme_tranchante;
arme_tranchante &monTableauArmes = *new arme_tranchante[nb_armes];

 

  On accède alors aux fonctions à l'aide de l'opérateur ''point''.

cout << "Donnes membres de l'objet 'monArme'" << endl << endl;;
monArme.Ajouter_arme();
cout << endl;
cout << "Lecture des membres de l'objet 'monArme'" << endl << endl;
monArme.Lire_arme();

 

   Sans oublier cette forme spéciale de libération de la mémoire (voir chapitre 13) :

delete (arme_tranchante*)(&monArme);
delete[2](arme_tranchante*)(&monTableauArmes); 

 

Pour rappel : la valeur entre les crochets représente le nombre d'objets du tableau créé dans le tas !

 


   Un petit challenge (Exercice 1)

 

   Dans le dossier de ce chapitre 14, j'ai ajouté un fichier dénommé ''Armes.txt'' qui contient les enregistrements de trois armes différentes. Vous déplacerez ce fichier dans le dossier de votre exercice et vous créerez une structure ''arme_tranchante'', identique à celle de l'exemple 1. Ensuite, en utilisant la technique d'écriture et de lecture d'éléments dans un fichier, technique vue dans le chapitre 13, vous ajouterez deux autres types d'armes dans le fichier ''Armes.txt'' et vous afficherez ensuite son contenu à l'écran à l'aide de fonctions membres de la structure ''arme_tranchante''.

   Dans la fonction main(), vous créerez un objet ''monTableauArmes'' dans le tas en renvoyant un pointeur sur l'objet (voir ci-dessus et chapitre 13) ainsi qu'un objet ''monfichier'' déclaré sur la pile. Les fonctions membres seront ensuite appelées à l'aide de l'opérateur point, soit :

monTableauArmes[i].Fichier_Armes_ajouter_arme();
   et
monFichier.Fichier_Armes_lire_arme();   

 

Fichier ''Armes.txt'' de départ. La structure est identique à celle du projet 089Struct_7.
Seuls les noms des fonctions membres ont été changés.

 

   Et heu... frown faites l'exercice hein, n'écrivez pas dans le fichier à la main !... surprise

 

 

   1.2 – Utilisation du tas à l'intérieur d'une structure

 

   Reprenons la structure de notre projet 089Struct_7. Rien ne nous empêche de déclarer nos variables membres comme pointeurs et de réserver de la mémoire dans le tas à l'intérieur de la structure.

   Comment ? surprise

   Et bien nous allons allouer de la mémoire dans le tas par l'intermédiaire d'une fonction membre. wink Nous voyons ceci dans le projet suivant :

 

//projet 090Struct_8
//Utilisation du tas à l'intérieur d'une structure
#include<iostream>
#include<string>
#include<conio.h>
 
using namespace std;
using uint = unsigned int;
 
struct arme_tranchante
{
//Données membres
string *nom;
double *prix_achat;
double *prix_revente;
uint *degats;
 
//Fonctions membres
void Ajouter_arme();
 
void Lire_arme();
};
 
void arme_tranchante::Ajouter_arme()
{
//Résevation mémoire
nom = new string;
prix_achat = new double;
prix_revente = new double;
degats = new uint;
 
//Saisie
cout << "Nom : ";
getline(cin, *nom);
cout << "Prix d'achat : ";
cin >> *prix_achat;
cout << "Prix de revente : ";
cin >> *prix_revente;
cout << "Degats : ";
cin >> *degats;
cin.ignore();
}
 
void arme_tranchante::Lire_arme()
{
cout << "Nom : " << *nom << endl;
cout << "Prix d'achat : " << *prix_achat << endl;
cout << "Prix de revente : " << *prix_revente << endl;
cout << "Degats : " << *degats << endl;
}
 
int main()
{
//Création d'un objet de type arme_tranchante et un tableau d'objets
//Un tableau de 2 armes pour ne pas avoir trop de saisie à effectuer
const uint nb_armes(2);
arme_tranchante monArme;
arme_tranchante monTableauArmes[nb_armes];
 
//Appel des fonctions membres
cout << endl;
cout << "Donnes membres de l'objet 'monArme'" << endl << endl;;
monArme.Ajouter_arme();
cout << endl;
cout << "Lecture des membres de l'objet 'monArme'" << endl << endl;
monArme.Lire_arme();
 
cout << endl;
cout << "Saisissez les champs suivants pour remplir les objets monTableauArmes'" <<
endl << endl;
 
for (uint i = 0; i != nb_armes; ++i)
monTableauArmes[i].Ajouter_arme();
 
cout << endl;
cout << "Lecture des elements du tableau 'monTableauArmes'" << endl << endl;
 
for (uint i = 0; i != nb_armes; ++i)
monTableauArmes[i].Lire_arme();
 
_getch();
return 0;
}

 

   Voilà, je ne vous montre pas le résultat dans la console qui est le même que celui du projet 089Struct_7. Voyons plutôt comment nous avons implémenté ce code. smiley

   Les données membres de notre structure ''armes_tranchantes'' sont désormais des pointeurs et vous voyez que la réservation de la mémoire dans le tas se fait à l'intérieur de la méthode arme_tranchante::Ajouter_arme(). Dans la fonction main(), nous avons créé les objets ''monArme'' et ''monTableauArmes'' sur la pile. La compilation de notre projet 090Struct_8 va donc se faire sans aucun problème et celui-ci tournera parfaitement sur notre PC mais il y a quand-même un problème de taille... frown

   Vous ne voyez pas ? surprise

   Et bien, il n'y a pas de libération de mémoire pour nos variables membres de la structure, dès que nous n'avons plus besoin de celles-ci. Cela ne gêne en rien le déroulement de notre programme mais pourrait se révéler catastrophique lors d'un code beaucoup plus élaboré, alors comment faire !? cheeky

   En gardant telle quelle l'implémentation de ce projet, il nous est impossible de libérer la mémoire pour nos variables membres. Rassurez-vous cependant, nous avons une solution. Nous allons quelque peu empiéter sur la théorie des classes C++ en introduisant la notion de constructeur et de destructeur dans une structure. Après tout, il est parfaitement possible d'avoir à utiliser des fonctions membres d'une structure sans pour cela avoir nécessairement besoin d'une classe. wink

 

 

   1.3 – Notion de constructeur et de destructeur

 

   Nous laisserons un peu plus de place à la théorie sur les constructeurs et destructeurs lors de la découverte des classes de C++ mais nous allons voir ici ce qui manque à notre programme afin de le rendre plus sûr. wink

   Nous avons besoin d'un constructeur et d'un destructeur. Les constructeurs et les destructeurs sont des formes spéciales de fonctions membres qui portent le nom de la structure ou de la classe à laquelle ils se réfèrent et qui ne renvoient aucune valeur, même pas ''void''. Le destructeur est précédé du signe ''~'' (tilde).

   Pour illustrer notre propos, nous pouvons, dans notre projet 090Struct_8, remplacer notre structure ''arme_tranchante'' par celle-ci :

struct arme_tranchante
{
//Données membres
string *nom;
double *prix_achat;
double *prix_revente;
uint *degats;
 
//Constructeur
arme_tranchante();
 
//Destructeur
~arme_tranchante();
 
//Fonctions membres
void Ajouter_arme();
void Lire_arme();
};

 

   Structure dans laquelle nous ajoutons les déclarations du constructeur et du destructeur.

   Nous allons maintenant implémenter ceux-ci. Dans le constructeur, nous réserverons la mémoire dans le tas pour nos variables membres et dans le destructeur nous libérerons cette mémoire, ce n'est pas plus difficile que cela.  

//Initialisation du constructeur
arme_tranchante::arme_tranchante()
{
//Réservation mémoire
nom = new string;
prix_achat = new double;
prix_revente = new double;
degats = new uint;
}
 
//Initialisation du destructeur
arme_tranchante::~arme_tranchante()
{
//Libération de la mémoire
delete nom;
delete prix_achat;
delete prix_revente;
delete degats;
}

 

   La méthode Ajouter_arme() devenant alors :

void arme_tranchante::Ajouter_arme()
{
//Saisie
cout << "Nom : ";
getline(cin, *nom);
cout << "Prix d'achat : ";
cin >> *prix_achat;
cout << "Prix de revente : ";
cin >> *prix_revente;
cout << "Degats : ";
cin >> *degats;
cin.ignore();
}

 

   Et dans la fonction main() avant tout appel de fonction, nous ferons appel au constructeur de la même manière qu'une fonction est appelée, c'est-à-dire par son nom :

//Appel du constructeur
arme_tranchante();

 

   Et il n'y a pas à se casser la tête pour chercher où placer le destructeur, celui-ci étant automatiquement appelé dès l'accolade fermante de la fonction main().

 

   Exercice 2

 

   A titre d'exercice, vous pouvez réécrire tout le projet 090Struct_8 en utilisant le constructeur et le destructeur que nous venons de voir ci-dessus. wink

 


   1.4 – Implémentation d'une fonction ''inline'' dans une structure

 

   Nous avons vu que certaines applications sensibles au temps d'exécution pouvaient nécessiter de déclarer l'une ou l'autre fonction ''inline'' (voir chapitre 6 - § 2.1 Fonctions inline). A chaque fois qu'une fonction ''inline'' est appelée, une nouvelle copie de la fonction est insérée dans le programme par le compilateur. Ceci a l'avantage d'augmenter la rapidité d'exécution d'un programme au prix d'un code un peu plus long.

   Une fonction membre d'une structure peut également être déclarée en ligne. L'implémentation d'une telle fonction dans la structure se fait de la façon suivante :

   Le mot-clé ''inline'' n'est pas requis. L'accolade ouvrante de la fonction est placée directement à la suite des parenthèses, suivi du code de la fonction.

   Si nous voulions déclarer ''inline'' la méthode Lire_arme() de notre exemple précédent, nous réécririons notre structure comme ceci :

struct arme_tranchante
{
//Données membres
string *nom;
double *prix_achat;
double *prix_revente;
uint *degats;
 
//Constructeur
arme_tranchante();
 
//Destructeur
~arme_tranchante();
 
//Fonctions membres
void Ajouter_arme();
void Lire_arme() {
cout << "Nom : " << *nom << endl;
cout << "Prix d'achat : " << *prix_achat << endl;
cout << "Prix de revente : " << *prix_revente << endl;
cout << "Degats : " << *degats << endl;
}
};

 

   et dans ce cas, en ce qui concerne les fonctions membres, nous n'aurions plus qu'à implémenter la méthode ''Ajouter_arme()'' comme nous l'avons fait dans l'exemple précédent. wink

   Voilà pour ce qui est de cette grosse partie sur les structures dans les ''Types de données complexes'', nous passons maintenant à un second type, les ''unions''.

 

 

   2 – Unions

        2.1 – Définition

 

   Au même titre qu'une structure, une union est un type de donnée complexe dans laquelle on peut incorporer des types de variables différents mais avec cette importante restriction qu'elle ne peut contenir qu'une seule valeur à un moment donné.

   A titre d'exemple, nous allons déclarer une union qui pourra contenir quelques variables numériques de types différents.
   Une union sera déclarée de la même manière qu'une structure , excepté que le mot réservé union remplarera le mot-clé struct.

union typeVar
{
uint ui;
int i;
float f;
long l;
double d;
};

 

   Nous avons déclaré une union dont le type est ''typeVar''. Au même titre qu'une structure, nous pouvons maintenant créer un objet de type ''typeVar'' en écrivant :

typeVar maVariable;

 

   Nous accéderons de même aux éléments de notre union à l'aide de l'opérateur point en écrivant :

maVariable.ui;
maVariable.i;
maVariable.f;
maVariable.l;
maVariable.d;

   tout en sachant que la variable ''maVariable'' ne peut contenir à un moment donné qu'une seule valeur de l'union, tous les autres membres étant indéfinis.

   Nous allons voir ceci avec un petit exemple :

 

   2.2 – Exemple

 

   Pour ce faire, nous allons modifier quelque peu notre projet 007Sizeof du chapitre 2 afin de démontrer que la taille d'une union est de même taille que la taille de la plus grande variable que contient cette union. 

 

//Projet 091union
//Taille d'une union
#include <iostream>
#include <conio.h>
 
using namespace std;
using uint = unsigned int;
 
union typeVar
{
uint ui;
int i;
float f;
long l;
double d;
};
 
int main()
{
cout << "Une union de type 'typeVar' contient des variables de types suivants : " << endl << endl;
cout << "La taille d'un type 'unsigned int' est \t\t\t" << sizeof(uint) << "bytes" << endl;
cout << "La taille d'un type 'int' est \t\t\t\t" << sizeof(int) << " bytes" << endl;
cout << "La taille d'un type 'float' est \t\t\t" << sizeof(float) << " bytes" << endl;
cout << "La taille d'un type 'long int' est \t\t\t" << sizeof(long int) << " bytes" << endl;
cout << "La taille d'un type 'double' est \t\t\t" << sizeof(double) << " bytes" << endl;
 
// création d'un objet maVariable
typeVar maVariable;
cout << endl;
 
maVariable.i = 5;
cout << "maVariable.i = " << maVariable.i << endl;
maVariable.f = 5569.45;
cout << "maVariable.f = " << maVariable.f << endl;
maVariable.d = 3.14159;
cout << "maVariable.d = " << maVariable.d << endl;
 
cout << endl;
cout << "La taille de l'union = sizeof(typeVar)" << endl;
cout << "soit " << sizeof(typeVar) << " bytes" << endl;
 
//Les lignes suivantes afficheront un résultat bizarre
//Les objets maVariable.i et maVariable.d ne pouvant exister en même temps
//maVariable.d = maVariable.i * maVariable.d;
//cout << maVariable.d << endl;
 
//Si nous voulons multiplier maVariable.d par la valeur 5,
//nous devons utiliser un nom de variable qui ne fait pas partie de l'union
int autreVariable = 5;
maVariable.d = static_cast<double>(autreVariable) * maVariable.d;
cout << "Nouvelle valeur de 'maVariable.d' = " << maVariable.d << endl;
 
_getch();
 
return 0;
}

 

   Après avoir déclaré une union de type ''typeVar'', nous affichons les quantités de mémoire utilisées par chacune des variables contenues dans notre union.

   Nous créons ensuite un objet ''maVariable'' de type ''typeVar'', objet nous permettant d'accéder aux différentes variables de l'union à l'aide de l'opérateur ''point''.

   En passant le type ''typeVar'' à la fonction sizeof(), nous voyons que celle-ci affiche 8 bytes, ce qui représente le type de la variable à qui est allouée la plus grande quantité de mémoire, soit le type double (sur un PC 32 bits). wink

  Nous voyons ensuite qu'il nous est impossible d'utiliser en même temps deux variables faisant partie de l'union. Le problème est que le compilateur accepte une telle écriture mais le résultat d'une opération comme celle tentée dans l'exemple donnera un résultat tout-à-fait bizarre, les autres valeurs de l'union étant des valeurs indéfinies. frown

   Essayez par exemple d'enlever les doubles-barres de remarque de la ligne !

//maVariable.d = maVariable.i * maVariable.d;

 

   et observez le résultat de cette multiplication, vous verrez que nous sommes loin du résultat réel du produit de 3.14159 par 5. surprise

   Ce qui donne comme résultat dans la console :

 

 

   Il est à remarquer qu'une union ne peut pas avoir une référence comme membre et que la norme C++ 11 offre de nouvelles possibilités aux unions. Celles-ci peuvent désormais accueillir des types de classes possédant des constructeurs et des destructeurs.

 

 

        3 – Champs de bits

 

   Un autre type de données complexes sont les champs de bits. Il s'agit d'un type de structure spécial qui permet de ''compresser'' des données contenues dans des variables de la structure.

   Un exemple souvent repris par la littérature informatique pour représenter des champs de bits est la création d'une structure contenant une date.

   struct date
   {
        uint jour;
        uint mois;
        uint annee;
    };

   Nous avons ici une structure dont chaque variable de type unsigned int utilise 4 bytes et la taille de la structure est donc de 4 x 3 = 12 bytes. wink

   En utilisant les champs de bits, nous pouvons avoir un arrangement plus compact (pour rappel, 1 byte = 8 bits).

   Voyons ceci avec un court exemple.

 

//Projet 092Champs_de_bits
//Compression des données
#include <iostream>
#include <conio.h>
 
using namespace std;
using uint = unsigned int;
 
struct date1
{
uint jour;
uint mois;
uint annee;
};
 
struct date2
{
uint jour : 5; //codé sur 5 bits
uint mois : 4; //codé sur 4 bits
uint annee : 16; //codé sur 16 bits
};
 
int main()
{
//On crée un objet de chaque structure
date1 cette_date1;
date2 cette_date2;
 
//On remplit les champs de chaque objet avec une date quelconque
cette_date1.jour = 11;
cette_date1.mois = 4;
cette_date1.annee = 2016;
 
cette_date2.jour = 11;
cette_date2.mois = 4;
cette_date2.annee = 2016;
 
//On affiche la date cette_date1
cout << endl;
cout << "Ceci est une date codee sur 12 bytes : ";
cout << cette_date1.jour << " ";
cout << cette_date1.mois << " ";
cout << cette_date1.annee << endl;
 
cout << "La taille de la structure 'date1' est de " << sizeof(date1) << " bytes" << endl;
 
//On affiche la date cette_date2
cout << endl;
cout << "Ceci est une date codee sur 4 bytes : ";
cout << cette_date2.jour << " ";
cout << cette_date2.mois << " ";
cout << cette_date2.annee << endl;
 
cout << "La taille de la structure 'date2' est de " << sizeof(date2) << " bytes" << endl;
 
_getch();
 
return 0;
}

 

   Allez, quelques commentaires. wink

   Nous créons deux structures, ''date1'' et ''date2''. Nous venons de voir un peu plus haut qu'une structure contenant 3 variables unsigned int a une taille de 12 bytes.

  Dans la structure ''date2'' nous compressons les données de la façon suivante :

   La variable ''jour'' de type unsigned int dont la taille normale est de 4 bytes est codée sur 5 bits et n'utilise donc qu' un seul byte.

  La variable ''mois'', codée sur 4 bits, n'utilise qu'un byte également tandis que la variable ''annee'', pour afficher les 4 chiffres de 2016 utilise quant-à-elle, 2 bytes.

   Finalement, la taille de la structure ''date2'' est bien de 1 + 1 + 2 = 4 bytes.

   Pour l'écriture de notre date, le gain de mémoire aura donc été de 12 – 4 = 8 bytes. Bon, je sais qu'avec les quantités de mémoires actuelles emmagasinées dans nos Pc's, utiliser les champs de bits dans de telles conditions ne représente que des économies de bout-de-chandelle. Mais puisque cela existe, autant le signaler. indecision

   Et nous affichons le résultat dans la console :

 

 

   Quelques restrictions concernant les champs de bits

 

   Par convention, les éléments de données que contiennent les champs de bits sont de type ''unsigned int''.

   Les champs de bits n'ayant pas d'adresse, on ne peut donc pas s'y référer avec l'opérateur ''&''.

   En général, l'accès aux champs de bits est moins rapide que l'accès aux variables, ce qui restreint leur utilisation là où une grande vitesse d'exécution est exigée. 

 

 

        4 – Enumérations

 

   Dans le chapitre 2 (Types de données, § 4 – Constantes), nous avons introduit la notion de constantes énumérées et nous en avions présenté un exemple dans le projet 013enum.

   Ce type d'énumérations nous permet de regrouper des ensembles de constantes de type entier non restreints par une ''étendue de validité'' (unscoped enumerations).

   Il se fait que le standard C++ 11 a introduit la notion d'énumérations définies dans une certaine étendue de validité (scoped enumerations). Nous voici donc avec la possibilité d'utiliser deux types d'énumérations différents, chacun d'entre-eux étant définis ou non dans une certaine résolution de portée.

   La déclaration d'une énumération dont les éléments sont définis dans leur étendue de validité se fait en ajoutant le mot-clé ''struct'' ou ''class'' qui suit le mot réservé ''enum''.

  Par exemple, sachant que les modes d'accès à un fichier peuvent être déclarés en mode lecture, écriture ou ajout, nous pouvons, dans le cas d'une ''scoped enumeration'' du standard C++ 11, déclarer une énumération comme suit :

enum struct mode_ouverture { lecture, ecriture, ajout };

   et avoir accès à ses membres (énumérateurs) à l'aide de l'opérateur de résolution de portée :

mode_ouverture mode = mode_ouverture::lecture;

 

   Enumérateurs

 

   Les noms des énumérateurs qui font partie d'une ''scoped enumeration'' suivent les règles normales de résolution de portée des variables et sont inaccessibles en dehors de leur étendue de validité.

   En réécrivant le projet 013enum du chapitre 2, nous pouvons mieux constater l'étendue de validité des énumérateurs d'une ''enum PNJ'' (unscoped enumeration) et d'une ''enum struct Personnage''
(scoped enumeration). 

 

//Projet 093Scoped_enum
//Enumération et résolution de portée
#include <iostream>
#include <conio.h>
 
using namespace std;
using uint = unsigned int;
 
int main()
{
//Déclaration d'un type énuméré Enemy (unscoped enumeration)
enum PNJ { Gobelin, Orc, Troll, Squelette, Liche, Elfe, Nain };
//enum Personnage { Gobelin, Orc, Troll, Squelette, Liche, Elfe, Nain };
//Erreur, les énumérateurs ne peuvent être redéfinis !
 
enum struct Personnage { Gobelin, Orc, Troll, Squelette, Liche, Elfe, Nain };
// Ok, membres cachés de la structure
 
PNJ unPNJ = Troll; //Ok, Troll se trouve dans l'enum PNJ
 
//Personnage unPersonnage = Elfe;
//Erreur, Elfe n'est pas dans le domaine de définition de Personnage
 
PNJ autrePNJ = PNJ::Liche; // ok, accès explicite aux énumérateurs de PNJ
 
//On déclare un objet 'Race' à qui on donne la valeur 'Elfe'
Personnage Race = Personnage::Elfe;
 
switch (Race)
{
case Personnage::Gobelin:
cout << "Mefiez-vous d'eux, ce sont des voleurs !" << endl;
break;
 
case Personnage::Orc:
cout << "Vous devriez etre bien prepare avant de les affronter !" << endl;
break;
 
case Personnage::Troll:
cout << "Si vous en rencontrez un, courez..." << endl;
break;
 
case Personnage::Elfe:
cout << "Cet Elfe est mon ami" << endl;
break;
}
 
cin.get();
return 0;
}

 

   Voilà, toutes les explications nécessaires sont dans le programme. Dans la ''scoped enumeration'' Personnage, nous déclarons un objet ''Race'' à qui l'on donne la valeur ''Elfe'' et comme nous l'avons vu plus haut, nous ne pourrons accéder à chaque énumérateur de cette énumération qu'à l'aide de l'opérateur de résolution de portée. C'est la différence entre ce programme et celui du projet 013enum du chapitre 2 qui utilisait quant-à-lui une ''unscoped enumeration''.

   Pour résumer :

- unscoped enumeration : Enumération (enum) dont les membres (énumérateurs) ne sont pas restreints à l'étendue de validité de l'énumération.
- scoped enumeration : Enumération (enum struct ou enum class) dont les membres sont restreints à l'étendue de validité de l'énumération.

 


   5 – Corrigés des exercices du chapitre 13

      Corrigé de l'exercice 1

 

   Pour cet exercice il était demandé :

   Créez une structure que vous nommerez : arme_de_traits_Arc

   A l'aide du type de cette structure créez trois objets en utilisant l'allocation dynamique de mémoire par laquelle ''new'' renvoie une référence sur un objet créé. Ces 3 objets seront respectivement dénommés arc_court, arc_long et arc_composite.

   Pour chaque objet, vous entrerez les champs de la structure au clavier ainsi que leurs valeurs correspondantes en vous basant sur les exemples cités dans le chapitre 13. Vous créerez également un champ ''prix_revente'' pour chaque objet auquel vous attribuerez respectivement les valeurs 12, 16 et 20.

   Pour terminer, affichez les valeurs saisies pour les champs de chaque objet.

   Voici le code :

 
//projet Chap13Exercice_1
//Saisie au clavier d'éléments de structures créées dans le tas.
#include<iostream>
#include<string>
#include<conio.h>
 
using namespace std;
using uint = unsigned int;
 
struct arme_de_traits_Arc
{
string nom;
uint prix_achat;
uint prix_revente;
uint portee;
};
 
void liberation_memoire(arme_de_traits_Arc &arc_court, arme_de_traits_Arc &arc_long, arme_de_traits_Arc &arc_composite);
 
int main()
{
//Création de 3 objets dans le tas renvoyant une référence sur l'objet
arme_de_traits_Arc &arc_court = *new arme_de_traits_Arc;
arme_de_traits_Arc &arc_long = *new arme_de_traits_Arc;
arme_de_traits_Arc &arc_composite = *new arme_de_traits_Arc;
 
{
//Bloc 1 : Saisie des champs pour l'objet 'arc_court'
cout << endl;
cout << "Saisissez les champs suivants pour l'objet 'arc_court'" << endl << endl;
cout << "Nom de l'arme : ";
getline(cin, arc_court.nom);
cout << "Prix d'achat : ";
cin >> arc_court.prix_achat;
cout << "Prix revente : ";
 
cin >> arc_court.prix_revente;
cout << "Portee : ";
cin >> arc_court.portee;
cout << endl;
}
 
{
//Bloc 2 : Saisie des champs pour l'objet 'arc_long'
cout << endl;
cout << "Saisissez les champs suivants pour l'objet 'arc_long'" << endl << endl;
cin.ignore();
cout << "Nom de l'arme : ";
getline(cin, arc_long.nom);
cout << "Prix d'achat : ";
cin >> arc_long.prix_achat;
cout << "Prix revente : ";
cin >> arc_long.prix_revente;
cout << "Portee : ";
cin >> arc_long.portee;
cout << endl;
}
 
{
//Bloc 3 : Saisie des champs pour l'objet 'arc_composite'
cout << endl;
cout << "Saisissez les champs suivants pour l'objet 'arc_composite'" << endl << endl;
cin.ignore();
cout << "Nom de l'arme : ";
getline(cin, arc_composite.nom);
cout << "Prix d'achat : ";
cin >> arc_composite.prix_achat;
cout << "Prix revente : ";
cin >> arc_composite.prix_revente;
cout << "Portee : ";
cin >> arc_composite.portee;
cout << endl;
}
 
{
//Bloc 4 : Lecture des champs des 3 objets
cout << "Lecture des valeurs des elements de l'objet 'arc_court'" << endl << endl;
cout << arc_court.nom << endl;
cout << arc_court.prix_achat << endl;
cout << arc_court.prix_revente << endl;
cout << arc_court.portee;
cout << endl;
 
cout << "Lecture des valeurs des elements de l'objet 'arc_long'" << endl <<
endl;
cout << arc_long.nom << endl;
cout << arc_long.prix_achat << endl;
cout << arc_court.prix_revente << endl;
cout << arc_long.portee;
cout << endl;
 
cout << "Lecture des valeurs des elements de l'objet 'arc_composite'" << endl << endl;
cout << arc_composite.nom << endl;
cout << arc_composite.prix_achat << endl;
cout << arc_composite.prix_revente << endl;
cout << arc_composite.portee;
cout << endl;
}
 
//On n'oublie pas de libérer la mémoire réservée avec 'new'
liberation_memoire(arc_court, arc_long, arc_composite);
 
_getch();
return 0;
}
 
 
void liberation_memoire(arme_de_traits_Arc &arc_court, arme_de_traits_Arc &arc_longarme_de_traits_Arc &arc_composite)
{
delete (arme_de_traits_Arc*)(&arc_court);
delete (arme_de_traits_Arc*)(&arc_long);
delete (arme_de_traits_Arc*)(&arc_composite);
} 

 

   Nous commençons par déclarer notre structure ''armes_de_traits_arc'' comme demandé dans l'énoncé. On déclare ensuite une fonction dénommée ''liberation_memoire()'' à qui l'on passe une référence sur les objets crées dans la fonction main(). La fonction ''libération_memoire()'' va, comme son nom l'indique, libérer la mémoire des trois objets ''arc_court'', ''arc_long'' et ''arc_composite'' créés dans le tas au début de la fonction main() en renvoyant une référence sur chaque objet.

   La fonction main() est ensuite divisée en quatre blocs dont les trois premiers saisissent les différents champs des trois objets créés tandis que le quatrième relit le tout.

   Pour terminer, la fonction ''liberation_memoire()'' est appelée.

   Ce qui nous donne comme résultat dans la console : 

 

 


   Corrigé de notre petit challenge (exercice 2)

 

   L'énoncé de cet exercice étant relativement long, je vous demanderai de bien vouloir vous en rappeler en le relisant tel qu'écrit dans le chapitre 13. Merci de votre compréhension. wink

   Voici le code :

 

//projet Chap13Exercice_2
//Une fonction qui calcule le stock d'éléments de structures
#include<iostream>
#include<string>
#include<conio.h>
 
using namespace std;
using uint = unsigned int;
 
struct inventaire_marchand //structure pour l'inventaire du marchand
{
static string nom_marchand;
string nom_item;
uint prix_vente;
uint quantite_en_stock;
};
 
struct inventaire_heros //structure pour l'inventaire du héros
{
string nom_item;
uint prix_achat;
uint prix_revente;
uint quantite_en_stock;
};
 
uint Transactions(inventaire_marchand &item_marchand, inventaire_heros &item_heros);
string inventaire_marchand::nom_marchand = "Ethiolas";
 
int main()
{
//Création d'objets dans le tas renvoyant un pointeur sur les objets
inventaire_marchand *item_marchand = new inventaire_marchand;
inventaire_heros *item_heros = new inventaire_heros;
 
cout << endl;
cout << "Au depart, inventaire marchand = 5 potions et inventaire heros = 0 potion" << endl << endl;
//Remplissage des champs de la structure inventaire_marchand
item_marchand->nom_item = "Petite potion de vie";
item_marchand->prix_vente = 80;
item_marchand->quantite_en_stock = 5;
 
//Remplissage des champs de la structure inventaire_heros
item_heros->nom_item = "Petite potion de vie";
item_heros->prix_achat = 80;
item_heros->prix_revente = 8;
item_heros->quantite_en_stock = 0;
 
//Lecture des champs de la structure inventaire_heros
//Au départ, pas de potion de vie dans l'inventaire
cout << "Lecture des champs de l'objet 'inventaire_heros'" << endl << endl;
cout << "Nom de l'item : " << item_heros->nom_item << endl;
cout << "Prix d'achat d'un item : " << item_heros->prix_achat << " PO" << endl;
 
cout << "Prix de revente d'un item : " << item_heros->prix_revente << " PO" << endl;
cout << "Quantite en stock : " << item_heros->quantite_en_stock << " piece(s)" << endl;
 
cout << endl;
//Appel de la fonction Transactions()
Transactions(*item_marchand, *item_heros);
cout << endl;
 
//Résultats après transactions
cout << "Retour dans main() : Resultats apres transactions" << endl << endl;
cout << "Inventaire du marchand " << item_marchand->nom_marchand<< endl;
cout << item_marchand->nom_item << " = " << item_marchand->quantite_en_stock << endl << endl;
 
cout << "Inventaire heros" << endl;
cout << item_heros->nom_item << " = " << item_heros->quantite_en_stock << endl;
 
//Libération mémoire
delete item_marchand;
delete item_heros;
 
_getch();
return 0;
}
 
 
uint Transactions(inventaire_marchand &item_marchand, inventaire_heros &item_heros)
{
//Le héros achète 2 petites potions de vie au marchand
cout << "Fonction Transactions() Le heros achete 2 potions de vie au marchand" << endl;
 
cout << "Mise a jour des inventaires 'marchand' et 'heros'" << endl << endl;
item_marchand.quantite_en_stock -= 2;
item_heros.quantite_en_stock += 2;
 
return 0;
}

 

   Bien, le but de cet exercice était donc de mettre à jour les stocks de l'inventaire du héros et du marchand après achat de deux petites potions de vie à celui-ci. wink

   Au départ, nous créons deux structures dénommées ''inventaire_marchand'' et ''inventaire_heros''.

   La structure ''inventaire_marchand'' ne comporte pas de variable membre ''prix_achat''. Il nous importe peu de savoir comment Ethiolas s'est procuré ses potions ni à quel prix il les a achetées. indecision

   Nous avons cependant ajouté une variable ''static'', représentant le nom de notre marchand et qui est définie à la suite des structures (voir chapitre 13 - §1.6 : Initialisation d'éléments ''static'' dans une structure).
   Dans la fonction main(), on crée deux objets dans le tas, ''item_marchand'' et ''item_heros'' avec new renvoyant un pointeur sur chacun des objets. Les variables membres de nos deux structures sont ensuite initialisées avec respectivement 5 et 0 comme quantités de potions contenues au départ dans les inventaires de nos personnages. wink

   On appelle ensuite la fonction Transactions() en lui passant des pointeurs sur les objets. Dans cette fonction qui ''simule'' l'achat de 2 petites potions de vie par notre héros, nous mettons les deux inventaires à jour en enlevant 2 unités à la variable ''quantite_en_stock'' de l'objet ''item_marchand'' et en ajoutant deux unités à la variable ''quantite_en_stock'' de l'objet ''item_heros''.

   Ce qui affiche dans la console :

 

 

   Et voilà, c'est tout en ce qui concerne ce chapitre.

   Les corrigés des exercices de ce chapitre seront présentés à la fin du chapitre 15.

      @ bientôt pour le chapitre 15 – Introduction aux Classes (1).                                                                     

            Gondulzak.  angel
 
 
 
 

Connexion

CoalaWeb Traffic

Today270
Yesterday178
This week770
This month4444
Total1743651

25/04/24