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

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

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

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

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

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

 #include  #include  #include  // 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  #include  #include  #include  // 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(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(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 чтобы помочь disambiguate std::isspace потому что на самом деле есть второе определение, которое поддерживает локали. Это может быть приведение точно так же, но мне нравится это лучше.

EDIT : для рассмотрения некоторых комментариев о принятии параметра по ссылке, изменении и возврате его. Согласен. Реализация, которую я, скорее всего, предпочла бы, – это два набора функций: один на месте и один, который делает копию. Лучший пример:

 #include  #include  #include  #include  // 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(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(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; } 

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

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

 #include  std::string str("hello world! "); boost::trim_right(str); 

str теперь "hello world!" , Там также trim_left и trim , которая обрезает обе стороны.


Если вы добавите суффикс _copy к любому из названных имен функций, например trim_copy , функция вернет обрезанную копию строки вместо изменения ее посредством ссылки.

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

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

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

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

Мы могли бы сделать обратный iterator из wsfront и использовать это как условие завершения во втором find_if_not но это полезно только в случае строки с wsfront пробелом, а gcc 4.8 по крайней мере недостаточно умен, чтобы вывести тип обратного iterator ( std::string::const_reverse_iterator ) с auto . Я не знаю, насколько дорого стоит построение обратного iteratorа, поэтому здесь 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()); } 

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

 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 (left & right) inline std::string& trim(std::string& s, const char* t = ws) { return ltrim(rtrim(s, t), t); } 

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

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

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

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

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

 std::stringstream trimmer; trimmer << str; str.clear(); trimmer >> 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()); } 

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

Снято с 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; } 

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

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

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

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

 #include  #include  #include  /** * @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(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(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 чтобы изучить эту опцию).

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

 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 } 

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

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

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

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

 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(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(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)); } 
 s.erase(0, s.find_first_not_of(" \n\r\t")); s.erase(s.find_last_not_of(" \n\r\t")+1); 

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

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

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

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

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

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

Trim C ++ 11:

 static void trim(std::string &s) { s.erase(s.begin(), std::find_if_not(s.begin(), s.end(), [](char c){ return std::isspace(c); })); s.erase(std::find_if_not(s.rbegin(), s.rend(), [](char c){ return std::isspace(c); }).base(), s.end()); } 

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

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

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

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

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

Внесение моего решения в шум. trim умолчанию создает новую строку и возвращает измененную, а trim_in_place изменяет переданную ей строку. Функция trim поддерживает семантику перемещения c ++ 11.

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

C ++ 11:

 int i{}; string s = " he ll \t\no"; string trim = " \n\t"; while ((i = s.find_first_of(trim)) != -1) s.erase(i,1); cout << s; 

вывод:

 hello 

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

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

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

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

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

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

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

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

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

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

 #include  #include  #include  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 ++, поэтому, пожалуйста, простите меня, если я здесь не нахожусь.

  • Как я могу узнать, использует ли Windows 7 TRIM для моего накопителя SSD?
  • Как настроить ОС (Mac / Win / Ubuntu) на SSD, чтобы повысить его долговечность (TRIM и т. Д.),
  • Как сильно SSD деградируют без TRIM?
  • Обрезка IE8 и JQuery ()
  • Поддерживается ли TRIM в конфигурации RAID 0 для SSD-дисков в Windows 7?
  • Какая хорошая альтернатива LTRIM и RTRIM в Java?
  • Interesting Posts
    Давайте будем гением компьютера.