Was ist der beste Weg, um std :: string zu trimmen?

Ich verwende derzeit den folgenden Code, um alle std::strings in meinen Programmen rechts zu trimmen:

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

Es funktioniert gut, aber ich frage mich, ob es einige Endfälle gibt, in denen es scheitern könnte?

Natürlich sind Antworten mit eleganten Alternativen und auch Links-Trim-Lösung willkommen.

    EDIT Seit c ++ 17 wurden einige Teile der Standardbibliothek entfernt. Zum Glück haben wir ab c ++ 11 Lambdas, die eine bessere Lösung darstellen.

     #include  #include  #include  // 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; } 

    Danke an https://stackoverflow.com/a/44973498/524503 für die moderne Lösung.

    Ursprüngliche Antwort:

    Ich neige dazu, eines dieser 3 für meine Bedürfnisse zu verwenden:

     #include  #include  #include  #include  // 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(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(std::isspace))).base(), s.end()); return s; } // trim from both ends static inline std::string &trim(std::string &s) { return ltrim(rtrim(s)); } 

    Sie sind ziemlich selbsterklärend und funktionieren sehr gut.

    BEARBEITEN : BTW, ich habe std::ptr_fun in dort, um zu helfen, std::isspace zu disambiguate, weil es tatsächlich eine zweite Definition gibt, die locales unterstützt. Das hätte genauso aussehen können, aber ich mag das besser.

    EDIT : Um einige Kommentare über das Akzeptieren eines Parameters als Referenz zu adressieren, zu modifizieren und zurückzugeben. Ich stimme zu. Eine Implementierung, die ich wahrscheinlich bevorzugen würde, wäre zwei Sätze von functionen, eine für vor Ort und eine, die eine Kopie macht. Ein besserer Satz von Beispielen wäre:

     #include  #include  #include  #include  // 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(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(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; } 

    Ich behalte die ursprüngliche Antwort oben für den Kontext und im Interesse, die Antwort mit hoher Wahl weiterhin verfügbar zu halten.

    Die Verwendung von Boosts String-Algorithmen wäre am einfachsten:

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

    str ist jetzt "hello world!" . Es gibt auch trim_left und trim , die beide Seiten trimmt.


    Wenn Sie ein _copy Suffix zu einem der obigen functionsnamen trim_copy , z. B. trim_copy , gibt die function eine getrimmte Kopie der Zeichenfolge zurück, anstatt sie über eine Referenz zu trim_copy .

    Wenn Sie einem der oben genannten functionsnamen, z. B. trim_copy_if , das _if Suffix trim_copy_if , können Sie alle Zeichen, die Ihrem benutzerdefinierten Prädikat entsprechen, im Gegensatz zu trim_copy_if Leerzeichen zuschneiden.

    Verwenden Sie den folgenden Code, um (nachgestellte) Leerzeichen und Tabulatorzeichen aus std::strings ( ideone ) zu trimmen :

     // 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)); } 

    Und um die Dinge auszugleichen, werde ich auch den linken Trim-Code ( ideone ) hinzufügen :

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

    Bis spät in die Party, aber egal. Jetzt C ++ 11 ist hier, wir haben Lambdas und Auto-Variablen. Also meine Version, die auch alle Whitespace und leere Strings behandelt, ist:

     #include  #include  #include  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)); } 

    Wir könnten einen umgekehrten Iterator von wsfront und diesen als Abbruchbedingung in der zweiten find_if_not aber das ist nur im Falle einer Whitespace-Zeichenfolge nützlich, und gcc 4.8 ist zumindest nicht schlau genug, um den Typ der Umkehrung abzuleiten Iterator ( std::string::const_reverse_iterator ) mit auto . Ich weiß nicht, wie teuer der Aufbau eines Reverse Iterators ist, also hier YMMV. Mit dieser Änderung sieht der Code folgendermaßen aus:

     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()); } 

    Was Sie tun, ist gut und robust. Ich habe die gleiche Methode schon lange benutzt und muss noch eine schnellere Methode finden:

     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 (left & right) inline std::string& trim(std::string& s, const char* t = ws) { return ltrim(rtrim(s, t), t); } 

    Wenn Sie die zuzuschneidenden Zeichen eingeben, können Sie Zeichen ohne Leerzeichen zuschneiden und nur die Zeichen zuschneiden, die Sie beschneiden möchten.

    Probieren Sie es aus, es funktioniert für mich.

     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; } 

    Ich mag Tzamans Lösung, das einzige Problem dabei ist, dass es keine Zeichenfolge schneidet, die nur Leerzeichen enthält.

    Um diesen 1 Fehler zu korrigieren, fügen Sie einen str.clear () zwischen den 2 Trimmlinien hinzu

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

    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()); } 

    Im Fall einer leeren Zeichenfolge geht Ihr Code davon aus, dass das Hinzufügen von 1 zu string::npos 0 ergibt. string::npos hat den Typ string::size_type und ist unsigniert. Sie verlassen sich also auf das Überlaufverhalten von Addition.

    Abgehackt von Cplusplus.com

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

    Dies funktioniert auch für den Nullfall. 🙂

    Meine Lösung basiert auf der Antwort von @Bill the Lizard .

    Beachten Sie, dass diese functionen die leere Zeichenfolge zurückgeben, wenn die Eingabezeichenfolge nichts als Leerzeichen enthält.

     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); } 

    Meine Antwort ist eine Verbesserung der obersten Antwort für diesen Beitrag, die Steuerzeichen sowie Leerzeichen (0-32 und 127 in der ASCII-Tabelle ) schneidet.

    std::isgraph bestimmt, ob ein Zeichen eine grafische Darstellung hat. Sie können damit Evans Antwort ändern, um alle Zeichen zu entfernen, die keine grafische Darstellung von beiden Seiten einer Zeichenkette haben. Das Ergebnis ist eine viel elegantere Lösung:

     #include  #include  #include  /** * @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(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(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)); } 

    Hinweis: Alternativ sollten Sie std::iswgraph wenn Sie Unterstützung für breite Zeichen benötigen, aber Sie müssen diesen Code auch bearbeiten, um std::wstring manipulation zu aktivieren, was ich nicht getestet habe (siehe Referenzseite für std::basic_string , um diese Option zu erkunden).

    Das ist was ich benutze. Entfernen Sie immer nur Platz von der Vorderseite, und wenn noch etwas übrig ist, machen Sie dasselbe von hinten.

     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 } 

    Für das, was es wert ist, hier ist eine Trim-Implementierung mit Blick auf die performance. Es ist viel schneller als viele andere Trim-Routinen, die ich gesehen habe. Anstatt Iteratoren und std :: founds zu verwenden, werden rohe c-Strings und Indizes verwendet. Es optimiert die folgenden Spezialfälle: Größe 0 string (nichts tun), string ohne Leerzeichen zum trimmen (nichts tun), string mit nur nachgestellten Leerzeichen zum trimmen (einfach die Größe des Strings ändern), String, der ganz aus Leerzeichen besteht (einfach den String löschen) . Und schließlich, im schlimmsten Fall (String mit führenden Leerzeichen), tut es sein Bestes, um eine effiziente Kopierkonstruktion durchzuführen, indem nur eine Kopie ausgeführt wird und dann diese Kopie anstelle der ursprünglichen Zeichenkette verschoben wird.

     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)); } } 

    Mit C ++ 11 kam auch ein Modul für reguläre Ausdrücke , das natürlich verwendet werden kann, um führende oder nachstehende Leerzeichen zu trimmen.

    Vielleicht in etwa so:

     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)); } 

    Eine elegante Art, es zu tun, kann wie sein

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

    Und die unterstützenden functionen sind implementiert als:

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

    Und wenn Sie all diese Dinge an Ort und Stelle haben, können Sie dies auch schreiben:

     std::string trim_copy(std::string const & str) { auto s = str; return ltrim(rtrim(s)); } 
     s.erase(0, s.find_first_not_of(" \n\r\t")); s.erase(s.find_last_not_of(" \n\r\t")+1); 

    Folgendes habe ich mir ausgedacht:

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

    Bei der Stream-Extraktion werden Leerräume automatisch entfernt. Dies wirkt wie ein Zauber.
    Ziemlich sauber und elegant auch, wenn ich das selbst sage. 😉

    Ich denke, wenn man nach dem “besten Weg” fragt, um eine Schnur zu trimmen, würde ich sagen, eine gute Implementierung wäre eine, die:

    1. Temporäre Zeichenfolgen werden nicht zugewiesen
    2. Hat Überlastungen für die In-Situ-Trimm- und Kopiertrimmung
    3. Kann leicht angepasst werden, um verschiedene validationssequenzen / Logik zu akzeptieren

    Offensichtlich gibt es zu viele verschiedene Wege, um dies zu erreichen und es hängt definitiv davon ab, was Sie wirklich brauchen. Die C-Standardbibliothek hat jedoch noch einige sehr nützliche functionen in , wie z. B. memchr. Es gibt einen Grund, warum C immer noch als die beste Sprache für IO angesehen wird – seine stdlib ist pure Effizienz.

     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; } 

    Trim C ++ 11 Implementierung:

     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()); } 

    Ich bin mir nicht sicher, ob Ihre Umgebung die gleiche ist, aber in meinem Fall führt der leere String dazu, dass das Programm abgebrochen wird. Ich würde diesen Löschanruf entweder mit einem if (! S.empty ()) umhüllen oder Boost wie bereits erwähnt verwenden.

    Dies kann in C ++ 11 aufgrund der Hinzufügung von back() und pop_back() .

     while ( !s.empty() && isspace(s.back()) ) s.pop_back(); 

    Hier ist meine 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); 

    Meine Lösung zum Lärm beitragen. trim standardmäßig eine neue Zeichenfolge und gibt die geänderte zurück, während trim_in_place die an sie übergebene trim_in_place ändert. Die trim function unterstützt die Semantik von c ++ 11 move.

     #include  // 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  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"); } 

    C ++ 11:

     int i{}; string s = " he ll \t\no"; string trim = " \n\t"; while ((i = s.find_first_of(trim)) != -1) s.erase(i,1); cout < < s; 

    Ausgabe:

     hello 

    funktioniert auch mit leeren Saiten

    Hier ist eine leicht verständliche Lösung für Anfänger, die nicht gewohnt sind, std:: everywhere zu schreiben und noch nicht vertraut mit const correctness, iterator s, STL- algorithm s, etc …

     #include  #include  // 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)); } 

    Ich hoffe es hilft…

    Die oben genannten Methoden sind großartig, aber manchmal möchten Sie eine Kombination von functionen für das, was Ihre Routine als Leerzeichen betrachtet. In diesem Fall kann die Verwendung von Funktoren zum Kombinieren von Operationen unordentlich werden, daher bevorzuge ich eine einfache Schleife, die ich für die Trimmung modifizieren kann. Hier ist eine leicht modifizierte Trimm-function, kopiert von der C-Version hier auf SO. In diesem Beispiel beschneide ich nicht alphanumerische Zeichen.

     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); } 

    Diese Version schneidet interne Leerzeichen und nicht-alphanumerische Zeichen ab:

     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; } 

    Noch eine weitere Option – entfernt ein oder mehrere Zeichen von beiden Enden.

     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); } 

    Was ist damit …?

     #include  #include  #include  std::string ltrim( std::string str ) { return std::regex_replace( str, std::regex("^\\s+"), std::string("") ); } std::string rtrim( std::string str ) { return std::regex_replace( str, std::regex("\\s+$"), std::string("") ); } std::string trim( std::string str ) { return ltrim( rtrim( str ) ); } int main() { std::string str = " \t this is a test string \n "; std::cout < < "-" << trim( str ) << "-\n"; return 0; } 

    Hinweis: Ich bin noch relativ neu in C ++, also bitte verzeih mir, wenn ich hier bin.