前言
C++ 至今为止(2019/09/05)没有官方实现的字符串分割函数。相比 Python、Java 等语言,多少是有些不便的。
参考资料:
1、《C++ 的 string 为什么不提供 split 函数?》
2、《Why doesn’t std::string have a split function》
这里我们来在 C++ 中实现字符串分割函数。
编译环境
系统环境:
patten@patten-hp:~$ sudo lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.6 LTS
Release: 16.04
Codename: xenial
patten@patten-hp:~$
IDE环境:Visual Studio Code,Version: 1.36.1
利用来自 C 的 strtok
函数
C 语言的 string.h
中提供了名为 strtok
函数,用于对 C 风格的字符串进行分割。其函数签名为:
char* strtok(char* str, const char* delim);
当 str
不是空指针时,strtok
会从头开始寻找第一个合法的分隔符,而后将分隔符替换成 \0
,并将分隔符的位置保存在一个静态变量中,最后返回 str
。这样,按照 C 风格的字符串,我们就能获取分割得到的第一个 token。当 str
是空指针时,strtok
将会从记录的空指针处继续尝试分割。
因此,我们可以定义这样的 split()
函数。
// main.cpp
#include <unistd.h>
#include <cstdio>
#include <iostream>
#include <sstream>
#include <string>
#include <cstring>
#include <vector>
using namespace std;
void split(const std::string& s, std::vector<std::string>& sv, const char* delim = " ") {
sv.clear(); // 1.
char* buffer = new char[s.size() + 1]; // 2.
std::copy(s.begin(), s.end(), buffer); // 3.
char* p = std::strtok(buffer, delim); // 4.
do {
sv.push_back(p); // 5.
} while ((p = std::strtok(NULL, delim))); // 6.
return;
}
int main() {
string pos("-5,6,0");
int buf[3];
std::vector<std::string> sv;
split(pos, sv, ",");
for (int i = 0; i < 3; i++) {
buf[i] = std::stoi(sv.at(i));
}
printf("(%d, %d, %d) \n", buf[0], buf[1], buf[2]);
}
对于 split
函数来说,它无法预知传入的 sv
变量的情况。因此,在 (1) 处,我们将 sv
这个 std::vector<std::string>
清空备用。由于 std::strtok
函数需要修改传入的 str
的内容,所以它需要 char*
类型的参数。故而,在 (2)(3) 两处,我们将 std::string
当中的内容复制一份。(4)(6) 两处对 std::strtok
的调用,帮助我们将 token 逐个压入 sv
当中。
在 C++11 及更高版本中,(5) 可替换为 sv.emplace_back(p)
,以避免额外的拷贝。
上述代码的结果是:
patten@patten-hp:~/workspace/others/cpp/split$ g++ main.cpp -std=c++11
patten@patten-hp:~/workspace/others/cpp/split$ ./a.out
(-5, 6, 0)
patten@patten-hp:~/workspace/others/cpp/split$
利用 C++ 中的流
实际上,利用纯 C++ 风格的代码,也是可以实现一个优雅的字符串分割函数的。
在《在 C++ 中读取字符串中成对定界符中的子串》中,介绍了 C++ 的 std::getline
函数。它接收一个输入流,将输入流至行末/分隔符部分的字符串保存在临时的字符串中;同时,返回输入流的左值引用。考虑到输入流本身可以用作条件判断,我们可以将 std::getline
与 while
循环联用,达成目的。
简单实现如下:
//main.cpp
#include <unistd.h>
#include <cstdio>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
void split(const std::string& s, std::vector<std::string>& sv, const char delim = ' ') {
sv.clear();
std::istringstream iss(s);
std::string temp;
while (std::getline(iss, temp, delim)) {
sv.emplace_back(std::move(temp));
}
return;
}
int main() {
string pos("-5,6,0");
int buf[3];
std::vector<std::string> sv;
split(pos, sv, ',');
for (int i = 0; i < 3; i++) {
buf[i] = std::stoi(sv.at(i));
}
printf("(%d, %d, %d) \n" , buf[0], buf[1], buf[2]);
}
代码中,我们借助字符串输入流 istringstream
处理带分割的字符串 s
。而后将各个 delim
之间的内容,保存在临时字符串 temp
当中,并移动到向量 sv
的末尾。
上述代码的结果是:
patten@patten-hp:~/workspace/others/cpp/split$ g++ main.cpp -std=c++11
patten@patten-hp:~/workspace/others/cpp/split$ ./a.out
(-5, 6, 0)
patten@patten-hp:~/workspace/others/cpp/split$