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.
- Qu'est-ce qu'un conteneur ?
- Qu'est-ce qu'un itérateur ?
- Quel conteneur choisir pour stocker mes objets ?
- Comment utiliser correctement les conteneurs standards avec du code C ?
- Comment créer et utiliser un tableau avec std::vector ?
- Dois-je détruire ce que j'ai stocké dans un vecteur ?
- Comment supprimer correctement des éléments d'un conteneur ?
- [Exemple] Comment détruire les pointeurs d'un conteneur ?
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.
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.
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 !
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
4void 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
15int* 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* ?.
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"; } } |
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.
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; } |
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} } |
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} } |
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} } } |
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; } |
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 çaLes 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.