学完 C 语言的 scanf / printf,再来看 C++ 的 cin / cout,你可能觉得"不就是换了个写法吗"。其实 C++ 把输入输出抽象成了"流",还提供了文件流、字符串流等一整套工具。本文从"流是什么"讲起,一路讲到 OJ 输入技巧、文件读写、stringstream,所有代码都在 g++ (C++17) 上实际运行验证过,照着抄就能跑。
目录
先回顾:C 语言的输入与输出
"流"到底是什么
C++ IO 流类家族
标准 IO 流:cin / cout / cerr / clog
OJ 刷题输入输出专题
为什么 while(cin >> x) 能当循环条件
文件 IO 流
stringstream:把字符串当成流来用
全文总结
1. 先回顾:C 语言的输入与输出
C 语言最常用的两个函数:
scanf():从键盘读取数据,存入变量;
printf():把数据格式化后输出到屏幕,重点掌握宽度控制和精度控制(比如%5d、%.2f)。
不管是输入还是输出,C 语言在程序和硬件之间都加了一层缓冲区:
键盘 ──► 输入缓冲区 ──► 程序 ──► 输出缓冲区 ──► 显示器缓冲区有两个好处,用大白话讲:
屏蔽底层差异:数据从哪来、到哪去(键盘、文件、网卡……),底层操作完全不同。缓冲区把这些脏活累活统一包起来,同一套
scanf/printf代码换个平台也能跑,可移植性好;攒一批再处理,效率高:直接操作硬件很慢,缓冲区先把数据攒起来,一次读/写一批。另外计算机本身没有"行"的概念,有了缓冲区,我们才能定义"读到换行符就算一行"这样的规则。
C++ 的流继承了缓冲思想,所以理解缓冲区对后面很重要。
2. "流"到底是什么
流(stream),顾名思义就是"流动"——数据从一个地方流向另一个地方。它是对"有序、连续、带方向"的数据的一种抽象。
数据从键盘流向内存(变量),叫输入;
数据从内存流向显示器/文件,叫输出。
可以把流想象成一根水管:数据是水,>>是把水从管里抽进变量,<<是把变量里的水灌进管子。
C++ 提供了一整套 I/O 标准类库,里面的每个类都叫"流类",分别负责控制台、文件、字符串等不同场景。
3. C++ IO 流类家族
C++ 实现了一个庞大的流类库,继承关系简化后是这样:
ios_base(格式、状态等最基础功能) └── ios(持有一个 streambuf,streambuf 才是真正存数据的缓冲区) ├── istream(输入流:负责"读",cin 就是它的对象) ├── ostream(输出流:负责"写",cout/cerr/clog 是它的对象) └── iostream(多重继承 istream + ostream,既能读又能写)按用途分,常用的有三大类:
头文件 | 场景 | 常用类 |
|---|---|---|
| 控制台输入输出 |
|
| 文件读写 |
|
| 把字符串当流用 |
|
一句话总结:istream管读,ostream管写,streambuf管缓冲区,所有流都是同一套家族里的成员,所以它们的用法惊人地一致——都是>>和<<。
4. 标准 IO 流:cin / cout / cerr / clog
C++ 标准库内置了四个全局流对象,程序一启动就能用:
对象 | 类型 | 作用 | 是否缓冲 |
|---|---|---|---|
| istream | 标准输入,默认从键盘读 | 缓冲 |
| ostream | 标准输出,输出到显示器 | 缓冲 |
| ostream | 标准错误,输出错误信息 | 不缓冲,立刻显示 |
| ostream | 标准错误,输出日志信息 | 缓冲 |
小知识:
cerr和clog都输出到标准错误(stderr),区别是 cerr 不缓冲——程序崩之前错误信息也一定能刷到屏幕上;clog 带缓冲,适合量大的日志。
使用它们要包含<iostream>,并引入命名空间(后面的代码统一using namespace std;)。
关于 cin 的 5 个注意点
① cin 是缓冲流。键盘敲的内容先进缓冲区,
cin >>直接从缓冲区取;一次输入太多,用不完的数据会留在缓冲区里给下次用。② 类型不匹配不会让程序崩溃,但流会"坏掉"。比如变量是 int,你却输入字母,流会进入错误状态(failbit),之后所有输入语句全部跳过。需要手动用
cin.clear()恢复状态、cin.ignore()清掉脏数据,这属于进阶内容,初学先有个印象。③ 默认以空格、Tab、换行作为分隔符。所以
cin >> s读字符串时遇到空格就停,读不了带空格的一整行(读整行要用getline,后面讲)。④ cin/cout 能直接处理 int、double、char 等内置类型,是因为标准库已经为这些类型重载好了
>>和<<运算符。⑤ 自定义类型不能直接 cin/cout。想写
cout << 对象,就得自己重载<<;想写cin >> 对象,就得自己重载>>。
#include <iostream> #include <string> using namespace std; struct Student { int id; string name; }; // 重载 << :左边必须是 ostream,所以不能写成 Student 的成员函数,只能写在全局 ostream& operator<<(ostream& os, const Student& s) { os << s.id << " " << s.name; return os; // 返回流对象,才能支持 cout << a << b 链式调用 } // 重载 >> :参数是非 const 引用,因为要往里写数据 istream& operator>>(istream& is, Student& s) { is >> s.id >> s.name; return is; } int main() { Student st; cin >> st; // 可以直接输入整个对象 cout << st << '\n'; return 0; }运行示例:
补充一个暖心细节:为什么
cout << "请输入:"后面不写endl,提示语也能立刻显示?因为cin 和 cout 默认绑在一起,cin 读数据前会自动把 cout 缓冲区刷出去,不用我们操心。
5. OJ 刷题输入输出专题
刷算法题时,逻辑全对但输入输出格式写错导致 WA(答案错误)是最冤的。这一节把几种固定模板讲清楚。
5.1 最重要的概念:EOF
OJ 会把测试数据一次性喂给程序,数据读完就到了文件末尾,即EOF(End Of File)。while (cin >> x)读到 EOF 时循环条件为假,自动结束——不用我们自己写结束标记。
在Windows / VS本地调试:输入完数据后按
Ctrl + Z再回车,手动模拟 EOF;在Linux / Mac(包括云端虚拟机):按
Ctrl + D;在线提交:什么都不用做,系统自动处理。
另外,输出格式必须和题目描述逐字符一致:该空格的地方空格,该换行的地方换行,多一个少一个都可能 WA。
5.2 模板一:循环读单个数据
int a; while (cin >> a) { // 处理 a }原理:cin >> a自动跳过空格、Tab、换行,只抓有效数据;读到数据就继续循环,读到 EOF 就停止。
适用场景:多组测试数据,不告诉你有多少组,每组一个数(或一个不带空格的字符串)。
5.3 模板二:每组读多个数据
int a, b, c; while (cin >> a >> b >> c) { // 一次读一组的三个数 }数据之间用空格还是换行隔开都可以,cin 会自动忽略空白。
注意区分另一种题型:题目第一行先给一个 n,表示后面恰好有 n 组数据。这时不能用 EOF 循环,要用 for 循环固定读 n 次:
int n; cin >> n; for (int i = 0; i < n; ++i) { int a, b; cin >> a >> b; // 处理 }两种写法千万别写混。比如题目第一行给组数2,后面是两组数据,你却用while (cin >> a >> b)读 EOF,实测会把组数2也当成数据:读成(2,10)、(20,30),最后一个数40直接丢失,答案全部错位 WA(本地交互式调试时还可能表现为程序一直等输入)。
5.4 模板三:读取一整行(极易踩坑)
cin >>碰到空格就停。要保留空格、读取完整一行,用getline:
string line; while (getline(cin, line)) { // line 里就是完整的一行(不含行末的换行符本身) }经典大坑:cin 和 getline 混用
int n; cin >> n; // 输入 3 后按回车,数字 3 被取走,换行符 '\n' 还留在缓冲区 string s; getline(cin, s); // getline 一上来就碰到这个残留的 '\n',读到一个空字符串!解决方法:在 getline 前把残留的换行吃掉。
int n; cin >> n; cin.ignore(); // 忽略 1 个字符(正好是那个 '\n') // 更保险的写法:一直忽略到换行符为止 // cin.ignore(1024, '\n'); string s; getline(cin, s); // 这次正常读到一整行5.5 模板怎么选
输入特征 | 选择 |
|---|---|
一个个数字/单词,空白分隔,不需要保留空格 |
|
必须拿到带空格的一整行 |
|
第一行明确给了组数 n |
|
5.6 输出的实战细节
少用
endl,多用'\n'。endl每输出一次都强制刷新缓冲区,数据量大时频繁刷盘会超时;'\n'只换行不刷新,快得多。题目要求"每组答案占一行",正常在每组末尾输出一个
'\n'即可;个别判题严格的 OJ 不喜欢末尾多余空行,拿不准时保持"每组恰好一个换行"。题目要求两个数字之间用空格分隔时,行尾不要多加空格,这是高频扣分点。
6. 为什么 while(cin >> x) 能当循环条件
cin >> x调用的是operator>>,它的返回值是istream 流对象本身(所以才能cin >> a >> b链式调用)。一个对象怎么能放进 if / while 的条件里呢?
因为 istream 提供了一个类型转换运算符:
explicit operator bool() const; // 把流对象转换成 bool
流状态正常 → 转换为
true;读到 EOF 或类型不匹配(流进入 fail 状态)→ 转换为
false。
所以while (cin >> x)的完整含义是:读一个数据,然后检查流还好不好,好就进循环,不好就结束。下面用自定义的 Date 类完整演示:
#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; return out; } int main() { Date d; while (cin >> d) // 读到 EOF 自动结束,OJ 多组输入首选写法 { cout << d << '\n'; } return 0; }7. 文件 IO 流
文件按内容形式分为文本文件(人能看懂的字符)和二进制文件(字节原样存储)。C++ 用文件流操作文件,固定四步:
定义文件流对象:
ifstream ifile;—— 只读;ofstream ofile;—— 只写;fstream iofile;—— 可读可写。
open 打开文件,把流对象和磁盘文件关联起来,打开后一定要判断是否成功;
读写:文本文件用
>>/<<,二进制文件用read()/write()成员函数;close 关闭文件(对象析构时也会自动关,但建议显式调用,代码更清晰)。
7.1 常用打开模式
模式 | 含义 |
|---|---|
| 只读打开 |
| 只写打开,默认会清空(截断)原文件内容(实测 |
| 追加写入,数据加到文件末尾,不覆盖旧内容 |
| 二进制方式打开 |
多种模式可以用
|组合,例如ios::out | ios::binary。
7.2 四个易错点
默认是文本模式,要读写二进制必须显式加
ios::binary;
ofstream默认打开就清空文件,想追加内容要用ios::app;
while (ifile >> x)读到文件末尾自动结束,原理和while (cin >> x)完全一样;文件流对象销毁时会自动 close,但手动 close 是更好的习惯。
7.3 入门 Demo:文件不存在就创建,存在就读数字
#include <fstream> #include <iostream> using namespace std; int main() { ifstream ifile("test.txt"); if (!ifile) // 打开失败(文件不存在或没权限) { cout << "文件不存在,正在创建...\n"; ofstream ofile("test.txt"); if (ofile.is_open()) { ofile << "10 20 30 40 50\n"; ofile.close(); cout << "文件已创建,请重新运行程序读取数据。\n"; } return 0; } int a; while (ifile >> a) { cout << a << " "; } cout << '\n'; ifile.close(); return 0; }7.4 进阶 Demo:配置信息的二进制读写与文本读写
#include <iostream> #include <fstream> #include <string> #include <cstring> using namespace std; class Date { public: int _year, _month, _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) { // 文本格式固定为 2026-9-5,用两个字符变量吃掉分隔符 '-' char ch1, ch2; in >> d._year >> ch1 >> d._month >> ch2 >> 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) { // strncpy 是跨平台的;strcpy_s 是 VS 提供的安全版本,g++ 默认不认识 strncpy(_address, address, sizeof(_address) - 1); } }; class ConfigManager { public: ConfigManager(const char* filename) : _filename(filename) {} void WriteBin(const ServerInfo& info) { ofstream ofs(_filename, ios::out | ios::binary); if (!ofs.is_open()) { cerr << "二进制文件打开失败(写入)\n"; return; } ofs.write((const char*)&info, sizeof(info)); } void ReadBin(ServerInfo& info) { ifstream ifs(_filename, ios::in | ios::binary); if (!ifs.is_open()) { cerr << "二进制文件打开失败(读取)\n"; return; } ifs.read((char*)&info, sizeof(info)); } void WriteText(const ServerInfo& info) { ofstream ofs(_filename); if (!ofs.is_open()) { cerr << "文本文件打开失败(写入)\n"; return; } ofs << info._address << " " << info._port << " " << info._date; } void ReadText(ServerInfo& info) { ifstream ifs(_filename); if (!ifs.is_open()) { cerr << "文本文件打开失败(读取)\n"; return; } ifs >> info._address >> info._port >> info._date; } private: string _filename; }; int main() { ServerInfo winfo("192.168.0.1", 80, Date(2026, 9, 5)); ConfigManager cf_bin("test.bin"); cf_bin.WriteBin(winfo); ServerInfo rbinfo; cf_bin.ReadBin(rbinfo); cout << "二进制读回: " << rbinfo._address << " " << rbinfo._port << " " << rbinfo._date << '\n'; ConfigManager cf_text("test.txt"); cf_text.WriteText(winfo); ServerInfo rtinfo; cf_text.ReadText(rtinfo); cout << "文本读回: " << rtinfo._address << " " << rtinfo._port << " " << rtinfo._date << '\n'; return 0; }运行结果(本机实测):
生成的 test.txt 用记事本可以直接看懂:192.168.0.1 80 2026-9-5;test.bin 则是一串"乱码"字节。
7.5 关于这段代码的 5 个关键理解
① 为什么 Date 要重载 << 和 >>?
编译器只认识内置类型。ofs << info._date里 Date 是自定义类型,你不告诉它"年月日按什么格式输出",它就不知道怎么办。
② 为什么重载函数必须写成全局函数?
cout << d等价于operator<<(cout, d),其左操作数是 ostream 对象,而非 Date。由于成员函数的左操作数必须是所属类自身,因此该重载只能写成全局函数;若需访问私有成员,可借助友元声明。
③ 二进制读写的本质是"内存原样拷贝"
ofs.write((const char*)&info, sizeof(info)); // 把结构体所占内存一字节不差地写进文件 ifs.read((char*)&info, sizeof(info)); // 读回来重新盖到同类型结构体的内存上④ 二进制读写有两个大坑:
结构体里不能有指针(
char*、std::string内部都有指针)。存指针存的是当前进程的内存地址,下次运行地址早就失效了;有结构体对齐填充字节,而且不同机器/编译器的对齐方式、大小端可能不同,所以二进制文件跨平台有隐患。只适合"全是值类型(int、char 数组、嵌套值类型对象)且在同一环境读写"的场景。
⑤ 二进制 vs 文本怎么选?
方式 | 核心操作 | 优点 | 缺点 |
|---|---|---|---|
二进制 | write/read 整块内存拷贝 | 速度快、代码简单 | 人看不懂,含指针的结构体不能用,跨平台有隐患 |
文本 |
| 人能直接看懂、方便调试、跨平台好 | 稍慢,自定义类型要重载运算符 |
这段代码最大的亮点是统一:不管 int、char 数组还是 Date 对象,全用同一套<</>>,自定义类型用起来和内置类型一样自然——这就是运算符重载的价值。
8. stringstream:把字符串当成流来用
8.1 C 语言是怎么做类型转换的
C 语言想把整数转成字符串,常用两个函数:
#include <cstdio> int main() { int n = 123456789; char s[32]; // 方法1:itoa —— 不是标准 C,Windows/VS 上可用(VS 里叫 _itoa_s),Linux 下没有 // _itoa_s(n, s, sizeof(s), 10); // 方法2:snprintf —— 标准 C 函数,跨平台,推荐 snprintf(s, sizeof(s), "%d", n); printf("%s\n", s); return 0; }函数 | 优点 | 缺点 | 跨平台 |
|---|---|---|---|
| 整数转字符串很直接 | 不是标准 C,Linux 下没有 | ❌ |
| 标准函数,格式灵活 | 要记 | ✅ |
两个血泪教训:
缓冲区给小了会溢出。
123456789是 9 个数字加结尾的\0,共占 10 个字节——char buf[10]恰好够,写少一个字节才会溢出。所以用 snprintf 时第二个参数一定要传sizeof(缓冲区),它超了会截断,不会越界;格式符必须和类型匹配。
%f要配 double,配 int 属于未定义行为,程序可能打印 0、打印乱码,甚至崩溃,编译器也会给警告。
C++ 给出的更省心的方案就是stringstream。
8.2 stringstream 三兄弟
包含头文件<sstream>,标准库提供三个类:
类 | 用途 |
|---|---|
| 从字符串里读数据(字符串 → 变量) |
| 往字符串里写数据(变量 → 字符串) |
| 既能读又能写 |
用法和 cin/cout 一模一样:<<往里写,>>往外读。
8.3 用途一:数值和字符串互转
#include <sstream> #include <string> #include <iostream> using namespace std; int main() { int a = 12345678; string sa; stringstream ss; ss << a; // 像写 cout 一样写进去 ss >> sa; // 像读 cin 一样读出来,sa 就是 "12345678" cout << "int 转 string: " << sa << '\n'; // 复用同一个 ss:清空两件套 ss.clear(); // ① 清状态标志 ss.str(""); // ② 清底层内容 double d = 12.34; ss << d; cout << "double 转 string: " << ss.str() << '\n'; // str() 直接取出整个字符串 return 0; }运行结果:
两种取出方式的区别:
ss << 123; int n; ss >> n; // 方式①:>> 提取,内容像被 cin 读走一样被"消费" ss << 123; string s = ss.str();// 方式②:str() 直接拷贝一份底层字符串,不影响 ss 的读取位置8.4 重点中的重点:清空"两件套"
复用同一个 stringstream 时,必须分清它内部有两样独立的东西:
状态标志(eofbit / failbit 等):记录"我是不是读完了、有没有出错";
底层字符串:真正存放内容的地方。
ss.clear(); // 只清状态标志,不动内容 ss.str(""); // 只清空内容,不动状态标志为什么读过之后必须两件套?看实测(g++ C++17):
stringstream ss; string s; ss << 123; ss >> s; // "123" 被读完,流到达末尾,状态被置为 eof ss.str(""); // 只清内容,不清状态 ss << 456; // 写入直接失败!标准要求写入前流必须是"好的"状态 ss.str(); // 结果是空字符串 "",不是 "123456"注意:这是 C++ 标准规定的确定行为,不是"看编译器心情"。 还要特别纠正一个常见误解:状态是脏的情况下
ss << 456根本写不进去,内容不会变成 "123456";"追加成 123456" 只可能发生在状态正常但没清内容时。
记不住规则就按这个表来:
使用情况 | 怎么清 |
|---|---|
只用 | 状态不会脏, |
用过 | 必须 |
拿不准 / 懒得记 | 永远两件套,多写一行不吃亏 |
只用一次 | 干脆每次新建一个 |
8.5 用途二:字符串拼接
#include <sstream> #include <iostream> using namespace std; int main() { stringstream ss; ss << "first" << " " << "string,"; ss << " second string"; cout << "拼接结果: " << ss.str() << '\n'; // 拼接结果: first string, second string return 0; }写入默认是追加不是覆盖,想从头来过就str("")。
8.6 用途三:结构体的序列化与反序列化
序列化:把结构体数据按一定格式拼成字符串(方便存文件、发网络);反序列化:收到字符串后再还原回结构体。有了流和运算符重载,这两件事和普通输入输出完全一样。
#include <iostream> #include <sstream> #include <string> using namespace std; class Date { public: int _year, _month, _day; Date(int y = 0, int m = 0, int d = 0) : _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) { 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() { ChatInfo winfo = { "hjq", 135246, Date(2026, 9, 5), "晚上一起看电影吧" }; // ① 序列化:结构体 -> 字符串 ostringstream oss; oss << winfo._name << " " << winfo._id << " " << winfo._date << " " << winfo._msg; string str = oss.str(); cout << "序列化后: " << str << '\n'; // ② 模拟通过网络发送 str,对方收到后反序列化:字符串 -> 结构体 ChatInfo rInfo; istringstream iss(str); iss >> rInfo._name >> rInfo._id >> rInfo._date >> rInfo._msg; cout << "还原姓名: " << rInfo._name << " (" << rInfo._id << ")\n"; cout << "还原消息: " << rInfo._name << "> " << rInfo._msg << '\n'; return 0; }运行结果:
注意这个简单协议的局限:字段之间用空格分隔,所以消息内容里不能带空格(否则
"晚上 看电影"会被切成两个字段)。真实项目(JSON、Protobuf 等)会用更完善的格式解决这个问题,学习阶段先理解"拼字符串—拆字符串"的思想即可。顺便对比一下:ChatInfo 里有 string 成员,所以它不能用
write((char*)&info, sizeof(info))做二进制序列化(string 内部有指针),只能像这样转成文本。
8.7 stringstream 的底层原理
一句话:stringstream 内部维护了一个 string 对象。
<<写入:往这个 string 后面追加内容;
>>读取:在这个 string 里维护一个读取位置,从头按类型提取;
str():返回这个 string 的拷贝;string 会自动扩容,所以不用像 C 语言那样担心 char 数组缓冲区不够。
对比 C 风格写法:
char buf[6]; int ret = snprintf(buf, sizeof(buf), "%d", 123456789); // 缓冲区不够:snprintf 只会截断成 "12345",绝不越界(换成 sprintf 就溢出了) // 返回值 9 表示"本该写 9 个字符",ret >= 缓冲区大小就说明被截断了 // 另外还得自己记住 %d、%f 这些格式符 stringstream ss; ss << 123456789; // 底层 string 自动扩容,多大都不怕 string s = ss.str(); // 自动识别类型,没有格式符不匹配的问题8.8 使用建议
场景一:一次性转换,直接新建,用完销毁:
string IntToStr(int n) { stringstream ss; // 局部对象,不用操心清空 ss << n; return ss.str(); }场景二:频繁转换、复用同一个对象,老老实实用两件套:
stringstream ss; ss << 123; string s1 = ss.str(); ss.clear(); ss.str(""); ss << 456; string s2 = ss.str();补充:单纯做"数字 → string",C++11 以后还可以直接用
to_string();"string → 数字"用stoi/stod。它们更轻量;stringstream 的优势在于一次处理多个字段、任意类型混搭,比如 8.6 的结构体序列化。
9. 全文总结
流是对有序、连续、带方向数据的抽象,
>>负责输入、<<负责输出,控制台流、文件流、字符串流用法完全一致;cin/cout处理控制台输入输出,带缓冲;cerr不缓冲用于报错,clog缓冲用于日志;
自定义类型想直接用 cin/cout,必须重载
>>和<<,且要写成全局函数(左操作数是流对象);OJ 刷题记住三套模板:单个/多个数据用
while(cin >> x),整行用getline,给了组数用for;cin 和 getline 混用时记得ignore()吃掉残留换行;输出少用endl多用'\n';
while(cin >> x)能当条件,靠的是 istream 的operator bool:流正常为 true,读到 EOF / 出错为 false;文件流四步:定义对象 → open(判断成功)→ 用
>> <<或read/write读写 → close;ofstream 默认清空文件,二进制要加ios::binary;二进制读写是内存原样拷贝,快但结构体里不能有指针、跨平台有隐患;文本读写人能看懂、好调试;
stringstream底层就是一个自动扩容的 string,能做类型转换、字符串拼接、序列化;复用时记住
clear()清状态 +str("")清内容两件套,只用一次就直接新建对象。
一句话收尾:流的本质,就是让你用操作键盘/屏幕的同一套方式(>>、<<),去操作文件和字符串——接口统一,是 C++ IO 流最大的设计思想。