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


812

我目前正在使用以下代码对std::strings程序中的所有内容进行右修剪:

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

它可以正常工作,但我想知道是否存在某些可能会失败的最终情况?

当然,我们欢迎您提供其他优雅的解决方案以及左修剪解决方案。


549
这个问题的答案证明了C ++标准库的缺乏。
伊丹·K

83
@IdanK并且它在C ++ 11中仍然没有此功能。
2012年

44
@IdanK:很好,不是吗!看看所有的竞争方案,我们现在有在我们的处置,由一个人的“思想支配方式,我们必须这样做!”
2013年

59
@LightnessRacesinOrbit类型中的功能,这是一个设计决定,并且向字符串添加trim函数可能(至少在c ++下)不是最佳解决方案-但未提供任何标准方法来实现,而是让所有人烦恼同样的小问题一遍又一遍,当然也
无济于事

27
您可能会质疑为什么修整函数没有内置在std::string类中,因为正是这样的函数使其他语言很好用(例如Python)。
HelloGoodbye 2014年

Answers:


648

编辑自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 &ltrim(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;
}

我出于上下文的考虑而保留了上面的原始答案,并且为了使获得高票的答案仍然可用。


28
该代码在某些国际字符串上失败(在我的情况下,shift-jis存储在std :: string中);我最终使用它boost::trim来解决问题。
汤姆

5
我将使用指针而不是引用,以便从调用点更容易理解这些函数在原地编辑字符串,而不是创建副本。
Marco Leogrande'9

3
请注意,使用isspace可以轻松地使用非ASCII字符获得未定义的行为stacked-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f
R. Martinho Fernandes

10
为什么是静态的?这是首选匿名名称空间的地方吗?
Trevor Hickey

3
@TrevorHickey,当然,如果愿意,可以改用匿名名称空间。
埃文·特兰

417

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

#include <boost/algorithm/string.hpp>

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

str现在"hello world!"。也有trim_lefttrim,用于修剪两侧。


如果将_copy后缀添加到上述任何函数名称中,例如trim_copy,该函数将返回该字符串的修剪后的副本,而不是通过引用对其进行修改。

如果将_if后缀添加到上述任何函数名称(例如)中trim_copy_if,则可以修剪满足自定义谓词的所有字符,而不是空白。


7
这取决于语言环境。我的默认语言环境(VS2005,en)意味着对选项卡,空格,回车符,换行符,垂直选项卡和换页符进行了修剪。
MattyT

117
Boost可以解决如此微小的问题。
Casey Rodarmor 2012年

143
@rodarmor:Boost解决了许多小问题。这是一把很大的锤子,可以解决很多问题。
尼科尔·波拉斯

123
Boost是一组各种尺寸的锤子,可以解决许多不同的问题。
Ibrahim 2013年

11
@rodarmor您说的是,Boost似乎是一个全有或全无的巨石,其中包含其标头之一会以某种方式在程序上造成整个事情。显然不是这样。顺便说一句,我从来没有用过Boost。
underscore_d

61

使用以下代码从std::stringsideone)右修剪(跟踪)空格和制表符:

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

4
这不会检测到其他形式的空格...特别是换行符,换行符,回车符。
汤姆”

1
对。您必须针对要修剪的空白对其进行自定义。我的特定应用程序只期望空格和制表符,但是您可以添加\ n \ r来捕获其他字符。
比尔蜥蜴

5
str.substr(...).swap(str)更好。保存作业。
updogliu 2012年

4
@updogliu不会使用移动分配basic_string& operator= (basic_string&& str) noexcept;吗?
nurettin

8
此答案不会更改所有空格的字符串。这是失败的。
Tom Andersen 2014年

56

您正在做的事情很好而且很健壮。我已经使用相同的方法很长时间了,但我还没有找到一个更快的方法:

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)效率更高
CITBL

1
@CITBL首先执行内部功能,这样您将从左开始修剪,然后从右开始修剪。我认为这是不太有效的,不是吗?
Galik

究竟。我的错
CITBL

如果在CharT上使用basic_string和模板,则可以对所有字符串执行此操作,只需对空白使用模板变量,以便像ws <CharT>一样使用它。从技术上讲,您可以将其准备好用于c ++ 20并将其标记为constexpr,因为这暗示着内联
搁浅

@Beached确实。不过,在这里给出答案有点复杂。我已经为此编写了模板函数,并且确实涉及其中。我尝试了很多不同的方法,但仍然不确定哪种方法最好。
加里克

55

参加聚会有点晚,但是没关系。现在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_iteratorauto。我不知道构造反向迭代器的成本是多少,所以这里是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());
}

9
真好 向我+1。太糟糕的C ++ 11并未将trim()引入std :: string中,并使每个人的生活更加轻松。
米兰·巴布斯科夫

3
我一直想要一个函数调用来修剪字符串,而不是实现它
linquize 2014年

22
对于它的价值,不需要使用该lambda。您可以通过std::isspaceauto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);
vmrob

4
+1可能是仅执行一个O(N)字符串复制的实现的唯一答案。
阿列克谢·阿维琴科

4
@vmrob编译器不一定那么聪明。做你所说的是模棱两可的:candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)
johnbakers

42

试试这个,对我有用。

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

12
如果您的字符串不包含后缀空格,则将从npos + 1 == 0开始擦除,并将删除整个字符串。
mhsmith

3
@rgove请解释。str.find_last_not_of(x)返回不等于x的第一个字符的位置。如果没有字符与x不匹配,则仅返回npos。在此示例中,如果没有后缀空格,它将返回的等价项str.length() - 1,本质上会产生,str.erase((str.length() - 1) + 1).也就是说,除非我非常误解。
特拉维斯

5
这应该返回std :: string&,以避免不必要地调用复制构造函数。
heksesang 2014年

7
我很困惑为什么修改返回参数后会返回副本?
Galik 2015年

3
@MiloDC我的困惑是为什么返回副本而不是引用。回归对我来说更有意义std::string&
Galik '17

25

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

要纠正这1个缺陷,请在2条微调线之间添加一个str.clear()

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

很好:)但是,我们两个解决方案的问题在于它们会修剪两端;不能使一个ltrimrtrim喜欢这个。
tzaman

44
很好,但是不能用内部空格处理字符串。例如修剪(ABC DEF“) - > ABC,ABC只留下。
liheyuan

一个很好的解决方案,如果您知道不会有任何内部空格!
Elliot Gorokhovsky

这是很好而且容易的,但是随着字符串被复制到和从字符串中复制出来,它也相当慢std::stringstream
Galik

23

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
最后的基本空间修剪的优雅解决方案... :)
jave.web

运作方式:这是一个类似复制的解决方案-它查找不是空格(it)的第一个字符的位置,然后反向查找:只有空格(rit)的字符的位置-之后它返回一个新创建的字符串==原始字符串部分的副本-基于这些迭代器的部分...
jave.web

谢谢,为我工作:std:string s =“哦,noez:空格\ r \ n”; std :: string clean = trim(s);
Alexx Roche

15

在为空字符串的情况下,您的代码假定将1加到string::npos0。string::npos类型为string::size_type,这是无符号的。因此,您依赖于加法的溢出行为。


23
您说的好像不好。有符号整数溢出行为不好。
MSalters

2
添加1std::string::npos 必须放弃0根据C++ Standard。因此,这是一个可以绝对依赖的良好假设。
Galik


13

在C ++ 17中,您可以使用basic_string_view :: remove_prefixbasic_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));
}

我不确定您要测试什么,但是在您的示例中std :: find_first_not_of将返回std :: string :: nposstd :: string_view :: size将返回4。显然,最小值为4,要处理的元素数被std :: string_view :: remove_prefix删除。gcc 9.2和clang 9.0都可以正确处理此问题:godbolt.org/z/DcZbFH
Phidelux

1
谢谢!在我看来很好。
康坦戈

11

我的解决方案基于@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);
}

9

我的答案是对本篇文章的最高答案的改进,该内容减少了控制字符和空格(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了解此选项) 。


3
std :: ptr_fun已弃用
johnbakers

8

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

8

这就是我用的。只是继续从前面移开空间,然后,如果还有剩余,请从后面移开。

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
}

8
s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
s.erase(s.find_last_not_of(" \n\r\t")+1);   

2
如果您按照相反的顺序进行操作,然后先从右侧进行修剪,然后再通过修剪左侧来进行移位,则效率会更高。
Galik

7

值得一提的是,这里有一个针对性能的调整实现。它比我周围看到的许多其他修整例程快得多。代替使用迭代器和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));
    }
}

@bmgda也许理论上最快的版本可能具有此签名:extern“ C” void string_trim(char ** begin_,char ** end_)...赶上我的漂流了吗?

6

优雅的方式可以像

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

6

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

5

我想,如果您开始要求“最佳方式”来修剪字符串,我想说一个好的实现将是:

  1. 不分配临时字符串
  2. 对于原位裁切和复印裁切有过载
  3. 可以轻松定制以接受不同的验证序列/逻辑

显然,有太多不同的方法可以解决此问题,并且这完全取决于您的实际需求。但是,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;
}

3

我不确定您的环境是否相同,但是在我的情况下,空字符串会导致程序中止。我要么用if(!s.empty())包装该擦除调用,要么使用已经提到的Boost。


3

这是我想出的:

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

流提取会自动消除空格,因此它就像一个符咒。
如果我自己也这么说的话,也很干净优雅。;)


15
嗯 这假定字符串没有内部空格(例如空格)。OP仅表示他想在左侧或右侧修剪空白。
SuperElectric 2010年

3

为噪音做出贡献。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");  
}

3

由于添加了back()和,因此可以在C ++ 11中更简单地完成此操作pop_back()

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

OP建议的方法也不错-很难遵循。
nobar

3

这是我的版本:

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

您缺少最后一个字符。长度+1可以解决此问题
galinette

2

上面的方法很棒,但是有时您想对例程认为是空白的地方使用功能组合。在这种情况下,使用函子来合并操作会变得混乱,因此我更喜欢一个可以修改的简单循环。这是在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);
}

2

这是一个简单的实现。对于这样一个简单的操作,您可能不应该使用任何特殊的构造。内置的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);
}

2

对于初学者来说,这是一个易于理解的解决方案,他们不习惯std::到处书写,并且还不熟悉const-correctness,iterators,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));
}

希望能帮助到你...


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

1

另一种选择-从两端删除一个或多个字符。

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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.