一.C语言的输入与输出
C 语言最常用的输入输出函数是 scanf() 和 printf()。
- scanf():从键盘读取标准输入,把读取到的数据存入对应变量。
- printf():把文本、数据输出到屏幕。
这里要注意掌握输出的宽度控制与精度控制。
同时C语言借助了缓冲区进行输入与输出,如下图所示:

对输入输出缓冲区的理解
- 缓冲区可以屏蔽底层 I/O 的实现细节。底层 I/O 依赖操作系统内核,屏蔽这部分差异后,写出来的程序具备更好的可移植性。
- 借助缓冲区还能实现按行读取。计算机本身并没有 "行" 的概念,依靠缓冲区,我们可以定义出行的逻辑,解析缓冲区内的数据,完成一行内容的读取返回。
二.流是什么
对 C++ 流的理解
流,顾名思义就是流动,代表数据从一个地方流向另一个地方,是对有序、连续、带方向数据的抽象,数据单位可以是比特、字节或者数据包。
C++ 里的流,用来描述信息的输入输出过程:数据从键盘这类外部设备流向内存,或是内存中的数据输出到显示器等外部设备,这个过程就被形象称作流。
流具备两个特点:有序连续、带有方向。
C++ 提供了I/O 标准类库来实现这套数据流动,库中每一个类都叫做流类,分别负责不同的输入输出功能。
三.C++IO流
如下图所示,C++ 系统实现了一个庞大的类库,其中 ios 为基类,其他类都是直接或间接派生自 ios 类。

C++ 流类继承结构图简要说明
最顶层基类ios_base --> ios,提供流的状态、格式控制等基础功能。streambuf:负责缓冲区管理(数据真正存放的地方)。
输入 / 输出流基类
istream:输入流(读数据),cin就是它的对象
ostream:输出流(写数据),cout/cerr/clog是它的对象
iostream:多重继承istream+ostream,同时支持读写
三类不同用途的流(右侧)
<iostream>:标准控制台流,键盘输入、屏幕输出(cin/cout)
<fstream>:文件流
ifstream 文件读;ofstream 文件写;fstream 文件读写;filebuf 文件缓冲区
<sstream>:字符串流,把字符串当作流使用
istringstream 读字符串;ostringstream 写字符串;stringstream 读写;stringbuf 字符串缓冲区
简单总结:所有流都继承自顶层基类,分为控制台、文件、字符串 3 种流,istream 负责读,ostream 负责写,streambuf 管理缓冲区。
1.C++标准IO流
C++ 标准库提供了四个常用的全局流对象:cin、cout、cerr 和 clog。
cout 用于标准输出,将数据从程序输出到控制台(显示器)。
cin 用于标准输入,通过键盘将数据输入到程序中。
cerr 专门用于输出标准错误信息。
clog 主要用于输出日志信息。
从类继承关系来看,cout、cerr 和 clog 都是 ostream 类的实例。虽然它们都用于输出,但设计上存在细微差异:cout 和 clog 是缓冲输出,而 cerr 是非缓冲输出,确保错误信息能立即显示。使用这些流对象时,需要包含相应的头文件(通常是 <iostream>)并引入 std 命名空间。
关于 cin 的几点注意事项:
1.缓冲特性:cin 是缓冲流。键盘输入的数据会先存储在缓冲区中,程序提取数据时直接从缓冲区读取。输入过多的数据会保留在缓冲区中供后续使用。
2.错误处理:如果输入的数据类型与程序期望的类型不匹配,会导致流状态错误,但程序不会立即终止,而是需要程序员手动检查和处理流状态。
3.分隔符:空格和回车符默认作为数据分隔符,因此多个数据可以在同一行或不同行输入。但需要注意,cin 默认会跳过空格,无法直接读取空格字符,字符串中也不能包含空格。
4.cin 和 cout 能够直接处理内置数据类型(如 int、float、char 等),是因为标准库已经为这些类型这些类型重载了
<<和>>运算符。5**.C++ 里自定义的结构体 / 类,直接用cin、cout读写会报错。 想要写成 cin >> 对象、cout << 对象,必须重载>>和<<运算符。**
cpp#include <iostream> using namespace std; //自定义类型 struct Student { int id; string name; }; //重载输出运算符 << ostream& operator<<(ostream& os, const Student& s) { os << s.id << " " << s.name; return os; } //重载输入运算符 >> istream& operator>>(istream& is, Student& s) { is >> s.id >> s.name; return is; } int main() { Student st; //直接用cin、cout读写自定义对象 cin >> st; cout << st << endl; return 0; }
6.OJ 在线判题里 C++ 输入输出:
做算法题提交到在线 OJ 的时候,输入输出是很容易踩坑丢分的地方,很多人代码逻辑没问题,就是输入写法或者输出空格、换行不对,直接判成答案错误 WA。这里把循环输入的几种写法、适用场景、常见 bug 和细节全部整理清楚。
先说最重要的一个概念:EOF。在线 OJ 会一次性把所有测试数据给到程序,程序读到数据末尾没有内容了,就是 EOF,循环条件cin>>变量在读到 EOF 的时候返回 false,循环自动结束。 本地用 VS 调试的时候,键盘按下 Ctrl+Z,就相当于手动模拟 EOF,程序终止循环。在线提交不用自己加结束标记,系统自动处理。还有一个硬性要求:输出格式必须严格和题目描述一模一样,多一个空格、少一个空格,多换行或者末尾多出空行,都会 WA,这点一定要留心
6.1.循环不断读取单个元素(数字、不带空格的字符串)
原始写法:
cpp
while(cin>>a)
{
//处理代码
}
**原理:**cin >> a会自动跳过所有空格、换行、Tab,只抓取有效数据。只要成功读到数据,循环继续;读到末尾 EOF,循环停止。 适用场景:题目有多组测试样例,没有先给出一共有多少组,每组只输入一个数。 注意:这种写法只能拿到一个个单独的数据,不能读取一整行,遇到空格就停下。
6.2.循环一次读取多个元素
先看这个写法,while(c>>a>>b>>c),它是是错误的。
问题在哪:输入流名字是 c,后面又定义了变量 c,名字冲突,编译直接报错。 正确写法:
cpp
int a,b,c;
while(cin>>a>>b>>c)
{
//处理每组的a、b、c
}
一次循环读取一组里的多个数据,数据之间换行或者空格隔开都没关系,cin 自动忽略空白。 适用场景:多组样例,每组给出好几个数字,不知道一共有多少组,读到输入结束为止。
补充区分另一种题型:题目第一行先输入 n,代表一共有 n 组测试数据。这种情况不能用 while 循环读 EOF,要用 for 循环固定循环 n 次。 示例如下:
cpp
int n,a,b;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a>>b;
}
我们大家有时候两种场景写混,直接造成超时或者读取数据错乱。
6.3.读取一整行数据(重点,极易混淆)
cin>>str只要碰到空格就停止读取,只能获取一个单词,根本读不了一整行,如果一行里面包含空格,用它会读不全。想要读取完整一行(保留行里面的空格),要用 getline 函数:
cpp
string line;
while(getline(cin,line))
{
//line保存完整一行内容
}
getline 会从当前位置读到换行符为止,换行本身不会存入字符串,读完一行继续下一行,直到全部输入结束。
这里举个非常经典的坑:cin 和 getline 混用
如果前面代码用过cin >> x;,输入完成按下回车,回车对应的换行符会留在输入缓冲区里。紧接着执行 getline,它会直接读到这个残留换行,得到一个空字符串。 举个出错例子:
cpp
int n;
cin>>n;
string s;
getline(cin,s); //这里直接读到空行!
解决办法,读取完数字之后,用 ignore 把缓冲区剩下的换行清理掉:
cpp
int n;
cin>>n;
cin.ignore(); //吃掉换行符
string s;
getline(cin,s);
6.4.两种输入模板怎么选,简单分清
数据是一个个数字、单词,数据之间用空格换行隔开,不需要接收带空格的整行 → 使用while(cin>>变量)
需要读取完整一行,一行内部带有空格,要把整行全部拿下来处理 → 使用while(getline(cin,line)) 不要盲目套用模板,看题目输入格式决定
6.5.OJ 输出的一些实战小细节
尽量少用 endl。endl 每次输出都会刷新缓冲区,数据量大的时候刷新太频繁,容易超时。日常优先写 '\n' 换行,速度更快。
多组样例输出,每组答案换行。警惕最后一组答案后面多输出一个换行,部分 OJ 判题严格,多余空行直接 WA。
输出空格:题目要求两个数字中间空格隔开,不要在每行最后额外多加一个空格,很多人栽在这里。
6.6本地调试和在线提交的区别
VS 本地测试时,输入完数据按 Ctrl+Z 结束循环;代码提交到 OJ 上运行,不用手动输入 Ctrl+Z,系统读到文件末尾自动触发 EOF 退出循环,代码不用做任何修改。
7.istream 类型对象转换为逻辑条件判断值
cpp
istream& operator>> (int& val);
explicit operator bool() const;
https://cplusplus.com/reference/istream/istream/operator%3E%3E/

https://cplusplus.com/reference/ios/ios/operator_bool/

实际上我们看到使用 while(cin>>i) 去流中提取对象数据时,调用的是 operator>>,返回值是 istream 类型的对象。 这里能作为逻辑判断条件,根源在于 istream 对象自带 operator bool。 当流读取失败或者读到结束标志 EOF 时,istream的operator bool返回 false,循环终止。
cpp
#include <iostream>
using namespace std;
class Date
{
friend ostream& operator << (ostream& out, const Date& d);
friend istream& operator >> (istream& in, Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
istream& operator >> (istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
ostream& operator << (ostream& out, const Date& d)
{
out << d._year << " " << d._month <<" "<< d._day << '\n';
return out;
}
int main()
{
Date d;
//标准写法:while判断cin流状态,调用istream自带operator bool()
//读到EOF、输入格式错误自动退出,OJ多组输入首选
while (cin >> d)
{
cout << d;
}
return 0;
}

2.C++文件IO流
C++ 根据文件内容的数据格式分为二进制文件和文本文件。采用文件流对象操作文件的一般步骤:
(1)定义一个文件流对象。
- ifstream ifile(只输入用,读取文件)
- ofstream ofile(只输出用,写入文件)
- fstream iofile(既输入又输出用)
(2)使用文件流对象的成员函数 open 打开一个磁盘文件,使得文件流对象和磁盘文件之间建立联系。 补充:打开后最好判断打开是否成功。
(3)使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写。 文本文件多用 >> <<;二进制文件用 read、write 成员函数。
(4)关闭文件,调用 close ()。
补充知识点:
1.常用打开模式
- ios::in 只读打开
- ios::out 只写打开,会清空原有内容
- ios::app 在文件末尾追加写入,不覆盖旧数据
- ios::binary 二进制方式打开
- 多种模式可以用 | 组合
2.易错识别
① 默认是文本模式;二进制读写必须带上 ios::binary
② ofstream 默认打开会清空文件,如果想要追加内容,不能直接使用默认模式
③ while (ifile>> x) 读到 EOF 自动结束,原理和 while (cin >> x) 保持一致
④ 文件流对象销毁时会自动关闭文件,从代码规范角度,建议手动调用 close ()
简单写个小Demo
cpp
#include <fstream>
#include <iostream>
using namespace std;
int main()
{
ifstream ifile;
ifile.open("test.txt");
if (!ifile) // 文件不存在,创建并写入一些默认数据
{
cout << "文件不存在,正在创建..." << endl;
ofstream ofile("test.txt");
if (ofile.is_open())
{
ofile << "10 20 30 40 50" << endl;
ofile.close();
cout << "文件已创建,请重新运行程序读取数据。" << endl;
}
else
{
cout << "创建文件失败!" << endl;
return 1;
}
return 0; // 创建后退出,下次运行即可读取
}
int a;
while (ifile >> a)
{
cout << a << " "; // 处理读取的数据
}
cout << endl;
ifile.close();
return 0;
}
创建文件:我在里面写了几个数字


再看一个比较难的代码:
cpp
#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
using namespace std;
// Date 类
class Date
{
public:
int _year;
int _month;
int _day;
Date() : _year(0), _month(0), _day(0) {}
Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}
// 友元声明:让外部函数能访问私有成员(如果成员是私有的话)
// 这里成员是 public,友元不是必须的,但为了演示规范写法保留
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
};
//流插入运算符重载(输出)
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "-" << d._month << "-" << d._day;
return out;
}
//流提取运算符重载(输入)
istream& operator>>(istream& in, Date& d)
{
// 支持格式:2023-11-10 或 2023 11 10
char ch1, ch2;
in >> d._year >> ch1 >> d._month >> ch2 >> d._day;
// 如果分隔符不是 '-',尝试用空格分隔的方式
if (ch1 != '-' || ch2 != '-')
{
// 回退到用空格分隔
in.clear(); // 清除错误状态
in >> d._year >> d._month >> d._day;
}
return in;
}
struct ServerInfo
{
char _address[32];
int _port;
Date _date;
// 默认构造函数
ServerInfo() : _port(0)
{
memset(_address, 0, sizeof(_address));
}
// 带参构造函数
ServerInfo(const char* address, int port, const Date& date)
: _port(port), _date(date)
{
strcpy_s(_address, sizeof(_address), address);
}
};
// ConfigManager 配置管理类
class ConfigManager
{
public:
ConfigManager(const char* filename)
: _filename(filename)
{
}
// 二进制写入
void WriteBin(const ServerInfo& info)
{
ofstream ofs(_filename, ios_base::out | ios_base::binary);
if (!ofs.is_open())
{
cerr << "二进制文件打开失败(写入): " << _filename << endl;
return;
}
ofs.write((const char*)&info, sizeof(info));
ofs.close();
cout << "二进制写入成功: " << _filename << endl;
}
//二进制读取
void ReadBin(ServerInfo& info)
{
ifstream ifs(_filename, ios_base::in | ios_base::binary);
if (!ifs.is_open())
{
cerr << "二进制文件打开失败(读取): " << _filename << endl;
return;
}
ifs.read((char*)&info, sizeof(info));
ifs.close();
cout << "二进制读取成功: " << _filename << endl;
}
// 文本写入
void WriteText(const ServerInfo& info)
{
ofstream ofs(_filename);
if (!ofs.is_open())
{
cerr << "文本文件打开失败(写入): " << _filename << endl;
return;
}
// C++ 流操作符优势:对内置类型和自定义类型使用相同方式
ofs << info._address << " " << info._port << " " << info._date;
ofs.close();
cout << "文本写入成功: " << _filename << endl;
}
// 文本读取
void ReadText(ServerInfo& info)
{
ifstream ifs(_filename);
if (!ifs.is_open())
{
cerr << "文本文件打开失败(读取): " << _filename << endl;
return;
}
ifs >> info._address >> info._port >> info._date;
ifs.close();
cout << "文本读取成功: " << _filename << endl;
}
private:
string _filename; // 配置文件名
};
// 主函数
int main()
{
cout << "开始测试 " << endl << endl;
// 创建服务器信息对象
ServerInfo winfo("192.0.0.1", 80, Date(2026, 9, 5));
//测试二进制读写
cout << "二进制读写测试 " << endl;
ConfigManager cf_bin("test.bin");
cf_bin.WriteBin(winfo);
ServerInfo rbinfo;
cf_bin.ReadBin(rbinfo);
cout << "读取结果: " << rbinfo._address << " "
<< rbinfo._port << " " << rbinfo._date << endl << endl;
// 测试文本读写
cout << "文本读写测试" << endl;
ConfigManager cf_text("test.txt");
cf_text.WriteText(winfo);
ServerInfo rtinfo;
cf_text.ReadText(rtinfo);
cout << "读取结果: " << rtinfo._address << " "
<< rtinfo._port << " " << rtinfo._date << endl;
cout << endl << " 测试完成" << endl;
return 0;
}

1. 为什么 Date 必须重载 << 和 >>?
因为 ofs << info._date 这种写法,编译器只认识内置类型。Date 是自定义类型,必须告诉它怎么输入输出。
2. 为什么重载函数必须是全局的?
cpp
cout << d; // 等价于 operator<<(cout, d)
左操作数是 cout(ostream 类型),不是 Date 对象。所以函数不能是 Date 的成员,必须是全局函数。
3. 二进制 vs 文本
| 方式 | 核心操作 | 优点 | 缺点 |
|---|---|---|---|
| 二进制 | write/read 整体内存拷贝 |
速度快,代码简单 | 文件不可读,跨平台有隐患 |
| 文本 | << >> 逐字段转换 |
人类可读,方便调试 | 速度慢,需要重载运算符 |
4. 这代码的亮点
统一写法:不管 int、char\[\] 还是 Date,都用同样的 << 和 >> 操作。
cpp
ofs << info._address << " " << info._port << " " << info._date;
这就是运算符重载的价值------让自定义类型用起来像内置类型。
5. 二进制写入的坑
cpp
ofs.write((const char*)&info, sizeof(info));
只是把内存字节原样拷贝。如果结构体里有指针 ,存的是地址,下次读回来地址失效。所以二进制读写只适合全是值类型的结构体。
简单小结:想让自定义类型跟内置类型一样用流操作,就得给它实现 << 和 >>。二进制快但不可读,文本慢但方便,按需选择。
四.stringstream的简单介绍
在 C 语言中,如果想要将一个整形变量的数据转化为字符串格式,如何去做?
两种方法:
- 使用 itoa() 函数
- 使用 sprintf() 函数
来看代码:
cpp
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
int main()
{
int n = 123456789;
// 方法1:使用 _itoa(Windows 特有)
char s1[32];
_itoa_s(n, s1, sizeof(s1), 10); // _itoa_s 更安全,需要指定缓冲区大小
cout << "_itoa: " << s1 << endl;
// 方法2:使用 sprintf(标准 C 函数)
char s2[32];
sprintf_s(s2, sizeof(s2), "%d", n); // sprintf_s 防止缓冲区溢出
cout << "sprintf %%d: " << s2 << endl;
// 问题演示:格式不匹配
char s3[32];
sprintf_s(s3, sizeof(s3), "%f", n); // 用 %f 格式化整数,得到错误结果
cout << "sprintf %%f (错误): " << s3 << endl;
return 0;
}
_itoa vs sprintf
| 函数 | 优点 | 缺点 | 跨平台 |
|---|---|---|---|
_itoa |
专用于整数转字符串 | Windows 专用,不是标准 C | 不可以 |
sprintf |
标准 C 函数,格式灵活 | 需要正确匹配格式符 | 可以 |
C++ 里可以借助stringstream 类对象来处理这类问题。
想要使用 stringstream,必须引入对应的头文件。在这个头文件中,标准库提供了三个类: istringstream、ostringstream、stringstream,分别实现流输入、流输出、同时输入输出。下面重点讲解 stringstream。
stringstream 主要可以用来:
a.将数值类型数据格式化为字符串
来看代码:
cpp
#include <sstream>
#include <string>
#include <iostream>
using namespace std;
int main()
{
int a = 12345678;
string sa;
// 1. int 转 string
stringstream ss;
ss << a;
ss >> sa;
cout << "int转string: " << sa << endl;
// 2. 转换下一个(注意清空步骤)
ss.clear(); // 先清状态标志
ss.str(""); // 再清内容
double d = 12.34;
ss << d;
ss >> sa;
cout << "double转string: " << sa << endl;
// 3. 另一种获取方式:直接用 str()
ss.clear();
ss.str("");
float f = 56.78f;
ss << f;
string sValue = ss.str(); // 直接拿,不用经过 >>
cout << "用str()获取: " << sValue << endl;
return 0;
}

为什么要 clear()?
stringstream 在读取完数据后,内部会设置 eofbit 或 badbit 状态。不清除的话,下次转换会失败:
cpp
ss << 123;
ss >> sa; // 读取成功,但内部状态变了
ss << 456; // 写不进去,因为状态还是坏的
ss >> sa; // 读不出来
为什么要 str("")?
str("") 是清空底层字符串。不清的话:
cpp
ss << 123;
ss >> sa; // sa = "123"
ss << 456; // 底层还是 "123456",内容是追加
ss >> sa; // sa = "123456",不是你想要的
两种获取方式的区别
cpp
// 方式1:通过 >> 提取
ss << 123;
int n;
ss >> n; // 提取到 n,ss 里内容会被"消费"掉
// 方式2:通过 str() 获取
ss << 123;
string s = ss.str(); // 直接拿底层字符串,不影响 ss 状态
b.字符串拼接
cpp
#include <sstream>
#include <string>
#include <iostream>
using namespace std;
int main()
{
stringstream ss;
// 1. 拼接多个字符串
ss << "first" << " " << "string,";
ss << " second string";
cout << "拼接结果: " << ss.str() << endl;
// 2. 清空并重新使用(两件套)
ss.clear(); // 清状态
ss.str(""); // 清内容
ss << "third string";
cout << "清空后: " << ss.str() << endl;
// 3. 顺便展示一下:不调用 clear() 会怎样
ss.str(""); // 只清内容
ss << "第四个字符串"; // 可能失败,也可能成功
cout << "只清内容不请状态: " << ss.str() << endl;
// 结果不确定,取决于编译器实现
return 0;
}
cpp
ss.clear(); // 清状态
ss.str(""); // 清内容
stringstream 清空那点事 记住两条就行
第一条:清空要两件套
cpp
ss.clear(); // 清状态
ss.str(""); // 清内容
别嫌麻烦,少一个都可能翻车。
第二条:分情况对待
- 只用 << 往里写:只 str("") 就够
- 用过 >> 往外读:必须两件套都上
- 搞不清楚:直接两件套,多写一行不吃亏
为啥会这样?
stringstream 读完数据后,内部会记一笔"我读完了",状态变成 eofbit。不清掉的话,下次再写它以为已经结束了,就不让你写。所以只看你干过啥:
只写不读:状态一直是好的,清内容就行
写过也读过:状态可能脏了,状态和内容都得清
懒得记这些破事:每次都两件套,省心
那实际情况咋写呢?
cpp
// 情况1:纯写
ss << "hello";
ss.str(""); // 清内容够了
ss << "world"; // 没问题
// 情况2:读写都干过
ss << "123";
int n;
ss >> n; // 读了,状态脏了
ss.clear(); // 清状态
ss.str(""); // 清内容
ss << "456"; // 现在好了
// 情况3:嫌麻烦
stringstream ss; // 每次新的,啥都不用管
ss << "就用一次";
主要核心就是:拿不准就 clear() 配 str(""),保你不出错
c.序列化和反序列化结构数据
说白了就是把结构体信息变成字符串发出去,对方收到后再还原成结构体。网络通信里这叫序列化 和反序列化。
cpp
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
// Date类(前面说过,必须重载<<和>>才能在流里用)
class Date
{
public:
int _year;
int _month;
int _day;
Date() : _year(0), _month(0), _day(0) {}
Date(int y, int m, int d) : _year(y), _month(m), _day(d) {}
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "-" << d._month << "-" << d._day;
return out;
}
istream& operator>>(istream& in, Date& d)
{
// 按 "2022-4-10" 这种格式读
char ch1, ch2;
in >> d._year >> ch1 >> d._month >> ch2 >> d._day;
return in;
}
struct ChatInfo
{
string _name; // 名字
int _id; // id
Date _date; // 时间
string _msg; // 聊天信息
};
int main()
{
// 1. 原始数据
ChatInfo winfo = { "hjq", 135246, Date(2026, 9, 5), "晚上一起看电影吧" };
// 2. 序列化:结构体 --> 字符串
ostringstream oss;
oss << winfo._name << " "
<< winfo._id << " "
<< winfo._date << " "
<< winfo._msg;
string str = oss.str();
cout << "序列化后的字符串:" << endl;
cout << str << endl << endl;
// 模拟:通过网络发送这个字符串
// 实际开发中会复杂得多,这里只是演示
// 3. 反序列化:字符串 --> 结构体
ChatInfo rInfo;
istringstream iss(str);
iss >> rInfo._name
>> rInfo._id
>> rInfo._date
>> rInfo._msg;
// 4. 验证结果
cout << "反序列化后还原:" << endl;
cout << "姓名:" << rInfo._name << " (" << rInfo._id << ")" << endl;
cout << "时间:" << rInfo._date << endl;
cout << rInfo._name << ":>" << rInfo._msg << endl;
return 0;
}

五.stringstream 底层原理和用法
它底层到底是在干啥?
stringstream 内部维护了一个 string 对象,所有操作都是在这个对象上折腾的。写入的内容会往这个 string 末尾追加,读取的时候从这个 string 里取数据。
核心规则就三条
规则1:清空要用两件套
cpp
ss.clear(); // 清状态标志(eofbit 这些)
ss.str(""); // 清底层字符串
为啥要两个一起?因为:
- clear() 只管状态标志,不改内容
- str("") 只管清空内容,不改状态
- 少一个都可能出问题
规则2:内容会被累积
cpp
ss << "hello";
ss << " world";
cout << ss.str(); // "hello world" 看到没,是追加不是覆盖
想清空重来?那就用 str("")。
规则3:状态标志会影响后续操作
cpp
ss << "123";
int n;
ss >> n; // 读完数据后,内部状态变了
ss << "456"; // 这行可能写不进去,因为状态还是"已结束"
所以读过数据之后,必须 clear() 清状态。
三种常用操作
cpp
stringstream ss;
// 1. 写入数据
ss << 123 << " " << 45.67;
// 2. 取出底层字符串
string s = ss.str(); // "123 45.67"
// 3. 清空准备复用
ss.clear();
ss.str("");
ss << "新的数据";
和 C 风格对比
C 风格的问题:
cpp
char buf[10];
sprintf(buf, "%d", 123456789); // 缓冲区不够,崩了
stringstream 的优势:
cpp
stringstream ss;
ss << 123456789; // 多大都不怕
string s = ss.str(); // 自动扩容
实际使用建议
场景1:一次性转换,直接新建
cpp
string intToStr(int n) {
stringstream ss; // 每次新建,不用操心清空
ss << n;
return ss.str();
}
场景2:频繁转换同一个对象,记住两件套
cpp
stringstream ss;
ss << 123;
string s1 = ss.str();
ss.clear(); // 两个都要
ss.str("");
ss << 456;
string s2 = ss.str();
小结就是记住这几点:
- 底层是 string,所以会自动扩容,不怕溢出
- 状态和内容是两码事,清的时候都要照顾到
- 读过的对象要清状态,不然写不进去
- 类型自动识别,不用记 %d、%f 那些格式符
说通俗点就是,stringstream 就是让你像操作 cin/cout 一样操作字符串,不用管内存、不用管格式,省心。
简单总结就是:
- stringstream 底层维护了一个 string 对象,用来存放保存的数据。多次进行类型转换时,必须调用 clear () 清空流状态,才能正常转换;
- 但 clear () 并不会清空 stringstream 底层的 string 对象。
- 可以使用 s.str ("") 把底层 string 对象置为空字符串。
- 调用 s.str () 可以获取 stringstream 底层保存的 string 对象。
- stringstream 依靠 string 对象替代字符数组,规避了缓冲区溢出隐患;同时能够自动识别参数类型,无需格式化控制,不存在格式化失败的问题,使用起来更加便捷、安全。


