我目前正在使用以下代码对std::strings
程序中的所有内容进行右修剪:
std::string s;
s.erase(s.find_last_not_of(" \n\r\t")+1);
它可以正常工作,但我想知道是否存在某些可能会失败的最终情况?
当然,我们欢迎您提供其他优雅的解决方案以及左修剪解决方案。
std::string
类中,因为正是这样的函数使其他语言很好用(例如Python)。
我目前正在使用以下代码对std::strings
程序中的所有内容进行右修剪:
std::string s;
s.erase(s.find_last_not_of(" \n\r\t")+1);
它可以正常工作,但我想知道是否存在某些可能会失败的最终情况?
当然,我们欢迎您提供其他优雅的解决方案以及左修剪解决方案。
std::string
类中,因为正是这样的函数使其他语言很好用(例如Python)。
Answers:
编辑自c ++ 17起,标准库的某些部分已删除。幸运的是,从c ++ 11开始,我们有了lambda,它们是一种出色的解决方案。
#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提供了现代解决方案。
我倾向于使用以下三种之一来满足修整需求:
#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::trim
来解决问题。
使用Boost的字符串算法将是最简单的:
#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
,则可以修剪满足自定义谓词的所有字符,而不是空白。
使用以下代码从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));
}
// trim leading spaces
size_t startpos = str.find_first_not_of(" \t");
if( string::npos != startpos )
{
str = str.substr( startpos );
}
str.substr(...).swap(str)
更好。保存作业。
basic_string& operator= (basic_string&& str) noexcept;
吗?
您正在做的事情很好而且很健壮。我已经使用相同的方法很长时间了,但我还没有找到一个更快的方法:
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);
}
通过提供要修剪的字符,您可以灵活地修剪非空白字符,并且可以只修剪要修剪的字符。
trim
,即使其rtrim(ltrim(s, t), t)
效率更高
参加聚会有点晚,但是没关系。现在C ++ 11在这里,我们有lambda和auto变量。因此,我的版本也处理全空格和空字符串,它是:
#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());
}
std::isspace
:auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);
candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)
试试这个,对我有用。
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;
}
str.find_last_not_of(x)
返回不等于x的第一个字符的位置。如果没有字符与x不匹配,则仅返回npos。在此示例中,如果没有后缀空格,它将返回的等价项str.length() - 1
,本质上会产生,str.erase((str.length() - 1) + 1).
也就是说,除非我非常误解。
std::string&
。
我喜欢tzaman的解决方案,唯一的问题是它不会修剪仅包含空格的字符串。
要纠正这1个缺陷,请在2条微调线之间添加一个str.clear()
std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;
ltrim
或rtrim
喜欢这个。
std::stringstream
。
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());
}
it
)的第一个字符的位置,然后反向查找:只有空格(rit
)的字符的位置-之后它返回一个新创建的字符串==原始字符串部分的副本-基于这些迭代器的部分...
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情况。:-)
rtrim
,不是ltrim
在C ++ 17中,您可以使用basic_string_view :: remove_prefix和basic_string_view :: remove_suffix:
std::string_view trim(std::string_view s)
{
s.remove_prefix(std::min(s.find_first_not_of(" \t\r\v\n"), s.size()));
s.remove_suffix(std::min(s.size() - s.find_last_not_of(" \t\r\v\n") - 1, s.size()));
return s;
}
一个不错的选择:
std::string_view ltrim(std::string_view s)
{
s.remove_prefix(std::distance(s.cbegin(), std::find_if(s.cbegin(), s.cend(),
[](int c) {return !std::isspace(c);})));
return s;
}
std::string_view rtrim(std::string_view s)
{
s.remove_suffix(std::distance(s.crbegin(), std::find_if(s.crbegin(), s.crend(),
[](int c) {return !std::isspace(c);})));
return s;
}
std::string_view trim(std::string_view s)
{
return ltrim(rtrim(s));
}
我的解决方案基于@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);
}
我的答案是对本篇文章的最高答案的改进,该内容减少了控制字符和空格(ASCII表上的0-32和127 )。
std::isgraph
确定字符是否具有图形表示,因此您可以使用它来更改Evan的答案,以从字符串的任一侧删除没有图形表示的任何字符。结果是一个更加优雅的解决方案:
#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
了解此选项) 。
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 :: finds,它使用原始的c字符串和索引。它优化了以下特殊情况:大小为0的字符串(不执行任何操作),不带空格的字符串进行修剪(不执行任何操作),仅带尾部空格的字符串进行修剪(仅对字符串进行大小调整),完全为空格的字符串(仅清除字符串) 。最后,在最坏的情况(带前导空格的字符串)中,它会尽最大努力执行有效的副本构造,仅执行1个副本,然后将该副本替换为原始字符串。
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));
}
}
优雅的方式可以像
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));
}
我想,如果您开始要求“最佳方式”来修剪字符串,我想说一个好的实现将是:
显然,有太多不同的方法可以解决此问题,并且这完全取决于您的实际需求。但是,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;
}
这是我想出的:
std::stringstream trimmer;
trimmer << str;
trimmer >> str;
流提取会自动消除空格,因此它就像一个符咒。
如果我自己也这么说的话,也很干净优雅。;)
为噪音做出贡献。trim
默认情况下是创建一个新字符串并在trim_in_place
修改传递给它的字符串时返回修改后的字符串。该trim
函数支持c ++ 11 move语义。
#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()
和,因此可以在C ++ 11中更简单地完成此操作pop_back()
。
while ( !s.empty() && isspace(s.back()) ) s.pop_back();
上面的方法很棒,但是有时您想对例程认为是空白的地方使用功能组合。在这种情况下,使用函子来合并操作会变得混乱,因此我更喜欢一个可以修改的简单循环。这是在SO上从C版本复制的稍微修改的修整函数。在此示例中,我将修剪非字母数字字符。
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()函数可以处理各种形式的白色字符,因此我们应该利用它。您还必须考虑特殊情况,其中字符串为空或只是一堆空格。可以从以下代码派生向左或向右修剪。
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);
}
对于初学者来说,这是一个易于理解的解决方案,他们不习惯std::
到处书写,并且还不熟悉const
-correctness,iterator
s,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));
}
希望能帮助到你...
另一种选择-从两端删除一个或多个字符。
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);
}