CC++ & Algorithm

字符串流处理(istringstream、ostringstream、getline)

较难3
语言版本:通用
概述:学习如何把字符串当作流(像水流一样)来读写,方便地解析以空格分隔的数据,以及把格式化内容拼成一个字符串。

把字符串当水管——字符串流入门指南

你有没有想过,把字符串当成一根水管,让数据像水流一样从里面流进流出?在编程中,我们经常需要从一段文字里提取信息,或者把不同种类的数据拼成一条完整的句子。C++ 的 <sstream> 头文件提供了两个“水管”工具:istringstream(输入字符串流)和 ostringstream(输出字符串流)。它们使用起来和文件流 ifstreamofstream 很像,只是数据不来自硬盘文件,而是来自内存中的字符串。这篇文章会带你认识它们,并通过贴近生活的例子掌握它们的用法。


一、istringstream:从字符串中“抽水”提取数据

想象你有一张成绩单,上面写着 "小明 90 88.5",你想把姓名、整数分数、小数分数分别提取到变量里。如果手工用循环或 substr 来切分,会很麻烦。istringstream 就像一把智能吸管,插进字符串里,然后像使用 cin 一样用 >> 运算符就能按空格、换行等空白字符自动分割出数据。

生活场景:解析一行学生信息

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

int main() {
    // 一行数据:姓名 年龄 数学成绩
    string record = "小红 12 95.5";
    
    // 构造输入字符串流
    istringstream iss(record);
    
    // 定义变量,每行加中文注释
    string name;   // 姓名
    int age;       // 年龄
    double score;  // 成绩
    
    // 像 cin 一样依次读取
    iss >> name >> age >> score;
    
    cout << "姓名:" << name << ",年龄:" << age << ",成绩:" << score << endl;
    return 0;
}

输出:

姓名:小红,年龄:12,成绩:95.5

注意:

  • >> 会自动跳过前面的空白(空格、Tab、换行),然后读下一个非空白内容。
  • 读取时类型必须匹配,如果字符串里写的是 "xyz",而你要读 int,就会出错。

解析多个数字(比如游戏得分)

string scoreString = "100 200 300 400";
istringstream issScores(scoreString);
int s1, s2, s3, s4;  // 四个得分
issScores >> s1 >> s2 >> s3 >> s4;
cout << s1 + s2 + s3 + s4 << endl;  // 输出总分 1000

二、ostringstream:向字符串里“灌水”拼接数据

有时候你需要把多个变量拼成一个完整的句子,比如:"姓名:张三,年龄:12,成绩:95.5"。如果用 + 拼接,要反复创建新字符串,效率低。ostringstream 就像一根可以往里灌水的水管,你用 << 把数据写进去,最后通过 .str() 一次性得到完整字符串。

场景:生成一句问候语

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

int main() {
    string playerName = "小明";
    int level = 5;
    double hp = 88.5;
    
    // 创建输出字符串流
    ostringstream oss;
    // 用 << 像 cout 一样写入
    oss << "玩家" << playerName << ",等级" << level << ",生命值" << hp;
    
    // 获取拼接后的字符串
    string msg = oss.str();
    cout << msg << endl;  // 输出:玩家小明,等级5,生命值88.5
    return 0;
}

对比 + 拼接:

  • +string msg = "玩家" + name + ",等级" + to_string(level) + ... ——每次 + 都会生成临时对象,多个 + 效率低。
  • ostringstream:内部缓冲区一次性管理多次写入,最后只提取一次字符串。

还可以控制格式,比如设置小数位数:

#include <iomanip>
ostringstream oss2;
oss2 << fixed << setprecision(1);  // 保留一位小数
oss2 << 95.456;  // 实际写入 "95.5"

三、getline:按行或按分隔符“抽水”

有时候数据不是按空格分割,而是按行(比如一篇作文)或者按逗号(CSV 文件)。全局函数 getline 可以从任意输入流(包括 istringstream)中读取一行,或者指定一个分隔符,直到遇到该符号为止。

3.1 按行读取(模拟读取文件)

string multiLine = "第一行\n第二行\n第三行";
istringstream iss3(multiLine);
string line;  // 存储每一行的内容
while (getline(iss3, line)) {
    cout << "行:" << line << endl;
}
// 输出:
// 行:第一行
// 行:第二行
// 行:第三行

3.2 按指定分隔符读取(如逗号分隔的 CSV)

string csvLine = "苹果,12.5,好吃";
istringstream iss4(csvLine);
string fruit;    // 水果名
string weight;   // 重量(字符串)
string comment;  // 评价
getline(iss4, fruit, ',');   // 读到第一个逗号前
getline(iss4, weight, ',');  // 读到第二个逗号前
getline(iss4, comment);      // 读到末尾
cout << fruit << " 重" << weight << "克," << comment << endl;
// 输出:苹果 重12.5克,好吃

提示: 如果数据中有空格,用 getline 可以保留空格;而 >> 会跳过空格。比如名字 "张三 李四",用 >> 只能读到 "张三"(遇到空格停止),用 getline 可以读到整行。


四、新手容易犯的错误

❌ 忘记包含头文件 <sstream>

没有 #include <sstream>,编译器会不认识 istringstreamostringstream

❌ 重复使用流时忘记清空状态

当你用一个 istringstream 解析完一个字符串后,想用它解析另一个字符串,需要先重置内容并清除错误状态。

istringstream iss;
iss.str("123 456");  // 设置新内容
iss.clear();         // 必须清除之前的 EOF 等标记
int x, y;
iss >> x >> y;       // 正确

如果不加 clear(),即使换了新内容,流的状态仍可能标记为“已读完”,导致后续读取失败。

❌ 混合使用 >>getline 时吞掉换行

>> 读取后通常会留下一个换行符在流里,紧接着 getline 时会读到空行。解决方法:先 cin.ignore() 或者用 getline 读取换行符。

// 错误示例(假设从标准输入读取)
int age;
string name;
cin >> age;           // 输入 "12\n",读取 12,但换行符留在流中
getline(cin, name);   // 直接读到空行,因为换行符还在
// 正确做法:在 cin >> age 后加 cin.ignore(); 或再用一次 getline 吞掉换行

❌ 忘记把字符串转为数字

istringstream 读取时,如果目标变量是 intdouble,流会自动转换。但如果字符串里的内容不是有效数字,比如 "12a",读取会失败,需要检查流状态。

istringstream iss("12a");
int num;
if (iss >> num) {
    cout << "成功:" << num;
} else {
    cout << "转换失败,流状态错误";
}

五、完整可运行示例:解析学生成绩并生成报告

下面的程序从一行字符串中解析多个学生数据(姓名、成绩),然后汇总输出报告。演示了 istringstreamostringstreamgetline 的综合用法。

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;

int main() {
    // 模拟多行数据(每行:姓名 年龄 成绩)
    string input = "小明 12 95.5\n小红 11 88.0\n小刚 12 92.3\n";
    
    istringstream iss(input);          // 输入流,用来按行读取
    ostringstream report;              // 输出流,用来生成报告
    
    string line;                       // 保存读入的一行
    int totalAge = 0;                  // 年龄总和
    double totalScore = 0.0;           // 成绩总和
    int count = 0;                     // 学生人数
    
    report << "=== 成绩报告 ===" << endl;
    
    while (getline(iss, line)) {       // 按行读取
        istringstream lineStream(line); // 为这一行单独创建流,方便解析
        string name;                   // 姓名
        int age;                       // 年龄
        double score;                  // 成绩
        
        if (lineStream >> name >> age >> score) {
            report << name << ":年龄" << age << ",成绩" << score << endl;
            totalAge += age;
            totalScore += score;
            count++;
        } else {
            report << "无法解析:" << line << endl;
        }
    }
    
    if (count > 0) {
        report << "平均年龄:" << (double)totalAge / count << endl;
        report << "平均成绩:" << totalScore / count << endl;
    }
    
    cout << report.str();  // 输出完整报告
    return 0;
}

运行输出:

=== 成绩报告 ===
小明:年龄12,成绩95.5
小红:年龄11,成绩88
小刚:年龄12,成绩92.3
平均年龄:11.6667
平均成绩:91.9333

六、相关知识点指引

  • 文件流(ifstream/ofstream:字符串流的“亲兄弟”,用于读写文件,用法几乎一样。
  • 字符串分割与查找string::findsubstr 也可以手动分割,但流更方便。
  • 格式化输出<iomanip> 中的 setprecisionfixedsetw 等配合 ostringstream 可控制输出格式。
  • C++11 的 to_stringstoi:简单转换用它们,但需要多次拼接时还是流更高效。
  • Python 中的 io.StringIO:C++ 字符串流的 Python 对应品,用法类似。

现在,你已经学会了如何用字符串流像操作水管一样灵活地处理字符串数据。下次遇到要解析成绩单、拼写报告、或者处理 CSV 文件时,试试用它们吧!

例题精讲

1单选题

使用 istringstream 和 ostringstream 需要包含哪个头文件?

A<fstream>
B<sstream>
C<string>
D<iostream>
2判断题

使用 ostringstream 构造字符串时,调用 str() 方法获取当前构建的字符串,之后对 ostringstream 的后续操作不会影响之前获取的字符串。

3填空题
以下代码使用 istringstream 从字符串 s 中依次读取单词并输出(每词一行)。请填空。\n\nstring s = "Hello World C++";\n___ iss(s);\nstring word;\nwhile (iss >> word) {\n    cout << word << endl;\n}
4填空题
使用 ostringstream 将整数 a=10,浮点数 b=3.14,字符串 c="test" 拼接为格式 "a=10, b=3.14, c=test"。请填空。\n\nint a = 10;\ndouble b = 3.14;\nstring c = "test";\nostringstream oss;\n___;\nstring result = oss.str();
5单选题

关于 getline 函数,下列说法正确的是?

Agetline 可以从任意输入流中读取一行,直到遇到分隔符或 EOF。
Bgetline 的第三个参数可以指定分隔符,默认为换行符 '\n'。
Cgetline 读取后,分隔符会保留在目标字符串中。
Dgetline 只能用于文件流,不能用于 stringstream。