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

724

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

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

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

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

  • 481
    Ответы на этот вопрос являются свидетельством того, насколько не хватает стандартной библиотеки C ++.
  • 73
    @IdanK И у него до сих пор нет этой функции в C ++ 11.
Показать ещё 20 комментариев
Теги:
trim
stdstring

40 ответов

592

EDIT. Так как С++ 17, некоторые части стандартной библиотеки были удалены. К счастью, начиная с С++ 11, у нас есть lambdas, которые являются превосходным решением.

#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/questions/44973435/stdptr-fun-replacement-for-c17 для создания современного решения.

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

Я предпочитаю использовать один из этих 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, чтобы помочь disambiguate 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;
}

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

  • 24
    Этот код не работал в некоторых международных строках (в моем случае это shift-jis, хранится в std :: string); Я решил использовать boost::trim для решения проблемы.
  • 5
    Я бы использовал указатели вместо ссылок, так что с точки вызова гораздо проще понять, что эти функции редактируют строку на месте, а не создают копию.
Показать ещё 21 комментарий
408

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

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

  • 1
    Что использует повышение, чтобы определить, является ли символ пробелом?
  • 7
    Это зависит от локали. Моя локаль по умолчанию (VS2005, en) означает, что табуляция, пробелы, возврат каретки, новые строки, вертикальные табуляции и подача формы обрезаются.
Показать ещё 9 комментариев
58

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

Бит поздно на вечеринку, но неважно. Теперь С++ 11, у нас есть lambdas и auto variables. Поэтому моя версия, которая также обрабатывает все пробелы и пустые строки, это:

#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());
}
  • 7
    Ницца. +1 от меня. Жаль, что C ++ 11 не вводит trim () в std :: string и делает жизнь проще для всех.
  • 2
    Я всегда хочу, чтобы один вызов функции обрезал строку, а не реализовывал ее
Показать ещё 7 комментариев
39

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

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

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

  • 0
    если вы измените порядок в trim , то есть сделайте его rtrim(ltrim(s, t), t) это будет немного более эффективным
  • 1
    @CITBL Внутренняя функция выполняется в первую очередь, так что, по-вашему, она будет обрезаться слева до обрезки справа. Я думаю, что это будет менее эффективно, не так ли?
Показать ещё 1 комментарий
34

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

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;
}
  • 10
    Если ваша строка не содержит пробелов с суффиксами, она будет удалена, начиная с npos + 1 == 0, и вы удалите всю строку.
  • 2
    @rgove Пожалуйста, объясните. str.find_last_not_of(x) возвращает позицию первого символа, не равного x. Он возвращает npos только если никакие символы не совпадают с x. В этом примере, если нет пробелов с суффиксами, будет возвращен эквивалент str.length() - 1 , в результате чего str.erase((str.length() - 1) + 1). существу, str.erase((str.length() - 1) + 1). То есть, если я не ошибаюсь.
Показать ещё 9 комментариев
25

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

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

std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;
  • 0
    Хорошо :) проблема с обоими нашими решениями состоит в том, что они обрежут оба конца; не могу сделать ltrim или rtrim как это.
  • 44
    Хорошо, но не может справиться со строкой с внутренним пробелом. например, trim (abc def ") -> abc, осталось только abc.
Показать ещё 3 комментария
19

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
    Наконец-то элегантное решение для базовой отделки пространства ... :)
  • 0
    Как это работает: Это решение, похожее на копию - оно находит позицию первого символа, которая не является пробелом ( it ) и наоборот: позиция символа, после которой есть только пробелы ( rit ) - после этого она возвращает вновь созданную строку == копия части исходной строки - части, основанной на этих итераторах ...
Показать ещё 1 комментарий
15

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

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

Снято Cplusplus.com

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

    return str;
}

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

  • 2
    Это всего лишь rtrim , а не ltrim
  • 0
    ^ Вы не возражаете против использования find_first_not_of? Это относительно легко изменить.
9

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

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

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

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

Для того, что стоит, вот реализация отделки с прицелом на производительность. Это намного быстрее, чем многие другие подпрограммы, которые я видел вокруг. Вместо использования итераторов и std:: find используется строковые строки и индексы. Он оптимизирует следующие особые случаи: строка размера 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));
    }
}
  • 0
    @bmgda, возможно, теоретически самая быстрая версия может иметь эту подпись: extern "C" void string_trim (char ** begin_, char ** end_) ... Поймать мой дрейф?
7

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

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

С С++ 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));
}
  • 0
    Я хотел бы увидеть эталон этого.
5

Выполнение Trim С++ 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());
}
4

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

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

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

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

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);
  • 0
    Вам не хватает последнего персонажа. +1 в длину решает это
  • 0
    Спасибо за исправление, вы были правы.
3

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

while ( !s.empty() && isspace(s.back()) ) s.pop_back();
  • 0
    Подход, предложенный ФП, тоже неплох - чуть сложнее следовать.
3

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

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

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

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

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

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

2

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

Здесь легко понять начинающих, которые не использовали для записи std:: всюду и еще не знакомы с const -корректностью, iterator s, STL algorithm s и т.д.

#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

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

Как я хотел обновить мою старую функцию С++ trim с помощью подхода С++ 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;
     }
1

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

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

}

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

  • 6
    Использование regex для обрезки немного излишне.
  • 1
    Это намного более интенсивно использует процессор, чем некоторые другие представленные варианты?
Показать ещё 1 комментарий
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);
}
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;
}
0

В С++ 17 вы можете использовать basic_string_view :: remove_prefix и basic_string_view :: remove_suffix:

std::string_view trim(std::string_view s) const
{
    s.remove_prefix(std::min(s.find_first_not_of(" \t\r\v\n"), s.size()));
    s.remove_suffix((s.size() - 1) - std::min(s.find_last_not_of(" \t\r\v\n"), s.size() - 1));

    return s;
}
0

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

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

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

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

С++ 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

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

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

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

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

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

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, просто измените поляризатор, индексы.

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

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

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

  • 0
    Ваш второй цикл может читать вне буфера
-4

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

  • нужно google it
  • узнайте, что я должен использовать ракетную науку
  • что в строке нет простой функции trim/toupper

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

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

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

  • 2
    CString - это мерзость для MFC / ATL, характерная для Windows (которая, вероятно, ближе к «ракетостроению», чем несколько строк стандарта C ++ без сторонних зависимостей). Вы не можете предполагать, что кто-то может использовать это, или даже хочет использовать это.
  • 1
    Конечно, я не хочу его использовать, но это более короткий способ избежать записи множества итераторов. Я не горжусь этими строками кода, но должен быть более простой способ сделать это в stdlib

Ещё вопросы

Сообщество Overcoder
Наверх
Меню