修剪std :: string的最佳方法是什么?
我正在使用下面的代码来修剪我的程序中的所有std::strings
:
std::string s; s.erase(s.find_last_not_of(" \n\r\t")+1);
它工作正常,但我不知道是否有一些可能会失败的最终情况?
当然,欢迎使用优雅的替代scheme和左修剪的解决scheme。
编辑自c ++ 17以来,标准库的一些部分被删除。 幸运的是,从C ++ 11开始,我们有了一个优秀的解决schemelambda。
#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提出了现代解决scheme。;
原始答案:
我倾向于使用这三个之一来满足我的修剪需求:
#include <algorithm> #include <functional> #include <cctype> #include <locale> // trim from start static inline std::string <rim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace)))); return s; } // trim from end static inline std::string &rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end()); return s; } // trim from both ends static inline std::string &trim(std::string &s) { return ltrim(rtrim(s)); }
他们相当自我解释,工作得很好。
编辑 :顺便说一句,我有std::ptr_fun
在那里,以帮助消除歧义的std::isspace
因为实际上有一个支持语言环境的第二个定义。 这可能是一个演员一样,但我更喜欢这个更好。
编辑 :要解决一些关于通过引用接受参数,修改和返回它的意见。 我同意。 我可能更喜欢的一个实现是两套函数,一个用于实现,一个用于复制。 一套更好的例子是:
#include <algorithm> #include <functional> #include <cctype> #include <locale> // trim from start (in place) static inline void ltrim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace)))); } // trim from end (in place) static inline void rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end()); } // trim from both ends (in place) static inline void trim(std::string &s) { ltrim(s); rtrim(s); } // trim from start (copying) static inline std::string ltrim_copy(std::string s) { ltrim(s); return s; } // trim from end (copying) static inline std::string rtrim_copy(std::string s) { rtrim(s); return s; } // trim from both ends (copying) static inline std::string trim_copy(std::string s) { trim(s); return s; }
我保留上面的原始答案,但为了保持高投票答案仍然可用。
使用Boost的stringalgorithm将是最简单的:
#include <boost/algorithm/string.hpp> std::string str("hello world! "); boost::trim_right(str);
str
现在是"hello world!"
。 还有trim_left
和trim
,修剪两边。
如果将_copy
后缀添加到上面的任何函数名称(例如trim_copy
,函数将返回string的修剪副本,而不是通过引用修改它。
如果将_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在这里,我们有lambda和自动variables。 所以我的版本,它也处理所有空白和空string,是:
#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
创build一个反向迭代器,并将其用作第二个find_if_not
的终止条件,但是这仅在全空白string的情况下有用,并且gcc 4.8至less不够聪明以推断反向types迭代器( std::string::const_reverse_iterator
)与auto
。 我不知道构build一个反向迭代器是多么昂贵,所以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()); }
我喜欢tzaman的解决scheme,唯一的问题是它不修剪只包含空格的string。
为了纠正这个缺陷,在两条微调线之间添加一个str.clear()
std::stringstream trimmer; trimmer << str; str.clear(); trimmer >> str;
你在做什么是好的,健壮的。 我已经使用了相同的方法很长一段时间,我还没有find一个更快的方法:
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; }
在空string的情况下,你的代码假定给string::npos
加1会得到0. string::npos
的types是string::size_type
,它是无符号的。 因此,您正在依靠加法的溢出行为。
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()); }
砍掉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; }
这也适用于空情况。 🙂
我的解决scheme基于蜥蜴@Bill的答案 。
请注意,如果inputstring只包含空格,则这些函数将返回空string。
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); }
我的回答是对这篇文章的最佳答案的一个改进,修改控制字符以及空格( ASCII表格上的0-32和127)。
std::isgraph
决定一个字符是否有一个graphics表示,所以你可以用这个来改变Evan的答案,从string的任何一边删除任何没有graphics表示的字符。 结果是一个更优雅的解决scheme:
#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
操作,这是我没有testing的东西(请参阅浏览此选项的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 }
对于什么是值得的,这里是一个微调执行与性能的眼睛。 这比我见过的许多其他修剪程序快得多。 它不使用迭代器和std ::查找,而是使用原始cstring和索引。 它优化了以下特殊情况:大小为0的string(什么都不做),没有空白的string(不做任何事情),只有尾部空白的string(只是调整string大小),string完全是空白string(只是清除string) 。 最后,在最坏的情况下(带有空白字符的string),最好执行一个有效的复制构造,只执行1个副本,然后将该副本移到原始string的位置。
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中,还有一个正则expression式模块,当然可以用来修剪前导或尾随空格。
也许这样的事情:
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)); }
支持function的实现方式如下:
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)); }
我想如果你开始寻求修剪一个string的“最佳方法”,我会说一个好的实现将是:
- 不分配临时string
- 有过载的地方修剪和复制修剪
- 可以很容易地定制接受不同的validation序列/逻辑
显然有太多不同的方法来解决这个问题,这绝对取决于你真正需要什么。 但是,C标准库在<string.h>中仍然有一些非常有用的function,就像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; }
s.erase(0, s.find_first_not_of(" \n\r\t")); s.erase(s.find_last_not_of(" \n\r\t")+1);
修剪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()); }
我不确定你的环境是否一样,但是在我的空string的情况下会导致程序中止。 我要么用if(!s.empty())来包装这个擦除调用,要么像前面提到的那样使用Boost。
以下是我想到的:
std::stringstream trimmer; trimmer << str; trimmer >> str;
stream提取自动消除空白,所以这就像一个魅力。
如果我自己也这样说的话,那也挺干净的,也很漂亮。 ;)
贡献我的解决scheme的噪音。 trim
默认为创build一个新的string,并返回修改后的string,而trim_in_place
修改传递给它的string。 trim
function支持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"); }
由于添加了back()
和pop_back()
这可以在C ++ 11中更简单地完成。
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);
这里有一个易于理解的解决scheme,用于不习惯编写std::
everywhere并且还不熟悉const
正确性, iterator
,STL algorithm
等的初学者。
#include <string> #include <cctype> // for isspace using namespace std; // Left trim the given string (" hello! " --> "hello! ") string left_trim(string str) { int numStartSpaces = 0; for (int i = 0; i < str.length(); i++) { if (!isspace(str[i])) break; numStartSpaces++; } return str.substr(numStartSpaces); } // Right trim the given string (" hello! " --> " hello!") string right_trim(string str) { int numEndSpaces = 0; for (int i = str.length() - 1; i >= 0; i--) { if (!isspace(str[i])) break; numEndSpaces++; } return str.substr(0, str.length() - numEndSpaces); } // Left and right trim the given string (" hello! " --> "hello!") string trim(string str) { return right_trim(left_trim(str)); }
希望它有帮助…
上面的方法很好,但是有时候你想使用你的例程认为是空白的函数的组合。 在这种情况下,使用函数来组合操作可能会变得麻烦,所以我更喜欢一个简单的循环,我可以修改修剪。 这里是一个稍微修改的修剪function,从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; }
还有一个select – 从两端删除一个或多个字符。
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 <iostream> #include <string> #include <regex> std::string ltrim( std::string str ) { return std::regex_replace( str, std::regex("^\\s+"), std::string("") ); } std::string rtrim( std::string str ) { return std::regex_replace( str, std::regex("\\s+$"), std::string("") ); } std::string trim( std::string str ) { return ltrim( rtrim( str ) ); } int main() { std::string str = " \t this is a test string \n "; std::cout << "-" << trim( str ) << "-\n"; return 0; }
注意:我对C ++还是比较陌生的,所以请原谅我,如果我不在这里。
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
空串也可以正常工作