IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
logo

FAQ C++Consultez toutes les FAQ

Nombre d'auteurs : 34, nombre de questions : 368, dernière mise à jour : 14 novembre 2021  Ajouter une question

 

Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums de http://www.developpez.com et de l'expérience personnelle des auteurs.

Je tiens à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur ou si vous souhaitez devenir rédacteur, lisez ceci.

Sur ce, nous vous souhaitons une bonne lecture.

SommaireLa STLConteneurs (8)
précédent sommaire suivant
 

Un conteneur (container) est, comme son nom l'indique, un objet qui contient d'autres objets. Il fournit un moyen de gérer les objets contenus (au minimum ajout / suppression, parfois insertion, tri, recherche…) ainsi qu'un accès à ces objets qui dans le cas de la STL consiste très souvent en un itérateur. Les itérateurs permettent de parcourir une collection d'objets sans avoir à se préoccuper de la manière dont ils sont stockés. Ceci permet aussi d'avoir une interface de manipulation commune, et c'est ainsi que la STL fournit des algorithmes génériques qui s'appliquent à la majorité de ses conteneurs (tri, recherche…).
Parmi les conteneurs disponibles dans la STL on trouve les tableaux (vector), les listes (list), les ensembles (set), les piles (stack), et beaucoup d'autres.

Mis à jour le 19 octobre 2004 Aurelien.Regat-Barrel LFE Luc Hermitte

Les itérateurs (iterator) sont une généralisation des pointeurs : ce sont des objets qui pointent sur d'autres objets. Comme son nom l'indique, les itérateurs sont utilisés pour parcourir une série d'objets de telle façon que si on incrémente l'itérateur, il désignera l'objet suivant de la série.

Mis à jour le 20 avril 2003 LFE

La panoplie de conteneurs proposée par la STL est conséquente : conteneurs ordonnés, associatifs, listes chaînées…
Le choix de la bonne structure dépend principalement des opérations que l'on va effectuer sur nos données : suppression, ajout, recherche…
Voici un schéma récapitulatif qui vous aidera à y voir plus clair :



Pour stocker des chaînes de caractères, std::string sera le conteneur le plus approprié. Il fournit des fonctions de recherche, de découpage, et des opérateurs surchargés pour une manipulation plus intuitive (voir Y a-t-il un type chaîne de caractères en C++ ?). std::string n'étant qu'un typedef sur std::basic_string<char>, pour manipuler d'autres types de caractères (unicode par exemple, ou ne tenant pas compte de la casse) il suffit d'utiliser le type approprié (voir Comment manipuler des chaînes de caractères Unicode ? et Comment manipuler des chaînes de caractères ne tenant pas compte de la casse ?).

À noter qu'il existe également une classe pour manipuler les champs de bits : std::bitset.

On peut aussi trouver dans certaines versions de la STL d'autres conteneurs, non standards mais bien connus : tables de hachage, listes simplement chaînées, etc. Leur utilisation est généralement similaire à celle des autres conteneurs standards, mais pensez tout de même à bien vous documenter avant de les utiliser !

Mis à jour le 17 octobre 2005 Laurent Gomila

L'utilisation de code C dans un projet C++ est parfois inévitable, si l'on doit manipuler des bibliothèques écrites en C ou encore des fonctions codées dans un mauvais style. Il convient dans ce cas de prendre quelques précautions si l'on manipule des tableaux.

  • Utilisez std::vector si vous devez passer un tableau à une fonction C, car seul ce conteneur garantit la contiguïté des données en mémoire.

    Code c++ : Sélectionner tout
    1
    2
    3
    4
    void Fonction_C(const int* Tableau, unsigned int Taille); 
      
    std::vector<int> v; 
    Fonction_C(&v[0], v.size());
  • Si vous devez alimenter un conteneur avec les données d'un tableau C, la STL fournit tout ce qu'il faut pour le faire proprement.

    Code c++ : Sélectionner tout
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    int* Fonction_C(int* Taille); // Renvoie un tableau et sa taille 
      
    int Taille = 0; 
    int* Tableau = Fonction_C(&Taille); 
      
    // Méthode 1 : utilisation du constructeur prenant en paramètre une paire d'itérateurs 
    std::set<int> s(Tableau, Tableau + Taille); 
      
    // Méthode 2 : utilisation de la fonction membre assign 
    std::list<int> l; 
    l.assign(Tableau, Tableau + Taille); 
      
    // Méthode 3 : utilisation de std::copy 
    std::vector<int> v; 
    std::copy(Tableau, Tableau + Taille, std::back_inserter(v));
  • Pour les chaînes de caractères, std::string permet d'obtenir une chaîne C (caractères contigus et '\0' final) via sa fonction membre c_str().
    Attention cependant : la chaîne renvoyée peut très bien être temporaire et ne pas survivre à l'appel, il ne faut donc jamais stocker le pointeur retourné pour l'utiliser ultérieurement.
    Voir Comment convertir un char* en un string ? et Comment convertir une string en char* ?.

Mis à jour le 17 octobre 2005 Laurent Gomila

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include <iostream> 
#include <vector> 
#include <algorithm> 
#include <numeric> 
  
  
int main() 
{ 
    using std::cout; 
  
    // créer un tableau d'entiers, vide 
    std::vector<int> v; 
  
    // ajouter l'entier 10 à la fin 
    v.push_back( 10 ); 
  
    // afficher le premier élément (10) 
    cout << v.front() << '\n'; 
  
    // afficher le dernier élément (10) 
    cout << v.back() << '\n'; 
  
    // enlever le dernier élément 
    v.pop_back(); // supprime '10' 
  
    // le tableau est vide 
    if ( v.empty() ) 
    { 
        cout << "Tout est normal : tableau vide\n"; 
    } 
  
    // redimensionner le tableau 
    // resize() initialise tous les nouveaux entiers à 0 
    v.resize( 10 ); 
  
    // quelle est sa nouvelle taille ? 
    cout << v.size() << '\n'; // affiche 10 
  
    // sa taille est de 10 : on peut accéder directement aux 
    // 10 premiers éléments 
    v[ 9 ] = 5; 
  
    // initialiser tous les éléments à 100 
    std::fill( v.begin(), v.end(), 100 ); 
  
    // vider le tableau 
    v.clear(); // size() == 0 
  
    // on va insérer 50 éléments 
    // réserver (allouer) de la place pour au moins 50 éléments 
    v.reserve( 50 ); 
  
    // vérifier que la taille n'a pas bougée (vide) 
    cout << v.size() << '\n'; 
  
    // capacité du tableau = nombre d'éléments qu'il peut stocker 
    // sans devoir réallouer (modifié grâce à reserve()) 
    cout << v.capacity() << '\n'; // au moins 50, sûrement plus 
  
    for ( int i = 0; i < 50; ++i ) 
    { 
        // grâce à reserve() on économise de multiples réallocations 
        // du fait que le tableau grossit au fur et à mesure 
        v.push_back( i ); 
    } 
  
    // afficher la nouvelle taille 
    cout << v.size() << '\n'; // affiche 50 
  
    // rechercher l'élément le plus grand (doit être 49) 
    cout << *std::max_element( v.begin(), v.end() ) << '\n'; 
  
    // tronquer le tableau à 5 éléments 
    v.resize( 5 ); 
  
    // les trier par ordre croissant 
    std::sort( v.begin(), v.end() ); 
  
    // parcourir le tableau 
    for ( size_t i = 0, size = v.size(); i < size; ++i ) 
    { 
        // attention : utilisation de l'opérateur [] 
        // les accès ne sont pas vérifiés, on peut déborder ! 
        cout << v[ i ] << '\t';         
    } 
    cout << '\n'; 
  
    // utilisation de at() : les accès sont vérifiés 
    try 
    { 
        v.at( v.size() ) = 10; // accès en dehors des limites ! 
    } 
    catch ( const std::out_of_range & ) 
    { 
        cout << "at() a levé une exception std::out_of_range\n"; 
    } 
  
    // parcours avec un itérateur en inverse 
    for ( std::vector<int>::reverse_iterator i = v.rbegin(); 
          i != v.rend(); 
          ++i ) 
    { 
        cout << *i << '\t'; 
    } 
    cout << '\n'; 
  
    // on crée un tableau v2 de taille 10 
    std::vector<int> v2( 10 ); 
    v2.at( 9 ) = 5; // correct, le tableau est de taille 10 
  
    // on crée un tableau v3 de 10 éléments initialisés à 20 
    std::vector<int> v3( 10, 20 ); 
  
    // faire la somme de tous les éléments de v3 
    // on doit obtenir 200 (10 * 20) 
    cout << std::accumulate( v3.begin(), v3.end(), 0 ) << '\n'; 
  
    // on recopie v3 dans v 
    v = v3; 
  
    // on vérifie la recopie 
    if ( std::equal( v.begin(), v.end(), v3.begin() ) ) 
    { 
        cout << "v est bien une copie conforme de v3\n"; 
    } 
}

Mis à jour le 22 novembre 2004 Aurelien.Regat-Barrel

La réponse dépend de la nature de ce qui est stocké dans un vecteur.
S'il s'agit d'un objet, il n'est pas utile de le détruire, il le sera lorsqu'il est retiré du vecteur, ou lorsque le vecteur est détruit.
Par contre, s'il s'agit d'un pointeur sur un objet, il faut le détruire car un pointeur n'est pas un objet. Si cette destruction n'est pas faite, le programme présentera une fuite de mémoire.

Mis à jour le 9 octobre 2003 LFE

Effacer des éléments d'un conteneur est une tâche fréquente, mais souvent mal réalisée.

L'approche la plus naïve est de parcourir le conteneur à l'aide d'une boucle, et de supprimer (via la fonction membre erase()) les éléments concernés. Mais attention, il faut veiller à bien manipuler l'itérateur afin de ne pas oublier d'éléments ou de pointer en dehors du conteneur.
Le méthode correcte est la suivante :

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
std::vector<int> v; 
for (std::vector<int>::iterator it = v.begin(); it != v.end(); ) 
{ 
    if (*it == 5) 
        it = v.erase(it); 
    else 
        ++it; 
}
Il existe cependant des fonctions plus appropriées pour chaque conteneur.
Pour std::vector et std::string, on utilise ce qu'on appelle l'idiome erase-remove : on appelle std::remove() (ou std::remove_if() qui va prendre en paramètre un prédicat plutôt qu'une valeur -- voir Qu'est-ce qu'un prédicat ?) qui va déplacer les éléments à supprimer à la fin du conteneur, puis la fonction membre erase() qui va les supprimer définitivement.

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <string> 
#include <vector> 
#include <algorithm> 
  
int main() { 
    std::string s = "blah blah"; 
    s.erase(std::remove(s.begin(), s.end(), 'h'), s.end()); 
    // s = "bla bla" 
  
    std::vector<int> v {1, 3, 5, 7, 9, 11}; 
    v.erase(std::remove_if(v.begin(), v.end(), [](int value) { return value > 3; }), v.end()); 
    // v = {1, 3} 
}
Pour std::list, on utilise plus simplement la fonction membre remove() (ou remove_if()) qui contrairement à std::remove() va bien supprimer les éléments.

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
#include <list> 
  
int main() { 
    std::list<int> l {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 
    l.remove(5); 
    // l = {1, 2, 3, 4, 6, 7, 8, 9, 10} 
  
    l.remove_if([](int value) { return value > 3; }); 
    // l = {1, 2, 3} 
}
Pour les std::(unordered_)(multi)set et les std::(unordered_)(multi)map il existe la fonction membre erase(), qui remplit exactement le même rôle que std::list::remove() (attention aux noms, cela peut facilement porter à confusion). À noter que pour les std::(unordered_)(multi)map, erase() prend en paramètre la clé à effacer, non la valeur.

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
#include <map> 
#include <string> 
  
int main () { 
    std::unordered_map<std::string, int> m { 
        {"un",    1}, 
        {"deux",  2}, 
        {"trois", 3} 
    }; 
    m.erase("deux"); 
    // m = { {"un", 1}, {"trois", 3} } 
}

Mis à jour le 23 novembre 2019 Laurent Gomila

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <list>  
#include <algorithm> 
  
// Foncteur servant à libérer un pointeur - applicable à n'importe quel type 
struct Delete  
{  
   template <class T> void operator ()(T*& p) const  
   {  
      delete p; 
      p = NULL; 
   }  
};  
  
int main()  
{  
   // Création d'une liste de pointeurs  
   std::list<int*> l;  
   l.push_back(new int(5));  
   l.push_back(new int(0));  
   l.push_back(new int(1));  
   l.push_back(new int(6));  
  
   // Destruction de la liste : attention il faut bien libérer les pointeurs avant la liste !  
   std::for_each(l.begin(), l.end(), Delete());  
  
   return 0;  
}
Voir aussi la question [Exemple] Comment détruire les pointeurs d'un conteneur en utilisant Boost ?.

Mis à jour le 19 octobre 2004 Laurent Gomila

Proposer une nouvelle réponse sur la FAQ

Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.