Big Tuto : Apprenez le C++

Chapitre 23 : Les conteneurs séquentiels (2)

 

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

 

Retrouvez les projets complets de ce chapitre :

  

 

 

   Préliminaires

   Avec ce 23ème chapitre nous continuons notre découverte des conteneurs séquentiels. Le sujet étant relativement vaste mais aussi très important, nous l'écrirons sur trois chapitres afin que vous puissiez profiter d'un maximum d'éléments s'y rapportant. wink

   Nous commençons immédiatement par la suite du paragraphe 8 du chapitre 22 (Suppression d'éléments dans les conteneurs séquentiels). angel

 


1 – Opérations sur les conteneurs séquentiels (suppression d'éléments) - Suite :

   1.1 – Suppression d'éléments multiples

 

   Nous pouvons supprimer les éléments compris entre une paire d'itérateurs à l'aide de la fonction erase() en utilisant les membres begin et end du conteneur comme nous pouvons supprimer tous les éléments d'un conteneur avec la fonction clear().

   Voyons ceci avec un simple exemple dans lequel nous créons un conteneur ''vector'' de type double et un conteneur ''vector'' de type string.  

 

   Exemple : Projet 136ContSequentiels_5

 

//Projet 136ContSequentiels_5
//Fichier ContSequentiels_5.cpp
//Suppression d'éléments multiples dans un conteneur
#include <iostream>
#include <conio.h>
#include <vector>
#include <string>
 
using namespace std;
 
 
int main()
 
{
//Initialisation d'un vector de doubles
vector<double> val = { 3.5, -2.1, -9.9, 6.7, 12.4, 21.6, -17.1, -11.3, 7.9, 1.8 };
 
//Initialisation d'un vector de strings
vector<string> armes = { "Arc de la fureur de Myin", "Terrible hache de plaie""Dague de Nelos" };
 
cout << endl;
cout << "Creation d'un vector de 10 valeurs de type double" << endl;
cout << "Lecture du vector :" << endl << endl;
 
for (auto iter = begin(val); iter != end(val); ++iter)
cout << *iter << " ";
 
cout << endl << endl;
 
cout << "Creation d'un vector de 3 armes de type string" << endl;
cout << "Lecture du vector :" << endl << endl;
 
for (auto iter = begin(armes); iter != end(armes); ++iter)
cout << *iter << " " << endl;
 
//Dans le vector de doubles nous ne voulons conserver que les
//deux premiers et les deux derniers éléments du vector
//Nous allons donc supprimer les éléments compris entre
//begin() + 2 et end() - 2
 
val.erase(val.begin() + 2, val.end() - 2);
 
cout << endl;
cout << "On ne conserve que les deux premiers et les deux derniers" << endl;
cout << "elements du vector de doubles" << endl << endl;
cout << "Lecture du vector : ";
 
for (auto iter = begin(val); iter != end(val); ++iter)
cout << *iter << " ";
 
cout << endl << endl;
cout << "On supprime maintenant tous les elements du vector 'val' en utilisant les" << endl;
cout << "membres begin() et end()" << endl << endl;
 
val.erase(val.begin(), val.end());
 
cout << "Lecture du vector 'val' : ";
 
if (!val.empty())
{
for (auto iter = begin(val); iter != end(val); ++iter)
cout << *iter << " ";
}
else
cout << "Les elements du vector 'val' ont ete supprimes !" << endl << endl;
 
cout << "Finalement, on supprime tous les elements du vector 'armes' en utilisant la" << endl;
cout << "fonction clear()" << endl << endl;
 
armes.clear();
 
cout << "Lecture du vector 'armes' : ";
 
if (!armes.empty())
{
for (auto iter = begin(armes); iter != end(armes); ++iter)
cout << *iter << " ";
}
 
else
cout << "Les elements du vector 'armes' ont ete supprimes !" << endl;
 
_getch();
return 0;
} 

 

   Dans ce fichier nous créons donc un vector de doubles et un vector de strings. Nous allons montrer deux manières de suppression d'éléments multiples dans un conteneur. wink

   Après affichage du contenu de nos deux vectors, nous présentons la première manière d'effacer une suite d'éléments dans un conteneur.

   Nous ne voulons pas supprimer tous les éléments du vector ''val'' mais seulement une suite d'éléments quelconques. Nous choisissons de supprimer les éléments situés au-delà de la seconde position jusqu'à l'élément situé deux éléments avant la fin du vector ''val''. Ceci revient à dire que nous voulons conserver les deux premiers ainsi que les deux derniers éléments du vector. Nous savons que begin() retourne un itérateur sur le premier élément d'un vector et end() un autre sur le dernier plus un élément de ce vector.

   Les éléments à supprimer sont donc bien ceux compris entre begin() + 2 et end() - 2.

   L'instruction val.erase(val.begin() + 2, val.end() - 2) effacera ces éléments. wink

   Avec erase(), nous pouvons également effacer tous les éléments d'un conteneur. Notre vector ''val'' ne possède plus que quatre éléments après avoir effectué l'opération ci-dessus. Maintenant que begin() est le premier élément du vector modifié et end() en est le dernier élément, l'instruction val.erase(val.begin(), val.end()) va supprimer les éléments restants du vector ''val''.

   La fonction clear(), effacera quant-à-elle, tous les éléments d'un conteneur séquentiel. Donc pour supprimer entièrement le vector ''armes'' nous écrivons simplement : armes.clear();

   d'où l'identité des deux fonctions :

conteneur.clear() <=> conteneur.erase(conteneur.begin(), conteneur.end() )

   Et ceci nous donne comme résultat dans la console : 

 

 

 

 

   2 – Opérations spéciales du conteneur forward_list

      2.1 – Accès à un élément

 

   Dans le chapitre 22, § 1.1 – Types de conteneurs séquentiels, nous avons vu que contrairement aux autres conteneurs séquentiels, nous ne pouvons accéder au conteneur forward_list que dans une seule direction, du début vers la fin du conteneur.

   Le conteneur forward_list, étant considéré comme une liste simplement liée, nous allons voir à l'aide d'une illustration ce qui se passe lorsqu'on enlève un élément de la liste.

 

 Elément 1              Elément 2             Elément 3                Elément 4               Elément 5

 

   Supposons que nous devions supprimer l'élément n°4. Nous allons avoir besoin d'accéder à l'élément n°3 pour mettre à jour un nouveau lien. En effet, après suppression, l'élément n°3 va maintenant devoir pointer sur l'élément n°5 dans la liste. wink

   Ceci revient à dire que nous devons connaître l'emplacement de l'élément précédent l'élément à supprimer.

   Pour cette raison, l'insertion ou la suppression d'éléments dans une forward_list se fera toujours en opérant un changement à la suite d'un élément donné.

   Ces opérations se comporteront donc différemment des opérations effectuées sur les autres conteneurs séquentiels. Le conteneur séquentiel forward_list ne définira donc pas les opérations ''insert'', ''emplace'' ou ''erase'' mais utilisera d'autres membres dénommés ''insert_after'', ''emplace_after'' et ''erase_after''.

   Dans l'illustration ci-dessus, pour supprimer l'élément n°4, nous ferons appel à l'opération ''erase_after'' sur un itérateur positionné sur l'élément n°3. angel

   Et si nous voulons supprimer le premier élément de la liste, alors ? surprise

   Dans ce cas, le conteneur forward_list ne peut pas supprimer un élément à partir du membre begin. Il définit à la place un membre nommé ''before_begin'' qui retournera un itérateur sur l'élément précédent le premier élément de la liste. wink

 

 

      2.2 – Opérations d'insertion ou de suppression d'éléments dans une forward_list

 

fList.before_begin()  Itérateur se rapportant à un élément non existant juste avant le premier de la liste. Cet itérateur ne peut pas être déréférencé.
flist.cbefore_begin()  Retourne un const_iterator.
flist.insert_after(it, elem)  Insère un élément après celui pointé par l'itérateur it.
flist.insert_after(it,n,elem) n est le nombre d'éléments insérés.
flist.erase_after(it) Supprime l'élément après celui pointé par l'itérateur it.
flist.erase_after(it1, it2)
Supprime un ensemble d'éléments à partir de l'élément suivant l'élément ponté par l'itérateur it1 (élément pointé par it2 non inclus).
Retourne un itérateur sur l'élément suivant celui qui a été supprimé.

 

 

 

 

 

 

 

 

 

 

 

 

 

   Exercice (Un petit challenge)

 

   Ecrivez un programme dans lequel vous créerez une forward_list contenant des unsigned int de 1 à 15.

   Affichez la liste et recherchez tous les éléments impairs que vous supprimerez. Affichez à nouveau la liste modifiée. wink

 

 


   3 – Changer la taille d'un conteneur séquentiel

 

   Excepté le conteneur ''array'', nous pouvons modifier la taille d'un conteneur séquentiel. Cette taille peut-être agrandie ou rétrécie à l'aide de la fonction resize(). Cette fonction peut prendre un ou deux argument selon que l'on veuille ou non changer le type des éléments modifiant le conteneur.

 

Opérations qui modifient la taille d'un conteneur séquentiel (autre que array)
nom.resize(n) Change la taille du conteneur défini par ''nom''. Si n < nom.size(), les éléments du conteneur d'origine de position > n sont supprimés.
nom.resize(n, val) Change la taille du conteneur pour obtenir éléments. Les éléments supplémentaires à la taille d'origine du conteneur prennent la valeur ''val''.

 

 

   Quelques exemples :

   Soit une liste de 15 unsigned initialisés avec la valeur 5 :

list<uint> ulist (15, 5)

   ulist.resize(25)                  ajoute 10 élément de valeur 0 à la fin de la liste d'origine.
   ulist.resize(26, -5)             ajoute 1 élément de valeur -5 en fin de liste
   ulist.resize(10)                  supprime les 16 derniers éléments de la liste.

 

 


   4 – Gestion de la taille d'un vector

 

   Dans un vector, les éléments sont stockés les uns à la suite des autres. Ceci permet d'avoir un accès aléatoire rapide sur ce type de conteneur. wink

   Etant donné que les éléments sont contigus dans le conteneur et que celui-ci peut-être rétréci ou agrandi à volonté, considérons le cas où nous voudrions ajouter un élément dans un conteneur de type vector ou string mais qu'il n'y ait plus de place pour ce nouvel élément.

  Nous savons que le conteneur ne peut pas juste ajouter cet élément n'importe où dans la mémoire puisque tous les éléments doivent être contigus. Il va donc s'ensuivre des opérations relativement pénalisantes pour une exécution rapide de la gestion de ce conteneur. En effet, le conteneur va devoir allouer un nouvel emplacement mémoire pour y transférer tous les éléments existants plus le nouvel élément à ajouter. En plus, il va devoir désallouer l'ancien emplacement mémoire. Si ces opérations doivent se répéter chaque fois que l'on ajoute un élément dans le vector, la performance de l'accès à ce conteneur va rapidement chuter. blush

   A cet effet, les conteneurs vector et string nous fournissent des membres qui permettent à l'utilisateur d'interagir directement sur l'allocation mémoire. wink

 

Fonctions de gestion de la taille d'un conteneur
nom.capacity() Nombre d'élément que le conteneur défini par ''nom'' peut stocker avant qu'une ré-allocation mémoire ne soit nécessaire.
nom.reserve(n) Alloue de l'espace mémoire pour au moins éléments.
nom.shrink_to_fit() Demande de réduction de capacité pour avoir capacity() = size().

 

 

Remarques :

      capacity et reserve ne sont valides que pour les conteneurs vector et string.
      shrink_to_fit est valide pour les conteneurs vector, string et deque.

 

 

   Bien, ces fonctions de gestion de la taille d'un conteneur demandent quand-même quelques explications complémentaires. cheeky

   L'opération reserve ne change pas le nombre d'éléments d'un conteneur, elle affecte seulement la quantité de mémoire que le conteneur va pré-allouer.

   Un appel à reserve() changera la capacité d'un vector seulement si l'espace requis excède la capacité actuelle.

   Si la taille désirée est supérieure à la capacité courante, la fonction reserve() allouera au minimum la même quantité que la valeur désirée mais l'allocation est souvent supérieure à celle-ci. wink   

   Si la taille désirée est inférieure ou égale à la capacité courante, la fonction reserve() sera ignorée.

   Il s'ensuit qu'après avoir appelé la fonction reserve(), la capacité sera soit plus grande soit égale à la valeur n passée à la fonction.

 

   De le même manière, la fonction resize() ne changera que le nombre d'élément d'un conteneur, pas sa capacité.

   Le standard C++ 11 a ajouté la fonction shrink_to_fit() pour demander à un conteneur vector, string ou deque, de pouvoir désallouer une quantité de mémoire non nécessaire. Le programme est cependant libre d'ignorer cette requête et il n'y a aucune garantie qu'un appel à shrink_to_fit() ne désalloue une quelconque quantité de mémoire excédentaire. frown

 

 


      4.1 - Taille et capacité d'un vector

 

   Nous allons maintenant, à l'aide d'un exemple, mieux nous rendre compte des subtilités apportées par ces différentes fonctions.

   Exemple : Projet 137ContSequentiels_6 

 

//Projet 137ContSequentiels_6
//Taille et capacité d'un vector
#include <iostream>
#include <conio.h>
#include <vector>
 
using namespace std;
using uint = unsigned int;
 
 
int main()
 
{
//Création d'un vector d'unsigned vide
vector<uint> uvec;
 
cout << endl;
cout << "uvec est un vector d'entiers non signes vide" << endl;
cout << "La taille du vector uvec est de " << uvec.size() << " elements" << endl;
cout << "Sa capacite est de " << uvec.capacity() << " elements" << endl << endl;
 
//On ajoute 20 éléments dans le vector
cout << "On ajoute 20 elements dans le vector" << endl;
 
for (vector<uint>::size_type i = 0; i != 20; ++i)
uvec.push_back(i);
 
cout << "La taille du vector uvec est de " << uvec.size() << " elements" << endl;
cout << "Sa capacite est de " << uvec.capacity() << " elements" << endl << endl;
 
//On reserve de l'espace additionnel pour 100 éléments
cout << "On reserve de l'espace additionnel pour un total de 100 elements" << endl;
uvec.reserve(100);
 
cout << "La taille du vector uvec est de " << uvec.size() << " elements" << endl;
cout << "Sa capacite est de " << uvec.capacity() << " elements" << endl << endl;
 
//Maintenant nous allons utiliser toute la capacité du vector
cout << "Maintenant on utilise la capacite totale du vector" << endl;
 
while (uvec.size() != uvec.capacity())
uvec.push_back(uvec.size());
 
cout << "La taille du vector uvec est de " << uvec.size() << " elements" << endl;
cout << "Sa capacite est de " << uvec.capacity() << " elements" << endl << endl;
 
//On augmente de 1 élément la taille du vector
cout << "On augmente de 1 element la taille du vector" << endl;
uvec.push_back(uvec.size());
 
cout << "La taille du vector uvec est de " << uvec.size() << " elements" << endl;
cout << "Sa capacite est de " << uvec.capacity() << " elements" << endl << endl;
 
//Nous voulons maintenant désallouer de la mémoire excédentaire
cout << "Nous voulons maintenant desallouer de la memoire excedentaire" << endl;
uvec.shrink_to_fit();
 
cout << "La taille du vector uvec est de " << uvec.size() << " elements" << endl;
cout << "Sa capacite est de " << uvec.capacity() << " elements" << endl << endl;
 
_getch();
return 0;
}  

 

   Quelques remarques importantes sur ce programme :

  Nous commençons par créer un vector vide ''uvec'' d'entiers non signés. Nous affichons ensuite sa taille et sa capacité et à ce moment, celles-ci sont bien entendu de 0 chacune.

  Nous ajoutons ensuite 20 éléments dans le vector. Sa taille passe donc à 20 et sa capacité devrait également être de 20 au minimum. Nous voyons que le conteneur étend la capacité de uvec à 28, ce qui est tout à fait possible car si la capacité doit au moins être égale à la taille d'un vector, elle peut cependant avoir une plus grande étendue.

   Nous réservons ensuite un espace additionnel pour avoir une capacité de 100 éléments dans le vector. uvec.reserve(100) porte la capacité du vector à 100 mais n'intervient pas dans sa taille qui reste actuellement de 20 éléments.

   On décide par la suite d'utiliser toute la capacité du vector en augmentant sa taille avec les éléments 21....100. wink

   Les deux lignes suivantes procèdent à cette opération :

while (uvec.size() != uvec.capacity())
uvec.push_back(uvec.size());

 

   En effet, tant que la capacité du vector n'est pas atteinte, la fonction push_back() ajoute à chaque tour de boucle un élément de valeur équivalente à la taille du vector, celui-ci étant bien entendu incrémenté d'une unité par tour. En fin de boucle notre vector uvec possède donc une taille et une capacité de 100 chacune.

   Par la suite nous désirons augmenter la taille du vector de 1 élément, bien que la capacité du vector soit à ce moment égal à sa taille (100). Nous allons donc maintenant nous trouver dans le cas cité plus haut où le conteneur va devoir allouer un nouvel emplacement mémoire pour y transférer tous les éléments existants plus le nouvel élément à ajouter, et en plus, il va devoir désallouer l'ancien emplacement mémoire. A la suite de quoi la taille du vector passe à 101 éléments, le conteneur étendant sa capacité à une valeur de 150 afin d'avoir une réserve supplémentaire. wink

   Pour terminer, nous décidons de désallouer cette mémoire excédentaire à l'aide de la méthode shrink_to_fit(). Et comme, nous l'avons dit auparavant, bien que celle-ci soit tout à fait libre d'ignorer notre requête, elle ramène la capacité de uvec égale à sa taille, c'est-à-dire 101.


   Et ceci nous donne comme résultat dans la console : 

 

 

Remarque :

   Vous aurez noté que pour l'ajout de 20 éléments dans le vector uvec, nous avons utilisé le type size_type pour l'initialisation de la boucle. Nous en avons parlé dès le chapitre 7 quand nous l'avons associé à la classe ''string''. Dans le chapitre 7 nous disions:

''Il se fait que la classe ''string'' ainsi que d'autres types de librairies définissent un certain nombre de ce qu'on appelle des ''types compagnons'' ou ''companions types''. Le type size_type est l'un de ces types compagnons et pour pouvoir l'utiliser nous devons ajouter l'opérateur de résolution de portée pour signaler qu'il est défini dans la classe <string>''.

 

   Dans notre exemple ci-dessus, le conteneur n'est pas ''string'' mais ''vector'' d'où notre boucle :

for (vector<uint>::size_type i = 0; i != 20; ++i)
uvec.push_back(i);

 

   Ce petit rappel effectué, notez que nous aurions pu utiliser, comme nous en avons l'habitude maintenant, le spécificateur de type ''auto'' pour un résultat absolument identique et plus facile à implémenter. cheeky

for (auto i = 0; i != 20; ++i)

 uvec.push_back(i);

 

 

 

   5 – Modification de champs dans des objets d'un vector d'objets

 

   Considérons l'objet suivant :

Arme arme_2("Arc de la fureur de Myin", "Arc long composite", 5, 900, 2.5, 220);

 

   Nous avons ici un objet dénommé ''arme_2'' et appartenant à la classe Arme. A l'intérieur des parenthèses, nous voyons que cet objet contient 6 champs :

- Le champ ''Nom''       contient ''Arc de la fureur de Myin''
- Le champ ''Type''       contient ''Arc long composite''
- Le champ ''Degats''   contient la valeur ''5''
- Le champ ''Etat''        contient la valeur ''900''
- Le champ ''Poids''     contient la valeur ''2.5''
- Le champ ''Prix''        contient la valeur ''220''

   Nous allons maintenant voir comment modifier certains de ces champs dans des objets rassemblés dans un vecteur d'objets de la classe Arme. Ceci nous sera très utile pour la réalisation d'un exercice commenté sur une gestion Achats/Ventes entre un avatar et un marchand que nous pourrions introduire dans un rpg.

   Nous allons créer 9 objets de type Arme dans un projet contenant 3 fichiers comme nous l'avons déjà fait plusieurs fois jusqu'ici.

 

   Voyons ceci à l'aide d'un exemple :

      Projet 138ContSequentiels_7

         Fichier : ContSequentiel_7.h 

 

//Projet 138ContSequentiels_7
//Accéder aux champs des éléments d'un vector d'une classe "Armes"
//Fichier ContSequentiels_7.h
 
#ifndef DEF_138CONTSEQUENTIELS_7
#define DEF_138CONTSEQUENTIELS_7
 
#include <iostream>
#include <string>
 
using uint = unsigned int;
 
class Arme
{
 
public:
 
//Constructeur
Arme(std::string nom, std::string type, uint degats, uint etat, double poids, double prix);
 
//Constructeur de copie
Arme(const Arme &rhs);
 
//Destructeur
~Arme();
 
//Méthodes membres d'accès publiques
//Assignation de données
void set_Name(std::string nom);
void set_Type(std::string type);
void set_Degats(uint degats);
void set_Etat(uint etat);
void set_Poids(double poids);
void set_Prix(double prix);
 
//Acquisition de données
std::string get_Name() const;
std::string get_Type() const;
uint get_Degats() const;
uint get_Etat() const;
double get_Poids() const;
double get_Prix() const;
 
 
private:
 
//Données membres privées
std::string _nom;
std::string _type;
uint _degats;
uint _etat;
double _poids;
double _prix;
 
};
 
 
//Fonction operator<< (définie en dehors de la classe)
inline
std::ostream &operator<<(std::ostream &out, Arme &rhs)
{
out << rhs.get_Name();
out << rhs.get_Type();
out << rhs.get_Degats();
out << rhs.get_Etat();
out << rhs.get_Poids();
out << rhs.get_Prix();
return out;
}
 
#endif 

 

   Dans le fichier header, à l'intérieur de la classe Arme, nous déclarons un constructeur, un constructeur de copie, le destructeur, les méthodes membres d'accès publique ainsi que les données membres privées.

   Ces dernières représentent respectivement, comme nous l'avons fait remarquer plus haut, les champs Nom, Type, Degats, Etat, Poids et Prix d'un objet de la classe Arme.

   A l'extérieur de la classe, nous définissons une fonction operator<< qui nous servira à afficher les objets dans la fonction mainFile() (Voir chapitre 20, § 1.1, ''Surcharge de l'opérateur '<<' de la classe ostream'').

 

   Fichier : ContSequentiel_7.cpp 

 

//Projet 138ContSequentiels_7
//Accéder aux champs des éléments d'un vector d'une classe "Armes"
//Fichier ContSequentiels_7.cpp
 
#include "ContSequentiels_7.h"
 
using namespace std;
 
//Constructeur
Arme::Arme(string nom, string type, uint degats, uint etat, double poids, double prix) :
_nom(nom), _type(type), _degats(degats), _etat(etat), _poids(poids), _prix(prix) {}
 
//Constructeur de copie
Arme::Arme(const Arme &rhs) : _nom(rhs.get_Name()), _type(rhs.get_Type()), _degats(rhs.get_Degats()),
_etat(rhs.get_Etat()), _poids(rhs.get_Poids()), _prix(rhs.get_Prix()) {}
 
//Destructeur
Arme::~Arme() {}
 
//Méthodes membres d'assignation de données
void Arme::set_Name(string nom) { _nom = nom; }
void Arme::set_Type(string type) { _type = type; }
void Arme::set_Degats(uint degats) { _degats = degats; }
void Arme::set_Etat(uint etat) { _etat = etat; }
void Arme::set_Poids(double poids) { _poids = poids; }
void Arme::set_Prix(double prix) { _prix = prix; }
 
//Méthodes membres d'acquisition de données
string Arme::get_Name() const { return _nom; }
string Arme::get_Type() const { return _type; }
uint Arme::get_Degats() const { return _degats; }
uint Arme::get_Etat() const { return _etat; }
double Arme::get_Poids() const { return _poids; }
double Arme::get_Prix() const { return _prix; } 

 

   Rien de spécial dans le fichier .cpp. Nous définissons les constructeurs et les méthodes membres déclarés dans le fichier ConSequentiels_7.h.

   Fichier : ContSeq_7MainFile.cpp

 

//Projet 138ContSequentiels_7
//Accéder aux champs des éléments d'un vector d'une classe "Armes"
//Fichier ContSeq_7MainFile.cpp
 
#include <iostream>
 
#include <conio.h>
#include <vector>
#include "ContSequentiels_7.h"
 
using namespace std;
 
 
int main()
 
{
//Création d'un vector et de 9 objets de type "Arme"
vector<Arme> uneArme;
 
Arme arme_1("Epee longue d'acier du heros Rabidja", "Lame longue une main", 4, 600, 3.5, 175.0);
Arme arme_2("Arc de la fureur de Myin", "Arc long composite", 5, 900, 2.5, 220);
Arme arme_3("Terrible hache de plaie", "Hache lourde deux mains", 7, 1600, 6.0, 450.0);
Arme arme_4("Dague furtive de l'Assassin", "Lame courte une main", 3, 300, 0.75, 150.0);
Arme arme_5("Claymore d'acier", "Lame longue deux mains", 5, 1100, 5.5, 280.0);
Arme arme_6("Lance cruelle perceuse d'ames", "Lance une main", 7, 1500, 4.5, 410.0);
Arme arme_7("Epee courte en fer", "Lame courte une main", 2, 300, 2.5, 140.0);
Arme arme_8("Arc de misere", "Arc court leger", 3, 600, 1.5, 155.0);
Arme arme_9("Coutelas en fer peu solide", "Lame courte une main", 1, 100, 0.5, 75.0);
 
 
//On remplit le vector uneArme
uneArme.push_back(arme_1);
uneArme.push_back(arme_2);
uneArme.push_back(arme_3);
uneArme.push_back(arme_4);
uneArme.push_back(arme_5);
uneArme.push_back(arme_6);
uneArme.push_back(arme_7);
uneArme.push_back(arme_8);
uneArme.push_back(arme_9);
 
//On affiche le vector
cout << "On affiche un vector d'armes :" << endl << endl;
 
for (auto iter = begin(uneArme); iter != end(uneArme); ++iter)
cout << *iter << " " << endl;
 
cout << endl;
 
/* ***Modification du champ "dégats" de l'objet arme_3*** */
cout << "On double les degats de " << arme_3.get_Name() << endl;
auto cible = begin(uneArme) + 2;
arme_3 = *cible;
arme_3.set_Degats(arme_3.get_Degats()*2);
 
//L'itérateur déréférencé contient maintenant la modif des dégats.
*cible = arme_3;
 
cout << "Les degats de " << arme_3.get_Name() << " sont passes a " << arme_3.get_Degats();
 
//On réaffiche le vector*/
cout << endl;
cout << "On affiche le vector armes modifie :" << endl << endl;
 
for (auto iter2 = begin(uneArme); iter2 != end(uneArme); ++iter2)
cout << *iter2 << " " << endl;
 
/* ***Modification du champ "état" de l'objet arme_8*** */
cout << endl;
cout << "Maintenant, l'arc de misere n'est plus en tres bon etat," << endl;
 
cout << "sa resistance a diminue de moitie" << endl;
auto cible2 = begin(uneArme) + 7;
arme_7 = *cible2;
arme_7.set_Etat(arme_7.get_Etat()/2);
 
//L'itérateur déréférencé contient maintenant la modif de l'état.
*cible2 = arme_7;
 
cout << "Les degats de " << arme_7.get_Name() << " sont passes a " << arme_7.get_Etat();
 
//On réaffiche le vector*/
cout << endl;
cout << "On affiche le vector armes modifie :" << endl << endl;
 
for (auto iter3 = begin(uneArme); iter3 != end(uneArme); ++iter3)
cout << *iter3 << " " << endl;
 
cout << "uneArme.size() = " << uneArme.size() << endl;
cout << "uneArme.capacity() = " << uneArme.capacity() << endl << endl;
 
_getch();
return 0;
 
} 

 

      Il n'y a rien de difficile au début de la fonction main(). Nous créons un vector de type Arme et nous initialisons neuf objets Arme avec lesquels nous remplissons notre vector. Le vector uneArme est ensuite affiché.

   Il faut remarquer que dans le vector, tous les champs d'un objet sont stockés directement les uns à la suite des autres. Cela signifie que si nous affichons l'objet arme_3, celui-ci sera représenté de cette façon à l'écran :

Terrible hache de plaieHache lourde deux mains716006450

   Ceci est donc l'objet arme_3 situé en troisième position dans le vector. Les 6 champs différents sont stockés les uns à la suite des autres, nous l'avons signalé un peu plus haut. wink


   Nous reconnaissons facilement tous ces champs représentés par les différentes variables du constructeur :

   string         :   Nom     :   Terrible hache de plaie
   string         :   Type     :   Hache lourde deux mains
   unsigned   :   Dégats :   7
   unsigned   :   Etat      :   1600
   double       :   Poids   :   6
   double       :   Prix      :   450

 

   Par la suite, nous décidons de modifier (doubler) la valeur du champ Dégâts de l'objet arme_3. Pour ce faire nous initialisons un itérateur dénommé ''cible'' que nous plaçons sur le troisième élément du vector uneArme :

auto cible = begin(uneArme) + 2

   Ici nous devons réfléchir. Il s'agit du champ ''Dégâts'' de l'objet situé dans le vector que nous voulons modifier et non l'objet crée au début de la fonction. C'est pourquoi nous devons affecter à arme_3 la valeur du contenu de l'emplacement mémoire pointé par l'itérateur ''cible'', soit l'itérateur déréférencé *cible.

   Et nous modifions le champ ''Dégâts'' en appliquant la méthode set_Degats(get_Degats()*2) sur l'objet arme_3 en lui passant la valeur 14 en paramètre, valeur obtenue par la méthode d'accès arme3.get_Degats()*2. Nous aurons donc bien doublé la valeur des dégâts.

   Il ne nous reste plus alors qu' à réassigner l'objet modifié dans le vector en écrivant *cible = arme_3.

   Un nouvel affichage du vector à l'écran nous montrera alors l'objet arme_3 avec la valeur du champ ''Dégats'' modifiée (voir la console ci-dessous wink).

   Et la modification du champ ''Etat'' de l'objet arme_8 (Arc de misère) suit le même raisonnement que ce qui vient d'être expliqué plus haut.

   Pour terminer, on affiche la taille et la capacité du vector.

   Et tout cela nous donne comme résultat dans la console :

 

 

 

   6 – Opérations STL sur le conteneur ''string''

 

   Dans le chapitre 5 nous avons effectué quelques opérations élémentaires sur les chaînes de caractères, que celles-ci soient issues de l'ancien Style-C ou de la classe ''string'' de la STL (Voir chapitre 5 - § 1.2 ''La classe string de C++'' ainsi que les opérations diverses sur les chaînes).

   Nous savons maintenant que ''string'' fait partie des conteneurs séquentiels et nous allons également voir que le type string propose d'autres opérations complémentaires pouvant être effectuées sur les chaînes de caractères, indépendamment des opérations communes à tous les conteneurs séquentiels.

   Dans leur plus grande partie, ces opérations additionnelles supportent une interaction étroite entre la classe string et les tableaux de caractères de style C.

 

 

      6.1 – Opérations additionnelles de construction de chaînes

 

Dans le chapitre 5 - § 1.2 ''La classe string de C++'', nous avons vu quelques définitions et initialisations de chaînes de caractères. En addition aux constructeurs couvrant celles-ci et aux constructeurs que string partage avec les autres conteneurs séquentiels, le type string supporte trois autres constructeurs que nous décrivons ci-dessous.

 

Opérations additionnelles de construction de chaînes
string chaine (ptr, n) chaine est une copie des premiers caractères d'un tableau sur lequel ptr pointe. Le tableau doit avoir au moins n caractères.
string chaine2 (chaine1, pos1) chaine2 est une copie des caractères de chaine1 à partir du caractère positionné à l'index pos1 de la chaîne chaine1.
Indéfini si pos1 > chaine1.size().
string chaine2 (chaine1, pos1, len1) chaine2 est une copie de len1 caractères à partir du caractère positionné à l'index pos1 de chaine1.
Indéfini si pos1 > chaine1.size(). Selon la valeur de len1, les caractères copiés ne dépasseront pas la quantité s1.size() - pos1.

 

 

   Exemple :  

 

//string chaine (ptr, n)
//donnera Meruvia comme résultat
char *tab[] = { "Bonjour Meruvia" };
string chaine (*tab + 8, 7);
std::cout << chaine << std::endl;
 
//string chaine2 (chaine1, pos1)
//donnera également Meruvia comme résultat
string chaine1 = "Bonjour Meruvia";
string chaine2(chaine1, 8);
std::cout << chaine2 << std::endl;
 
//string chaine3 (chaine1, pos1, len1)
//donnera Bonjour comme résultat
string chaine3(chaine1, 0, 7);
std::cout << chaine3 << std::endl; 

 

 

     6.2 – La fonction substr()

 

Opération substr
str.substr (pos, n) Retourne une chaîne contenant n caractères de str à partir de pos.

 

 

Exemple :     

 

//string str2 = str1.substr (pos, n)
//donnera Bonjour comme résultat
string str1("Bonjour Meruvia");
string str2 = str1.substr(0, 7);
std::cout << str2 << std::endl; 

 

      Si pos est omis, la copie se fera en position 0 par défaut. wink

  

//string str2 = str1.substr (n)
//donnera Bonjour comme résultat
string str1("Bonjour Meruvia");
string str2 = str1.substr(7);
std::cout << str2 << std::endl; 

 

     Si le nombre de caractères à copier est omis, la chaîne entière sera copiée.

 

//string str2 = str1.substr ()
//donnera 'Bonjour Meruvia' comme résultat
string str1("Bonjour Meruvia");
string str2 = str1.substr();
std::cout << str2 << std::endl; 

 

 

 

      6.3 – Modes d'accès aux caractères d'une chaîne de la STL

 

    Nous avons deux possibilités : ou bien utiliser nos bons vieux indices issus du C ou mieux, parcourir notre chaîne à l'aide d'itérateurs. angel

   Voyons ceci à l'aide d'un exemple :

      Projet 139ContSequentiels_8   

 

//Projet 138ContSequentiels_8
//Modes d'accès aux caractères d'une chaîne de la STL
#include <iostream>
#include <conio.h>
 
 
#include <string>
 
using namespace std;
 
 
int main()
{
 
string maChaine("Bonjour Meruvia");
cout << endl;
cout << "Initialisation de la chaine " << "'" << maChaine << "'" << endl << endl;
 
//Accès aux éléments de la chaîne à l'aide d'indices
cout << "Acces aux elements a l'aide d'indices :" << endl;
 
for (size_t compteur = 0; compteur != maChaine.size(); ++compteur)
cout << maChaine[compteur] << " ";
 
cout << endl << endl;
 
 
//Accès aux éléments de la chaîne à l'aide d'itérateurs
cout << "Acces aux elements a l'aide d'iterateurs :" << endl;
 
for (auto iter = maChaine.begin(); iter != maChaine.end(); ++iter)
cout << *iter << " ";
 
cout << endl << endl;
 
cout << "Nous pouvons ecrire la chaine entiere sous la forme string :" << endl;
cout << maChaine << endl << endl;
 
cout << "Ou egalement l'ecrire sous l'ancienne forme C :" << endl;
cout << maChaine.c_str() << endl;
 
_getch();
return 0;
 
} 

 

     Et nous voyons le résultat de notre exemple dans la console :

 

 

 

 

      6.4 – Opérations additionnelles de modification de chaînes


    En complément des versions qui utilisent insert, erase ainsi que les itérateurs, la classe string nous procure également d'autres versions utilisant un index. Cet index désigne le nombre  d'éléments à supprimer à partir de la fin de chaîne ou donne l'élément et sa quantité à insérer en fin de chaîne. 

 

//Ajoute un point d'exclamation en fin de chaîne
//Affiche Legends of Meruvia!
string chaine("Legends of Meruvia");
chaine.insert(chaine.size(), 1, '!');
std::cout << chaine << std::endl;
 
//Supprime les 12 derniers caractères précédant la fin de chaîne
//Affiche Legends
chaine.erase(chaine.size() - 12);
std::cout << chaine << std::endl; 

 

     La bibliothèque string procure également des versions de insert et assign qui se réfèrent à des tableaux de caractères de style-C.

 

//Ajoute les 7 premiers caractères du tableau dans la chaîne
//Affiche Legends
const char *tab = "Legends of Meruvia";
string chaine;
chaine.assign(tab, 7);
std::cout << chaine << std::endl;
 
//Insère à la fin de chaine.size() les 11 caractères situés a partir de tab + 7
//Affiche Legends of Meruvia
chaine.insert(chaine.size(), tab + 7);
std::cout << chaine << std::endl; 

 

     Et pour connaître le contenu des Légendes de Meruvia, demandez à … Roswyn !? laugh

 


   7 – Corrigés des exercices du chapitre 22

 

      Exercice 1 ( projet Chap22Exercice_1)

 

   Dans cet exercice on demandait d'écrire un programme avec une fonction qui affiche la liste inversée des entiers du projet 132ContSequentiels_1 du chapitre 22. Utilisez le conteneur ''deque''.

   Le code :

 

//Projet Chap22Exercice_1
//Une fonction qui lit les valeurs inversées d'un conteneur deque
 
#include <iostream>
 
#include <deque>
#include <conio.h>
 
using namespace std;
 
// Déclaration de la fonction Lis_liste()
void Lis_liste(deque<int> &nombres);
 
 
 
int main()
 
{
//Initialisation de la liste d'entiers
 
deque<int> nombres = { 3, -2, -9, 6, 12, 21, -17, -11, 7, 1 };
 
cout << endl;
cout << "Une fonction qui lit les valeurs inversees d'un conteneur 'deque'" << endl << endl;
 
Lis_liste(nombres);
 
_getch();
return 0;
}
 
 
void Lis_liste(deque<int> &nombres)
{
//Lecture de la liste à l'endroit
//Le domaine d'itération de la liste est défini par les membres begin et end.
auto debut = begin(nombres); // Identique à deque<int>::iterator debut =
                            // begin(nombres);
auto fin = end(nombres); // Identique à deque<int>::iterator fin = end(nombres);
 
cout << "Lecture de la liste a l'endroit :" << endl;
 
while (debut != fin)
{
cout << *debut++ << " ";
}
 
cout << endl << endl;
 
//Lecture de la liste inversée
//Le domaine d'itération de la liste est défini par les membres rbegin et rend.
auto debut2 = rbegin(nombres); // Identique à deque<int>::reverse_iterator debut =
                               // rbegin(nombres);
auto fin2 = rend(nombres); // Identique à deque<int>::reverse_iterator fin =
                           // rend(nombres);
 
cout << "Lecture de la liste inversee :" << endl;
 
while (debut2 != fin2)
{
cout << *debut2++ << endl;
}
 
} 

 

   Nous déclarons premièrement une fonction Lis_liste() à laquelle on passe une référence sur un conteneur deque d'entiers en paramètre.

   Dans la fonction main(), nous initialisons un conteneur deque avec une série d'entiers quelconques.

   On appelle ensuite la fonction Lis_liste(). Dans celle-ci, nous affichons en premier la liste à l'endroit. Nous utilisons pour ce faire les fonctions STL begin() et end(). Remarquez à nouveau l'écriture concise utilisant le spécificateur de type auto pour l'initialisation des variables ''debut'' et ''fin''.

   Pour rappel :

      auto debut = begin(nombres) <=> deque<int>::iterator debut = begin(nombres)
      auto fin = end(nombres) <=> deque<int>::iterator fin = end(nombres)

   Pour la lecture de la liste à l'envers, nous utilisons les membres rbegin et rend qui retournent un itérateur du dernier vers le premier élément moins un du conteneur (voir chapitre 22 §4 – Les différentes versions des membres begin et end d'un conteneur).

   Et vous devriez obtenir quelque chose de ce genre dans la console :  

 

 

 

   Exercice 2 ( projet Chap22Exercice_2)

 

   Pour cet exercice on demandait : Ecrivez une fonction à qui l'on passe une référence sur une chaîne représentant un mot quelconque. Cette fonction devra mettre le mot au pluriel et heu... si vous vous en souvenez, vous ferez un test sur les mots qui forment leur pluriel avec un 'X'. cheeky

   Pour les perfectionnistes, vous pouvez ajouter les cas où le mot possède déjà un 'S' ou un 'X' au singulier.

   Le code :

 

//Projet Chap23Exercice_2
//Une fonction qui met un mot au pluriel
 
#include <iostream>
#include <string>
#include <conio.h>
 
using namespace std;
 
// Déclaration de la fonction Pluriel()
 
void Pluriel(string &mot);
 
 
int main()
 
{
string unMot;
int nb_car(0);
 
cout << endl;
cout << "Entrez un mot au singulier : ";
 
cin >> unMot;
 
if (unMot.size() < 2)
cout << "Vous devez entrer au moins deux caracteres !" << endl;
else
{
cout << "Le mot entre est : ";
cout << unMot;
nb_car = unMot.size();
cout << endl;
 
cout << "Le nb de caracteres est : ";
cout << nb_car;
cout << endl;
 
Pluriel(unMot);
}
 
_getch();
return 0;
 
}
 
 
void Pluriel(string &mot)
{
//Caractère de fin de mot
char car_fin;
 
//Si il y a déjà un 's' ou un 'x' au singulier
if ((mot.back() == 's') || mot.back() == 'x')
cout << "Le pluriel de " << mot << " est : " << mot << endl;
 
else {
//Si un mot forme son pluriel en 'x'
if (mot == "chou" || mot == "genou" || mot == "hibou" || mot == "joujou" ||
mot == "bijou" || mot == "caillou" || mot == "pou")
car_fin = 'x';
else
//Sinon le pluriel prend un's'
car_fin = 's';
 
mot.push_back(car_fin);
cout << "Le pluriel de " << mot << " est : ";
cout << mot << endl;
}
 
} 

 

   Et voici le corrigé d'écriture d'une fonction de mise au pluriel d'un mot quelconque de type string.

   Après la déclaration de la fonction Pluriel() à laquelle on passe une référence sur une chaîne en paramètre, dans la fonction main() on vous demande d'entrer un mot d'au moins deux lettres.

   Ben oui, c'est quand-même un minimum pour former un pluriel, non ? cheeky

   La fonction size() nous retourne la longueur du mot entré. Le nombre de caractères est affiché et nous appelons la fonction Pluriel() qui mettra notre mot au pluriel selon les différents critères que nous nous sommes fixés.

   Dans la fonction Pluriel(), nous commençons par créer une variable de type char qui prendra un ''x'' ou un ''s'' suivant les cas envisagés.

   Notre premier test porte sur le cas où il y a déjà soit un s soit un x à la fin du mot que nous avons entré. Le dernier caractère de la chaîne est trouvé par la fonction back() (Voir chapitre 22 § 7.8 - ''Accéder aux éléments d'un conteneur séquentiel'').

   Et dans ce cas nous affichons simplement le pluriel (qui est égal au mot au singulier). cheeky

   Si le dernier caractère du mot n'est ni un s ni un x, nous faisons alors un test pour voir si notre mot forme son pluriel en x. Et ici nous nous devons nous rappeler quels sont ces mots et heu... blush c'est déjà peut-être loin pour certains, tout ça... laugh

   Bien, si le mot entré est un de ceux de notre test, nous mettons alors le caractère 'x' dans la variable char_fin, sinon le mot entré ne peut plus former son pluriel qu'avec un s et nous mettons le
caractère 's' dans la variable char_fin.

   La fonction push_back() ajoute alors le caractère de fin dans la chaîne et le mot au pluriel est affiché.

   Avec quelques résultats dans la console :  

 

 

 

   Exercice 3 (un petit challenge)

 

   Pour cet exercice on demandait de créer deux listes de valeurs de type double. Pour la première, il fallait reprendre la liste du projet 135ContSequantiels_4 du chapitre 22 et une liste vide pour la seconde, soit : 

 

list<double> val = { 3.5, -2.1, -9.9, 6.7, 12.4, 21.6, -17.1, -11.3, 7.9, 1.8 };

 

   ainsi qu'une liste vide pour recevoir des nombres négatifs, soit :

 

list<double> neg;

 

   Vous devrez enlever les nombres négatifs de la liste ''val'' et les transférer dans la liste ''neg''.

 

   Utilisez la fonction emplace_back() pour créer la nouvelle liste ''neg'' plutôt que de faire une copie des éléments négatifs de ''val''.

 

   Affichez ensuite les deux listes.

 

   Le code : 

 

 

//Projet Chap22Exercice_3
//Supprimer des éléments dans un conteneur pour
//les recréer dans un autre
#include <iostream>
#include <conio.h>
#include <list>
 
using namespace std;
 
 
int main()
{
 
//Initialisation de la liste d'entiers
list<double> val = { 3.5, -2.1, -9.9, 6.7, 12.4, 21.6, -17.1, -11.3, 7.9, 1.8 };
list<double> neg;
 
cout << endl;
cout << "Creation d'une liste de 10 valeurs de type double" << endl;
cout << "Lecture de la liste :" << endl << endl;
 
for (auto iter = begin(val); iter != end(val); ++iter)
cout << *iter << " ";
 
cout << endl << endl;
cout << "On recree les elements negatifs de la liste ci-dessus dans une" << endl;
cout << "nouvelle liste et on supprime ces elements negatifs dans la liste d'origine" << endl;
cout << endl;
 
//On initialise deux nouveaux itérateurs
auto iter2 = val.begin();
auto iter3 = iter2;
 
while (iter2 != val.end())
{
if (*iter2 < 0)
{
//On utilise iter3 pour créer un élément négatif dans la nouvelle liste
iter3 = iter2;
neg.emplace_back(*iter3);
 
//Et on supprime cet élément de la liste d'origine
iter2 = val.erase(iter2);
}
 
 
else ++iter2;
}
 
//On affiche les deux listes
cout << "Finalement on affiche les deux listes" << endl << endl;
 
for (auto iter2 = begin(val); iter2 != end(val); ++iter2)
cout << *iter2 << " ";
 
cout << endl;
 
for (auto iter3 = begin(neg); iter3 != end(neg); ++iter3)
cout << *iter3 << " ";
 
_getch();
return 0;
 
} 

 

    Ok, rien de chinois là-dedans. laugh On parcourt la liste de valeurs de type double à l'aide d'un itérateur de begin(val) à end(val) – 1 et on affiche ces valeurs.

   On initialise enuite deux autres itérateurs, iter2 et iter3, tous deux positionnés sur val.begin().

   Iter2 va parcourir toute la liste et lorsque le premier élément négatif sera trouvé, iter3 prendra alors la position de iter2 . La fonction emplace_back() crée et ajoute l'élément négatif dans la liste neg, soit l'itérateur déréférencé *iter3.

   L'élément négatif trouvé est alors supprimé de la liste val. Si l'élément pointé n'est pas un nombre négatif on incrémente alors iter2 et on continue à boucler.

   Pour terminer, on affiche les deux listes.

   Et ceci nous donne dans la console :

 

 

   

   Et c'est tout en ce qui concerne ce chapitre. cool

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

      @ bientôt pour le chapitre 24 – Conteneurs séquentiels et conteneurs adaptatifs (3).                                                                     

            Gondulzak.  angel
 
 
 

Connexion

CoalaWeb Traffic

Today160
Yesterday178
This week660
This month4334
Total1743541

25/04/24