Какой лучший способ обрезать std :: string? - PullRequest
722 голосов
/ 19 октября 2008

В настоящее время я использую следующий код, чтобы обрезать вправо все std::strings в моих программах:

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

Работает нормально, но мне интересно, есть ли какие-нибудь конечные случаи, когда он может потерпеть неудачу?

Конечно, ответы с изящными альтернативами, а также с левым решением приветствуются.

Ответы [ 40 ]

586 голосов
/ 20 октября 2008

РЕДАКТИРОВАТЬ Начиная с c ++ 17, некоторые части стандартной библиотеки были удалены. К счастью, начиная с c ++ 11, у нас есть лямбды, которые являются превосходным решением.

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

Спасибо https://stackoverflow.com/a/44973498/524503 за разработку современного решения.

Оригинальный ответ:

Я склонен использовать один из этих 3 для своих нужд обрезки:

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

Они довольно понятны и работают очень хорошо.

РЕДАКТИРОВАТЬ : Кстати, у меня есть std::ptr_fun, чтобы помочь устранить неоднозначность std::isspace, потому что на самом деле есть второе определение, которое поддерживает локали. Это мог быть тот же актерский состав, но мне это нравится больше.

РЕДАКТИРОВАТЬ : чтобы ответить на некоторые комментарии о принятии параметра по ссылке, его изменении и возврате. Согласен. Я бы предпочел реализацию, состоящую из двух наборов функций: одну на месте и одну, которая делает копию. Лучшим набором примеров будет:

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

Я оставляю первоначальный ответ выше, хотя для контекста и в интересах сохранения по-прежнему высокого ответа.

400 голосов
/ 19 октября 2008

Использование строковых алгоритмов Boost будет проще всего:

#include <boost/algorithm/string.hpp>

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

str теперь "hello world!". Также есть trim_left и trim, которые обрезают обе стороны.


Если вы добавите суффикс _copy к любому из названий функций, например, trim_copy, функция будет возвращать усеченную копию строки вместо изменения через ссылку.

Если вы добавите суффикс _if к любому из названий функций, например, trim_copy_if, вы можете обрезать все символы в соответствии с вашим пользовательским предикатом, а не только пробелами.

59 голосов
/ 07 декабря 2008

Используйте следующий код для выравнивания (задних) пробелов и символов табуляции справа от 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));
}

И чтобы уравновесить ситуацию, я также добавлю код левой обрезки ( ideone ):

// trim leading spaces
size_t startpos = str.find_first_not_of(" \t");
if( string::npos != startpos )
{
    str = str.substr( startpos );
}
52 голосов
/ 31 июля 2013

Немного опоздал на вечеринку, но не бери в голову. Теперь C ++ 11 здесь, у нас есть лямбды и авто переменные. Итак, моя версия, которая также обрабатывает все пробелы и пустые строки, выглядит так:

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

Мы могли бы сделать обратный итератор из wsfront и использовать его в качестве условия завершения во втором find_if_not, но это полезно только в случае строки со всеми пробелами, а gcc 4.8 по крайней мере недостаточно умен вывести тип обратного итератора (std::string::const_reverse_iterator) с помощью auto. Я не знаю, насколько дорогой является создание обратного итератора, так что YMMV здесь. С этим изменением код выглядит так:

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());
}
40 голосов
/ 19 августа 2014

То, что вы делаете, прекрасно и надежно. Я использовал один и тот же метод в течение длительного времени, и мне еще не удалось найти более быстрый метод:

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

Предоставляя обрезаемые символы, вы можете обрезать непробельные символы и эффективно обрезать только те символы, которые хотите обрезать.

34 голосов
/ 28 июня 2011

Попробуйте, у меня все работает.

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;
}
25 голосов
/ 05 июля 2010

Мне нравится решение Цамана, единственная проблема в том, что оно не обрезает строку, содержащую только пробелы.

Чтобы исправить этот 1 недостаток, добавьте str.clear () между двумя линиями триммера

std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;
19 голосов
/ 11 февраля 2014

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());
}
15 голосов
/ 19 октября 2008

В случае пустой строки ваш код предполагает, что добавление 1 к string::npos дает 0. string::npos имеет тип string::size_type, что без знака. Таким образом, вы полагаетесь на поведение переполнения сложения.

14 голосов
/ 20 октября 2008

взломан от 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;
}

Это работает и для нулевого случая. : -)

...