IO 库

C++ 的 IO(输入输出)基于流(stream)

可以把流理解成:
数据流动的通道

  • 输入流:数据流入程序
  • 输出流:数据从程序流出

常见对象:

  • cin:标准输入
  • cout:标准输出
  • cerr:标准错误输出

常见操作:

  • >>:读入数据
  • <<:输出数据
  • getline:读取一整行

IO 库的常见组件

  • istream:输入流类型
  • ostream:输出流类型
  • iostream:既能输入也能输出的流类型
  • cinistream 对象,从标准输入读
  • coutostream 对象,向标准输出写
  • cerrostream 对象,通常输出错误信息
  • >>:从输入流提取数据
  • <<:向输出流插入数据
  • getline:从输入流读取一行到 string

IO 库类型和头文件

头文件 主要类型
iostream istreamostreamiostream
fstream ifstreamofstreamfstream
sstream istringstreamostringstreamstringstream

说明:

  • iostream:控制台 IO
  • fstream:文件 IO
  • sstream:字符串 IO

宽字符版本

很多 IO 类型还有宽字符版本,名字前加 w

  • wcin
  • wcout
  • wcerr
  • wifstream
  • wostringstream

基础阶段一般先掌握普通字符版本即可。

IO 对象不能拷贝

IO 对象不能拷贝、不能赋值

因此:

  • 不能按值传递流对象
  • 不能返回流对象(按值)
  • 通常要用引用传递和返回

例如:

1
2
3
4
5
istream& read(istream &is) {
int x;
is >> x;
return is;
}

注意:

  • 因为读写流会改变流状态,所以通常不能是 const 引用

标准输入输出

cincoutcerr

1
2
3
4
int x;
cin >> x;
cout << x << endl;
cerr << "error" << endl;

说明:

  • cin:读数据
  • cout:正常输出
  • cerr:错误信息,通常不经过普通缓冲或更适合及时显示错误

>> 运算符

>> 从输入流中读取数据,并跳过开头的空白字符(空格、换行、Tab 等)。

1
2
3
int n;
string s;
cin >> n >> s;

特点:

  • 以空白作为分隔
  • string 时,遇到空格就停止

例如输入:

1
hello world
1
2
string s;
cin >> s;

结果 s == "hello"

getline

getline 用于读取一整行,包括空格,直到换行符为止。

1
2
string line;
getline(cin, line);

例如输入:

1
hello world

line == "hello world"

>>getline 混用问题

这是常见易错点。

1
2
3
4
5
int n;
string line;

cin >> n;
getline(cin, line);

getline 可能读到的是上一次输入留下的换行符,导致 line 为空串。

解决方法:

1
2
3
cin >> n;
cin.ignore();
getline(cin, line);

或先处理掉换行符再读整行。

IO 条件状态

流对象会维护自己的状态,用来表示当前 IO 是否成功。

常见状态位:

状态 含义
badbit 流严重错误,流已损坏
failbit 本次 IO 操作失败
eofbit 到达文件结束
goodbit 流状态正常,值为 0

常用状态检查函数

函数 含义
s.eof() 到达文件末尾返回 true
s.fail() 发生失败或严重错误返回 true
s.bad() 严重错误返回 true
s.good() 流状态完全正常返回 true
s.rdstate() 返回当前状态
s.clear() 清除所有错误状态
s.clear(flags) 将状态设为指定值
s.setstate(flags) 添加某些状态位

流作为条件

最常见写法:

1
2
3
4
int x;
while (cin >> x) {
cout << x << endl;
}

原因:

  • cin >> x 成功时,流处于可用状态,条件为真
  • 输入失败时,条件为假,循环结束

这也是最推荐的输入循环方式。

输入失败后的恢复

例如输入类型不匹配:

1
2
int x;
cin >> x;

如果用户输入 "abc",则读取失败,流进入失败状态。
这时后续输入通常也会继续失败,除非恢复状态:

1
2
cin.clear();              // 清除错误状态
cin.ignore(1000, '\n'); // 丢弃错误输入

管理输出缓冲

输出流通常带有缓冲区
程序写入的数据可能先放进缓冲区,不会立刻显示或写到设备。

刷新缓冲区

常见刷新方式:

1
2
3
cout << endl;   // 输出换行并刷新缓冲区
cout << flush; // 只刷新,不换行
cout << ends; // 插入空字符并刷新(较少用)

说明:

  • endl = 换行 + 刷新
  • 如果只想换行,不一定要用 endl
  • 频繁刷新会影响性能

例如:

1
2
cout << "hello\n";   // 只换行,不保证立刻刷新
cout << "hello" << endl; // 换行并刷新

unitbuf

可以让流在每次输出后都自动刷新:

1
cout << unitbuf;

取消:

1
cout << nounitbuf;

注意

如果程序异常崩溃,缓冲区中的内容可能来不及刷新,因此有些输出可能看不到。

文件输入输出

fstream 头文件支持文件 IO:

  • ifstream:读文件
  • ofstream:写文件
  • fstream:读写文件

文件流的基本操作

创建并打开文件

1
2
ifstream in("input.txt");
ofstream out("output.txt");

也可以先创建,再打开:

1
2
ifstream in;
in.open("input.txt");

关闭文件

1
2
in.close();
out.close();

关闭后,该流不再和文件绑定。

判断是否成功打开

1
2
3
4
ifstream in("input.txt");
if (!in) {
cerr << "open failed\n";
}

也可以:

1
2
3
if (in.is_open()) {
// 成功打开
}

fstream 特有操作

操作 含义
fstream f; 创建未绑定文件的流
fstream f(s); 打开名为 s 的文件
fstream f(s, mode); 按指定模式打开文件
f.open(s); 打开文件
f.close(); 关闭文件
f.is_open(); 判断文件是否已打开

说明:

  • s 可以是 string 或 C 风格字符串
  • 文件流构造函数通常是 explicit

文件打开模式

常见文件模式:

模式 含义
in 读方式打开
out 写方式打开
app 每次写前定位到文件末尾
ate 打开后立即定位到文件末尾
trunc 截断文件原内容
binary 二进制方式打开

常见组合

1
2
3
4
ofstream out("a.txt");                       // 默认写
ofstream out("a.txt", ios::out | ios::app); // 追加写
ifstream in("a.txt", ios::in); // 读
fstream f("a.txt", ios::in | ios::out); // 读写

appate 的区别

  • app:每次写之前都跳到文件末尾
  • ate:打开文件后先到末尾,但之后仍可移动位置

复习时先记:

想“追加写入”,常用 app

out 的默认行为

对于 ofstream,若只用 out 打开文件,通常会清空原文件内容(相当于带 trunc)。

如果不想清空、想追加,应该使用:

1
ios::app

文件读写示例

逐个单词读取

1
2
3
4
5
6
ifstream in("input.txt");
string word;

while (in >> word) {
cout << word << endl;
}

按行读取

1
2
3
4
5
6
ifstream in("input.txt");
string line;

while (getline(in, line)) {
cout << line << endl;
}

写入文件

1
2
3
ofstream out("output.txt");
out << "hello\n";
out << 123 << endl;

string 流

sstream 头文件定义了在字符串上进行 IO 的类型:

  • istringstream:从 string
  • ostringstream:向 string
  • stringstream:可读可写

它本质上是把字符串当作流来处理。

stringstream 特有操作

操作 含义
stringstream ss; 创建空流
stringstream ss(s); 用字符串 s 初始化流
ss.str() 返回流中的字符串副本
ss.str(s) 用字符串 s 替换流内容

istringstream 的常见用途

拆分一行中的数据

1
2
3
4
5
string line = "10 20 30";
istringstream iss(line);

int a, b, c;
iss >> a >> b >> c;

适合:

  • 先按行读入
  • 再对该行内容进一步解析

getline 配合

这是非常常见的模式:

1
2
3
4
5
6
7
8
string line;
while (getline(cin, line)) {
istringstream record(line);
string word;
while (record >> word) {
cout << word << endl;
}
}

作用:

  • 外层按行读
  • 内层按单词拆分

ostringstream 的常见用途

把各种类型的数据拼成一个字符串:

1
2
3
ostringstream oss;
oss << "name: " << "Tom" << ", age: " << 18;
string s = oss.str();

适合:

  • 格式化字符串
  • 拼接输出内容
  • 构造日志信息

三类 IO 的区别

类型 用途
iostream 控制台输入输出
fstream 文件输入输出
sstream 字符串输入输出

常见输入模式总结

读到 EOF

1
2
3
4
int x;
while (cin >> x) {
// 处理 x
}

按行读取

1
2
3
4
string line;
while (getline(cin, line)) {
// 处理一整行
}

先按行读,再按词拆

1
2
3
4
5
6
7
8
string line;
while (getline(cin, line)) {
istringstream iss(line);
string word;
while (iss >> word) {
// 处理 word
}
}

易错点总结

>> 读取字符串时遇到空白就停止

1
cin >> s;

不会读入整行。

读取整行要用 getline

1
getline(cin, line);

>>getline 混用时要处理残留换行符

常用:

1
cin.ignore();

流对象不能拷贝

函数参数和返回值一般用引用。

文件流打开后要先检查是否成功

1
if (!in) { ... }

ofstream 默认可能清空原文件

若要追加,使用:

1
ios::app

endl 会刷新缓冲区

如果只想换行,用 '\n' 更轻量。

输入失败后要 clear() 才能继续使用流

必要时还要配合 ignore() 丢弃错误输入。

小结

C++ 标准库把 IO 统一成“流”的形式:

  • iostream:处理标准输入输出
  • fstream:处理文件
  • sstream:处理字符串

最常见的复习重点:

  • cin / cout / cerr
  • >> / << / getline
  • 流状态检查
  • 文件流打开与模式
  • istringstream 解析字符串