0%

C++ 去除空格

在 C++ 中怎么去除字符串首尾的空格?怎么去掉字符串中所有的空格?

网上可以查到很多,有用 C 写的,有用纯 C++ 写的,混合的更是大有人在。功能都能实现,但哪个是最佳实践的?有以下几个标准 or 问题?

  • 代码足够简洁
  • 怎么保证效率?
  • 通用性:跨平台
  • 要不要考虑 C 调用问题?

一、trim

去除首尾空格,示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <string>

int main(void)
{
std::string str = " niel loves liyw ! ";
std::cout << "str: |" << str << "|" << std::endl;
str.erase(0, str.find_first_not_of(" "));
std::cout << "lef: |" << str << "|" << std::endl;
str.erase(str.find_last_not_of(" ")+1);
std::cout << "rig: |" << str << "|" << std::endl;

return 0;
}

使用 string 的 find_first_not_of()find_last_not_of() 函数找到字符串第一个非空字符和最后一个非空字符的位置,然后进行 erase() 操作。

二、trim_all

在进入主题之前,先来学习两个函数:remove()remove_if()

注意这两个函数的“移除”并不完美,他们并不改变容器的大小。一般结合 erase() 同时使用。

Return value
Past-the-end iterator for the new range of values (if this is not end, then it points to an unspecified value, and so do iterators to any values between this iterator and end)

调用 remove()remove_if() 函数会返回移除目标成员之后的区间的尾后迭代器。此迭代器及其后面的元素(如果有的话,一直到原区间的末尾)是不确定的。而这些不确定元素就是我们需要调用 erase() 清理的。看一段代码来理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <string>
#include <algorithm>

using namespace std;
int main(void)
{
// remove
string str = "nielonglovesliyawei";
cout << "origin str: |" << str << "|" << endl;
auto itor = remove(str.begin(), str.end(), 'e');
cout << "after rem: |" << str << "|" << endl;
str.erase(itor, str.end());
cout << "after era: |" << str << "|" << endl;

return 0;
}

执行结果如下,可以看到移除所有的字符 'e' 之后字符串长度并没有变。删除了3个,就把原字符串末尾的3个字符重复打印了一次(需要强调的是,此行为在其他编译器上不一定会复现,因为 c++ 标准里并未定义)。

1
2
3
4
5
6
vimer@debian8light:~/see-the-world/code/trim_test$ make test CXXFLAGS=-std=c++11 && ./test 
g++ -std=c++11 test.cpp -o test
origin str: |nielonglovesliyawei|
after rem: |nilonglovsliyawiwei|
after era: |nilonglovsliyawi|
vimer@debian8light:~/see-the-world/code/trim_test$

进入正题。我们来看去除所有空白字符的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <string>
#include <algorithm>

using namespace std;
int main(void)
{
std::string str = " niel loves liyw ! ";
std::cout << "str: |" << str << "|" << std::endl;
// trim all spaces: remove_if
auto itor = remove_if(str.begin(), str.end(), ::isspace);
std::cout << "str: |" << str << "|" << std::endl;
str.erase(itor, str.end());
std::cout << "str: |" << str << "|" << std::endl;

return 0;
}

执行结果:

1
2
3
4
5
6
vimer@debian8light:~/see-the-world/code/trim_test$ make test CXXFLAGS=-std=c++11 && ./test 
g++ -std=c++11 test.cpp -o test
str: | niel loves liyw ! |
str: |niellovesliyw!liyw ! |
str: |niellovesliyw!|
vimer@debian8light:~/see-the-world/code/trim_test$

::isspace

需要强调的是代码中 ::isspace 的作用域前缀,如果使用 isspace (因为全局 using namespace std;,所以同 std::isspace)就会报错。

删除 using namespace std;,使用 isspace 也是正确的。意义等同 ::isspace

::isspace means you’re explicitly calling the global method isspace. The C Standard Library methods are all globals, and <ctype.h> is a C Standard Library header.

Namespaces don’t exist in C, so when using the C library headers, you don’t use the std namespace. The C++ counterpart to <ctype.h> which uses the std namespace is <cctype>. 引用来源

std::isspace is an overloaded function in C++, with a template function declared in <locale>.

前者没有重载版本;而后者有(一份是 <cctype> 中从 <ctype.h> 拷贝过来加上 std:: 作用域的;一份是 <locale> 中的模板函数),参见 isspace-cctypeisspace-locale。所以如果使用后者(单独使用函数名,没有参数)就存在指代不明的问题。

为什么明确引入 <cctype> 还是报错,还是存在指代不明的问题呢?另外,示例中未明确引用 <ctype.h> 却未报错是因为什么原因呢?

Implementations are allowed to silently include additional headers you didn’t ask for, and many do so. They do so because they internally make use of those additional headers. 引用来源

如果坚持使用后者需要 解决指代不明的问题 - 使用 lambda

1
2
3
4
std::remove_if(str.begin(), str.end(), 
[](char c){
return std::isspace(static_cast<unsigned char>(c));
});

还有几个通过类型转换来解决后者指代不明问题的例子(但和上述的解决方案相比,终非正途):

  1. http://stackoverflow.com/questions/18589525/removing-all-spaces-from-a-string
  2. http://stackoverflow.com/questions/4537930/removing-a-character-from-a-string
  3. http://stackoverflow.com/questions/8364678/g-string-remove-if-error

好吧,我承认,主要是太丑陋了。丑得我都没有仔细看的想法。

总的来说 isspace ::isspace std::isspace 的问题:

isspace is defined both in the global namespace and in namespace std, so when you have using namespace std, and you write isspace, there’s an ambiguity. To be specific, there’s one overload in and another defined in .

Just write ::isspace and you’re good to go.