Який найкращий спосіб обрізати 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).
HelloGoodbye

Відповіді:


648

EDIT Оскільки 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));
}

Вони досить пояснюють себе та працюють дуже добре.

EDIT : BTW, я маю std::ptr_funтам, щоб допомогти роз'єднати, std::isspaceтому що насправді є друге визначення, яке підтримує локалі. Це могло б бути акторським складом точно так само, але мені, як правило, подобається це краще.

EDIT : Щоб вирішити деякі коментарі щодо прийняття параметра шляхом посилання, зміни та повернення його. Я згоден. Я реалізую, що я, швидше за все, хотів би мати два набори функцій, одну на місці та одну, яка робить копію. Кращим набором прикладів буде:

#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
Зауважте, що за допомогою простору ви можете легко визначити поведінку з символами, що не належать до 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
Boost - це такий масивний молоток для такої крихітної проблеми.
Кейсі Родармор

143
@rodarmor: Boost вирішує багато крихітних проблем. Це масивний молоток, який багато вирішує.
Нікол Болас

123
Boost - це набір молотків різного розміру, що вирішує багато різних проблем.
Ібрагім

11
@rodarmor Ви кажете, що ніби "Boost" - це моноліт цілком або нічого, де включення одного з його заголовків якимось чином завдає все це програмі. Що, очевидно, не так. До речі, я ніколи не використовував Boost, fwiw.
підкреслюйте_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)краще. Збережіть завдання.
updogliu

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

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

Поставляючи символи, які підлягають обрізанню, ви маєте гнучкість до обрізання символів, що не є пробілами, та ефективність обрізки лише тих символів, які ви хочете обрізати.


якщо змінити замовлення trim, тобто зробити його, rtrim(ltrim(s, t), t)воно буде дещо ефективнішим
CITBL

1
@CITBL Внутрішня функція виконується спочатку, так що ваш шлях буде обрізати зліва, перш ніж обрізати справа. Я думаю, що цього було б менше ефективно, чи не так?
Галик

Саме так. Моя помилка
CITBL

якщо ви використовуєте basic_string та шаблон на CharT, ви можете зробити це для всіх рядків, просто використовуйте змінну шаблону для пробілу, щоб ви використовували його як ws <CharT>. технічно в цей момент ви можете зробити його готовим до c ++ 20 і позначити його також contexpr, оскільки це означає
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;
}

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

Мені подобається рішення tzaman, єдина проблема в ньому полягає в тому, що він не обрізає рядок, що містить лише пробіли.

Щоб виправити 1 недолік, додайте str.clear () між двома лініями тримера

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

Приємно :) Проблема обох наших рішень, однак, полягає в тому, що вони обріжуть обидва кінці; не може зробити це ltrimчи rtrimподібне.
цзаман

44
Добре, але не може мати справу з рядком із внутрішнім пробілом. наприклад обрізка (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());
}

1
Елегантне рішення для базового оздоблення простору нарешті ... :)
jave.web

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

Дякую, працювали для мене: std: string s = "О, noez: простір \ r \ n"; std :: string clean = обробка (и);
Алекс Рош

15

У випадку порожнього рядка ваш код передбачає, що додавання 1 до string::npos0 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 :: delete_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

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

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
}

8
s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
s.erase(s.find_last_not_of(" \n\r\t")+1);   

2
Було б трохи ефективніше, якщо ви робите ті, що в зворотному порядку, і обрізайте праворуч спочатку, перш ніж викликати зсув, обрізаючи лівий.
Галик

7

Для чого це варто, ось детальна реалізація з огляду на ефективність. Це набагато швидше, ніж багато інших процедур, які я бачив навколо. Замість використання ітераторів та std :: found, він використовує необроблені 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));
    }
}

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

6

Реалізація 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());
}

5

Я думаю, якщо ви почнете просити "найкращий спосіб" обрізати рядок, я б сказав, що гарною реалізацією було б таке:

  1. Не виділяє тимчасових рядків
  2. Має перевантаження для обрізки на місці та копіювання
  3. Можна легко налаштувати для прийняття різних послідовностей / логіки перевірки

Очевидно, що існує занадто багато різних способів підходу до цього, і це, безумовно, залежить від того, що вам насправді потрібно. Однак у стандартній бібліотеці С все ще є деякі дуже корисні функції в <string.h>, як memchr. Є причина, чому C досі вважається найкращою мовою для IO - його 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;
}

3

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


3

Ось що я придумав:

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

Витяг потоку автоматично усуває пробіли, тому це працює як шарм.
Дуже чистий і елегантний, якщо я сам так скажу. ;)


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

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

Вам не вистачає останнього символу. +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 algorithms тощо.

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

Сподіваюся, це допомагає ...


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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.