处理表格数据或配置项时,我们经常需要把类似 "apple,banana,orange" 的字符串拆成多个独立的子串,也就是 “字符串分割”。
在 C++ 中,std::string 没有提供现成的 split() 函数。在实际开发中,我们通常会根据分隔符是 “单个字符”、“空白字符” 还是 “多个字符”,选择对应的拆分方式。
按单字符分割字符串
当分隔符是英文逗号 ','、竖线 '|' 或分号 ';' 这类单个字符时,最简单的做法是使用 <sstream> 头文件中的 std::stringstream 配合 std::getline() 来实现。
其中,stringstream 是 “string stream(字符串流)” 的意思。平时从键盘读取数据使用的是 std::cin,而 std::stringstream 可以把现有的字符串包装成类似 std::cin 的输入流,让程序一段一段地从中读取内容。
关于字符串流的更多用法,后面章节还会专门介绍,这里小伙伴们先把它当作拆分字符串的固定工具来使用即可。
前面学习 std::getline() 时,双参数写法默认遇到换行符 '\n' 停止。实际上,它还可以传入第 3 个参数,用来指定 “读到哪个分隔字符就停下来”。
语法:
std::getline(ss, token, delim)说明:
该语法中 3 个参数的含义如下:
- ss(必选):由原字符串创建的
std::stringstream字符串流对象,需要引入<sstream>头文件。 - token(必选):用来保存当前拆出来的这一小段字符串的
std::string变量。token 在编程中常用来表示切分出来的 “片段”。 - delim(必选):作为分隔标志的单个
char字符,比如','。每调用一次std::getline(),它就会一直读到该分隔字符为止,把内容存入token,并将分隔字符本身取走丢弃。
示例 1:按逗号分割字符串
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::string text = "apple,banana,orange";
std::stringstream ss(text);
std::string token;
while (std::getline(ss, token, ',')) {
std::cout << token << '\n';
}
return 0;
}运行结果如下。
apple
banana
orange分析:
在这个例子中,std::stringstream ss(text); 先把原字符串 text 包装成字符串流 ss。
接着在 while (std::getline(ss, token, ',')) 中,每当成功读出一段内容,循环就会继续执行。等流里的字符全部读完后,条件变为假,循环自动结束。具体过程如下:
- 第 1 次循环:读到第 1 个逗号之前,
token得到"apple",逗号本身被自动丢弃。 - 第 2 次循环:继续往后读到第 2 个逗号之前,
token得到"banana"。 - 第 3 次循环:读完最后剩下的内容,
token得到"orange"。之后流中已无内容可读,循环结束。
上面例子中的字符串非常规整,每两个逗号之间都有单词。但有时待处理的文本可能开头就是逗号,或者中间出现了两个连续的逗号,比如 ",apple,,banana,"。
遇到这种情况时,std::getline() 在两个紧挨着的逗号之间读取,拆出来的 token 就会是一个空字符串 ""。如果我们不希望保留这些空字符串,只需要配合 empty() 函数判断一下,当 token 不为空时再处理即可。
示例 2:跳过连续分隔符的空串
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::string text = ",apple,,banana,";
std::stringstream ss(text);
std::string token;
while (std::getline(ss, token, ',')) {
if (!token.empty()) {
std::cout << token << '\n';
}
}
return 0;
}运行结果如下。
apple
banana分析:
因为 std::getline() 每次都是从当前位置一直读到下一个逗号为止,当字符串开头就是逗号,或者中间有两个逗号紧挨着时,两个逗号之间没有任何字符,读出来的 token 就是空字符串 ""。
对于这个例子来说,如果把 if (!token.empty()) 去掉,循环一共会读出 4 段内容:依次是空字符串 ""、"apple"、空字符串 "" 和 "banana"。加上非空判断后,空字符串被直接跳过,最终只输出了两个有效单词。
按空白字符拆分单词
如果我们想把一句英文按空格拆分成一个个独立的单词,而句子首尾或单词之间可能敲了多个连续空格,直接使用字符串流的 >> 运算符会比 std::getline() 更省心。
这是因为 std::getline(ss, token, ' ') 是严格按 “单个空格” 切分的,遇到连续空格就会切出空字符串 ""。而 >> 运算符会自动跳过所有的空格、制表符 \t 和换行符 \n,每次只提取一个干净的单词。
示例 3:按空白字符拆分单词
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::string text = " Learn C++ with lvyenet ";
std::stringstream ss(text);
std::string word;
while (ss >> word) {
std::cout << "[" << word << "]" << '\n';
}
return 0;
}运行结果如下。
[Learn]
[C++]
[with]
[lvyenet]分析:
在上面代码中,text 的开头、末尾以及单词之间都包含不止一个空格。while (ss >> word) 在读取时会自动忽略所有多余的空白字符,依次提取出 4 个干净的单词,不会产生多余的空字符串。
按多字符分割字符串
std::getline() 的第 3 个参数只能传入单个 char 字符。如果遇到像 "C++::Python::Go" 这样使用多个字符作为分隔符的字符串,std::getline() 就无法直接处理了。
此时我们可以结合 find() 和 substr() 函数来实现:先找出分隔符所在的下标,再把分隔符前面的这一段内容截取出来,循环往后推进即可。
示例 4:按多字符分隔符分割
#include <iostream>
#include <string>
int main() {
std::string text = "C++::Python::Go";
std::string delim = "::";
std::size_t start = 0;
std::size_t pos = text.find(delim, start);
while (pos != std::string::npos) {
std::string token = text.substr(start, pos - start);
std::cout << token << '\n';
start = pos + delim.size();
pos = text.find(delim, start);
}
// 输出最后一段剩余内容
std::cout << text.substr(start) << '\n';
return 0;
}运行结果如下。
C++
Python
Go分析:
在上面代码中,变量 start 记录当前段的起始下标,pos 记录找到的分隔符 "::" 的起始下标。由于冒号本身不用保留,当前段要截取的字符数正好是 pos - start。每一轮的数值变化如下:
- 第 1 次循环:
start为 0,找到第 1 个"::"的位置pos为 3,调用text.substr(0, 3 - 0)截出"C++"。接着将start更新为3 + 2,从下标 5 继续往后找。 - 第 2 次循环:从下标 5 往后找,找到第 2 个
"::"的位置pos为 11,调用text.substr(5, 11 - 5)截出"Python"。接着将start更新为 13。 - 循环结束后:从下标 13 往后已经找不到
"::"了,pos变为std::string::npos,循环结束。
这里大家别忘了循环外面的最后一行 text.substr(start)。因为最后一个片段 "Go" 后面已经没有分隔符 "::" 了,末尾剩下的这一段需要在循环结束后单独截取出来。
常见问题
1. 分割出来的多个子字符串,如果想保存起来应该放在哪里?
上面的例子都是每拆出一段内容就直接打印出来。如果后续还需要统计总段数或反复使用这些片段,可以根据实际情况选择保存方式:
段数固定时:比如拆分固定由 3 部分组成的日期 "2026-10-20",可以直接按顺序存入长度为 3 的 std::string 数组中。
段数不确定时:在实际开发中,由于拆分前往往不确定一共有多少段,我们通常会把每次拆出来的片段依次存入动态数组 std::vector 中,下一章就会详细介绍它的用法。
