What's the best way to trim std::string?
我目前正在使用以下代码来修正程序中的所有
1 2 3 4 | std::string s; s.erase(s.find_last_not_of(" \t")+1); |
它工作正常,但我想知道是否有一些可能会失败的最终案例?
当然,欢迎使用优雅的替代品和左侧解决方案的答案。
编辑从c ++ 17开始,删除了标准库的某些部分。幸运的是,从c ++ 11开始,我们有lambda这是一个很好的解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #include #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提出现代解决方案。
原始答案:
我倾向于使用这些中的一个来满足我的修剪需求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include #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)); } |
他们相当自我解释,工作得很好。
编辑:BTW,我有
编辑:解决有关通过引用接受参数,修改并返回参数的一些注释。我同意。我可能更喜欢的实现是两组函数,一组用于实现,另一组用于复制。一组更好的例子是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include #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的字符串算法是最简单的:
1 2 3 4 | #include <boost/algorithm/string.hpp> std::string str("hello world!"); boost::trim_right(str); |
如果您将
如果您将
使用以下代码从
1 2 3 4 5 6 7 8 9 10 11 | // 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):
1 2 3 4 5 6 | // trim leading spaces size_t startpos = str.find_first_not_of(" \t"); if( string::npos != startpos ) { str = str.substr( startpos ); } |
派对迟到了,但没关系。现在C ++ 11在这里,我们有lambdas和auto变量。所以我的版本,也处理所有空格和空字符串,是:
1 2 3 4 5 6 7 8 9 10 | #include <cctype> #include <string> #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)); } |
我们可以从
1 2 3 4 5 | 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()); } |
你在做什么是好的和强大的。我已经使用了相同的方法很长一段时间,我还没有找到一个更快的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | const char* ws =" \t \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); } |
通过提供要修剪的字符,您可以灵活地修剪非空白字符,并且只能修剪您想要修剪的字符。
试试这个,它对我有用。
1 2 3 4 5 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; } |
我喜欢tzaman的解决方案,唯一的问题是它不修剪只包含空格的字符串。
要纠正1个缺陷,请在2条修剪线之间添加str.clear()
1 2 3 4 | std::stringstream trimmer; trimmer << str; str.clear(); trimmer >> str; |
http://ideone.com/nFVtEo
1 2 3 4 5 6 7 8 9 10 11 12 | 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黑掉了
1 2 3 4 5 6 7 8 9 10 11 12 | 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; } |
这适用于null情况。 :-)
我的解决方案基于@Bill蜥蜴的回答。
请注意,如果输入字符串只包含空格,则这些函数将返回空字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | const std::string StringUtils::WHITESPACE =" \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)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | #include #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)); } |
注意:或者,如果您需要支持宽字符,则应该可以使用
这就是我使用的。只需继续从前面移除空间,然后,如果还有任何东西,请从背面做同样的事情。
1 2 3 4 5 6 | 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 } |
使用C ++ 11还有一个正则表达式模块,当然可以用来修剪前导或尾随空格。
也许是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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)); } |
对于它的价值,这是一个关注性能的修剪实现。它比我见过的许多其他修剪程序要快得多。它使用原始c字符串和索引,而不是使用迭代器和std :: finds。它优化了以下特殊情况:size 0 string(什么都不做),没有要修剪的空格的字符串(什么都不做),只有尾随空格的字符串要修剪(只调整字符串大小),字符串完全是空格(只是清除字符串) 。最后,在最坏的情况下(带有前导空格的字符串),它会尽最大努力执行有效的复制构造,只执行1个副本,然后移动该副本来代替原始字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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)); } } |
1 2 3 4 5 6 | s.erase(0, s.find_first_not_of(" \t")); s.erase(s.find_last_not_of(" \t")+1); |
这样做的优雅方式可能就像
1 2 3 4 | std::string & trim(std::string & str) { return ltrim(rtrim(str)); } |
支持功能实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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; } |
一旦你完成所有这些,你也可以这样写:
1 2 3 4 5 | std::string trim_copy(std::string const & str) { auto s = str; return ltrim(rtrim(s)); } |
修剪C ++ 11实现:
1 2 3 4 | 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()); } |
使用C ++ 17,您可以使用basic_string_view :: remove_prefix和basic_string_view :: remove_suffix:
1 2 3 4 5 6 7 8 9 10 11 | std::string_view trim(std::string_view s) const { s.remove_prefix(std::min(s.find_first_not_of(" \t \v "), s.size())); s.remove_suffix((s.size() - 1) - std::min(s.find_last_not_of(" \t \v "), s.size() - 1)); return s; } |
我想如果你开始要求修剪字符串的"最佳方法",我会说一个好的实现将是:
显然,有太多不同的方法可以解决这个问题,这绝对取决于你真正需要什么。但是,C标准库在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | inline const char* trim_start(const char* str) { while (memchr(" \t ", *str, 4)) ++str; return str; } inline const char* trim_end(const char* end) { while (memchr(" \t ", 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 hello \t "; string trimmed = trim(str, strlen(str)); cout <<"'" << trimmed <<"'" << endl; system("pause"); return 0; } |
这是我想出的:
1 2 3 | std::stringstream trimmer; trimmer << str; trimmer >> str; |
流提取自动消除空白,因此这就像一个魅力。
如果我自己这么说的话,也很干净优雅。 ;)
为噪音贡献我的解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | #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 "); std::string s2(" c"); std::string s3("c \t"); std::string s4(" c"); assert(trim(s1) ==""); assert(trim(s2) =="c"); assert(trim(s3) =="c"); assert(trim(s4) =="c"); assert(s1 ==" \t "); assert(s2 ==" c"); assert(s3 =="c \t"); assert(s4 ==" c"); 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"); } |
由于添加了
1 | while ( !s.empty() && isspace(s.back()) ) s.pop_back(); |
这是我的版本:
1 2 3 4 5 6 | size_t beg = s.find_first_not_of(" "); return (beg == string::npos) ?"" : in.substr(beg, s.find_last_not_of(" ") - beg); |
我不确定你的环境是否相同,但在我的情况下,空字符串大小写会导致程序中止。我要用if(!s.empty())包装那个erase调用,或者如前所述使用Boost。
上面的方法很棒,但有时候你想要将一些函数组合用于你的例程所认为的空格。在这种情况下,使用仿函数来组合操作会变得混乱,所以我更喜欢一个简单的循环,我可以为修剪修改。这是在SO上从C版本复制的略微修改的修剪函数。在此示例中,我正在修剪非字母数字字符。
1 2 3 4 5 6 7 8 9 10 11 | 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); } |
这是一个直接的实现。对于这样一个简单的操作,您可能不应该使用任何特殊构造。内置的isspace()函数负责处理各种形式的白色字符,因此我们应该利用它。您还必须考虑字符串为空或只是一堆空格的特殊情况。向左或向右修剪可以从以下代码派生。
1 2 3 4 5 6 7 8 9 10 11 12 | 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); } |
由于我想用C ++ 11方法更新旧的C ++ trim函数,我已经测试了很多已发布的问题答案。我的结论是我保留了旧的C ++解决方案!
它是最快的一个,甚至添加更多的字符来检查(例如 r n我看到没有用于 f v的用例)仍然比使用算法的解决方案更快。
1 2 3 4 5 6 7 8 9 10 11 | 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #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 "; std::cout <<"-" << trim( str ) <<"- "; return 0; } |
注意:我还是比较新的C ++,所以如果我不在这里,请原谅我。
这是一个易于理解的解决方案,对于不习惯在任何地方编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #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 2 3 4 5 6 7 8 9 10 11 | 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | static inline std::string &trimAll(std::string &s) { if(s.size() == 0) { return s; } int val = 0; for (int cur = 0; cur < s.size(); cur++) { if(s[cur] != ' ' && std::isalnum(s[cur])) { s[val] = s[cur]; val++; } } s.resize(val); return s; } |
这有什么好处? (因为这篇文章完全需要另一个答案:)
1 2 3 4 5 6 7 8 9 10 | string trimBegin(string str) { string whites ="\t "; int i = 0; while (whites.find(str[i++]) != whites::npos); str.erase(0, i); return str; } |
类似于trimEnd的情况,只需反转极化指标。
我正在使用这个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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); } |
C ++ 11:
1 2 3 4 5 6 7 8 9 10 | int i{}; string s =" h e ll \t o"; string trim =" \t"; while ((i = s.find_first_of(trim)) != -1) s.erase(i,1); cout << s; |
输出:
1 | hello |
使用空字符串也可以正常工作
我知道这是一个非常古老的问题,但我已经为你添加了几行代码,它从两端修剪了空白。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void trim(std::string &line){ auto val = line.find_last_not_of(" \t") + 1; if(val == line.size() || val == std::string::npos){ val = line.find_first_not_of(" \t"); line = line.substr(val); } else line.erase(val); } |
以下是一次通过(可能是两次通过)解决方案。它遍历字符串的白色空间部分两次和非空白部分一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | std::string trim( std::string && str ) { size_t end = str.find_last_not_of(" \t" ); if ( end != std::string::npos ) str.resize( end + 1 ); size_t start = str.find_first_not_of(" \t" ); if ( start != std::string::npos ) str = str.substr( start ); return std::move( str ); } |
看来我参加派对的时间已经很晚了 - 我简直不敢相信7年前这个问题!
这是我对这个问题的看法。我正在研究一个项目,我现在不想经历使用Boost的麻烦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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); } |
此解决方案将从左侧和右侧进行修剪。
我真烦人
- 必须谷歌吧
- 发现我必须使用火箭科学
- 字符串中没有简单的trim / toupper函数
对我来说,这是解决它的最快方法:
1 2 3 | CString tmp(line.c_str()); tmp = tmp.Trim().MakeLower(); string buffer = tmp; |
好吧,我可以使用lambda操作,迭代器和所有东西。但我只需要处理字符串而不是字符......