C++C++IOC++ IO 指南
小米里的大麦C++ IO 指南
1. 文件流 ifstream / ofstream / fstream(核心)
1. 一句话记住三个类
| 类名 | 作用 | 记忆口诀 |
|---|
ifstream | 读文件(input) | i = input |
ofstream | 写文件(output) | o = output |
fstream | 读 + 写 | 全能型 |
工程建议:读写分离优先,用 ifstream + ofstream,可读性更好。
2. 读文件(最常用)
1. 标准写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <fstream> #include <iostream> #include <string>
int main() { std::ifstream ifs("data.txt");
if (!ifs) { std::cerr << "错误:无法打开 data.txt" << std::endl; return 1; }
std::string line; while (std::getline(ifs, line)) { std::cout << line << std::endl; }
return 0; }
|
2. 为什么必须 while (getline(...))
1 2 3 4 5 6 7 8 9
| while (std::getline(ifs, line)) { }
while (!ifs.eof()) { }
|
3. 写文件(最常用)
1. 标准写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <fstream> #include <iostream>
int main() { std::ofstream ofs("app.log", std::ios::app);
if (!ofs) { std::cerr << "错误:无法打开 app.log" << std::endl; return 1; }
ofs << "[INFO] service started" << std::endl; ofs << "[INFO] health check passed" << std::endl;
return 0; }
|
2. 常用打开模式
| 模式 | 含义 | 场景 |
|---|
std::ios::out | 输出模式(默认) | 写文件 |
std::ios::app | 追加模式 | 日志 |
std::ios::trunc | 截断模式 | 覆盖写 |
std::ios::in | 输入模式 | 读文件 |
std::ios::binary | 二进制模式 | 二进制读写 |
组合模式示例:
1 2
| std::ofstream ofs("data.bin", std::ios::app | std::ios::binary); std::fstream fs("data.txt", std::ios::in | std::ios::out);
|
4. fstream 读写(了解即可)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <fstream> #include <iostream> #include <string>
int main() { std::fstream fs("test.txt", std::ios::in | std::ios::out); if (!fs) { std::cerr << "文件打开失败" << std::endl; return 1; }
std::string firstLine; std::getline(fs, firstLine); std::cout << "首行: " << firstLine << std::endl;
fs.clear(); fs.seekp(0, std::ios::end); fs << "\nappend by fstream";
return 0; }
|
工程建议:只有“边读边改同一个文件”才用 fstream,否则分开更稳。
5. 文件流的传参陷阱(工程易错点)
问题:想把文件流传给函数处理,编译直接报错。
1 2 3 4 5 6
| void processFile(std::ifstream file);
std::vector<std::ifstream> files; files.push_back(std::ifstream("a.txt"));
|
原因:文件流继承自 std::basic_ios,内部管理文件句柄,禁止拷贝(防止重复关闭同一个句柄)。
正确做法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include <fstream> #include <iostream> #include <string>
void processLineByRef(std::ifstream& file) { std::string line; while (std::getline(file, line)) { } }
void takeOwnership(std::ifstream&& file) { std::string line; std::getline(file, line); }
int main() { std::ifstream ifs("data.txt"); if (!ifs) return 1;
processLineByRef(ifs);
takeOwnership(std::ifstream("other.txt"));
return 0; }
|
工程建议:
- 优先用引用
std::ifstream&,简单直观。 - 需要转移所有权时用移动语义
&&。 - 别把文件流放进容器(
vector<ifstream>),管理麻烦且容易出错。
2. 字符串流 stringstream(核心)
1. 三个类怎么选
| 类名 | 作用 | 常见用途 |
|---|
istringstream | 字符串 -> 数据 | 解析配置、协议 |
ostringstream | 数据 -> 字符串 | 拼日志、拼 SQL |
stringstream | 读写都可 | 临时处理(工程里少用) |
2. ostringstream:拼接字符串
1. 标准写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <iostream> #include <sstream> #include <string>
int main() { std::ostringstream oss;
std::string user = "alice"; int code = 200; double cost = 12.5;
oss << "user=" << user << ", code=" << code << ", cost=" << cost;
std::string line = oss.str(); std::cout << line << std::endl; return 0; }
|
2. 工程例子:日志行构造
1 2 3 4 5 6 7 8 9
| #include <sstream> #include <string>
std::string makeLogLine(const std::string& level, int requestId, const std::string& msg) { std::ostringstream oss; oss << "[" << level << "] req_id=" << requestId << " msg=\"" << msg << "\""; return oss.str(); }
|
3. istringstream:解析字符串
1. 标准写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <iostream> #include <sstream> #include <string>
int main() { std::string raw = "bob 18 99.5"; std::istringstream iss(raw);
std::string name; int age = 0; double score = 0;
if (!(iss >> name >> age >> score)) { std::cerr << "解析失败" << std::endl; return 1; }
std::cout << name << " " << age << " " << score << std::endl; return 0; }
|
2. 工程例子:解析命令
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <sstream> #include <string>
bool parseCommand(const std::string& data, std::string& cmd, std::string& arg1, std::string& arg2) { std::istringstream iss(data); if (!(iss >> cmd >> arg1 >> arg2)) { return false; } return true; }
|
4. stringstream(算法题中会常用一点点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <iostream> #include <sstream> #include <string>
int main() { std::stringstream ss; ss << "Hello " << 42;
std::string word; int num = 0; ss >> word >> num;
std::cout << word << " " << num << std::endl; return 0; }
|
3. getline 详解(核心)
快速记忆:getline(字符串流, 变量, 分隔符);
1. 两种常见用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <fstream> #include <iostream> #include <string>
int main() { std::ifstream ifs("data.txt"); std::string line;
if (std::getline(ifs, line)) { std::cout << line << std::endl; }
std::string input; std::getline(std::cin, input);
return 0; }
|
2. cin >> 和 getline 混用大坑
1. 正确模板(直接用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <iostream> #include <limits> #include <string>
int main() { int age = 0; std::string name;
std::cin >> age; std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); std::getline(std::cin, name);
std::cout << "age=" << age << ", name=" << name << std::endl; return 0; }
|
口诀:cin >> 后接 getline,中间必须 ignore。
2. ignore(max, '\n') 和 ignore() 的联系与区别
联系:两者本质都是“丢弃输入缓冲区中的字符”,避免后续读取被残留内容干扰。
区别:
std::cin.ignore(); 只丢弃 1 个字符(默认参数就是 ignore(1))。std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); 会持续丢弃,直到遇到换行符 \n(或达到上限)。
1 2 3 4 5 6 7
| std::cin >> age; std::cin.ignore();
std::cin >> age; std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
|
工程建议:默认使用带 max(), '\n' 的版本,容错更好。
3. 指定分隔符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <iostream> #include <sstream> #include <string>
int main() { std::istringstream iss("apple,banana,cherry"); std::string item;
while (std::getline(iss, item, ',')) { std::cout << item << std::endl; }
return 0; }
|
4. 二进制读写(重要)
1. 必须知道的点
- 文本模式下,Windows 可能做换行转换。
- 二进制必须加
std::ios::binary。 - 只直接写 POD/平凡类型,不写指针,不直接写含
std::string 的结构体。
2. 写二进制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <fstream> #include <iostream>
int main() { std::ofstream ofs("data.bin", std::ios::binary); if (!ofs) { std::cerr << "open failed" << std::endl; return 1; }
int x = 100; double y = 3.14159;
ofs.write(reinterpret_cast<const char*>(&x), sizeof(x)); ofs.write(reinterpret_cast<const char*>(&y), sizeof(y));
return 0; }
|
3. 读二进制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <fstream> #include <iostream>
int main() { std::ifstream ifs("data.bin", std::ios::binary); if (!ifs) { std::cerr << "open failed" << std::endl; return 1; }
int x = 0; double y = 0;
if (!ifs.read(reinterpret_cast<char*>(&x), sizeof(x))) { std::cerr << "read x failed" << std::endl; return 1; }
if (!ifs.read(reinterpret_cast<char*>(&y), sizeof(y))) { std::cerr << "read y failed" << std::endl; return 1; }
std::cout << "x=" << x << ", y=" << y << std::endl; return 0; }
|
4. 工程增强:带文件头的二进制格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <cstdint> #include <fstream> #include <iostream>
struct FileHeader { std::uint32_t magic; std::uint16_t version; std::uint16_t reserved; };
int main() { const FileHeader h{0x43494F31, 1, 0};
std::ofstream ofs("records.bin", std::ios::binary); if (!ofs) { return 1; }
ofs.write(reinterpret_cast<const char*>(&h), sizeof(h));
return 0; }
|
收益:后续升级格式时,可先读 magic/version,快速做兼容判断。
5. 常见问题和工程建议
1. 路径问题(Windows)
1 2 3 4 5 6 7 8 9 10
| #include <fstream>
int main() { std::ifstream a("C:\\Users\\name\\file.txt"); std::ifstream b(R"(C:\Users\name\file.txt)"); std::ifstream c("./data/file.txt"); return 0; }
|
2. 文件定位(seekg/tellg)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <fstream> #include <iostream>
int main() { std::ifstream ifs("data.txt", std::ios::binary); if (!ifs) { return 1; }
std::streampos pos = ifs.tellg(); std::cout << "pos=" << pos << std::endl;
ifs.seekg(0, std::ios::end); std::streampos size = ifs.tellg(); std::cout << "size=" << size << std::endl;
return 0; }
|
3. 缓冲区刷新
1 2 3 4 5 6 7 8 9 10
| #include <fstream>
int main() { std::ofstream ofs("log.txt", std::ios::app); ofs << "important line"; ofs.flush(); return 0; }
|
4. 性能优化(只在高频 IO 场景启用)
sync_with_stdio(false) 到底是啥?
简单说:C++ 默认会和 C 的 stdio 同步,保证 printf 和 cout 不乱序,关掉之后:
- cin/cout 不再和 stdio 共享缓冲
- 性能会明显提高
本质就是 减少锁 + 减少同步开销,刷题常开,工程基本不用。
1 2 3 4 5 6 7 8
| #include <iostream>
int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); return 0; }
|
注意:启用后不要混用 printf/scanf 和 cin/cout。
5. 工程实战建议(强烈推荐)
- 日志写入优先
append,避免误覆盖历史日志。 - 关键数据落盘用“临时文件 + rename”做原子替换,避免写一半崩溃导致坏文件。
- 读取外部输入(文件/网络)时,每一步都校验返回值,不要假设一定成功。
- 跨平台二进制格式务必定义字节序和版本号。
- 文本协议优先 UTF-8,减少编码问题。
6. std::cerr 和 std::clog 的使用建议
std::cerr:偏“错误输出”,适合立即关注的错误信息。std::clog:偏“常规日志输出”,适合运行过程中的信息日志。
1 2 3 4 5 6 7 8
| #include <iostream>
int main() { std::clog << "[INFO] server started" << std::endl; std::cerr << "[ERROR] config load failed" << std::endl; return 0; }
|
工程建议:业务日志用 clog,错误路径用 cerr,便于后续日志分流。顺便简单提一嘴(需要一些其他知识才能理解):
| 流 | fd(文件描述符) | 缓冲 | 用途 |
|---|
| cout | 1 | 有 | 正常输出 |
cerr(常搭配 strerror 把错误码变成人能看懂的错误信息) | 2 | 无 | 错误输出 |
| clog(少用,通常会选择第三方日志库 spdlog、glog) | 2 | 有 | 日志 |
cerr 不缓冲的原因:程序崩溃时,你至少能看到错误信息。如果用 cout,可能还在缓冲区里,来不及刷就挂了。
6. 速查表
1. 头文件
1 2 3 4 5
| #include <fstream> #include <sstream> #include <iostream> #include <string> #include <limits>
|
2. 最常用模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| std::ifstream ifs("file.txt"); if (!ifs) { return 1; } std::string line; while (std::getline(ifs, line)) { }
std::ofstream ofs("file.txt", std::ios::app); if (!ofs) { return 1; }
ofs << "content" << std::endl;
int x = 0; std::string s; std::cin >> x;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); std::getline(std::cin, s);
|
7. 小结
90% 工程场景只要熟练掌握这 5 条:
ifstream + getline 逐行读取。ofstream + ios::app 追加日志。ostringstream 安全拼接字符串。istringstream 解析结构化文本。cin >> 后接 getline 必加 ignore。
这 5 条能覆盖绝大多数日常开发中的 IO 问题。简单提一嘴,知道名字就行,用到再查:
seekg / seekp → 文件定位tellg / tellp → 获取位置flush() → 刷缓冲std::hex / std::dec → 进制setw / setfill → 格式控制sync_with_stdio(false) → 提速