Как лучше всего обрезать std :: string?

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

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

Он работает нормально, но мне интересно, есть ли какие-то крайние случаи, когда он может выйти из строя?

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

Ответы на этот вопрос свидетельствуют о том, насколько не хватает стандартной библиотеки C++.

Idan K 24.08.2010 22:15

@IdanK И в C++ 11 до сих пор нет этой функции.

quantum 10.03.2012 22:10

@IdanK: Отлично, не правда ли! Посмотрите на все конкурирующие варианты, которые теперь есть в нашем распоряжении, не обремененные идеей одного человека о «в способе, которым мы должны это сделать»!

Lightness Races in Orbit 30.01.2013 05:40

@LightnessRacesinOrbit: наличие удобных функций в библиотеке не исключает возможности делать что-то по-другому. В библиотеке есть vector, но мы не обязаны использовать только вектор.

Mooing Duck 27.03.2013 04:34

@MooingDuck: Выбор типов - это одно; выбор функциональности в пределах данного типа является другим, поскольку предоставление этих вариантов может создать ограничения для остальной части типа и его реализации.

Lightness Races in Orbit 27.03.2013 05:14

Функциональность @LightnessRacesinOrbit внутри типа, ну, это дизайнерское решение, и добавление функции обрезки к строке может (по крайней мере, в C++) в любом случае быть не лучшим решением, но не предоставляет какой-либо стандартный способ сделать это, вместо этого позволяя всем беспокоиться одни и те же такие мелкие проблемы снова и снова, конечно, тоже никому не помогают

codeling 29.03.2013 15:01

Я действительно не знаю, о чем ты. является - стандартный способ делать эти вещи в C++: путем включения специализированной библиотеки по вашему выбору и использования функции обрезки это вместе со всеми другими специализированными функциями для вашей проблемной области.

DevSolar 31.07.2013 21:51

@DevSolar, для этого нет способа стандарт, потому что std :: string в библиотеке стандарт ("std" означает стандарт FYI) не предоставляет этого. Вы должны использовать стороннюю библиотеку.

Milan Babuškov 01.08.2013 01:16

@ MilanBabuškov: Такие языки, как Java или C#, пытаются предоставить библиотеки «все поет, все танцуют» как часть языка. Такие языки, как C или C++, предоставляют только базовые знания, и предоставляют специалистам право придумывать решения для предметной области. Это их «стандартный» подход. Я являюсь сопровождающим проприетарной библиотеки C++, которая выполняет токенизацию, обрезку, анализ, сравнение и дедупликацию международных адресов, и я могу сказать вам, что наличие trim() в std:: не спасло бы меня от работы, поскольку моя проблемная область требовала, чтобы я работал в первую очередь со строками, не относящимися к std:: / STL.

DevSolar 01.08.2013 09:54

Вы можете спросить, почему функции обрезки не встроены в класс std::string, когда именно такие функции делают другие языки такими удобными в использовании (например, Python).

HelloGoodbye 25.01.2014 22:04

@IdanK, по крайней мере, один влиятельный гуру C++ считает, что в стандартном строковом классе уже есть слишком много. Не уверен, что согласен, но это интересное чтение: gotw.ca/gotw/084.htm

Mark Ransom 13.03.2014 22:47

@MarkRansom, да, есть аргументы, почему это не должен быть в строковый класс, но это может быть бесплатная функция, например std :: trim (...), как в boost.

Ela782 29.05.2014 20:11

Слишком много того, что, по мнению Степанова, должно быть в STL, чтобы сделать его универсальным, и слишком мало того, что потребности должно быть в STL, чтобы сделать его полезным; пример разрыва в понимании и того, что происходит, когда что-то разрабатывается на основе теории, а не требований. (но я все время им пользуюсь :)

Nick 22.08.2014 15:49

Нет проблем, я напишу свою 1.000.000-ю версию. Использование виртуального интерфейса trimmable и его упаковка в полдюжины слоев хороших общих шаблонов, так что вы также можете обрезать обезьян и жирафов (возможно, на месте и / или с помощью специального распределителя) практически без накладных расходов на кодирование и молниеносной оптимизации.

kuroi neko 10.09.2014 05:14

1. Что-то вроде буста должен быть новый новый STL. 2. В C++ должны быть директивы условной компиляции (например, weak, но встроенные в компилятор). И тогда C++, наконец, сможет снова соревноваться на равных с другими языками как в удобстве использования, так и в переносимости.

Erik Aronesty 11.02.2015 19:29

Левая отделка: s = s.substr(s.find_first_not_of(" "));

PinkTurtle 17.11.2015 17:45

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

Bill Forster 21.01.2016 07:34

Есть одна вещь, которая раздражает меня в C++, даже когда я приобрел опыт. Если ожидается импорт других библиотек для более высоких уровней абстракции, почему в стандарте не указаны модули? Мне потребовалось 6 лет, чтобы преодолеть многочисленные препятствия и, наконец, скомпилировать, связать и включить внешние библиотеки, не полагаясь на (часто устаревшие) руководства по быстрому запуску.

Aaron3468 15.08.2016 16:42

Для всех, кто работает в мире .Net, вот хорошая статья о тестировании некоторых из самых быстрых способов обрезки строк там: Самый быстрый способ обрезать строки в C# .Net

user3810913 23.02.2017 03:01

Да здравствует Java, API лучший.

Bahramdun Adil 16.04.2017 18:17

Этот тип вопросов показывает как лучшее, так и худшее в C++.

Xam 28.07.2018 02:28

По-прежнему нет поддержки ltrim и rtrim в C++ 17 ?!

ConsistentProgrammer 28.03.2019 17:25

Я начал писать код на C++ в 2014 году. Вскоре перешел на JS, Java и PHP. Возвращаясь к C++ пятью годами позже, прискорбно видеть, насколько мало улучшилась стандартная библиотека.

Oloff Biermann 01.08.2019 19:46

Кто-нибудь знает, почему у C++ есть эти основные проблемы? По сравнению с C# работа со строкой в ​​C++ невероятно убогая. Я не могу поверить, что в C++ каждый должен писать свою функцию для строки Trim, TrimStart, TrimEnd, Split, Join, ....

bmi 30.12.2020 14:13

@bmi QtCore - хорошая альтернатива стандартной библиотеке для строк, контейнеров и т.д. Хорошая сторона в том, что Qt имеет целую экосистему библиотек, плохая сторона в том, что это не стандарт (снижает совместимость). Стандартная библиотека реализует концепции очень низкого уровня и имеет неудобный дизайн.

Kiruahxh 30.12.2020 17:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
862
25
762 064
48

Ответы 48

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

Вы формулируете это так, как будто это плохо. Подписано целочисленное поведение переполнения плохое.

MSalters 20.10.2008 12:21

Добавление 1 к std::string::nposдолжен дает 0 согласно C++ Standard. Так что это хорошее предположение, на которое можно полностью положиться.

Galik 25.09.2018 01:51

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

Что использует boost, чтобы определить, является ли символ пробелом?

Tom 08.12.2008 00:22

Это зависит от региона. Моя локаль по умолчанию (VS2005, en) означает, что табуляция, пробелы, возврат каретки, перевод строки, вертикальные табуляции и подача формы обрезаны.

MattyT 26.01.2009 16:11

Я уже использую много ускорения, #include <boost/format.hpp> #include <boost/tokenizer.hpp> #include <boost/lexical_cast.hpp>, но беспокоился о раздувании кода для добавления в <boost/algorithm/string.hpp>, когда уже есть альтернативы на основе std::string::erase. С удовольствием сообщаю при сравнении сборок MinSizeRel до и после его добавления, что эта обрезка ускорения вообще не увеличила мой размер кода (должно быть, уже где-то за это платит), и мой код не загроможден еще несколькими функциями.

Rian Sanderson 25.07.2011 09:36

@MattyT: Какую ссылку вы используете для этого списка (определяя, является ли символ пробелом)?

Faheem Mitha 30.12.2011 08:04

Boost - такой мощный молоток для такой крошечной проблемы.

Casey Rodarmor 27.03.2012 08:44

@rodarmor: Boost решает множество мелких проблем. Это огромный молоток, который многое решает.

Nicol Bolas 26.05.2012 00:22

Boost - это набор молотков разных размеров, решающий множество различных задач.

Ibrahim 18.01.2013 15:14

Boost - это большой швейцарский армейский нож, который нужно толкать и толкать, чтобы он поместился в кармане.

gbmhunter 21.08.2014 09:17

Единственное, что является практичным для IMO, - это предоставление контента для стандартной библиотеки (идеи, которые добавляются в результате повышения).

developerbmw 06.11.2014 09:02

@Ibrahim Boost - это универсальный трансконтинентальный завод по производству инструментов обсудить.joelonsoftware.com/default.asp?joel.3.219431.12&

José Ernesto Lara Rodríguez 24.11.2015 21:43

@rodarmor Вы говорите, что Boost - это монолит по принципу «все или ничего», где включение одного из его заголовков каким-то образом накладывает все на свою программу. Что явно не так. Кстати, Boost никогда не использовал, черт возьми.

underscore_d 12.09.2016 18:45

@underscore_d но это так. Он никогда не включает «только один файл». В итоге вы включаете больше исходного кода из boost, чем из вашего собственного проекта.

David Nogueira 20.05.2019 13:34

Несколько очень быстрых тестов на моей конкретной машине с моей конкретной строкой говорят мне, что ускорение требует чуть более чем в два раза больше времени для его обрезки, чем решение Эвана, приведенное выше.

Lieuwe 16.07.2019 18:16

на самом деле не отвечает на вопрос, который запрашивает std :: string (не для boost или любой другой библиотеки ...)

hfrmobile 04.11.2019 17:20

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

Взломал 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;
}

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

Это просто rtrim, а не ltrim

ub3rst4r 14.03.2014 11:05

^ вы не против использовать find_first_not_of? Его относительно легко изменить.

Abhinav Gauniyal 03.06.2015 05:09

РЕДАКТИРОВАТЬ Начиная с 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(), [](unsigned char ch) {
        return !std::isspace(ch);
    }));
}

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

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

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

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

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

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

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

Для стрижки я обычно использую один из этих трех:

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

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

Этот код не работал с некоторыми международными строками (в моем случае shift-jis хранится в std :: string); Я решил использовать boost::trim для решения проблемы.

Tom 22.07.2012 08:36

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

Marco Leogrande 06.09.2012 04:40

Обратите внимание, что с isspace вы можете легко получить неопределенное поведение с символами, отличными от ASCII stacked-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f

R. Martinho Fernandes 04.02.2013 17:52

@ R.MartinhoFernandes, правда, решение, которое я использовал в прошлом, - это шаблон: template<int (&F)(int)> int safe_ctype(unsigned char c) { return F(c); }, который можно использовать так: safe_ctype<std::isspace>(ch);

Evan Teran 04.02.2013 21:31

Почему статика? Здесь предпочтительнее анонимное пространство имен?

Trevor Hickey 30.09.2013 13:29

@TrevorHickey, конечно, вы можете использовать анонимное пространство имен вместо этого, если хотите.

Evan Teran 30.09.2013 15:17

Если это не компилируется для вас или вы согласны с идеей, что «строка кода не является абстракцией», см. моя суть реализации, который реализует эти функции с использованием только итераторов.

bobobobo 19.04.2014 14:42

Для согласованности имен с std::string я бы рекомендовал имена trim_front и trim_back.

Evan Moran 29.05.2014 09:59

Еще одно предложение - сделать это универсальным и поддерживать различные формы std::basic_string. <br /> template<class Ch, class Tr, class Al>inline void ltrim(std::basic_string<Ch, Tr, Al>& s) {...}

Andrew 08.12.2014 18:29

См. мой ответ ниже для поддержки более широкого диапазона обрезанных символов.

Clay Freeman 22.03.2015 00:12

Возвращение и с изменением аргумента: Плохо! :-)

Johannes Overmann 08.03.2016 16:25

@JohannesOvermann, я в целом согласен. Намерение состояло в том, чтобы упростить объединение этой функции с другими вызовами. Если бы я переписал это сегодня. Я бы сделал trim (который выполняет работу на месте) и trimmed, который возвращал бы копию обрезанной строки.

Evan Teran 08.03.2016 20:01

@JohannesOvermann, я добавил правку к этому ответу, чтобы решить ваши (и некоторые другие) проблемы.

Evan Teran 10.03.2016 21:30

@EvanTeran AFAICT идиоматический современный способ C++ - возвращать по значению, а не изменять ссылку. В любом случае проблема Йоханнеса заключалась в том, что вы оба принимали неконстантный ссылочный аргумент и изменяли его на месте, и возвращал ту же ссылку на тот же аргумент. Какая в этом цель? Мне кажется, что он в конечном итоге будет вызываться в ситуациях, когда он выглядит возвращается как новая строка и, следовательно, как будто входная строка не изменяется - но это было все время - что похоже на рецепт путаницы и / или катастрофа.

underscore_d 12.09.2016 18:42

@underscore_d посмотрите внимательно, текущая версия этого поста имеет две версии: те, которые принимают по ссылке, модифицируют на месте и ничего не возвращают. Те, которые заканчиваются на «ed» (копирующие). Возьмите параметр копировать, измените его и верните по значению. Из-за семантики перемещения С ++ 11 «копирующие» версии будут выполнять минимальное количество копий. Что касается того, "какова цель этого", я уже ответил на это в своем предыдущем комментарии. По сути, это была «цепочка вызовов».

Evan Teran 14.09.2016 18:56

std :: ptr_fun больше не рекомендуется

johnbakers 08.12.2016 20:20

@johnbakers правда, можно было бы использовать приведение или лямбду в более современном коде.

Evan Teran 08.12.2016 21:18

До сих пор не понимаю, почему у него так много голосов. Использовать find_last_not_of просто и понятно!

frankliuao 30.06.2017 17:43

в rtime, какова цель базового вызова find_if ??

Arup 06.07.2017 15:53

std :: ptr_fun устарел в C++ 17. Чем его заменить?

user3717478 07.07.2017 17:12

@ user3717478: Перекрестная публикация на свой вопрос по теме: stackoverflow.com/a/44973511/560648. Он объявлен устаревшим в C++ 11; удаленный в C++ 17.

Lightness Races in Orbit 07.07.2017 17:42

С C++ 17, вероятно, будет лучше использовать std::string_view.

WorldSEnder 06.08.2017 17:44

@WorldSEnder Согласен. Я только что сделал свою версию, и она работает в 4 раза быстрее на 1000000 строках, чем принятый ответ. Я использую ручной цикл и настраиваемую функцию is_space, которая использует случай переключения.

smoothware 20.01.2018 02:31

@tom Замените int ch на unsigned char ch в закрытии, и это не вызовет сбой утверждения.

Zmey 30.04.2019 00:42

Обратите внимание, что в зависимости от реализации обрезка строки справа перед обрезкой слева может быть быстрее, чем наоборот.

jotik 02.11.2019 13:24

@Tom: Shift-JIS не является «международным», если вы живете в стране, которая его использует. Вы имеете в виду не-ASCII.

Lightness Races in Orbit 05.11.2019 20:17

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

Konrad Rudolph 24.02.2020 20:55

Поведение передачи «int ch» в «isspace» не определено, замените «int ch» на «unsigned char ch». [] (беззнаковый символ ch) {возврат! std :: isspace (ch); } см .: en.cppreference.com/w/cpp/string/byte/isspace

Rman 15.09.2020 10:41

Используйте следующий код для обрезки справа (завершающих) пробелов и символов табуляции из std::strings (идеона):

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

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

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

Это не обнаружит другие формы пробелов ... в частности, новую строку, перевод строки, возврат каретки.

Tom 08.12.2008 00:23

Верно. Вы должны настроить его для пробелов, которые вы хотите обрезать. Мое конкретное приложение ожидало только пробелов и вкладок, но вы можете добавить \ n \ r, чтобы поймать другие.

Bill the Lizard 08.12.2008 03:20
str.substr(...).swap(str) лучше. Сохраните задание.
updogliu 30.08.2012 12:55

@updogliu Не будет ли он использовать назначение перемещения basic_string& operator= (basic_string&& str) noexcept;?

nurettin 08.10.2013 12:47

Этот ответ не изменяет строки, состоящие из ВСЕХ пробелов. Это провал.

Tom Andersen 10.04.2014 02:19

^ Прежде чем оставлять комментарии, убедитесь, что вы пытаетесь ответить на опубликованный ответ. @Tom, чтобы включить все это, убедитесь, что вы включаете их в вызовы функции first / last_not_of.

Abhinav Gauniyal 03.06.2015 05:18

@TomAndersen, проверьте условие, при котором string::npos != startpos не работает, что означает оператор else для вышеуказанных if. Внутри него обрезайте все пробелы, используя идиому «стереть-удалить». Вот пример: ideone.com/eelaVO

Abhinav Gauniyal 03.06.2015 05:19

@BilltheLizard, вы можете включить в ответ оператор else, чтобы он мог принести пользу другим.

Abhinav Gauniyal 03.06.2015 05:27

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

Lightness Races in Orbit 09.10.2015 00:28

Этот ответ не выполняется, если строка содержит только пробелы.

Galik 22.06.2017 13:12

Спасибо, @AbhinavGauniyal. Наконец-то я нашел время отредактировать ваш код в свой ответ.

Bill the Lizard 22.06.2017 16:46

Вместо "\ t" я бы сделал "\ t \ n"

Ratah 25.09.2019 11:57

Вышеупомянутые методы хороши, но иногда вы хотите использовать комбинацию функций для того, что ваша подпрограмма считает пробелом. В этом случае использование функторов для объединения операций может стать беспорядочным, поэтому я предпочитаю простой цикл, который я могу изменить для обрезки. Вот немного измененная функция обрезки, скопированная из версии 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);
}

забыл const char* end?

SSpoke 27.01.2021 12:56

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

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

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

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

SuperElectric 09.11.2010 23:33

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

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

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

Хорошо :) Проблема с обоими нашими решениями заключается в том, что они обрезают оба конца; не могу сделать ltrim или rtrim такими.

tzaman 07.07.2010 12:12

Хорошо, но не может работать со строкой с внутренними пробелами. например trim (abc def ") -> abc, остается только abc.

liheyuan 22.09.2011 07:15

Хорошее решение, если вы знаете, что внутренних пробелов не будет!

Elliot Gorokhovsky 24.04.2016 17:20

Это приятно и легко, но также довольно медленно, поскольку строка копируется в std::stringstream и из него.

Galik 27.09.2017 22:02

В этой версии удаляются внутренние пробелы и символы, отличные от букв и цифр:

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

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

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

Если ваша строка не содержит пробелов в суффиксе, она будет удалена, начиная с npos + 1 == 0, и вы удалите всю строку.

mhsmith 17.10.2012 07:51

@rgove Объясните, пожалуйста. str.find_last_not_of(x) возвращает позицию первого символа, не равную x. Он возвращает npos только в том случае, если ни один символ не соответствует x. В этом примере, если нет пробелов в суффиксах, он вернет эквивалент str.length() - 1, что даст по существу str.erase((str.length() - 1) + 1)., то есть, если я не сильно ошибаюсь.

Travis 30.10.2013 18:46

Это не работает для строк, состоящих только из пробелов. (Добавление если не пустой перед вторым 'erase ()' исправляет это.) Также возвращает измененное значение, и изменение аргумента, переданного по ссылке, вероятно, вводит в заблуждение при чтении кода с использованием возвращаемого значения.

Johannes Overmann 10.01.2014 13:50

@JohannesOvermann: Почему он должен терпеть неудачу для строк, состоящих только из пробелов?

robert 22.01.2014 15:42

@robert: Ага, да, ладно. Это работает, потому что на практике npos + 1 == 0. Но я не очень люблю арифметику с npos. :-)

Johannes Overmann 26.01.2014 18:06

Это должно вернуть std :: string &, чтобы избежать ненужного вызова конструктора копирования.

heksesang 17.10.2014 14:49

Я не понимаю, почему это возвращает копию после изменения возвращаемого параметра?

Galik 18.06.2015 03:51

@Galik Итак, вы можете обрезать и назначать или возвращать результат одновременно, например std::string myString(trim(myOtherString)) или return Trim(myString). Общая практика.

MiloDC 21.12.2017 04:43

@MiloDC. Я не понимаю, зачем возвращать копию вместо по ссылке. Мне больше смысла возвращать std::string&.

Galik 21.12.2017 05:16

Если вы измените порядок (сначала удалите пробелы в суффиксе, а затем в префиксе), он будет более эффективным.

CITBL 07.01.2019 21:05

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

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

Мое решение на основе ответ @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);
}

Немного поздно на вечеринку, но неважно. Теперь здесь 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());
}

Хороший. +1 от меня. Жаль, что C++ 11 не ввел функцию trim () в std :: string и облегчил жизнь всем.

Milan Babuškov 31.07.2013 21:41

Я всегда хочу, чтобы один вызов функции обрезал строку, а не реализовывал ее

linquize 17.01.2014 12:56

Как бы то ни было, эту лямбду использовать не нужно. Вы можете просто передать std::isspace: auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);

vmrob 11.05.2014 02:52

+1, вероятно, единственный ответ с реализацией, которая копирует только одну строку O (N).

Alexei Averchenko 12.08.2014 13:12

Компиляторы @vmrob не обязательно настолько умны. делать то, что вы говорите, двусмысленно: candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)

johnbakers 21.12.2016 18:50

@johnbakers Он работает в Visual Studio (cl 19), но не в clang (3.9.1) или gcc (6.3).

Zitrax 23.01.2017 18:01

Также я заметил, что количество сборочных линий для этого ответа (1-я функция) составляет ~ 1100 строк, в то время как, например, этот ответ составляет <100 строк. Однако дальше этого не исследовали.

Zitrax 23.01.2017 18:05

Мне интересно, что не означает .base()? не могли бы вы сказать мне, пожалуйста

小文件 11.06.2018 06:37

На самом деле это RTFM. std::reverse_iterator::base возвращает базовый итератор, на который указывает конкретный reverse_iterator. Обратные итераторы всегда указывают на один шаг за своим базовым итератором (то есть со смещением -1), поэтому base() используется для исправления ссылочного элемента. Фактически rev_iter.base() - 1 == rev_iter.

David G 14.06.2018 19:12

@vmrob Нет, нельзя. isspace имеет две перегрузки. Более того, начиная с C++ 20, взятие адреса функции в стандартной библиотеке - это UB.

L. F. 22.07.2019 16:17

@ L.F. Ой, давай. Я написал этот ответ 5 лет назад. Об этом лучше предупредить компилятор. Какая вторая перегрузка? Неофициальные документы оставляют меня в подвешенном состоянии: en.cppreference.com/w/cpp/string/byte/isspace

vmrob 17.09.2020 23:00

@vmrob другая перегрузка - это локаль. Однако ::isspace работал до C++ 20 (при условии, что вы включили заголовок C). На самом деле, дополнительная проблема заключается в том, что аргумент должен быть приведен к unsigned char перед передачей в isspace, но это уже другая история.

L. F. 18.09.2020 13:16

@ L.F. Ах я вижу! Спасибо за информацию!

vmrob 18.09.2020 21:29

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

Как насчет этого...?

#include <iostream>
#include <string>
#include <regex>

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;

}

Примечание: я все еще относительно новичок в C++, поэтому, пожалуйста, простите меня, если я здесь не на базе.

Использование regex для обрезки - это немного излишне.

user1095108 04.10.2013 00:17

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

Duncan 04.10.2013 00:24

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

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
}

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

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

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

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;
}
std::string trim( std::string && str )
{
    size_t end = str.find_last_not_of( " \n\r\t" );
    if ( end != std::string::npos )
        str.resize( end + 1 );

    size_t start = str.find_first_not_of( " \n\r\t" );
    if ( start != std::string::npos )
        str = str.substr( start );

    return std::move( 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());
}

Наконец-то изящное решение для базовой отделки пространства ... :)

jave.web 26.08.2015 17:43

Как это работает: это решение, подобное копированию - оно находит позицию первого символа, не являющуюся пробелом (it), и обратное: положение символа, после которого остаются только пробелы (rit) - после этого он возвращает вновь созданную строку == копия части исходной строки - часть, основанная на этих итераторах ...

jave.web 26.08.2015 17:52

Спасибо, у меня сработало: std: string s = "Oh noez: space \ r \ n"; std :: string clean = обрезка (и);

Alexx Roche 19.11.2015 22:31

Это хорошо? (Потому что этот пост полностью нуждается в другом ответе :)

string trimBegin(string str)
{
    string whites = "\t\r\n ";
    int i = 0;
    while (whites.find(str[i++]) != whites::npos);
    str.erase(0, i);
    return str;
}

Аналогичный случай для trimEnd, просто поменяйте полярность, индексы.

Я не совсем уверен, каковы накладные расходы при использовании временной строки для сравнения, но я думаю, что для ясности я бы предпочел использовать while (isspace(str[i++]));. Самым большим мотивирующим фактором на самом деле является то, что мне пришлось прочитать некоторую документацию, чтобы понять код, потому что я изначально думал, что это имеет порядок сложности O (n ^ 2) (чего, как вы знаете, нет). Затем код можно было бы сократить до string trimBegin(string str) { size_t i = 0; while(isspace(str[i++]); return str.erase(0, i); }.

vmrob 06.05.2014 06:46

Свой вклад в устранение шума. 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");  
}

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

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

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

если вы измените порядок в trim, т.е. сделаете его rtrim(ltrim(s, t), t), это будет немного эффективнее

CITBL 07.01.2019 21:31

@CITBL Сначала выполняется внутренняя функция, поэтому она будет обрезать слева до обрезку справа. Я думаю, это было бы меньше эффективным, не так ли?

Galik 07.01.2019 22:01

Точно. Виноват

CITBL 07.01.2019 22:12

если вы используете basic_string и template в CharT, вы можете сделать это для всех строк, просто используйте переменную шаблона для пробела, чтобы вы использовали ее как ws <CharT>. технически на этом этапе вы могли бы подготовить его для C++ 20 и также пометить его constexpr, поскольку это подразумевает встроенный

Beached 02.12.2019 04:09

@ Действительно на пляже. Хотя здесь немного сложно дать ответ. Я написал для этого шаблонные функции, и это, безусловно, довольно сложно. Я пробовал кучу разных подходов и до сих пор не уверен, какой из них лучший.

Galik 02.12.2019 04:18

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

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

@bmgda, возможно, теоретически самая быстрая версия могла бы иметь такую ​​сигнатуру: extern "C" void string_trim (char ** begin_, char ** end_) ... Поймайте мой дрейф?

user10133158 19.09.2018 13:58

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

std :: ptr_fun устарел

johnbakers 08.12.2016 20:20

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

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

Я использую вот это:

void trim(string &str){
    int i=0;

    //left trim
    while (isspace(str[i])!=0)
        i++;
    str = str.substr(i,str.length()-i);

    //right trim
    i=str.length()-1;
    while (isspace(str[i])!=0)
        i--;
    str = str.substr(0,i+1);
}
s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
s.erase(s.find_last_not_of(" \n\r\t")+1);   

Было бы немного эффективнее, если вы сделаете это в обратном порядке и сначала обрежете справа, а затем вызовете сдвиг, обрезав левый.

Galik 25.09.2018 01:56

Обрезать реализацию 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());
}

Похоже, я очень опаздываю на вечеринку - не могу поверить, что об этом спросили 7 лет назад!

Вот мой взгляд на проблему. Я работаю над проектом, и я не хотел сейчас испытывать трудности с использованием Boost.

std::string trim(std::string str) {
    if (str.length() == 0) return str;

    int beg = 0, end = str.length() - 1;
    while (str[beg] == ' ') {
        beg++;
    }

    while (str[end] == ' ') {
        end--;
    }

    return str.substr(beg, end - beg + 1);
}

Это решение будет обрезать слева и справа.

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

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

Подход, предложенный OP, тоже неплох - просто ему немного сложнее.

Brent Bradburn 31.01.2016 22:13

с ++ 11:

int i{};
string s = " h e ll \t\n  o";
string trim = " \n\t";

while ((i = s.find_first_of(trim)) != -1)
    s.erase(i,1);

cout << s;

выход:

hello

отлично работает также с пустыми строками

Ваш код удаляет все пробелы даже между непробелами. Это не то, что должна делать "обрезка".

CITBL 07.01.2019 19:32

Поскольку я хотел обновить свою старую функцию обрезки C++ с помощью подхода C++ 11, я протестировал множество опубликованных ответов на вопрос. Я пришел к выводу, что я сохраняю свое старое решение на C++!

По большому счету, это самый быстрый, даже добавление большего количества символов для проверки (например, \ r \ n, я не вижу варианта использования для \ f \ v) все равно быстрее, чем решения, использующие алгоритм.

std::string & trimMe (std::string & str)
{
   // right trim
   while (str.length () > 0 && (str [str.length ()-1] == ' ' || str [str.length ()-1] == '\t'))
      str.erase (str.length ()-1, 1);

   // left trim
   while (str.length () > 0 && (str [0] == ' ' || str [0] == '\t'))
      str.erase (0, 1);
   return str;
}

Вот простая реализация. Для такой простой операции вам, вероятно, не следует использовать какие-либо специальные конструкции. Встроенная функция 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);
}

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

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

Вам не хватает последнего символа. +1 в длине решает эту проблему.

galinette 12.10.2016 15:52

Вот решение, легкое для понимания новичками, которые не привыкли везде писать std:: и еще не знакомы с корректностью const, iterator, 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));
}

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

Это так раздражает, что я

  • нужно погуглить
  • узнать, что я должен использовать ракетостроение
  • что в строке нет простой функции обрезки / туппера

Для меня это самый быстрый способ решить:

CString tmp(line.c_str());
tmp = tmp.Trim().MakeLower();
string buffer = tmp;

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

CString - это специфическая для Windows мерзость MFC / ATL (которая, возможно, ближе к «ракетостроению», чем несколько строк стандартного C++ без сторонних зависимостей). Вы не можете предположить, что кто-то может его использовать или даже хочет его использовать.
Christian Hackl 08.07.2018 13:43

Конечно, я не хочу его использовать, но это более короткий способ избежать записи большого количества итераторов. Я не горжусь этими строками кода, но должен быть более простой способ сделать это в stdlib

Slesa 25.09.2018 23:49

Я согласен с тем, что std :: string не хватает некоторых очевидных функций. Простые вещи не должны требовать нескольких строк кода.

ragnarius 20.08.2020 01:21

Я знаю, что это очень старый вопрос, но я добавил к вам несколько строк кода, и он обрезает пробелы с обоих концов.

void trim(std::string &line){

    auto val = line.find_last_not_of(" \n\r\t") + 1;

    if (val == line.size() || val == std::string::npos){
        val = line.find_first_not_of(" \n\r\t");
        line = line.substr(val);
    }
    else
        line.erase(val);
}

Ниже приведено однопроходное (может быть двухпроходное) решение. Он проходит через часть строки с пробелами дважды и часть без пробелов один раз.

void trim(std::string& s) {                                                                                                                                                                                                               
    if (s.empty())                                                                                                                                                                                                                        
        return;                                                                                                                                                                                                                           

    int l = 0, r = s.size()  - 1;                                                                                                                                                                                                         

    while (l < s.size() && std::isspace(s[l++])); // l points to first non-whitespace char.                                                                                                                                               
    while (r >= 0 && std::isspace(s[r--])); // r points to last non-whitespace char.                                                                                                                                                      

    if (l > r)                                                                                                                                                                                                                            
        s = "";                                                                                                                                                                                                                           
    else {                                                                                                                                                                                                                                
        l--;                                                                                                                                                                                                                              
        r++;                                                                                                                                                                                                                              
        int wi = 0;                                                                                                                                                                                                                       
        while (l <= r)                                                                                                                                                                                                                    
            s[wi++] = s[l++];                                                                                                                                                                                                             
        s.erase(wi);                                                                                                                                                                                                                      
    }                                                                                                                                                                                                                                     
    return;                                                                                                                                                                                                                               
}                                          

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

Я не уверен, что вы тестируете, но в вашем примере 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 24.02.2020 10:36

Хорошо, это может быть не самый быстрый, но ... простой.

str = "   aaa    ";
int len = str.length();
// rtrim
while(str[len-1] == ' ') { str.erase(--len,1); }
// ltrim
while(str[0] == ' ') { str.erase(0,1); }

Это неверно. Вам нужно инвертировать ltrim и rtrim, чтобы это работало.

Mathieu Westphal 16.10.2019 08:02

Это неверно: это вызывает ошибку исключения массива, если str= = "" или str= = " " (три пробела). Чтобы исправить это, добавьте проверку !copy.empty() && в качестве проверки первый для обоих циклов while. Реализация rtrim сдвигает всю строку вниз на каждом шаге, что может быть неэффективным. Если важна эффективность, предложите другие ответы, в которых выполняется сканирование с последующей единственной операцией обрезки.

Contango 23.02.2020 16:56

Вот решение для обрезки с регулярным выражением

#include <string>
#include <regex>

string trim(string str){
    return regex_replace(str, regex("(^[ ]+)|([ ]+$)"),"");
}

Принятый ответ и даже версия Boost у меня не работали, поэтому я написал следующую версию:

std::string trim(const std::string& input) {
    std::stringstream string_stream;
    for (const auto character : input) {
        if (!isspace(character)) {
            string_stream << character;
        }
    }

    return string_stream.str();
}

Это удалит любой пробельный символ из любого места в строке и вернет новую копию строки.

Я прочитал большинство ответов, но не нашел никого, кто использовал бы istringstream
.

std::string text = "Let me split this into words";

std::istringstream iss(text);
std::vector<std::string> results((std::istream_iterator<std::string>(iss)),
                                 std::istream_iterator<std::string>());

Результатом является вектор слов, и он может работать со строками, имеющими внутренние пробелы, Надеюсь, это помогло.

почему бы не использовать лямбду?

auto no_space = [](char ch) -> bool {
  return !std::isspace<char>(ch, std::locale::classic());
};
auto ltrim = [](std::string& s) -> std::string& {
  s.erase(s.begin(), std::find_if (s.begin(), s.end(), no_space));
  return s;
};
auto rtrim = [](std::string& s) -> std::string& {
  s.erase(std::find_if (s.rbegin(), s.rend(), no_space).base(), s.end());
  return s;
};
auto trim_copy = [](std::string s) -> std::string& { return ltrim(rtrim(s)); };
auto trim = [](std::string& s) -> std::string& { return ltrim(rtrim(s)); };

Обрезает оба конца.

string trim(const std::string &str){
    string result = "";
    size_t endIndex = str.size();
    while (endIndex > 0 && isblank(str[endIndex-1]))
        endIndex -= 1;
    for (size_t i=0; i<endIndex ; i+=1){
        char ch = str[i];
        if (!isblank(ch) || result.size()>0)
            result += ch;
    }
   return result;
}
str.erase(0, str.find_first_not_of("\t\n\v\f\r ")); // left trim
str.erase(str.find_last_not_of("\t\n\v\f\r ") + 1); // right trim

Это не дает ответа на вопрос. Чтобы критиковать или запрашивать разъяснения у автора, оставьте комментарий под его сообщением. - Из обзора

k.s. 22.12.2020 11:25

Обрезка шнурка бедняги (только пробелы):

std::string trimSpaces(const std::string& str)
{
    int start, len;
    
    for (start = 0; start < str.size() && str[start] == ' '; start++);
    for (len = str.size() - start; len > 0 && str[start + len - 1] == ' '; len--);
    
    return str.substr(start, len);
}

Другие вопросы по теме