当前位置:  开发笔记 > 编程语言 > 正文

修剪std :: string的最佳方法是什么?

如何解决《修剪std::string的最佳方法是什么?》经验,为你挑选了19个好方法。

我目前正在使用以下代码来修正std::strings程序中的所有内容:

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

它工作正常,但我想知道是否有一些可能会失败的最终案例?

当然,欢迎使用优雅替代品和左侧解决方案的答案.



1> Evan Teran..:

编辑从c ++ 17开始,删除了标准库的某些部分.幸运的是,从c ++ 11开始,我们有lambda这是一个很好的解决方案.

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

感谢/sf/ask/17360801/提出现代解决方案.

原始答案:

我倾向于使用这些中的一个来满足我的修剪需求:

#include  
#include  
#include 
#include 

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

他们相当自我解释,工作得很好.

编辑:顺便说一下,我std::ptr_fun在那里帮助消除歧义,std::isspace因为实际上有第二个定义支持语言环境.这可能是同样的演员阵容,但我倾向于更喜欢这样.

编辑:解决有关通过引用接受参数,修改并返回参数的一些注释.我同意.我可能更喜欢的实现是两组函数,一组用于实现,另一组用于复制.一组更好的例子是:

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

我保留上面的原始答案,但是为了保持高投票的答案仍然可用.


这段代码在一些国际字符串上失败了(在我的情况下,shift-jis存储在std :: string中); 我最终使用`boost :: trim`来解决问题.
为什么静电?这是一个优先使用匿名命名空间的地方吗?
我使用指针而不是引用,因此从调用点更容易理解这些函数编辑字符串,而不是创建副本.
请注意,对于isspace,您可以轻松地使用非ASCII字符获取未定义的行为http://stacked-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f
@TrevorHickey,当然,如果您愿意,可以使用匿名命名空间.
为了命名与`std :: string`的一致性,我建议使用名称`trim_front`和`trim_back`

2> Leon Timmerm..:

使用Boost的字符串算法是最简单的:

#include 

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

str现在"hello world!".还有trim_lefttrim,其修剪两侧.


如果您_copy为上述任何函数名称添加后缀,例如trim_copy,该函数将返回字符串的修剪副本,而不是通过引用修改它.

如果_if为上述任何函数名添加后缀,例如trim_copy_if,您可以修剪满足自定义谓词的所有字符,而不仅仅是空格.


@rodarmor:Boost解决了许多小问题.这是一个解决了很多问题的巨大锤子.
Boost是一组具有许多不同尺寸的锤子,可以解决许多不同的问题.
对于这么小的问题,Boost是如此巨大的锤子.
@rodarmor你说好像Boost是一个全有或全无的巨石,包括其中一个标题以某种方式在一个程序上造成整个事情.显然并非如此.顺便说一句,我从未使用过Boost,fwiw.
这取决于区域设置.我的默认语言环境(VS2005,en)表示剪裁选项卡,空格,回车符,换行符,垂直选项卡和换页符.
我已经使用了很多提升,`#include #include #include `但是担心代码膨胀会加入``当已有`std :: string :: erase`的替代品时.在添加它之前和之后比较MinSizeRel构建时,很高兴报告,这个提升的修剪根本没有增加我的代码(必须已经在某处支付)并且我的代码没有多了几个函数的混乱.
IMO唯一能提升的是为标准库提供内容(从boost中添加的想法).
@Ibrahim Boost是通用跨大陆工具建筑工厂工厂http://discuss.joelonsoftware.com/default.asp?joel.3.219431.12&

3> Bill the Liz..:

使用以下代码从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 );
}


这个答案不会改变所有空格的字符串.这是一个失败.
`str.substr(...).swap(str)`更好.保存作业.
这不会检测到其他形式的空白...特别是换行,换行,回车.
@updogliu它不会使用移动赋值`basic_string&operator =(basic_string && str)noexcept;`?

4> David G..:

派对迟到了,但没关系.现在C++ 11在这里,我们有lambdas和auto变量.所以我的版本,也处理所有空格和空字符串,是:

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

我们可以创建一个反向迭代器,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());
}


对于它的价值,没有必要使用那个lambda.你可以传递`std :: isspace`:`auto wsfront = std :: find_if_not(s.begin(),s.end(),std :: isspace);`
尼斯.来自我的+1.太糟糕了C++ 11没有将trim()引入到std :: string中,让每个人的生活更轻松.
+1可能是唯一一个只有一个O(N)字符串副本的实现的答案.
我总是想要一个函数调用来修剪字符串,而不是实现它

5> Galik..:

你在做什么是好的和强大的.我已经使用了相同的方法很长一段时间,我还没有找到一个更快的方法:

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


如果你的字符串不包含后缀空格,这将从npos + 1 == 0开始擦除,你将删除整个字符串.
我很困惑为什么在修改返回参数后返回副本?
@Travis:看起来你是对的.向回答者道歉.
这应该返回std :: string&以避免不必要地调用复制构造函数.
@rgove请解释一下.`str.find_last_not_of(x)`返回第一个不等于x的字符的位置.如果没有字符匹配x,它只返回npos.在这个例子中,如果没有后缀空格,它将返回相当于`str.length() - 1`,基本上产生`str.erase((str.length() - 1)+ 1).这是除非我非常错误.
@MiloDC我的困惑是为什么要返回副本*而不是*引用.返回`std :: string&`对我来说更有意义.

7> 小智..:

我喜欢tzaman的解决方案,唯一的问题是它不修剪只包含空格的字符串.

要纠正1个缺陷,请在2条修剪线之间添加str.clear()

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


好,但不能用内部空格来处理字符串.例如trim(abc def") - > abc,只剩下abc.

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> Greg Hewgill..:

在空字符串的情况下,您的代码假定添加1 string::npos给出0. string::npos是类型string::size_type,它是无符号的.因此,您依赖于添加的溢出行为.


你在说它好像很糟糕.*签名*整数溢出行为很糟糕.

10> Paul Nathan..:

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

这适用于null情况.:-)


这只是``rtrim``,而不是``ltrim``

11> DavidRR..:

我的解决方案基于@Bill蜥蜴的回答.

请注意,如果输入字符串只包含空格,则这些函数将返回空字符串.

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> Phidelux..:

在C ++ 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;
}



13> 小智..:

我的答案是对这篇文章的最佳答案的改进,它修剪了控制字符和空格(ASCII表上的0-32和127 ).

std::isgraph确定一个角色是否具有图形表示,因此您可以使用它来改变Evan的答案,以从字符串的任一侧删除任何没有图形表示的字符.结果是一个更优雅的解决方案:

#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以探索此选项) .


std :: ptr_fun已弃用

14> Some program..:

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



15> synaptik..:

这就是我使用的.只需继续从前面移除空间,然后,如果还有任何东西,请从背面做同样的事情.

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
}



16> mbgda..:

对于它的价值,这是一个关注性能的修剪实现.它比我见过的许多其他修剪程序要快得多.它使用原始c字符串和索引,而不是使用迭代器和std :: finds.它优化了以下特殊情况:size 0 string(什么都不做),没有要修剪的空格的字符串(什么都不做),只有尾随空格的字符串要修剪(只调整字符串大小),字符串完全是空格(只是清除字符串) .最后,在最坏的情况下(带有前导空格的字符串),它会尽最大努力执行有效的复制构造,只执行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));
    }
}



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


如果你以相反的顺序执行这些操作并且在通过修剪左边来调用移位之前从右边首先修剪它将会稍微更有效.

18> jha-G..:

这样做的优雅方式可能就像

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



19> GutiMac..:

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

推荐阅读
贴进你的心聆听你的世界
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有