886 votes

Comment découper une std::string ?

J'utilise actuellement le code suivant pour ajuster à droite tous les fichiers std::strings dans mes programmes :

std::string s;
s.erase(s.find_last_not_of(" \n\r\t")+1);

Cela fonctionne bien, mais je me demande s'il n'y a pas des cas extrêmes où cela pourrait échouer ?

Bien sûr, les réponses avec des alternatives élégantes et aussi la solution de la coupe à gauche sont les bienvenues.

610 votes

Les réponses à cette question témoignent des lacunes de la bibliothèque standard C++.

89 votes

@IdanK Et il n'y a toujours pas cette fonction dans C++11.

45 votes

@IdanK : Super, n'est-ce pas ! Regardez toutes les options concurrentes que nous avons maintenant à notre disposition, sans être encombrés par l'idée qu'une seule personne se fait de la " le site la manière dont nous devons le faire" !

731voto

Evan Teran Points 42370

EDIT Depuis c++17, certaines parties de la bibliothèque standard ont été supprimées. Heureusement, à partir de c++11, nous avons des lambdas qui sont une solution supérieure.

#include <algorithm> 
#include <cctype>
#include <locale>

// trim from start (in place)
static inline void ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
        return !std::isspace(ch);
    }));
}

// trim from end (in place)
static inline void rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
        return !std::isspace(ch);
    }).base(), s.end());
}

// trim from both ends (in place)
static inline void trim(std::string &s) {
    ltrim(s);
    rtrim(s);
}

// trim from start (copying)
static inline std::string ltrim_copy(std::string s) {
    ltrim(s);
    return s;
}

// trim from end (copying)
static inline std::string rtrim_copy(std::string s) {
    rtrim(s);
    return s;
}

// trim from both ends (copying)
static inline std::string trim_copy(std::string s) {
    trim(s);
    return s;
}

Merci à https://stackoverflow.com/a/44973498/524503 pour avoir proposé une solution moderne.

Réponse originale :

J'ai tendance à utiliser l'un de ces 3 modèles pour mes besoins en matière de coupe :

#include <algorithm> 
#include <functional> 
#include <cctype>
#include <locale>

// trim from start
static inline std::string &ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
            std::not1(std::ptr_fun<int, int>(std::isspace))));
    return s;
}

// trim from end
static inline std::string &rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(),
            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
    return s;
}

// trim from both ends
static inline std::string &trim(std::string &s) {
    return ltrim(rtrim(s));
}

Ils sont assez explicites et fonctionnent très bien.

EDIT : BTW, j'ai std::ptr_fun pour aider à désambiguïser std::isspace parce qu'il y a en fait une deuxième définition qui supporte les locales. Cela aurait pu être un casting tout de même, mais j'ai tendance à préférer ceci.

EDIT : Pour répondre à certains commentaires concernant l'acceptation d'un paramètre par référence, sa modification et son renvoi. Je suis d'accord. Une implémentation que je préférerais probablement serait deux ensembles de fonctions, un pour in place et un qui fait une copie. Un meilleur ensemble d'exemples serait :

#include <algorithm> 
#include <functional> 
#include <cctype>
#include <locale>

// trim from start (in place)
static inline void ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
            std::not1(std::ptr_fun<int, int>(std::isspace))));
}

// trim from end (in place)
static inline void rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(),
            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
}

// trim from both ends (in place)
static inline void trim(std::string &s) {
    ltrim(s);
    rtrim(s);
}

// trim from start (copying)
static inline std::string ltrim_copy(std::string s) {
    ltrim(s);
    return s;
}

// trim from end (copying)
static inline std::string rtrim_copy(std::string s) {
    rtrim(s);
    return s;
}

// trim from both ends (copying)
static inline std::string trim_copy(std::string s) {
    trim(s);
    return s;
}

Je garde cependant la réponse originale ci-dessus pour le contexte et dans l'intérêt de garder la réponse la plus votée toujours disponible.

30 votes

Ce code échouait sur certaines chaînes internationales (shift-jis dans mon cas, stocké dans une std::string) ; j'ai fini par utiliser boost::trim pour résoudre le problème.

5 votes

J'utiliserais des pointeurs au lieu de références, de sorte que du point d'appel, il est beaucoup plus facile de comprendre que ces fonctions modifient la chaîne de caractères sur place, au lieu d'en créer une copie.

4 votes

Notez qu'avec isspace, vous pouvez facilement obtenir un comportement non défini avec des caractères non ASCII. stacked-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f

435voto

Leon Timmermans Points 23230

Utilisation de Les algorithmes de chaînes de caractères de Boost serait le plus facile :

#include <boost/algorithm/string.hpp>

std::string str("hello world! ");
boost::trim_right(str);

str est maintenant "hello world!" . Il y a aussi trim_left y trim qui coupe les deux côtés.


Si vous ajoutez _copy suffixe à n'importe lequel des noms de fonctions ci-dessus, par ex. trim_copy la fonction renverra une copie tronquée de la chaîne au lieu de la modifier par le biais d'une référence.

Si vous ajoutez _if suffixe à n'importe lequel des noms de fonctions ci-dessus, par ex. trim_copy_if vous pouvez couper tous les caractères qui satisfont votre prédicat personnalisé, et pas seulement les espaces.

1 votes

Qu'utilise boost pour déterminer si un caractère est un espace blanc ?

8 votes

Cela dépend de la localisation. Ma locale par défaut (VS2005, en) signifie que les tabulations, les espaces, les retours chariot, les nouvelles lignes, les tabulations verticales et les sauts de page sont coupés.

4 votes

J'utilise déjà beaucoup de boost, #include <boost/format.hpp> #include <boost/tokenizer.hpp> #include <boost/lexical_cast.hpp> mais je m'inquiétais du gonflement du code pour l'ajout de <boost/algorithm/string.hpp> alors qu'il y a déjà std::string::erase des solutions de rechange basées sur la technologie. Je suis heureux de constater, en comparant les constructions de MinSizeRel avant et après l'ajout de boost, que ce dernier n'a pas du tout augmenté la taille de mon code (il doit déjà être payé quelque part) et que mon code n'est pas encombré de quelques fonctions supplémentaires.

62voto

Bill the Lizard Points 147311

Utilisez le code suivant pour couper à droite (les espaces de fin de ligne) et les caractères de tabulation à partir de std::strings ( idéone ):

// trim trailing spaces
size_t endpos = str.find_last_not_of(" \t");
size_t startpos = str.find_first_not_of(" \t");
if( std::string::npos != endpos )
{
    str = str.substr( 0, endpos+1 );
    str = str.substr( startpos );
}
else {
    str.erase(std::remove(std::begin(str), std::end(str), ' '), std::end(str));
}

Et juste pour équilibrer les choses, je vais aussi inclure le code d'habillage gauche ( idéone ):

// trim leading spaces
size_t startpos = str.find_first_not_of(" \t");
if( string::npos != startpos )
{
    str = str.substr( startpos );
}

5 votes

Cela ne détectera pas les autres formes d'espaces blancs... newline, saut de ligne, retour chariot en particulier.

1 votes

C'est vrai. Tu dois le personnaliser pour l'espace blanc que tu cherches à couper. Mon application particulière n'attendait que des espaces et des tabulations, mais vous pouvez ajouter \n\r pour attraper les autres.

5 votes

str.substr(...).swap(str) est meilleur. Sauvegarder une mission.

58voto

David G Points 430

Un peu tard pour la fête, mais tant pis. Maintenant que C++11 est là, nous avons les lambdas et les variables automatiques. Donc ma version, qui gère aussi les espaces blancs et les chaînes vides, est la suivante :

#include <cctype>
#include <string>
#include <algorithm>

inline std::string trim(const std::string &s)
{
   auto wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
   auto wsback=std::find_if_not(s.rbegin(),s.rend(),[](int c){return std::isspace(c);}).base();
   return (wsback<=wsfront ? std::string() : std::string(wsfront,wsback));
}

Nous pourrions créer un itérateur inverse à partir de wsfront et l'utiliser comme condition de terminaison dans la deuxième étape. find_if_not mais cela n'est utile que dans le cas d'une chaîne de caractères avec tous les espaces, et gcc 4.8, au moins, n'est pas assez intelligent pour déduire le type de l'itérateur inverse ( std::string::const_reverse_iterator ) avec auto . Je ne sais pas à quel point la construction d'un itérateur inverse est coûteuse, donc YMMV ici. Avec cette modification, le code ressemble à ceci :

inline std::string trim(const std::string &s)
{
   auto  wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
   return std::string(wsfront,std::find_if_not(s.rbegin(),std::string::const_reverse_iterator(wsfront),[](int c){return std::isspace(c);}).base());
}

13 votes

Joli. +1 de ma part. Dommage que C++11 n'ait pas introduit trim() dans std::string et rendu la vie plus facile à tout le monde.

3 votes

Je veux toujours un appel de fonction pour couper la chaîne de caractères, au lieu de l'implémenter

25 votes

Pour ce que ça vaut, il n'y a pas besoin d'utiliser cette lambda. Vous pouvez simplement passer std::isspace : auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);

25voto

J'aime la solution de tzaman, le seul problème avec elle est qu'elle ne coupe pas une chaîne contenant uniquement des espaces.

Pour corriger ce 1 défaut, ajoutez un str.clear() entre les 2 lignes de trimmer

std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;

0 votes

Joli :) le problème avec nos deux solutions, cependant, est qu'elles rognent les deux extrémités ; on ne peut pas faire un ltrim o rtrim comme ça.

44 votes

Bon, mais ne peut pas traiter les chaînes avec des espaces internes. Par exemple, trim( abc def") -> abc, il ne reste que abc.

0 votes

Une bonne solution si vous savez qu'il n'y aura pas d'espace interne !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X