1 C++缓冲区
在学习标准IO库之前,我们先了解C++中缓冲区的使用。关于操作系统中缓冲区的学习与理解,请查看操作系统-缓存管理。
1.1 什么是缓冲区
缓冲区又称缓存,是内存空间的一部分。系统在内存中预留一部分用于缓冲输入输出的数据,缓冲区根据用途分为输入缓冲区和输出缓冲区两种。
1.2 为什么要引入缓冲区
C++中缓冲区的使用主要有以下几个目的:
- 缓和CPU与I/O设备间速度不匹配的矛盾。
- 减少对CPU的中断频率,放宽对CPU中断响应时间的限制。
- 提高CPU和I/O设备之间的并行性,让CPU可以处理其他工作
1.3 缓冲区类型
C++缓冲区分为全缓冲、行缓冲和无缓冲三种。
全缓冲
在此类缓冲中,当填满标准I/O缓冲后,才进行实际的I/O操作。典型的代表是磁盘文件的读写。行缓冲
在此类缓冲中,当在输入和输出中遇到换行符时,执行真正的I/O操作。输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是键盘输入数据。无缓冲
标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。
1.4 缓冲区的刷新
下列情况将导致缓冲区刷新
程序正常结束
作为main
函数return
的一部分,缓冲刷新被执行。缓冲区满
需要刷新缓冲,新数据才可写入。使用操作符控制缓冲区刷新
endl
:输出内容加换行,然后刷新
ends
:输出内容加空字符,然后刷新
flush
:输出内容后直接刷新用
unitbuf
设置流的内部状态
cout << unitbuf; //所有输出操作后会立即刷新缓冲区
cout << <<nounitbuf; //回到正常的缓冲方式
- 一个输出流被关联到另一个流上
使用tie
函数可以将两个流关联在一起。当读写被关联的流,被关联的流会刷新。cin
和cerr
都关联到cout
,因此读cin
或者cerr
都会导致cout
的缓冲区更新。我们可以将istream
关联到ostream
上,也可将ostream
关联到另一个ostream
上。不建议将cin
关联到cerr
上。
警告:如果程序崩溃了,则不会刷新缓冲区。
1.5 行缓冲演示
getchar()
是行缓冲的,第一次调用getchar()
函数,会让程序使用者输入一行字符并直至按下回车键函数才返回。此时用户输入的字符和回车符都存放在行缓冲区。再次调用getchar()
函数,会逐步输出行缓冲区的内容。
int main(){
char c;
c = getchar();
cout << c << endl;
while ((c = getchar())!= '\n'){
cout << c << endl;
}
}
/*一次性输入 a b c
输出
a
b
c
*/
2 IO类
iostream
中的类型和对象都是操作char
数据的。默认情况下,这些对象都是关联到用户控制台的窗口的。
为了支持不同种类的IO处理操作,在istream
和ostream
之外,标准库还定义了一些其他的IO类型。iostream
定义了用于读写流的基本类型,fstream
定义了读写命名文件的类型,sstream
定义了读写内存string
对象的类型。
为了支持宽字符的语言,标准库定义了一组类型和对象来操作wchar_t
类型的数据。宽字符版本的类型和函数的名字以一个w
开始,与其普通char
版本定义在同一个头文件中。
2.1 IO对象无拷贝
由于不能拷贝IO对象,因此我们不能将形参或返回类型设置为流类型。进行IO的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const
。
2.2 条件状态
IO类所定义的一些函数和与机器无关的iostate
标志,可以帮助我们访问和操作流的条件状态。
1) 条件状态说明
上述任何一个IO对象在任意时刻都有一种状态,iostate
代表状态的枚举,badbit
,failbit
,eofbit
,goodbit
是iostate
的一个具体值。
操作good
在所有错误位均未置位的情况下返回true
。而badbit
、failbit
和eofbit
任一被置位,检测流的状态条件会失败,此时fail
操作返回true
。bad
、fail
和eof
只有在相应错误为被置位时才返回true
。
一个流一旦发生错误,其上后续的IO操作都会失败。只有当一个流处于无错状态时,我们才可对它读写数据。通常用以下语句检查一个流在使用前的状态:
while (cin >> word)
//输入操作成功,流保持有效,条件状态为真
** 2) 条件状态管理**
流对象的rdstate
成员返回一个iostate
值,对应流的当前状态。setstate
操作将给定条件为置位,表示发生了对应错误。
clear
成员是一个重载函数,提供两个版本:clear
无参数版本将复位所有错误标志位,带参数版本提供一个新的iostate
状态。
2.3 管理输出缓冲
关于缓冲区内容本章开头有介绍。
3 文件输入输出
ifstream
从一个给定的文件读取数据,ofstream
向一个给定文件写入数据,fstream
可以读写给定文件。
可以用getline
函数从一个ifstream
读取数据,除继承自iostream
类型中行为外,fsteam
还提供了一下成员来管理与流关联的文件。
3.1 使用文件流
当需要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来。文件名可以用string类型,也可以用传统C风格字符串。
1) 成员函数open和close
如果我们定义了一个空文件流对象,可以随后用open
将它与文件关联起来。如果调用open
失败。failbit
将会被置位。因此检测open
是否成功是一个好习惯。
使用close
函数可关闭文件流所绑定的文件。
string text;
ifstream fstm;
fstm.open("test.txt");
if (fstm){
getline(fstm, text);
cout << text;
fstm.close();
}
else{
cout << "failed";
}
\\如果顺利打开test.txt,条件为真,则从中读取内容,输出并关闭文件
\\打开失败则输出failed
2) 自动构造和析构
当一个fstream
离开其作用域,close
会自动被调用,与之关联的文件会自动关闭。
3.2 文件模式
在打开文件时,无论是调用 open
还是以文件名作为流初始化的一部分,都需指定文件模式(file mode
)。
只可以对
ofstream
或fstream
对象设定out模式只可以对
ifstream
或fstream
对象设定in模式只有打开
out
模式,才可设定trunc
模式只要
trunc
模式未被设定,就可设定app
模式。在app
模式下,即使没有显示指定out
模式,文件也总以输出模式被打开默认情况下,以
out
模式打开文件,在位指定trunc
时,也将被截断。为保留其中内容,可以使用app模式或者in模式(文件同时读写)ate
和binary
模式可用于任何类型的文件流对象且可以与其他模式组合使用。ate
模式只在打开时有效:文件打开后将定位在文件尾。以binary
模式打开的流则将文件以字节序列的形式处理,而不解释流中的字符。
默认情况下,当我们打开一个ofstream
时,文件内容会被丢弃。对于用 ofstream
打开的文件,要保存文件中存在的数据,唯二方法是显式地指定app
模式或 in
模式打开:
4 string流
sstream
头文件中定义了以下三个类型来支持内存IO。
istringstream
从string
中读取数据,ostringstream
向string
中写入数据,而stringstream
即可从string
读数据也可以向string
写数据。出了从iostream
中继承的操作外,sstream
中还支持以下操作:
4.1 sstream的使用
string line, word;
while (getline(cin, line)) {
istringstream stream(line);
while (stream >> word){
// do per-word processing
}
}
string
中数据全部读出后,同样会触发“文件结束”信号,此时while
循环条件为false
。
4.2 转换或格式化
sstream
类似于C语言风格中ssprintf
,可以方便地构造各种风格的string
。
string format_message = "cp 3 lbj 23";
istringstream input(format_message);
ostringstream output;
int val1 = 0, val2 = 0;
string str1, str2;
input >> str1 >> val1 >> str2 >> val2;
output << val1 << " " << val2 << endl;
cout << output.str();