Input and Output (I/O)¶
输入和输出功能并未定义为核心 C++ 语言的一部分,而是通过 C++ 标准库提供(因此驻留在 std 命名空间中)。
- 输入流用于保存来自数据生成器(例如键盘、文件或网络)的输入
- 例如,用户可能按下键盘上的某个键,而程序当前不需要任何输入。数据不会忽略用户的按键,而是被放入输入流中,在那里它将等待,直到程序准备好
- 输出流用于保存特定数据使用者的输出
- 例如监视器、文件或打印机。将数据写入输出设备时,该设备可能尚未准备好接受该数据
- 例如,当程序将数据写入其输出流时,打印机可能仍在预热。数据将位于输出流中,直到打印机开始使用它
C++ 中的标准流
标准流是由计算机程序的环境提供给计算机程序的预连接流。 C++ 附带了四个预定义的标准流对象:
流名称 | 标准 | 对象类别 | 缓冲区 |
---|---|---|---|
cin | 标准输入 (keyboard) | istream | 有缓冲输出 |
cout | 标准输出 (monitor) | ostream | 有缓冲输出 |
cerr | 标准错误 (monitor) | ostream | 无缓冲输出 |
clog | 标准日志 (monitor) | ostream | 有缓冲输出 |
非缓冲输出通常立即处理,而缓冲输出通常作为块存储和写出。因为 clog 不经常使用,所以它经常被从标准流列表中省略。
I/O and string stream¶
这一部分涉及到输入和输出的基本概念,包括换行符/制表符/... 以及使用get()
/ getline()
/ ...
太细节了,建议随用随查,不需要死记硬背
Basic file I/O¶
C++ 中的文件 I/O 的工作方式与普通 I/O 非常相似。
C++ 中有 3 个基本文件 I/O 类:
ifstream
(派生自istream
)文件输入ofstream
(派生自ostream
)文件输出fstream
(派生自iostream
)文件输入/输出
要使用文件 I/O 类,您需要包含 fstream
标头
如何对一个文件进行I/O操作(打开一个文件进行读取和/或写入):
- 实例化适当文件 I/O 类的对象,并将文件名作为参数即可
- 使用插入 (
<<
) 或提取 (>>
) 运算符向文件写入或读取数据 - 完成后,有多种方法可以关闭文件:显式调用
close()
函数,或者只是让文件 I/O 变量超出范围(文件 I/O 类析构函数将为您关闭文件)
文件输出¶
C++ | |
---|---|
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 |
|
如果查看项目目录,您应该会看到一个名为 Sample.txt
的文件。如果您使用文本编辑器打开它,您将看到它包含了我们写入该文件的两行
文件输入¶
现在,我们将获取在上一个示例中编写的文件并将其从磁盘读回。
注意,如果到达文件末尾 (EOF
),ifstream
将返回 0
。一般来说,我们将利用这一事实来确定要阅读多少内容。
1)初级示范:
C++ | |
---|---|
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 |
|
输出结果
Bash | |
---|---|
1 2 3 4 5 6 7 8 |
|
很显然这个结果非常不伦不类,按常理我们想把那两个句子直接输出,且泾渭分明。
出现上述的奇怪现象是因为:提取运算符会在空格处中断。为了读取整行,我们必须使用 getline()
函数。
2)改进示范:
C++ | |
---|---|
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 |
|
本质上就是把流读入这一步骤进行了修正:
C++ | |
---|---|
1 2 |
|
现在的输出
Bash | |
---|---|
1 2 |
|
缓冲输出¶
缓冲是什么¶
C++ 中的输出可以被缓冲。这意味着输出到文件流的任何内容可能不会立即写入磁盘。相反,多个输出操作可以批量处理并一起处理。
这样做主要是出于性能原因。当将缓冲区中的剩余内容写入磁盘时,这称为刷新缓冲区。
刷新缓冲区的一种方法是关闭文件 —— 缓冲区的内容将被刷新到磁盘,然后文件将被关闭。
为什么要刷新缓冲区¶
缓冲通常不是问题,但在某些情况下,如果不小心,它可能会导致并发症。这种情况的罪魁祸首是当缓冲区中有数据时,程序立即终止(通过崩溃或调用 exit()
)。
在这些情况下,文件流类的析构函数不会被执行,这意味着文件永远不会关闭,这意味着缓冲区永远不会刷新。
在这种情况下,缓冲区中的数据不会写入磁盘,因此会永远丢失。这就是为什么 在调用 exit()
之前显式关闭所有打开的文件始终是一个好主意。
如何操作(刷新缓冲区)¶
可以使用 ostream::flush()
函数手动刷新缓冲区或将 std::flush
发送到输出流。这些方法中的任何一种都有助于确保缓冲区的内容立即写入磁盘,以防程序崩溃。
std::endl
和 \n
一个有趣的注释是:
std::endl;
自带刷新输出流的属性\n
不会自动刷新输出流
因此,在执行刷新成本高昂的缓冲 I/O(例如写入文件)时,过度使用 std::endl
(导致不必要的缓冲区刷新)可能会对性能产生影响。
一般来说,注重性能的程序员通常会使用 \n
而不是 std::endl
在输出流中插入换行符,以避免不必要的缓冲区刷新。
例1:不刷新缓冲区,数据会丢失
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
命令行输出:
Bash | |
---|---|
1 2 3 4 5 |
|
文件输出:
Text Only | |
---|---|
1 |
|
原因是:
- 写入进
example.txt
的ofstream
是用\n
的,因此它需要手动缓冲才能将缓冲区积累的内容输出到文件里 - 按常理,如果程序没有故障,它会在析构函数里自动执行(hidden)缓冲函数,将缓冲区的内容输出到文件里
- 但是,程序在
std::exit(0)
时,程序出现“故障”,直接终止!不会执行析构函数,因此缓冲区的内容直接消失
现在我们注意到两个问题:
- 上述问题如何修复?如何将缓冲区的输出尽可能在程序崩溃前导入到文件中?
- 如果我将
std::cout << "Writing line " << i << std::endl;
改成std::cout << "Writing line " << i << '\n';
会如何?- 会不会在CLI中也出现“啥也没有”的现象?
例2:修复上述问题,手动刷新缓冲区
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
CLI输出:
Bash | |
---|---|
1 2 3 4 5 |
|
文件输出:
Text Only | |
---|---|
1 2 3 |
|
这次做的改进是在line3手动将当前缓冲区内的内容导入文件,然而line4和5的内容并没有被写入文件,这是因为程序在line5处被终止了,没有执行到文件关闭的操作,因此文件没有被正确关闭,文件的内容也没有被正确写入文件
不过这个例子已经很好的说明了“手动管理缓冲区”的方式
例3: 考察cout那行将 std::endl;
变成 \n
会怎样
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
CLI输出:
C++ | |
---|---|
1 2 3 4 5 |
|
这个例3的任何结果和例1的完全一致
我都将行结尾的 std:endl;
改成 \n
,为什么CLI中并没出现“啥也没有”的现象?
文件模式¶
现在我们会思考:如果我们尝试写入已经存在的文件会发生什么?
上述的例子如果依次执行,会发现:每次运行程序时,原始文件都被完全覆盖。相反,如果我们想将更多数据附加到文件末尾怎么办?
事实证明,文件流构造函数采用可选的第二个参数,该参数允许您指定有关如何打开文件的信息。该参数称为 mode
,它接受的有效标志存在于 ios
类中。
Ios file mode | Meaning |
---|---|
app | Opens the file in append mode |
ate | Seeks to the end of the file before reading/writing |
binary | Opens the file in binary mode (instead of text mode) |
in | Opens the file in read mode (default for ifstream) |
out | Opens the file in write mode (default for ofstream) |
trunc | Erases the file if it already exists |
例1:将另外两行附加到我们之前创建的 Sample.txt
文件中
C++ | |
---|---|
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 |
|
现在,如果我们看一下 Sample.txt
(使用上述示例程序之一打印其内容,或将其加载到文本编辑器中):
Text Only | |
---|---|
1 2 3 4 |
|
例2: 使用 open() 显式打开文件
就像可以使用 close()
显式关闭文件流一样,也可以使用 open()
显式打开文件流。
open()
的工作方式与文件流构造函数类似 —— 需要一个文件名和一个可选的文件模式。
格式:
C++ | |
---|---|
1 |
|
例子:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
more about open()
here