Какой лучший способ обрезать std :: string?

812

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

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

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

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

Милан Бабушков
источник
549
Ответы на этот вопрос являются свидетельством того, насколько не хватает стандартной библиотеки C ++.
Идан К
83
@IdanK И у него до сих пор нет этой функции в C ++ 11.
квант
44
@IdanK: Отлично, не правда ли! Посмотрите на все конкурирующие варианты мы теперь имеем в нашем распоряжении, не обремененный идеей одного человека о « в пути , что мы должны сделать это»!
Гонки легкости на орбите
59
Функциональность @LightnessRacesinOrbit внутри типа, ну, это дизайнерское решение, и добавление функции обрезки в строку может (по крайней мере, в c ++) не быть лучшим решением в любом случае - но не предоставляя никакого стандартного способа сделать это, вместо этого позволяя всем раздражаться одни и те же мелкие проблемы снова и снова, безусловно, никому не помогают
кодирование
27
Вы можете спросить, почему функции обрезки не встроены в std::stringкласс, когда именно такие функции делают использование других языков таким приятным (например, Python).
Здравствуйте, до свидания

Ответы:

648

РЕДАКТИРОВАТЬ Начиная с 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;
}

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

Эван Теран
источник
28
Этот код не выполнялся в некоторых международных строках (в моем случае shift-jis хранится в std :: string); Я в конечном итоге использовать boost::trimдля решения проблемы.
Том
5
Я бы использовал указатели вместо ссылок, так что с точки вызова гораздо проще понять, что эти функции редактируют строку на месте, а не создают копию.
Марко Леогранде
3
Обратите внимание , что с isspace вы можете легко получить неопределенное поведение с не-ASCII символы stacked-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f
R. Martinho Fernandes
10
Почему статический? Это где анонимное пространство имен будет предпочтительным?
Тревор Хикки
3
@TrevorHickey, конечно, вместо этого вы можете использовать анонимное пространство имен, если хотите.
Эван Теран
417

Использовать строковые алгоритмы 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, вы можете обрезать все символы, удовлетворяющие вашему пользовательскому предикату, а не только пробелам.

Леон Тиммерманс
источник
7
Это зависит от локали. Моя локаль по умолчанию (VS2005, en) означает, что табуляция, пробелы, возврат каретки, переводы строки, вертикальные табуляции и подача формы обрезаются.
MattyT
117
Повышение является таким огромным молотом для такой крошечной проблемы.
Кейси Родармор
143
@rodarmor: Boost решает множество мелких проблем. Это массивный молот, который много решает.
Николь Болас
123
Boost - это набор молотков разных размеров, решающих множество разных задач.
Ибрагим
11
@rodarmor Вы говорите так, как будто Boost - монолит "все или ничего", когда включение одного из его заголовков каким-то образом накладывает всю вещь на программу. Что явно не так. Кстати, я никогда не использовал Boost, кстати.
underscore_d
61

Используйте следующий код для выравнивания (конечных) пробелов и символов табуляции от 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 );
}
Билл Ящерица
источник
4
Это не обнаружит другие формы пробелов ... перевод строки, в частности, возврат каретки.
Том
1
Правильно. Вы должны настроить его для пробелов, которые вы хотите обрезать. Мое конкретное приложение ожидало только пробелы и табуляции, но вы можете добавить \ n \ r, чтобы перехватить остальные.
Билл Ящерица
5
str.substr(...).swap(str)лучше. Сохраните назначение.
обновлён
4
@updogliu Разве это не будет использовать назначение движения basic_string& operator= (basic_string&& str) noexcept;?
nurettin
8
Этот ответ не изменяет строки, которые являются ВСЕМИ пробелами. Который провал.
Том Андерсен
56

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

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

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

Galik
источник
если вы измените заказ trim, т.е. сделаете его, rtrim(ltrim(s, t), t)он будет немного более эффективным
CITBL
1
@CITBL Внутренняя функция выполняется в первую очередь, так что, по-вашему, она будет обрезаться слева до обрезки справа. Я думаю, что это будет менее эффективно, не так ли?
Галик
Точно. Моя ошибка
CITBL
если вы используете basic_string и template в CharT, вы можете сделать это для всех строк, просто используйте переменную шаблона для пробела, чтобы использовать ее как ws <CharT>. технически в этот момент вы могли бы подготовить его к c ++ 20 и пометить его как constexpr, поскольку это подразумевает inline
Beached
@ На самом деле. Немного сложно вставить ответ здесь. Я написал шаблонные функции для этого, и это, безусловно, довольно сложно. Я пробовал кучу разных подходов и до сих пор не уверен, какой из них лучший.
Галик
55

Немного опоздал на вечеринку, но не бери в голову. Теперь 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());
}
Дэвид Г
источник
9
Ницца. +1 от меня. Жаль, что C ++ 11 не вводит trim () в std :: string и делает жизнь проще для всех.
Милан Бабушков
3
Я всегда хочу, чтобы один вызов функции
обрезал
22
Для чего это стоит, нет необходимости использовать эту лямбду. Вы можете просто пройти std::isspace:auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);
vmrob
4
+1 за, вероятно, единственный ответ с реализацией, которая делает только одну O (N) строковую копию.
Алексей Аверченко
4
Компиляторы @vmrob не обязательно настолько умны. делать то, что вы говорите, неоднозначно:candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)
johnbakers
42

Попробуйте это, это работает для меня.

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
источник
12
Если в вашей строке нет пробелов, это приведет к удалению, начиная с npos + 1 == 0, и вы удалите всю строку.
Mhsmith
3
@rgove Пожалуйста, объясните. str.find_last_not_of(x)возвращает позицию первого символа, не равного x. Он возвращает npos только если никакие символы не совпадают с x. В примере, если нет пробелов с суффиксами, он вернет эквивалент str.length() - 1, по сути, str.erase((str.length() - 1) + 1).то есть, если я не ошибаюсь.
Трэвис
5
Это должно вернуть std :: string &, чтобы избежать ненужного вызова конструктора копирования.
heksesang
7
Я запутался, почему это возвращает копию после изменения возвращаемого параметра?
Галик
3
@MiloDC Моя путаница - зачем возвращать копию, а не ссылку. Это имеет больше смысла для меня, чтобы вернуться std::string&.
Галик
25

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

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

std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;
Михаэль Шунбруд
источник
Хорошо :) проблема с обоими нашими решениями состоит в том, что они обрежут оба конца; не может сделать ltrimили rtrimкак это.
Цаман
44
Хорошо, но не может справиться со строкой с внутренним пробелом. например, trim (abc def ") -> abc, только abc осталось.
liheyuan
Хорошее решение, если вы знаете, что не будет никаких внутренних пробелов!
Эллиот Гороховский
Это приятно и просто, но также довольно медленно, поскольку строка копируется в и из std::stringstream.
Галик
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
источник
1
Наконец-то элегантное решение для базовой отделки пространства ... :)
jave.web
Как это работает: Это решение, похожее на копию - оно находит позицию первого символа, которая не является пробелом ( it) и наоборот: позиция символа, после которой есть только пробелы ( rit) - после этого она возвращает вновь созданную строку == копия части исходной строки - части, основанной на этих итераторах ...
jave.web
Спасибо, сработало для меня: std: string s = "Oh noez: space \ r \ n"; std :: string clean = trim (s);
Алекс Рош
15

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

Грег Хьюгилл
источник
23
Вы формулируете это, как будто это плохо. Подпись поведение Целочисленное переполнение плохо.
MSalters
2
Добавление 1к std::string::npos должны дать в 0соответствии с C++ Standard. Так что это хорошее предположение, на которое можно абсолютно положиться.
Галик
13

Отрублены из 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;
}

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

Пол Натан
источник
4
Это просто rtrim, нетltrim
ub3rst4r
1
^ Вы не против использовать find_first_not_of? Это относительно легко изменить.
Абхинав Гауниял
13

В C ++ 17 вы можете использовать basic_string_view :: remove_prefix и 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;
}

Хорошая альтернатива:

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
источник
Я не уверен, что вы тестируете, но в вашем примере std :: find_first_not_of вернет std :: string :: npos а std :: string_view :: size вернет 4. Минимум, очевидно, равен четырем, количество элементов для удалено std :: string_view :: remove_prefix . И gcc 9.2, и clang 9.0 справляются с этим правильно: godbolt.org/z/DcZbFH
Phidelux
1
Спасибо! Выглядит хорошо для меня.
Контанго
11

Мое решение основано на ответе @Bill the Lizard .

Обратите внимание, что эти функции будут возвращать пустую строку, если входная строка не содержит ничего, кроме пробела.

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
источник
9

Мой ответ - это улучшение верхнего ответа на этот пост, который урезает управляющие символы, а также пробелы (0-32 и 127 в таблице ASCII ).

std::isgraphопределяет, имеет ли символ графическое представление, так что вы можете использовать это, чтобы изменить ответ Эвана, чтобы удалить любой символ, который не имеет графического представления с любой стороны строки. В результате получается гораздо более элегантное решение:

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

Примечание. В качестве альтернативы вы можете использовать ее, std::iswgraphесли вам нужна поддержка широких символов, но вам также придется отредактировать этот код, чтобы включить std::wstringманипуляции, что я не проверял (см. Страницу справки для std::basic_stringизучения этой опции) ,

Глина Фриман
источник
3
std :: ptr_fun устарела
johnbakers
8

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

Может быть, что-то вроде этого:

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));
}
Какой-то программист чувак
источник
8

Это то, что я использую. Просто продолжайте убирать пространство спереди, а затем, если что-то осталось, сделайте то же самое сзади.

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
источник
8
s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
s.erase(s.find_last_not_of(" \n\r\t")+1);   
freeboy1015
источник
2
Было бы немного эффективнее, если бы вы делали их в обратном порядке и сначала обрезали справа, прежде чем вызывать сдвиг, обрезая влево.
Галик
7

Для чего это стоит, вот аккуратная реализация с оглядкой на производительность. Это намного быстрее, чем многие другие процедуры обрезки, которые я видел вокруг. Вместо использования итераторов и std :: find он использует необработанные строки и индексы c. Он оптимизирует следующие особые случаи: строка размера 0 (ничего не делать), строка без пробелов для обрезки (ничего не делать), строка только с последующим пробелом для обрезки (просто изменить размер строки), строка, которая полностью пуста (просто очистить строку) , И, наконец, в худшем случае (строка с начальным пробелом), она делает все возможное, чтобы создать эффективную копию, выполнив только 1 копию, а затем переместив эту копию вместо исходной строки.

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
источник
@bmgda, возможно, теоретически самая быстрая версия может иметь эту подпись: extern "C" void string_trim (char ** begin_, char ** end_) ... Поймать мой дрейф?
6

Элегантный способ сделать это может быть как

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

И поддерживающие функции реализованы так:

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

И как только вы все это на месте, вы также можете написать это:

std::string trim_copy(std::string const & str)
{
   auto s = str;
   return ltrim(rtrim(s));
}
Джа-G
источник
6

Trim 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
источник
5

Я предполагаю, что если вы начнете спрашивать «лучший способ» обрезать строку, я бы сказал, что хорошей реализацией будет та, которая:

  1. Не выделяет временные строки
  2. Имеет перегрузки для обрезки на месте и обрезки копий
  3. Может быть легко настроен для принятия различных последовательностей проверки / логики

Очевидно, что существует слишком много разных способов приблизиться к этому, и это определенно зависит от того, что вам действительно нужно. Однако в стандартной библиотеке C все еще есть несколько очень полезных функций в <string.h>, таких как memchr. Есть причина, почему C по-прежнему считается лучшим языком для ввода-вывода - его стандартная эффективность - чистая эффективность.

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;
}
Йорма Ребане
источник
3

Я не уверен, что ваша среда такая же, но в моем случае случай с пустой строкой приведет к прерыванию работы программы. Я бы либо обернул этот вызов стирания с помощью if (! S.empty ()), либо использовал Boost, как уже упоминалось.

Стив
источник
3

Вот что я придумал:

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

Извлечение потока автоматически удаляет пробелы, поэтому это работает как шарм.
Довольно чистый и элегантный, если я так скажу. ;)

tzaman
источник
15
Хмм; это предполагает, что строка не имеет внутренних пробелов (например, пробелов). ОП только сказал, что хочет обрезать пробелы слева или справа.
SuperElectric
3

Вклад моего решения в шум. trimпо умолчанию создает новую строку и возвращает измененную, а trim_in_placeизменяет переданную ей строку. В trimфункции поддерживает 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
источник
3

Это можно сделать проще в C ++ 11 за счет добавления back()и pop_back().

while ( !s.empty() && isspace(s.back()) ) s.pop_back();
nobar
источник
Подход, предложенный ФП, тоже неплох, но следовать ему немного сложнее.
Нобар
3

Вот моя версия:

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
источник
Вам не хватает последнего персонажа. +1 в длину решает это
галинет
2

Вышеуказанные методы хороши, но иногда вы хотите использовать комбинацию функций для того, что ваша подпрограмма считает пробелом. В этом случае использование функторов для объединения операций может стать беспорядочным, поэтому я предпочитаю простой цикл, который можно изменить для обрезки. Вот слегка измененная функция обрезки, скопированная с версии C здесь, на SO. В этом примере я обрезаю не буквенно-цифровые символы.

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);
}
Корвин Джой
источник
2

Вот прямая реализация. Для такой простой операции вам, вероятно, не следует использовать какие-либо специальные конструкции. Встроенная функция isspace () заботится о различных формах белых символов, поэтому мы должны воспользоваться этим. Вы также должны рассмотреть особые случаи, когда строка пуста или просто группа пробелов. Обрезка влево или вправо может быть получена из следующего кода.

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);
}
Кемин Чжоу
источник
2

Вот решение, простое для понимания новичками, которые не привыкли писать std::везде и еще не знакомы с const-корректностью, iterators, STL algorithmи т. Д.

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

Надеюсь, поможет...

cute_ptr
источник
1

Эта версия урезает внутренние пробелы и не алфавитно-цифровые:

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;
}
Брайан
источник
1

Еще один вариант - удаляет один или несколько символов с обоих концов.

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);
}
Брайан В.
источник