从字符串中删除空白字符

30

我知道在Java和C中有几种不错的方法可以做到这一点,但在C++中,我似乎找不到一种轻松实现字符串修剪函数的方法。

这是我目前拥有的:

string trim(string& str)
{
    size_t first = str.find_first_not_of(' ');
    size_t last = str.find_last_not_of(' ');
    return str.substr(first, (last-first+1));
}

但每当我尝试打电话

trim(myString);

我遇到了编译器错误

/tmp/ccZZKSEq.o: In function `song::Read(std::basic_ifstream<char, 
std::char_traits<char> >&, std::basic_ifstream<char, std::char_traits<char> >&, char const*, char const*)':
song.cpp:(.text+0x31c): undefined reference to `song::trim(std::string&)'
collect2: error: ld returned 1 exit status

我想寻找一种简单而标准的方法来从字符串中删除前导和尾随空格,而不需要编写100行代码,我尝试使用正则表达式,但也没能让它工作。

我也不能使用Boost库。


song 是一个命名空间吗?还是一个类? - K-ballo
1
可能是从字符串中删除前导和尾随空格的重复问题。 - Anderson Green
4
这个问题与修剪无关,而是涉及到链接错误。很可能你会在修剪的定义不同的情况下得到相同的错误。 - Brandin
7个回答

42

你的代码没问题。你看到的是链接器问题。

如果将你的代码放在一个文件中,像这样:

#include <iostream>
#include <string>

using namespace std;

string trim(const string& str)
{
    size_t first = str.find_first_not_of(' ');
    if (string::npos == first)
    {
        return str;
    }
    size_t last = str.find_last_not_of(' ');
    return str.substr(first, (last - first + 1));
}

int main() {
    string s = "abc ";
    cout << trim(s);

}

然后执行g++ test.cc,运行a.out,你会看到它可以工作。

你应该检查包含trim函数的文件是否在编译过程的链接阶段中被包含。


5
这会导致空字符串崩溃! - Stepan Yakovenko
4
添加 'if (first == string::npos) return "";' 来避免崩溃。除此之外,这是一个漂亮简单的解决方案。我看到有人使用 boost (!!!) 来实现这样的事情。 - user6416335
string trim(string str) 是更加友好的函数签名。 - yanpas

30

以下是您可以完成此操作的步骤:

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

试一试


4
感谢您在介绍地区问题方面的贡献,这是其他答案中缺少的内容。具体的地区取决于应用场景,可能不一定正确,但在处理字符串时仍然是一个重要因素。 - Rado

7

如果 str 只包含空格,则 substr() 会抛出异常。

我会修改为以下代码:

string trim(string& str)
{
    size_t first = str.find_first_not_of(' ');
    if (first == std::string::npos)
        return "";
    size_t last = str.find_last_not_of(' ');
    return str.substr(first, (last-first+1));
}

这对于制表符或除空格之外的其他种类的空白字符无效。 - jocull
1
@jocull - 你说得对,但我认为很明显该怎么做才能指定更多的空格,即使用find_first_not_of(" \t\n\r\v\f")。 - Martin

4
使用正则表达式
#include <regex>
#include <string>

string trim(string s) {
    regex e("^\\s+|\\s+$");   // remove leading and trailing spaces
    return regex_replace(s, e, "");
}

// if you prefer the namespaced version
std::string trim(std::string s) {
    std::regex e("^\\s+|\\s+$"); // remove leading and trailing spaces
    return std::regex_replace(s, e, "");
}

Credit to: https://www.regular-expressions.info/examples.html for the regex
As @o_oTurtle mentioned - regexes are very slow.
An alternate approach, which includes additional whitespace characters:
std::string trim(const std::string& str, const std::string REMOVE = " \n\r\t")
{
    size_t first = str.find_first_not_of(REMOVE);
    if (std::string::npos == first)
    {
        return str;
    }
    size_t last = str.find_last_not_of(REMOVE);
    return str.substr(first, (last - first + 1));
}

正则表达式在C++中的性能非常慢,所以我总是尽量避免使用它们... - o_oTurtle

0
#include <vector>
#include <numeric>
#include <sstream>
#include <iterator>

void Trim(std::string& inputString)
{
    std::istringstream stringStream(inputString);
    std::vector<std::string> tokens((std::istream_iterator<std::string>(stringStream)), std::istream_iterator<std::string>());

    inputString = std::accumulate(std::next(tokens.begin()), tokens.end(),
                                 tokens[0], // start with first element
                                 [](std::string a, std::string b) { return a + " " + b; });
}

0

带有测试的解决方案

有点令人惊讶的是,这里的回答中没有提供一个测试函数来演示在边界情况下trim的行为。空字符串和完全由空格组成的字符串都可能会引起问题。

以下是这样一个函数:

#include <iomanip>
#include <iostream>
#include <string>

void test(std::string const& s)
{
    auto const quote{ '\"' };
    auto const q{ quote + s + quote };
    auto const t{ quote + trim(s) + quote };
    std::streamsize const w{ 6 };
    std::cout << std::left 
        << "s = " << std::setw(w) << q 
        << " : trim(s) = " << std::setw(w) << t 
        << '\n';
}

int main()
{
    for (std::string s : {"", " ", "   ", "a", " a", "a ", " a "})
        test(s);
}

测试接受的解决方案

当我在2023年8月5日运行这个接受的答案时,对它处理完全由空格组成的字符串感到失望。我期望它们被修剪为空字符串。相反,它们保持不变地返回了。这是if语句的原因。

// Accepted solution by @Anthony Kong. Copied on 2023-Aug-05.

using namespace std;

string trim(const string& str)
{
    size_t first = str.find_first_not_of(' ');
    if (string::npos == first)
    {
        return str;
    }
    size_t last = str.find_last_not_of(' ');
    return str.substr(first, (last - first + 1));
}

这是我的测试输出结果:
s = ""     : trim(s) = ""
s = " "    : trim(s) = " "
s = "   "  : trim(s) = "   "
s = "a"    : trim(s) = "a"
s = " a"   : trim(s) = "a"
s = "a "   : trim(s) = "a"
s = " a "  : trim(s) = "a"

测试“新的改进版”修剪功能

为了使其按照我想要的方式工作,我改变了if语句。

趁机还把using namespace std;删掉了,并将参数名称改为s。你知道的,因为我可以这样做。

// "New and improved" version of trim. Lol.
std::string trim(std::string const& s)
{
    auto const first{ s.find_first_not_of(' ') };
    if (first == std::string::npos)
        return {};
    auto const last{ s.find_last_not_of(' ') };
    return s.substr(first, (last - first + 1));
}

现在测试程序产生了我喜欢的输出结果。
s = ""     : trim(s) = ""
s = " "    : trim(s) = ""
s = "   "  : trim(s) = ""
s = "a"    : trim(s) = "a"
s = " a"   : trim(s) = "a"
s = "a "   : trim(s) = "a"
s = " a "  : trim(s) = "a"

0

除了@gjha的回答之外:

inline std::string ltrim_copy(const std::string& str)
{
    auto it = std::find_if(str.cbegin(), str.cend(),
        [](char ch) { return !std::isspace<char>(ch, std::locale::classic()); });
    return std::string(it, str.cend());
}

inline std::string rtrim_copy(const std::string& str)
{
    auto it = std::find_if(str.crbegin(), str.crend(),
        [](char ch) { return !std::isspace<char>(ch, std::locale::classic()); });
    return it == str.crend() ? std::string() : std::string(str.cbegin(), ++it.base());
}

inline std::string trim_copy(const std::string& str)
{
    auto it1 = std::find_if(str.cbegin(), str.cend(),
        [](char ch) { return !std::isspace<char>(ch, std::locale::classic()); });
    if (it1 == str.cend()) {
        return std::string();
    }
    auto it2 = std::find_if(str.crbegin(), str.crend(),
        [](char ch) { return !std::isspace<char>(ch, std::locale::classic()); });
    return it2 == str.crend() ? std::string(it1, str.cend()) : std::string(it1, ++it2.base());
}

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