从字符串中移除前导和尾随空格

120

如何在C++中从字符串对象中删除空格。
例如,如何从下面的字符串对象中删除前导和尾随空格。

//Original string: "         This is a sample string                    "
//Desired string: "This is a sample string"

据我所知,字符串类并没有提供任何方法来删除前导和尾随空格。

此外,如何扩展此格式化以处理字符串中单词之间的额外空格,例如:

// Original string: "          This       is         a sample   string    " 
// Desired string:  "This is a sample string"  

使用解决方案中提到的字符串方法,我可以考虑分两步完成这些操作。

  1. 删除前导和尾随空格。
  2. 重复使用find_first_of、find_last_of、find_first_not_of、find_last_not_of 和 substr 在单词边界获取所需的格式。
26个回答

144
这被称为修剪。如果您可以使用Boost,我建议您使用它。
否则,使用find_first_not_of获取第一个非空格字符的索引,然后使用find_last_not_of从末尾获取不是空格的索引。使用这些,使用substr获取没有周围空格的子字符串。
针对您的编辑,我不知道该术语,但我猜测类似于“减少”,所以我称之为这样。(注意,我已更改空格为参数,以提高灵活性)
#include <iostream>
#include <string>

std::string trim(const std::string& str,
                 const std::string& whitespace = " \t")
{
    const auto strBegin = str.find_first_not_of(whitespace);
    if (strBegin == std::string::npos)
        return ""; // no content

    const auto strEnd = str.find_last_not_of(whitespace);
    const auto strRange = strEnd - strBegin + 1;

    return str.substr(strBegin, strRange);
}

std::string reduce(const std::string& str,
                   const std::string& fill = " ",
                   const std::string& whitespace = " \t")
{
    // trim first
    auto result = trim(str, whitespace);

    // replace sub ranges
    auto beginSpace = result.find_first_of(whitespace);
    while (beginSpace != std::string::npos)
    {
        const auto endSpace = result.find_first_not_of(whitespace, beginSpace);
        const auto range = endSpace - beginSpace;

        result.replace(beginSpace, range, fill);

        const auto newStart = beginSpace + fill.length();
        beginSpace = result.find_first_of(whitespace, newStart);
    }

    return result;
}

int main(void)
{
    const std::string foo = "    too much\t   \tspace\t\t\t  ";
    const std::string bar = "one\ntwo";

    std::cout << "[" << trim(foo) << "]" << std::endl;
    std::cout << "[" << reduce(foo) << "]" << std::endl;
    std::cout << "[" << reduce(foo, "-") << "]" << std::endl;

    std::cout << "[" << trim(bar) << "]" << std::endl;
}

结果:

[too much               space]  
[too much space]  
[too-much-space]  
[one  
two]  

site_t 应该改为 size_t 吗?我认为你在注释中写的 no whitespace 意思是字符串全为空格或者为空。 - Fred Larson
@GMan 的解决方案非常优雅。谢谢。 - Ankur
错误:尝试通过trim()运行"one\two"。结果为空字符串。您还需要将endStr与std::string::npos进行测试。 - dlchambers
@jplatte 为什么这样做会更好呢? - glibg10b
@glibg10b 你在回复一个8年前的问题。我已经将其删除,因为它实际上没有任何意义。 - jplatte
显示剩余3条评论

61

在一行代码中,轻松去除std::string中的前导、尾随和额外空格

value = std::regex_replace(value, std::regex("^ +| +$|( ) +"), "$1");

仅移除前导空格

value.erase(value.begin(), std::find_if(value.begin(), value.end(), std::bind1st(std::not_equal_to<char>(), ' ')));
或者
value = std::regex_replace(value, std::regex("^ +"), "");

仅删除尾部空格

value.erase(std::find_if(value.rbegin(), value.rend(), std::bind1st(std::not_equal_to<char>(), ' ')).base(), value.end());
或者
value = std::regex_replace(value, std::regex(" +$"), "");

只删除额外的空格

value = regex_replace(value, std::regex(" +"), " ");

5
好的。提供一些这里正在发生什么的信息会很有用,因为很难理解这些代码。 - Marcin
仅适用于C++11。 - Martin Pecka
9
它不会移除制表符,但可以修复这个问题。无法修复的是它非常缓慢(比使用“substr”或“erase”答案慢100倍左右)。 - 4LegsDrivenCat
为了优化速度,正则表达式不是最佳解决方案,但可以通过创建一个正则表达式实例来改进。 - Evgeny Karpov

56

我目前正在使用这些函数:

// trim from left
inline std::string& ltrim(std::string& s, const char* t = " \t\n\r\f\v")
{
    s.erase(0, s.find_first_not_of(t));
    return s;
}

// trim from right
inline std::string& rtrim(std::string& s, const char* t = " \t\n\r\f\v")
{
    s.erase(s.find_last_not_of(t) + 1);
    return s;
}

// trim from left & right
inline std::string& trim(std::string& s, const char* t = " \t\n\r\f\v")
{
    return ltrim(rtrim(s, t), t);
}

// copying versions

inline std::string ltrim_copy(std::string s, const char* t = " \t\n\r\f\v")
{
    return ltrim(s, t);
}

inline std::string rtrim_copy(std::string s, const char* t = " \t\n\r\f\v")
{
    return rtrim(s, t);
}

inline std::string trim_copy(std::string s, const char* t = " \t\n\r\f\v")
{
    return trim(s, t);
}

28

Boost 字符串修剪算法

#include <boost/algorithm/string/trim.hpp>

[...]

std::string msg = "   some text  with spaces  ";
boost::algorithm::trim(msg);

9

C++17引入了std::basic_string_view,它是一个类模板,用于引用常量字符对象的连续序列,即字符串的视图。除了与std::basic_string有非常相似的接口外,它还具有两个额外的函数:remove_prefix()remove_suffix(),可以通过将其开头向前移动或将结尾向后移动来缩小视图。这些函数可用于修剪前导和尾随空格:

#include <string_view>
#include <string>

std::string_view ltrim(std::string_view str)
{
    const auto pos(str.find_first_not_of(" \t\n\r\f\v"));
    str.remove_prefix(std::min(pos, str.length()));
    return str;
}

std::string_view rtrim(std::string_view str)
{
    const auto pos(str.find_last_not_of(" \t\n\r\f\v"));
    str.remove_suffix(std::min(str.length() - pos - 1, str.length()));
    return str;
}

std::string_view trim(std::string_view str)
{
    str = ltrim(str);
    str = rtrim(str);
    return str;
}

int main()
{
    std::string str = "   hello world   ";
    auto sv1{ ltrim(str) };  // "hello world   "
    auto sv2{ rtrim(str) };  // "   hello world"
    auto sv3{ trim(str) };   // "hello world"

    //If you want, you can create std::string objects from std::string_view objects
    std::string s1{ sv1 };
    std::string s2{ sv2 };
    std::string s3{ sv3 };
}

注意:使用std :: min 以确保pos 不大于size(),当字符串中的所有字符都是空格并且 find_first_not_of 返回 npos 时会发生这种情况。此外,std :: string_view 是一个非拥有引用,因此只要原始字符串仍然存在,它才有效。修剪字符串视图对其所基于的字符串没有影响。

代码示例的最后一部分应该写成 //如果需要,你可以从std::string_view对象中创建std::string对象 std::string s1{ sv1 }; std::string s2{ sv2 }; std::string s3{ sv3 }; 或者s1、s2和s3仍然是std::string_view副本。 - Bo R
@BoR 正确。感谢指出。已更新如上。 - jignatius

8
这是我对去除字符串首尾空格的解决方案...
std::string stripString = "  Plamen     ";
while(!stripString.empty() && std::isspace(*stripString.begin()))
    stripString.erase(stripString.begin());

while(!stripString.empty() && std::isspace(*stripString.rbegin()))
    stripString.erase(stripString.length()-1);

结果是“Plamen”。

1
这是一个非常次优的解决方案。在字符串开头的每个erase调用都会导致字符串的每个字符向下移动1个位置,对于大型字符串来说代价相当高昂。最好找到第一个不是空格字符的字符,然后删除该点之前的部分。 - Human-Compiler

8
以下是您可以实现的方法:

以下是具体步骤:

std::string & trim(std::string & str)
{
   return ltrim(rtrim(str));
}

支持功能的实现如下:

std::string & ltrim(std::string & str)
{
  auto it2 =  std::find_if( str.begin() , str.end() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
  str.erase( str.begin() , it2);
  return str;   
}

std::string & rtrim(std::string & str)
{
  auto it1 =  std::find_if( str.rbegin() , str.rend() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
  str.erase( it1.base() , str.end() );
  return str;   
}

一旦您准备好了所有这些内容,您也可以编写以下内容:

std::string trim_copy(std::string const & str)
{
   auto s = str;
   return ltrim(rtrim(s));
}

7

使用boost的建议来修剪前导和尾随空格的示例(仅删除尾随和挂起空格):

#include <boost/algorithm/string/trim.hpp>

std::string str = "   t e s t    ";

boost::algorithm::trim ( str );

输出结果为"t e s t"

还有以下操作:

  • trim_left 输出结果为"t e s t "
  • trim_right 输出结果为" t e s t"

5

去除前导和尾随空格的示例

std::string aString("    This is a string to be trimmed   ");
auto start = aString.find_first_not_of(' ');
auto end = aString.find_last_not_of(' ');
std::string trimmedString;
trimmedString = aString.substr(start, (end - start) + 1);

或者

trimmedSring = aString.substr(aString.find_first_not_of(' '), (aString.find_last_not_of(' ') - aString.find_first_not_of(' ')) + 1);

3
人们不喜欢查看10页的代码来学习如何截取字符串。 - Thinkal VB
3
如果字符串只包含空格,则它是无效的。 - DAG

5
/// strip a string, remove leading and trailing spaces
void strip(const string& in, string& out)
{
    string::const_iterator b = in.begin(), e = in.end();

    // skipping leading spaces
    while (isSpace(*b)){
        ++b;
    }

    if (b != e){
        // skipping trailing spaces
        while (isSpace(*(e-1))){
            --e;
        }
    }

    out.assign(b, e);
}

在上面的代码中,isSpace() 函数是一个布尔函数,用于判断一个字符是否为空格。您可以根据需要实现此函数,或者如果您愿意,只需调用 "ctype.h" 中的 isspace() 函数即可。请注意保留 HTML 标记。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接