Menu

C++ 字符串流:用 stringstream 解析和拼接

如何使用 std::stringstream、istringstream 和 ostringstream 解析文本、按空白分割字符串、在字符串和数字之间转换,以及在内存中拼出格式化字符串。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

住在字符串里的流

你已经认识 cin 和 cout:连接到键盘和屏幕的流。字符串流是同样的思路,只不过数据存放在内存中的 std::string 里。你用 << 写入,用 >> 取出值,和控制台流完全一样,只是什么都不会碰到终端。

这一个思路就解决了两个日常问题:把文本解析成有类型的值,以及把各种零件拼接成一个格式化字符串。一切都在头文件 <sstream> 中,共有三种:

  • istringstream:只读,用于解析输入。
  • ostringstream:只写,用于构建输出。
  • stringstream:双向都可以。

ss.str() 把目前写入的全部内容作为一个普通的 string 返回。<< 运算符做的是和 cout 一样的 int 转文本格式化,只不过你把结果捕获了下来,而不是打印出来。

解析:取出有类型的值

真正的威力体现在 >> 上。给流喂一段文本,提取操作就会把每个词元转换成你要求的类型,并跳过它们之间的空白。把一行文本拆成多个有类型的字段,这就是干净的做法:

每个 >> 读到下一个空白为止并完成转换:"Ada" 变成 string,"36" 变成 int,"5.5" 变成 double。注意这里用的是 istringstream:只能输入的类型清楚地表明你只是在解析。

(安全地)把字符串转换成数字

一个常见的任务是把像 "42" 这样的单个字符串变成 int。stringstream 能做到,而且还会告诉你这段文本是否真的是一个有效的数字,而 atoi 从来不会告诉你:

这里 ss >> value 读到 123,在 a 处停下,并且成功了。所以如果这很重要,一定要验证整个字符串。稳妥的检查方法是先读出数字,再确认后面没有剩下有意义的内容。对于直接的单值转换,std::stoi、std::stod 等函数更短;当一个字符串里有多个类型不同的值时,再用 stringstream。

按分隔符分割字符串

分割文本是字符串流最常被搜索的用法。把流和 std::getline 结合起来,它可以读到任意分隔字符为止,而不只是换行符:

while (getline(...)) 这种写法会一直循环到流被读完,因为 getline 返回流本身,而流在没有剩余内容时会被判定为 false。每个词元都被放进一个 vector,这是收集数量不定的分割片段的自然之选。去掉第三个参数,就改为用 >> 按空白分割:根据你的数据选择合适的分隔符。

在内存中拼接字符串

反过来,ostringstream 可以拼出一个格式化字符串,既没有 sprintf 的缓冲区溢出风险,也没有手动拼接的脆弱。它非常适合日志行、文件名,或者由数字和文字拼成的消息:

你和 cout 一起用的所有 <iomanip> 格式化手段,比如 setw、setfill、setprecision、hex,在字符串流上效果完全相同,所以补齐、定宽或十六进制的输出可以直接捕获到一个 string 中。

陷阱:复用前先重置

人人都会踩的坑是不重置就复用一个流。流一旦读到数据末尾,它的 eof 和 fail 标志就会锁定,之后的每次 >> 都悄悄地什么也不做:没有错误,只有过期的值:

stringstream ss("10");
int a, b;
ss >> a;            // a = 10, stream now at end -> eof flag set
ss.str("20");       // load new text...
ss >> b;            // FAILS SILENTLY - error flags are still set, b is unchanged

你必须同时清除错误状态和内容。正确的顺序是先 clear(),再 str():

两个相关的错误:ss.clear() 只重置标志,不会清空缓冲区(清空要用 ss.str(""));另外,一个你不断用 << 追加内容的 stringstream 会一直变大,所以在循环里每次迭代都新建一个,而不是反复复用一个用脏了的。

下一节:数组

字符串流让你可以干净地把文本转换成有类型的值,再转换回来,而且常常是一整串值,就像分割 CSV 时填充的那个 vector。要存储固定数量的这类值并按下标访问,你需要语言中最基础的容器。接下来我们会讲解数组:如何声明它们、按下标访问、遍历它们,以及如何避开在 C++ 中造成大量未定义行为的越界访问。

常见问题

C++ 中的 stringstream 是什么?

std::stringstream 是一种以 string 为后端的流,而不是键盘或文件。你用 << 向它写入,用 >> 从它读取,和 cout、cin 一模一样,所以它是在内存中解析文本和拼接字符串的标准工具。它位于头文件 <sstream> 中。

C++ 中如何用 stringstream 把字符串转换成 int?

把文本放进一个流,再提取到一个数字中:stringstream ss("42"); int n; ss >> n;。用 if (ss)(或 if (ss >> n))确认转换确实成功了。简单情况下 std::stoi 更短,但当一个字符串中包含多个类型不同的值时,stringstream 就更胜一筹。

为什么要对 stringstream 调用 clear()?

流一旦读到数据末尾,它的 eof/fail 标志就会一直保持,之后的每次 >> 都会悄悄地什么也不做。如果你用新内容复用同一个 stringstream,要先调用 ss.clear() 重置这些错误标志,再用 ss.str(newText) 载入新数据,否则读取会悄无声息地失败。

Coddy 编程语言插图

和 Coddy 一起学编程

开始