C++ 字符串分割

处理表格数据或配置项时,我们经常需要把类似 "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 中,下一章就会详细介绍它的用法。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号