Quelle est la meilleure façon de couper std :: string?

812

J'utilise actuellement le code suivant pour couper à droite tous les std::stringsprogrammes:

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

Cela fonctionne bien, mais je me demande s'il y a des cas finaux où cela pourrait échouer?

Bien sûr, les réponses avec des alternatives élégantes et une solution de garniture gauche sont les bienvenues.

Milan Babuškov
la source
549
Les réponses à cette question témoignent de l'absence de bibliothèque standard C ++.
Idan K
83
@IdanK Et il n'a toujours pas cette fonction en C ++ 11.
quantum
44
@IdanK: Super, n'est-ce pas! Regardez toutes les options concurrentes que nous avons maintenant à notre disposition, sans être gêné par l'idée d'une seule personne de " la façon dont nous devons le faire"!
Courses de légèreté en orbite le
59
La fonctionnalité @LightnessRacesinOrbit dans un type, c'est une décision de conception, et l'ajout d'une fonction de découpage à une chaîne pourrait (au moins sous c ++) ne pas être la meilleure solution de toute façon - mais ne fournissant aucun moyen standard de le faire, au lieu de cela, tout le monde s'inquiète les mêmes petits problèmes, encore et encore,
n'aident
27
Vous pouvez vous demander pourquoi les fonctions de découpage ne sont pas intégrées dans la std::stringclasse, alors que ce sont des fonctions comme celles-ci qui rendent les autres langages si agréables à utiliser (Python par exemple).
HelloGoodbye

Réponses:

648

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(), [](int 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(), [](int 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 mis en place la solution moderne.

Réponse originale:

J'ai tendance à utiliser l'un de ces 3 pour mes besoins 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_funlà pour aider à lever l'ambiguïté std::isspacecar il existe en fait une deuxième définition qui prend en charge les paramètres régionaux. Cela aurait pu être un casting tout de même, mais j'ai tendance à aimer ça mieux.

EDIT : Pour répondre à certains commentaires concernant l'acceptation d'un paramètre par référence, sa modification et son retour. Je suis d'accord. Une implémentation que je préférerais probablement serait deux ensembles de fonctions, une pour en place et une 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 à vote élevé toujours disponible.

Evan Teran
la source
28
Ce code échouait sur certaines chaînes internationales (shift-jis dans mon cas, stocké dans une chaîne std ::); J'ai fini par utiliser boost::trimpour résoudre le problème.
Tom
5
J'utiliserais des pointeurs au lieu de références, de sorte qu'à partir du point d'appel, il est beaucoup plus facile de comprendre que ces fonctions modifient la chaîne en place, au lieu de créer une copie.
Marco Leogrande
3
Notez qu'avec isspace, vous pouvez facilement obtenir un comportement indéfini avec des caractères non ASCII stacked-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f
R. Martinho Fernandes
10
Pourquoi la statique? Est-ce là où un espace de noms anonyme serait préféré?
Trevor Hickey
3
@TrevorHickey, bien sûr, vous pouvez utiliser un espace de noms anonyme à la place si vous préférez.
Evan Teran
417

L'utilisation des algorithmes de chaîne de Boost serait plus simple:

#include <boost/algorithm/string.hpp>

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

strest maintenant "hello world!". Il y a aussi trim_leftet trim, qui coupe les deux côtés.


Si vous ajoutez un _copysuffixe à l'un des noms de fonction ci-dessus trim_copy, par exemple , la fonction renverra une copie tronquée de la chaîne au lieu de la modifier via une référence.

Si vous ajoutez un _ifsuffixe à l'un des noms de fonctions ci-dessus trim_copy_if, par exemple , vous pouvez supprimer tous les caractères satisfaisant votre prédicat personnalisé, par opposition aux seuls espaces blancs.

Leon Timmermans
la source
7
Cela dépend des paramètres régionaux. Mon environnement local par défaut (VS2005, en) signifie que les tabulations, les espaces, les retours chariot, les retours à la ligne, les tabulations verticales et les sauts de page sont tronqués.
MattyT
117
Boost est un marteau si massif pour un si petit problème.
Casey Rodarmor
143
@rodarmor: Boost résout de nombreux petits problèmes. C'est un marteau massif qui résout beaucoup.
Nicol Bolas
123
Boost est un ensemble de marteaux de différentes tailles qui résolvent de nombreux problèmes différents.
Ibrahim
11
@rodarmor Vous dites que comme si Boost est un monolithe tout ou rien, où l'inclusion d'un de ses en-têtes inflige en quelque sorte le tout à son programme. Ce qui n'est clairement pas le cas. Btw, je n'ai jamais utilisé Boost, fwiw.
underscore_d
61

Utilisez le code suivant pour couper les espaces et les tabulations à droite de std::strings( ideone ):

// 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 de garniture gauche ( ideone ):

// trim leading spaces
size_t startpos = str.find_first_not_of(" \t");
if( string::npos != startpos )
{
    str = str.substr( startpos );
}
Bill le lézard
la source
4
Cela ne détectera pas d'autres formes d'espaces ... saut de ligne, saut de ligne, retour chariot en particulier.
Tom
1
Droite. Vous devez le personnaliser en fonction de l'espace que vous souhaitez découper. Mon application particulière n'attendait que des espaces et des tabulations, mais vous pouvez ajouter \ n \ r pour attraper les autres.
Bill the Lizard
5
str.substr(...).swap(str)est mieux. Enregistrez une affectation.
updogliu
4
@updogliu N'utilisera-t-il pas l'affectation de déplacement basic_string& operator= (basic_string&& str) noexcept;?
nurettin
8
Cette réponse ne modifie pas les chaînes qui sont TOUS les espaces. Ce qui est un échec.
Tom Andersen
56

Ce que vous faites est bien et robuste. J'utilise la même méthode depuis longtemps et je n'ai pas encore trouvé de méthode plus rapide:

const char* ws = " \t\n\r\f\v";

// trim from end of string (right)
inline std::string& rtrim(std::string& s, const char* t = ws)
{
    s.erase(s.find_last_not_of(t) + 1);
    return s;
}

// trim from beginning of string (left)
inline std::string& ltrim(std::string& s, const char* t = ws)
{
    s.erase(0, s.find_first_not_of(t));
    return s;
}

// trim from both ends of string (right then left)
inline std::string& trim(std::string& s, const char* t = ws)
{
    return ltrim(rtrim(s, t), t);
}

En fournissant les caractères à rogner, vous avez la possibilité de rogner les caractères non blancs et l'efficacité de rogner uniquement les caractères que vous souhaitez rogner.

Galik
la source
si vous changez la commande trim, c'est- à -dire que vous la rendez rtrim(ltrim(s, t), t)un peu plus efficace
CITBL
1
@CITBL La fonction intérieure est exécutée en premier afin que votre façon de couper soit de gauche avant de couper de droite. Je pense que ce serait moins efficace, non?
Galik
Exactement. Mon erreur
CITBL
si vous utilisez basic_string et le modèle sur le CharT, vous pouvez le faire pour toutes les chaînes, utilisez simplement une variable de modèle pour l'espace blanc afin de l'utiliser comme ws <CharT>. techniquement, à ce stade, vous pouvez le préparer pour c ++ 20 et le marquer également constexpr car cela implique en ligne
Beached
@Beached Indeed. Un peu compliqué à répondre ici cependant. J'ai écrit des fonctions de modèle pour cela et il est certainement très impliqué. J'ai essayé un tas d'approches différentes et je ne sais toujours pas quelle est la meilleure.
Galik
55

Un peu tard pour la fête, mais tant pis. Maintenant C ++ 11 est là, nous avons des lambdas et des variables automatiques. Donc, ma version, qui gère également les espaces vides 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 inversé à partir de wsfrontet l'utiliser comme condition de terminaison dans le second, find_if_notmais cela n'est utile que dans le cas d'une chaîne entièrement blanche, et gcc 4.8 au moins n'est pas assez intelligent pour déduire le type de l'itérateur inversé ( std::string::const_reverse_iterator) avec auto. Je ne sais pas combien coûte la construction d'un itérateur inversé, alors 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());
}
David G
la source
9
Agréable. +1 de moi. Dommage que C ++ 11 n'ait pas introduit trim () dans std :: string et a rendu la vie plus facile pour tout le monde.
Milan Babuškov
3
Je veux toujours un appel de fonction pour couper la chaîne, au lieu de l'implémenter
linquize
22
Pour ce que ça vaut, il n'est pas nécessaire d'utiliser ce lambda. Vous pouvez simplement passer std::isspace:auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);
vmrob
4
+1 pour probablement la seule réponse avec l'implémentation qui ne fait qu'une seule copie de chaîne O (N).
Alexei Averchenko
4
Les compilateurs @vmrob ne sont pas nécessairement aussi intelligents. faire ce que vous dites est ambigu:candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)
johnbakers
42

Essayez ça, ça marche pour moi.

inline std::string trim(std::string& str)
{
    str.erase(0, str.find_first_not_of(' '));       //prefixing spaces
    str.erase(str.find_last_not_of(' ')+1);         //surfixing spaces
    return str;
}
user818330
la source
12
Si votre chaîne ne contient aucun espace suffixant, cela effacera à partir de npos + 1 == 0, et vous supprimerez la chaîne entière.
mhsmith
3
@rgove Veuillez expliquer. str.find_last_not_of(x)renvoie la position du premier caractère différent de x. Il ne renvoie npos que si aucun caractère ne correspond à x. Dans l'exemple, s'il n'y a pas d'espace suffixant, il retournera l'équivalent de str.length() - 1, ce qui donnera essentiellement str.erase((str.length() - 1) + 1).C'est-à-dire, sauf si je me trompe terriblement.
Travis
5
Cela devrait retourner std :: string & pour éviter d'appeler inutilement le constructeur de copie.
heksesang
7
Je ne comprends pas pourquoi cela renvoie une copie après avoir modifié le paramètre de retour?
Galik
3
@MiloDC Ma confusion est pourquoi retourner une copie au lieu d' une référence. Il est plus logique pour moi de revenir std::string&.
Galik
25

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

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

std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;
Michaël Schoonbrood
la source
Bien :) le problème avec nos deux solutions, cependant, c'est qu'elles vont couper les deux extrémités; ne peut pas faire un ltrimou rtrimcomme ça.
tzaman
44
Bon, mais ne peut pas gérer la chaîne avec un espace interne. par exemple trim (abc def ") -> abc, seulement abc à gauche.
liheyuan
Une bonne solution si vous savez qu'il n'y aura pas d'espace blanc interne!
Elliot Gorokhovsky
C'est agréable et facile mais c'est aussi assez lent car la chaîne est copiée dans et hors du std::stringstream.
Galik
23

http://ideone.com/nFVtEo

std::string trim(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it))
        it++;

    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit))
        rit++;

    return std::string(it, rit.base());
}
Pushkoff
la source
1
Une solution élégante pour la finition de l'espace de base enfin ... :)
jave.web
Comment cela fonctionne: Ceci est une solution de type copie - il trouve la position du premier caractère qui n'est pas espace ( it) et inverse: position du caractère après laquelle il n'y a que des espaces ( rit) - après cela, il retourne une chaîne nouvellement créée == une copie de la partie de la chaîne d'origine - une partie basée sur ces itérateurs ...
jave.web
Merci, travaillé pour moi: std: string s = "Oh noez: espace \ r \ n"; std :: string clean = trim (s);
Alexx Roche
15

Dans le cas d'une chaîne vide, votre code suppose que l'ajout de 1 à string::npos0 donne string::nposest de type string::size_type, qui n'est pas signé. Ainsi, vous comptez sur le comportement de dépassement de l'addition.

Greg Hewgill
la source
23
Vous formulez cela comme si c'était mauvais. Le comportement de dépassement d'entier signé est mauvais.
MSalters
2
Ajouter 1à std::string::npos doit donner 0selon le C++ Standard. C'est donc une bonne hypothèse sur laquelle on peut absolument compter.
Galik
13

Piraté de Cplusplus.com

std::string choppa(const std::string &t, const std::string &ws)
{
    std::string str = t;
    size_t found;
    found = str.find_last_not_of(ws);
    if (found != std::string::npos)
        str.erase(found+1);
    else
        str.clear();            // str is all whitespace

    return str;
}

Cela fonctionne également pour le cas nul. :-)

Paul Nathan
la source
4
C'est juste rtrim, pasltrim
ub3rst4r
1
^ cela vous dérange d'utiliser find_first_not_of? Il est relativement facile de le modifier.
Abhinav Gauniyal
13

Avec C ++ 17, vous pouvez utiliser basic_string_view :: remove_prefix et basic_string_view :: remove_suffix :

std::string_view trim(std::string_view s)
{
    s.remove_prefix(std::min(s.find_first_not_of(" \t\r\v\n"), s.size()));
    s.remove_suffix(std::min(s.size() - s.find_last_not_of(" \t\r\v\n") - 1, s.size()));

    return s;
}

Une belle alternative:

std::string_view ltrim(std::string_view s)
{
    s.remove_prefix(std::distance(s.cbegin(), std::find_if(s.cbegin(), s.cend(),
         [](int c) {return !std::isspace(c);})));

    return s;
}

std::string_view rtrim(std::string_view s)
{
    s.remove_suffix(std::distance(s.crbegin(), std::find_if(s.crbegin(), s.crend(),
        [](int c) {return !std::isspace(c);})));

    return s;
}

std::string_view trim(std::string_view s)
{
    return ltrim(rtrim(s));
}
Phidelux
la source
Je ne sais pas ce que vous testez, mais dans votre exemple std :: find_first_not_of renverra std :: string :: npos et std :: string_view :: size renverra 4. Le minimum est évidemment quatre, le nombre d'éléments à être supprimé par std :: string_view :: remove_prefix . Gcc 9.2 et clang 9.0 gèrent cela correctement: godbolt.org/z/DcZbFH
Phidelux
1
Merci! Cela me semble correct.
Contango
11

Ma solution basée sur la réponse de @Bill the Lizard .

Notez que ces fonctions renverront la chaîne vide si la chaîne d'entrée ne contient que des espaces.

const std::string StringUtils::WHITESPACE = " \n\r\t";

std::string StringUtils::Trim(const std::string& s)
{
    return TrimRight(TrimLeft(s));
}

std::string StringUtils::TrimLeft(const std::string& s)
{
    size_t startpos = s.find_first_not_of(StringUtils::WHITESPACE);
    return (startpos == std::string::npos) ? "" : s.substr(startpos);
}

std::string StringUtils::TrimRight(const std::string& s)
{
    size_t endpos = s.find_last_not_of(StringUtils::WHITESPACE);
    return (endpos == std::string::npos) ? "" : s.substr(0, endpos+1);
}
DavidRR
la source
9

Ma réponse est une amélioration par rapport à la première réponse de ce post qui coupe les caractères de contrôle ainsi que les espaces (0-32 et 127 sur la table ASCII ).

std::isgraphdétermine si un caractère a une représentation graphique, vous pouvez donc l'utiliser pour modifier la réponse d'Evan pour supprimer tout caractère qui n'a pas de représentation graphique de chaque côté d'une chaîne. Le résultat est une solution beaucoup plus élégante:

#include <algorithm>
#include <functional>
#include <string>

/**
 * @brief Left Trim
 *
 * Trims whitespace from the left end of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& ltrim(std::string& s) {
  s.erase(s.begin(), std::find_if(s.begin(), s.end(),
    std::ptr_fun<int, int>(std::isgraph)));
  return s;
}

/**
 * @brief Right Trim
 *
 * Trims whitespace from the right end of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& rtrim(std::string& s) {
  s.erase(std::find_if(s.rbegin(), s.rend(),
    std::ptr_fun<int, int>(std::isgraph)).base(), s.end());
  return s;
}

/**
 * @brief Trim
 *
 * Trims whitespace from both ends of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& trim(std::string& s) {
  return ltrim(rtrim(s));
}

Remarque: Alternativement, vous devriez pouvoir utiliser std::iswgraphsi vous avez besoin de prise en charge des caractères larges, mais vous devrez également modifier ce code pour permettre la std::wstringmanipulation, ce que je n'ai pas testé (voir la page de référence pour std::basic_stringexplorer cette option) .

Clay Freeman
la source
3
std :: ptr_fun est déconseillé
johnbakers
8

Avec C ++ 11 est également venu un module d' expression régulière , qui peut bien sûr être utilisé pour couper les espaces de début ou de fin.

Peut-être quelque chose comme ça:

std::string ltrim(const std::string& s)
{
    static const std::regex lws{"^[[:space:]]*", std::regex_constants::extended};
    return std::regex_replace(s, lws, "");
}

std::string rtrim(const std::string& s)
{
    static const std::regex tws{"[[:space:]]*$", std::regex_constants::extended};
    return std::regex_replace(s, tws, "");
}

std::string trim(const std::string& s)
{
    return ltrim(rtrim(s));
}
Un mec programmeur
la source
8

C'est ce que j'utilise. Continuez à retirer de l'espace à l'avant, puis, s'il reste quelque chose, faites de même à l'arrière.

void trim(string& s) {
    while(s.compare(0,1," ")==0)
        s.erase(s.begin()); // remove leading whitespaces
    while(s.size()>0 && s.compare(s.size()-1,1," ")==0)
        s.erase(s.end()-1); // remove trailing whitespaces
}
synaptik
la source
8
s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
s.erase(s.find_last_not_of(" \n\r\t")+1);   
freeboy1015
la source
2
Ce serait légèrement plus efficace si vous faites ceux dans l'ordre inverse et que vous coupez à partir de la droite avant d'invoquer un décalage en coupant à gauche.
Galik
7

Pour ce que ça vaut, voici une implémentation de finition avec un œil sur les performances. C'est beaucoup plus rapide que beaucoup d'autres routines de trim que j'ai vues. Au lieu d'utiliser des itérateurs et std :: find, il utilise des chaînes et des index C bruts. Il optimise les cas spéciaux suivants: chaîne de taille 0 (ne rien faire), chaîne sans espace à découper (ne rien faire), chaîne avec uniquement des espaces à droite à découper (juste redimensionner la chaîne), chaîne entièrement blanche (simplement effacer la chaîne) . Et enfin, dans le pire des cas (chaîne avec espace blanc de tête), il fait de son mieux pour effectuer une construction de copie efficace, en effectuant une seule copie, puis en déplaçant cette copie à la place de la chaîne d'origine.

void TrimString(std::string & str)
{ 
    if(str.empty())
        return;

    const auto pStr = str.c_str();

    size_t front = 0;
    while(front < str.length() && std::isspace(int(pStr[front]))) {++front;}

    size_t back = str.length();
    while(back > front && std::isspace(int(pStr[back-1]))) {--back;}

    if(0 == front)
    {
        if(back < str.length())
        {
            str.resize(back - front);
        }
    }
    else if(back <= front)
    {
        str.clear();
    }
    else
    {
        str = std::move(std::string(str.begin()+front, str.begin()+back));
    }
}
mbgda
la source
@bmgda peut-être théoriquement la version la plus rapide pourrait avoir cette signature: extern "C" void string_trim (char ** begin_, char ** end_) ... Catch my drift?
6

Une manière élégante de le faire peut être comme

std::string & trim(std::string & str)
{
   return ltrim(rtrim(str));
}

Et les fonctions de support sont implémentées comme:

std::string & ltrim(std::string & str)
{
  auto it =  std::find_if( str.begin() , str.end() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
  str.erase( str.begin() , it);
  return str;   
}

std::string & rtrim(std::string & str)
{
  auto it =  std::find_if( str.rbegin() , str.rend() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
  str.erase( it.base() , str.end() );
  return str;   
}

Et une fois que tout cela est en place, vous pouvez également écrire ceci:

std::string trim_copy(std::string const & str)
{
   auto s = str;
   return ltrim(rtrim(s));
}
jha-G
la source
6

Mettre en œuvre l'implémentation C ++ 11:

static void trim(std::string &s) {
     s.erase(s.begin(), std::find_if_not(s.begin(), s.end(), [](char c){ return std::isspace(c); }));
     s.erase(std::find_if_not(s.rbegin(), s.rend(), [](char c){ return std::isspace(c); }).base(), s.end());
}
GutiMac
la source
5

Je suppose que si vous commencez à demander la "meilleure façon" de couper une chaîne, je dirais qu'une bonne implémentation serait celle qui:

  1. N'alloue pas de chaînes temporaires
  2. A des surcharges pour le rognage en place et le rognage de copie
  3. Peut être facilement personnalisé pour accepter différentes séquences / logique de validation

De toute évidence, il existe trop de façons différentes d'aborder cela et cela dépend certainement de ce dont vous avez réellement besoin. Cependant, la bibliothèque standard C a encore quelques fonctions très utiles dans <string.h>, comme memchr. Il y a une raison pour laquelle C est toujours considéré comme le meilleur langage pour IO - son stdlib est une pure efficacité.

inline const char* trim_start(const char* str)
{
    while (memchr(" \t\n\r", *str, 4))  ++str;
    return str;
}
inline const char* trim_end(const char* end)
{
    while (memchr(" \t\n\r", end[-1], 4)) --end;
    return end;
}
inline std::string trim(const char* buffer, int len) // trim a buffer (input?)
{
    return std::string(trim_start(buffer), trim_end(buffer + len));
}
inline void trim_inplace(std::string& str)
{
    str.assign(trim_start(str.c_str()),
        trim_end(str.c_str() + str.length()));
}

int main()
{
    char str [] = "\t \nhello\r \t \n";

    string trimmed = trim(str, strlen(str));
    cout << "'" << trimmed << "'" << endl;

    system("pause");
    return 0;
}
Jorma Rebane
la source
3

Je ne sais pas si votre environnement est le même, mais dans le mien, le cas de chaîne vide entraînera l'abandon du programme. J'envelopperais cet appel d'effacement avec un if (! S.empty ()) ou j'utiliserais Boost comme déjà mentionné.

Steve
la source
3

Voici ce que j'ai trouvé:

std::stringstream trimmer;
trimmer << str;
trimmer >> str;

L'extraction de flux élimine automatiquement les espaces blancs, donc cela fonctionne comme un charme.
Assez propre et élégant aussi, si je le dis moi-même. ;)

tzaman
la source
15
Hmm; cela suppose que la chaîne n'a pas d'espace blanc interne (par exemple des espaces). L'OP a seulement dit qu'il voulait couper les espaces à gauche ou à droite.
SuperElectric
3

Contribuer ma solution au bruit. trimpar défaut, la création d'une nouvelle chaîne et le retour de la chaîne modifiée tout entrim_in_place modifient la chaîne qui lui est transmise. La trimfonction prend en charge la sémantique de déplacement c ++ 11.

#include <string>

// modifies input string, returns input

std::string& trim_left_in_place(std::string& str) {
    size_t i = 0;
    while(i < str.size() && isspace(str[i])) { ++i; };
    return str.erase(0, i);
}

std::string& trim_right_in_place(std::string& str) {
    size_t i = str.size();
    while(i > 0 && isspace(str[i - 1])) { --i; };
    return str.erase(i, str.size());
}

std::string& trim_in_place(std::string& str) {
    return trim_left_in_place(trim_right_in_place(str));
}

// returns newly created strings

std::string trim_right(std::string str) {
    return trim_right_in_place(str);
}

std::string trim_left(std::string str) {
    return trim_left_in_place(str);
}

std::string trim(std::string str) {
    return trim_left_in_place(trim_right_in_place(str));
}

#include <cassert>

int main() {

    std::string s1(" \t\r\n  ");
    std::string s2("  \r\nc");
    std::string s3("c \t");
    std::string s4("  \rc ");

    assert(trim(s1) == "");
    assert(trim(s2) == "c");
    assert(trim(s3) == "c");
    assert(trim(s4) == "c");

    assert(s1 == " \t\r\n  ");
    assert(s2 == "  \r\nc");
    assert(s3 == "c \t");
    assert(s4 == "  \rc ");

    assert(trim_in_place(s1) == "");
    assert(trim_in_place(s2) == "c");
    assert(trim_in_place(s3) == "c");
    assert(trim_in_place(s4) == "c");

    assert(s1 == "");
    assert(s2 == "c");
    assert(s3 == "c");
    assert(s4 == "c");  
}
vmrob
la source
3

Cela peut être fait plus simplement en C ++ 11 grâce à l'ajout de back()et pop_back().

while ( !s.empty() && isspace(s.back()) ) s.pop_back();
nobar
la source
L'approche suggérée par le PO n'est pas mauvaise non plus - juste un peu plus difficile à suivre.
nobar
3

Voici ma version:

size_t beg = s.find_first_not_of(" \r\n");
return (beg == string::npos) ? "" : in.substr(beg, s.find_last_not_of(" \r\n") - beg);
nulleight
la source
Il vous manque le dernier caractère. Un +1 dans la longueur résout ce problème
galinette
2

Les méthodes ci-dessus sont excellentes, mais vous souhaitez parfois utiliser une combinaison de fonctions pour ce que votre routine considère comme un espace. Dans ce cas, l'utilisation de foncteurs pour combiner des opérations peut devenir compliquée, je préfère donc une boucle simple que je peux modifier pour le trim. Voici une fonction de trim légèrement modifiée copiée de la version C ici sur SO. Dans cet exemple, je coupe des caractères non alphanumériques.

string trim(char const *str)
{
  // Trim leading non-letters
  while(!isalnum(*str)) str++;

  // Trim trailing non-letters
  end = str + strlen(str) - 1;
  while(end > str && !isalnum(*end)) end--;

  return string(str, end+1);
}
Corwin Joy
la source
2

Voici une implémentation simple. Pour une opération aussi simple, vous ne devriez probablement pas utiliser de constructions spéciales. La fonction intégrée isspace () prend en charge diverses formes de caractères blancs, nous devons donc en profiter. Vous devez également prendre en compte des cas particuliers où la chaîne est vide ou simplement un tas d'espaces. Le trim à gauche ou à droite peut être dérivé du code suivant.

string trimSpace(const string &str) {
   if (str.empty()) return str;
   string::size_type i,j;
   i=0;
   while (i<str.size() && isspace(str[i])) ++i;
   if (i == str.size())
      return string(); // empty string
   j = str.size() - 1;
   //while (j>0 && isspace(str[j])) --j; // the j>0 check is not needed
   while (isspace(str[j])) --j
   return str.substr(i, j-i+1);
}
Kemin Zhou
la source
2

Voici une solution facile à comprendre pour les débutants qui n'ont pas l'habitude d'écrire std::partout et qui ne sont pas encore familiarisés avec const-correctness, iterators, STL algorithms, etc ...

#include <string>
#include <cctype> // for isspace
using namespace std;


// Left trim the given string ("  hello!  " --> "hello!  ")
string left_trim(string str) {
    int numStartSpaces = 0;
    for (int i = 0; i < str.length(); i++) {
        if (!isspace(str[i])) break;
        numStartSpaces++;
    }
    return str.substr(numStartSpaces);
}

// Right trim the given string ("  hello!  " --> "  hello!")
string right_trim(string str) {
    int numEndSpaces = 0;
    for (int i = str.length() - 1; i >= 0; i--) {
        if (!isspace(str[i])) break;
        numEndSpaces++;
    }
    return str.substr(0, str.length() - numEndSpaces);
}

// Left and right trim the given string ("  hello!  " --> "hello!")
string trim(string str) {
    return right_trim(left_trim(str));
}

J'espère que cela aide...

cute_ptr
la source
1

Cette version réduit les espaces internes et les caractères non alphanumériques:

static inline std::string &trimAll(std::string &s)
{   
    if(s.size() == 0)
    {
        return s;
    }

    int val = 0;
    for (int cur = 0; cur < s.size(); cur++)
    {
        if(s[cur] != ' ' && std::isalnum(s[cur]))
        {
            s[val] = s[cur];
            val++;
        }
    }
    s.resize(val);
    return s;
}
Brian
la source
1

Encore une autre option - supprime un ou plusieurs caractères des deux extrémités.

string strip(const string& s, const string& chars=" ") {
    size_t begin = 0;
    size_t end = s.size()-1;
    for(; begin < s.size(); begin++)
        if(chars.find_first_of(s[begin]) == string::npos)
            break;
    for(; end > begin; end--)
        if(chars.find_first_of(s[end]) == string::npos)
            break;
    return s.substr(begin, end-begin+1);
}
Brian W.
la source