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

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

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

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

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

Ответ 1

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

Благодаря fooobar.com/questions/13355/... для создания современного решения.

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

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

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

Ответ 2

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

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

Ответ 3

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

Бит поздно на вечеринку, но неважно. Теперь С++ 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());
}

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

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

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

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

Ответ 8

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

Ответ 9

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

Ответ 10

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

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

Ответ 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);
}

Ответ 12

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

Ответ 13

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

Ответ 14

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

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
}

Ответ 15

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

Ответ 16

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

Ответ 17

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

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

Ответ 18

В С++ 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;
}

Ответ 19

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

Ответ 20

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

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

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

Ответ 21

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

Ответ 22

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

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

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

Ответ 23

Внесение моего решения в шум. 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");  
}

Ответ 24

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

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

Ответ 25

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

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

Ответ 26

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

Ответ 27

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

Ответ 28

Здесь легко понять начинающих, которые не использовали для записи 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));
}

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

Ответ 29

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

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

Ответ 30

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

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