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.
- 5.1. Conversions (8)
- Y a-t-il un type chaîne de caractères en C++ ?
- Quels sont les avantages de std::string par rapport à char* ?
- Utiliser string n'est-il pas plus lent ?
- Dans quels cas ne faut-il pas utiliser string ?
- Quelle est la différence entre char*, const char* et char const * ?
- Quelle est la différence entre #include <string> et #include <string.h> ?
- Quelle est la différence entre string::length() et string::size() ?
- Quelle différence entre string::size() et string::capacity() ?
- Quelle est la différence entre string::data() et string::c_str() ?
- Quelles précautions faut-il prendre avec string::c_str() et string::data() ?
- Quelle est la différence entre string::find() et string::find_first_of() ?
- Comment déterminer si une chaîne contient une valeur d'un certain type ?
- Comment manipuler des chaînes de caractères ne tenant pas compte de la casse ?
- Comment inverser le contenu d'une chaîne ?
- Comment découper une chaîne en fonction d'un séparateur ?
- Comment tester des chaînes de caractères dans un bloc switch ?
- Comment manipuler des chaînes de caractères Unicode ?
- Comment effectuer les conversions de texte ASCII < - > Unicode ?
- [Exemple] Comment supprimer des caractères d'une string ?
- [Exemple] Comment manipuler un tableau de string ?
Le C++ standard possède son propre type chaîne de caractères : std::string. Celui-ci est déclaré dans l'en-tête standard <string>.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | #include <iostream> // pour std::cout #include <string> // pour std::string int main() { std::string message = "Hello"; // création de la chaîne "Hello" message += " World !"; // concaténation de " Word !" std::cout << message << '\n'; // affichage de "Hello World !" } |
Au delà du simple conteneur de caractères, string est aussi et avant tout l'interface chaîne de caractères de la bibliothèque standard. Autrement dit en utilisant string vous bénéficiez des très nombreuses autres fonctionnalités de la bibliothèque standard, ce qui est plus délicat avec les char *.
Par exemple, il est possible de lire une ligne d'un fichier sans avoir à se préoccuper de sa taille.
Un travail important de gestion et de vérification est fait en toute transparence, ce qui rend le code plus maintenable. string est donc beaucoup plus simple et sûre d'utilisation que les nombreuses fonctions utilisant les char *.
Programmer en utilisant std::string est incontestablement plus rapide, plus lisible et plus sûr que de programmer en utilisant les antiques char*.
Si votre but est d'écrire un programme qui marche et qui soit facile à maintenir, utiliser string est un bien meilleur choix. Il est en effet tentant de dire qu'utiliser les char* fait surtout planter le programme plus vite.
Il faut aussi relativiser la possible lenteur de string par le fait que l'écart est très souvent sans conséquence pour l'utilisateur.
Même si lire et afficher une chaîne de caractères est deux fois plus lent avec les string qu'avec les char*, l'écart est dans ce cas dérisoire voire non mesurable.
Néanmoins, il est des cas où une application manipule un très grand nombre de chaînes de caractères, et on peut alors, à juste titre, se poser la question.
La réponse n'est pas évidente. Tout d'abord, le premier point en faveur des string est que celles-ci connaissent en permanence leur longueur (fonction size() ou length()), qui n'a donc pas besoin d'être calculée, contrairement aux char* ou il faut systématiquement effectuer un appel à strlen().
Ainsi, plus la longueur des chaînes manipulées est importante, et plus string se révèlera performante vis à vis des char*.
Un autre point important concerne la manière dont sont gérées les char*.
Beaucoup de programmeurs ne veulent pas s'embêter et définissent généralement une taille commune à toutes leurs chaînes de caractères.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | #define MAX_SIZE 100 void Exemple( void ) { char nom[ MAX_SIZE ]; printf( "Veuillez entrer votre nom : " ); fgets( stdin, nom, MAX_SIZE ); } |
La comparaison doit donc être faite avec une gestion dynamique de char*, chose laborieuse à gérer en C et faite de manière transparente et sûre par string.
Enfin, il est difficile de généraliser sur les string dans la mesure où celles-ci sont spécifiques à un compilateur (et même souvent une version de compilateur) et ce qui est vrai pour une implémentation ne l'est pas forcément pour une autre (d'autant plus que la capacité d'optimisation du compilateur entre aussi en jeu).
Ainsi, par exemple, la classe string de Visual C++ 6 implémente le Copy On Write (pas de copie réelle de contenu entre 2 string, mais un partage via un comptage de références) qui permet d'effectuer des affectations très rapides entre string. La version 7 de ce compilateur n'implémente plus cette fonctionnalité mais intègre un petit buffer de 16 octets destiné à contenir directement les chaînes de petite taille.
Cela permet de se passer d'allocation dynamique et donc d'augmenter les performances qui sont du coup égales à celles d'un tableau de char (dans le cas de petites chaînes).
La comparaison de performances entre string et char* est donc difficile, et elle ne peut être qu'au cas par cas.
Bien souvent, l'écart de performance entre les deux est de l'ordre de quelques pour cent.
Vous l'aurez compris, la performance est loin d'être le seul critère, aussi faites confiance aux nombreux développeurs de talent qui ont développé le C++ et utilisez le type chaînes de caractères qui lui est propre : string.
std::string effectue en général une allocation dynamique afin de stocker la chaîne de caractères. Cette allocation peut échouer si la mémoire fait défaut. Il faut donc être prudent avec l'utilisation de std::string et plus généralement des conteneurs de la STL lors de la gestion d'une exception std::bad_alloc.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | void Exemple() { try { // effectuer une allocation } catch ( std::bad_alloc & ) { // ATTENTION : utiliser std::string ou tout autre conteneur standard // peut déclencher une nouvelle exception std::bad_alloc } } |
const char* et char const * ont la même signification : un pointeur sur un caractère constant.
La règle est que le const s'applique toujours sur ce qui le précède. S'il n'y a rien avant, alors on inverse sa position avec ce qui est juste après.
Utiliser const signifie qu'il ne faut pas modifier le(s) caractère(s) référencé(s) par le pointeur.
Donc, typiquement, une fonction qui prend un char* déclare qu'elle modifiera le contenu du buffer pointé par le pointeur (accès en écriture). Une fonction qui prend un const char* déclare qu'elle va lire le contenu du buffer sans le modifier (accès en lecture seule).
<string> et <string.h> sont deux fichiers d'en-tête totalement différents. Le premier est l'en-tête standard C++ qui définit le type std::string.
Le second est un en-tête hérité du langage C qui définit diverses fonctions C de manipulation de chaînes de caractères. Il est à noter qu'inclure <string.h> est obsolète, il convient d'inclure <cstring> à la place.
À ce sujet lire aussi Quelle est la différence entre #include <iostream.h> et #include <iostream> ?.
Aucune ! Ces deux fonctions renvoient toutes les deux la longueur de la chaîne. En fait, length() retourne size(), mais on est libre d'utiliser l'une ou l'autre.
Voir aussi Quelle différence entre string::size() et string::capacity() ?
size renvoie le nombre de caractères contenus dans la chaîne (par exemple 4 pour "abcd") et capacity la capacité de stockage de la chaîne, c'est-à-dire le nombre total de caractères qu'elle peut stocker sans nécessiter une réallocation. capacity est donc toujours au moins égal à size, et peut être plus élevé, en particulier si vous faites un appel à reserve.
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 | #include <string> #include <iostream> int main() { using namespace std; string s; // la chaîne est vide cout << "size = " << s.size() << '\t' // size = 0 << "capacity = " << s.capacity() // capacity = 15 << '\n'; // on sait que l'on va faire de nombreuses concaténations, // alors on réserve de l'espace pour éviter de multiples allocations s.reserve( 26 ); cout << "size = " << s.size() << '\t' // size = 0 << "capacity = " << s.capacity() // capacity = 31 << '\n'; // concaténer les 26 lettres de l'alphabet for ( char c = 'A'; c <= 'Z'; ++c ) { s += c; } cout << "size = " << s.size() << '\t' // size = 26 << "capacity = " << s.capacity() // capacity = 31 << '\n'; s = "une assez longue chaîne qui oblige a faire une allocation"; cout << "size = " << s.size() << '\t' // size = 57 << "capacity = " << s.capacity() // capacity = 63 << '\n'; } |
C'est pourquoi il est fort probable que vous n'obteniez pas les mêmes résultats si votre compilateur n'est pas celui utilisé pour ce test (Visual C++ .Net 2003).
Par contre quelque soit votre compilateur les valeurs retournées par size devraient être les même.
Voir aussi Quelle est la différence entre string::length() et string::size() ?
c_str() retourne un pointeur sur une chaîne de caractères constante terminée par le caractère nul (chaîne de caractères C). data() retourne aussi un pointeur sur une chaîne de caractères constante, mais la présence du caractère terminal nul n'est pas exigée donc non garantie. Donc c_str() renvoie un pointeur constant sur un buffer contenant size() + 1 caractères et data() sur un buffer de size() caractères.
Il ne faut faire aucune hypothèse quant à la façon dont est implémentée la classe string. Le comportement de cette dernière est spécifique à presque chaque version de compilateur C++ existant.
Par exemple, les caractères peuvent ne pas être stockés en interne de manière contiguë (on peut envisager un système de concaténation rapide via un chaînage de sous chaînes de caractères).
Ou encore, certaines implémentations utilisent le Copy On Write (COW) qui implique que plusieurs objets string peuvent en interne partager le même espace mémoire pour stocker leurs caractères.
Le seul point commun à toutes ces implémentations est que l'on est assuré que le pointeur retourné par c_str() ou data() désigne une chaîne de caractères contigus. Mais rien n'empêche celui-ci de pointer vers une copie créée pour l'occasion !
C'est pourquoi il est très important de ne jamais modifier la chaîne retournée par ces fonctions.
Un autre point important est que ce pointeur peut être invalidé suite à une modification, et que sa durée de vie n'excède pas celle de l'objet associé. Le code suivant illustre ces deux points :
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 <string> int main() { using std::string; string str( "Hello" ); const char * c_str = str.c_str(); // ok // erreur 1 : invalidation de pointeur suite à une modification str += " World !"; // concaténer " World !" à "Hello" // maintenant c_str est invalide, et ne doit plus être utilisé ! c_str = str.c_str(); // erreur 2 : l'utilisation de [] peut aussi invalider c_str char c = str[ 0 ]; // ok, accès en lecture seulement str[ 0 ] = 'A'; // modification d'un caractère // maintenant c_str est invalide, et ne doit plus être utilisé ! // erreur 3 : invalidation de pointeur suite à une destruction { // variable temporaire à ce bloc string str2( "Temporaire" ); c_str = str2.c_str(); // ok } // fin du bloc : str2 est détruite // maintenant c_str est invalide car la chaîne pointée a été détruite ! } |
string (mais aussi <algorithm>) possède deux fonctions de recherche qui toutes les deux recherchent la première occurrence d'un élément :
Code c++ : | Sélectionner tout |
1 2 3 4 5 | string s = "abcdef"; if ( s.find( 'c' ) == s.find_first_of( 'c' ) ) { // ce test est vrai } |
Donc dans le cas de la recherche d'un caractère il n'y a pas de différence entre les deux, mais ces deux fonctions sont très différentes si leur argument est une chaîne de caractères. find considèrera cette chaîne comme une sous chaîne à rechercher, et find_first_of comme une liste de caractères à rechercher.
Code c++ : | Sélectionner tout |
1 2 3 4 | string s = "abcba"; cout << s.find( "ba" ) << '\n'; // affiche 3 cout << s.find_first_of( "ba" ) << '\n'; // affiche 0 |
L'appel à find_first_of renvoie la position de la première occurrence de n'importe lequel des caractères passés en paramètre, c'est-à-dire ici 0 (première lettre de "abcba" = a qui figure bien dans la liste "ba").
Il suffit de tester le résultat de l'opération de conversion, comme le fait la fonction générique from_string de la question Comment convertir une string en un objet de n'importe quel type ?. Ce principe est expliqué dans Comment fonctionne le test de réussite de conversion if ( str >> num ) ?
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <sstream> bool is_float( const std::string & Str ) { // créer un flux à partir de la chaîne donnée std::istringstream iss( Str ); // créer un objet temporaire pour la conversion float tmp; // tenter la conversion et // vérifier qu'il ne reste plus rien dans la chaîne return ( iss >> tmp ) && ( iss.eof() ); } int main() { is_float( "10.0" ); // vrai is_float( "abcd" ); // faux is_float( "10.0abcd" ); // faux grâce au 2° test } |
Cette solution spécifique aux float peut aisément être généralisée grâce aux templates :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <sstream> template<typename T> bool is_of_type( const std::string & Str ) { // créer un flux à partir de la chaîne donnée std::istringstream iss( Str ); // créer un objet temporaire pour la conversion T tmp; // tenter la conversion et // vérifier qu'il ne reste plus rien dans la chaîne return ( iss >> tmp ) && ( iss.eof() ); } int main() { is_of_type<float>( "10.5" ); // vrai is_of_type<int>( "10.5" ); // faux grâce au 2° test } |
Le type standard pour manipuler des chaînes en C++ est std::string. Mais si l'on regarde de plus près, on s'aperçoit qu'il ne s'agit en fait que qu'un typedef :
Code c++ : | Sélectionner tout |
1 2 3 4 | namespace std { typedef basic_string<char, char_traits<char>, allocator<char> > string; } |
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 | struct ci_char_traits : public std::char_traits<char> // on dérive du char_traits par défaut, ainsi on hérite des fonctions que nous ne voulons pas redéfinir { static bool eq(char c1, char c2) { return toupper(c1) == toupper(c2); } static bool ne(char c1, char c2) { return toupper(c1) != toupper(c2); } static bool lt(char c1, char c2) { return toupper(c1) < toupper(c2); } static int compare(const char* s1, const char* s2, size_t n) { return memicmp(s1, s2, n); // si disponible sur votre compilateur } static const char* find(const char* s, int n, char a) { while ((n-- > 0) && (toupper(*s) != toupper(a))) ++s; return s; } }; |
Code c++ : | Sélectionner tout |
typedef std::basic_string<char, ci_char_traits> ci_string;
Code c++ : | Sélectionner tout |
1 2 3 4 | ci_string s1 = "salut"; ci_string s2 = "SAluT"; cout << (s1 == s2); // affiche "1" (vrai) |
Le plus simple est d'utiliser la fonction std::reverse() sur la chaîne existante, ou d'en créer une nouvelle en passant un itérateur inverse au constructeur de std::string.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | #include <algorithm> // pour std::reverse() #include <string> std::string chaine = "Bonjour"; // Inversion directe de la chaîne std::reverse(chaine.begin(), chaine.end()); // Autre méthode : création d'une chaîne inversée std::string inverse( chaine.rbegin(), chaine.rend() ); |
std::string ne dispose pas de fonction équivalente à la fonction C standard strtok(). Il existe de nombreuses façons de réaliser un équivalent à cette fonction. L'une des solutions les plus simples et de procéder au découpage sur un istringstream au moyen de std::getline() :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <sstream> #include <string> #include <iostream> using namespace std; int main() { istringstream iss( "mot1;mot2;mot3;mot4" ); string mot; while ( std::getline( iss, mot, ';' ) ) { cout << mot << '\n'; } } |
Attention : getline() va considérer tout ce qui se trouve entre deux ';' comme étant une ligne à extraire. Cela veut dire que la phrase "mot1 mot2;;mot3" (notez le double point virgule entre mot2 et mot3) sera découpée en "mot1 mot2", "" (chaîne vide) et "mot3". Cette utilisation de getline() ne permet aussi de spécifier qu'un seul séparateur. Si vos mots sont séparés par de simples espaces, vous pouvez aussi vous inspirer de [Exemple] Comment manipuler un tableau de string ?.
Pour quelque chose de plus évolué, vous pouvez vous tourner vers boost::tokenizer (voir Comment découper une chaîne avec boost::tokenizer ?) ou encore réaliser votre propre fonction de découpage en vous inspirant de ceci : Tokenizing.
On ne peut pas. Le code suivant ne compile pas :
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 | // fonctions de traitement void parametre_input(); void parametre_output(); void parametre_inconnu(); // appel des fonctions de traitement void analyse_parametre( const char * Param ) { switch ( Param ) { case "/input": parametre_input(); break; case "/output": parametre_output(); break; default: parametre_inconnu(); } } |
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 | // fonctions de traitement void parametre_input(); void parametre_output(); void parametre_inconnu(); // type pointeur de fonction de traitement typedef void (*parametre_fct)(); void analyse_parametre( const string & Param ) { static map<string, parametre_fct> param_map; // initialiser la map si ce n'est pas fait if ( param_map.empty() ) { param_map[ "/input" ] = parametre_input; param_map[ "/output" ] = parametre_output; } // rechercher la fonction associée à Param map<string, parametre_fct>::const_iterator i = param_map.find( Param ); if ( i == param_map.end() ) { // échec parametre_inconnu(); } else { // appeler la fonction associée (i->second)(); } } |
De la même manière que les chaînes ANSI, mais au moyen du type std::wstring qui est défini dans le fichier d'en-tête standard <string>.
Il s'agit d'une spécialisation de std::basic_string pour le type wchar_t (caractère Unicode) au même titre que std::string l'est pour le type char.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | #include <string> #include <iostream> int main() { std::wstring s = L"Chaîne unicode"; wchar_t c = s[ 0 ]; // c = L'C' std::wcout << s; } |
Cependant, en ce qui concerne les fichiers, les caractères wchar_t sont convertis de manière transparente en char au moment de l'écriture. Le C++ standard ne permet pas en effet de manipuler des fichiers Unicode.
Pour réaliser les conversions string <-> wstring, voir Comment effectuer les conversions de texte ASCII <-> Unicode ?.
Les fonctions suivantes permettent, via l'utilisation de la bibliothèque standard, de transformer un texte Unicode en ASCII (narrow), et vice-versa (widen).
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 | #include <string> #include <locale> #include <vector> std::string narrow(const std::wstring& ws) { std::vector<char> buffer(ws.size()); std::locale loc("english"); std::use_facet< std::ctype<wchar_t> >(loc).narrow(ws.data(), ws.data() + ws.size(), '?', &buffer[0]); return std::string(&buffer[0], buffer.size()); } std::wstring widen(const std::string& s) { std::vector<wchar_t> buffer(s.size()); std::locale loc("english"); std::use_facet< std::ctype<wchar_t> >(loc).widen(s.data(), s.data() + s.size(), &buffer[0]); return std::wstring(&buffer[0], buffer.size()); } |
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 | #include <string> #include <algorithm> // remove(), erase() #include <iostream> // supprime toutes les occurrences du caractère C donné void SupprimeTousLesCaracteres( std::string & Str, char C ) { Str.erase( std::remove( Str.begin(), Str.end(), C ), Str.end() ); } // supprime tous les caractères C situés au début de Str std::string SupprimeLesPremiersCaractères( const std::string & Str, char C ) { return Str.substr( Str.find_first_not_of( C ) ); } int main() { using namespace std; // supprimer les quote entre les mots string s1 = "'mot1' 'mot2' 'mot3' 'mot4'"; SupprimeTousLesCaracteres( s1, '\'' ); cout << s1 << '\n'; // affiche "mot1 mot2 mot3 mot4" // enlever les espaces gênants au début de la chaîne string s2 = " exemple"; cout << SupprimeLesPremiersCaractères( s2, ' ' ); // affiche "exemple" } |
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 | #include <vector> #include <sstream> #include <iostream> #include <iterator> int main() { using namespace std; string str = "mot1 mot2 mot3 mot4 mot5 mot6"; vector<string> str_list; // liste de mots // remplir la liste de mots istringstream iss( str ); copy( istream_iterator<string>( iss ), istream_iterator<string>(), back_inserter( str_list ) ); // afficher la liste de mots sur cout copy( str_list.begin(), str_list.end(), ostream_iterator<string>( cout, "\n" ) ); // reconstituer une string à partir de la liste de mots ostringstream oss; copy( str_list.begin(), str_list.end(), ostream_iterator<string>( oss, " " ) ); string s = oss.str(); cout << s << '\n'; // "mot1 mot2 mot3 mot4 mot5 mot6" } |
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.