C++ IO库 详解
常用使用的IO库以及接口
- istream (输入流) 类型,提供输入操作。
- ostream (输出流) 类型,提供输出操作。
- cin,一个 istream 对象,从标准输入读取数据。
- cout,一个 ostream 对象,向标准输出写入数据。
- cerr,一个 ostream 对象,通常用于输出程序错误信息,写入到标准错误。
- >> 运算符,用来从一个 istream 对象读取输入数据。
- << 运算符,用来向一个 ostream 对象写入输出数据。
- getline 函数,从一个给定的 istream 对象读取一行数据,写入到给定的 string 对象中。
IO类
IO库类型和头文件 | |
---|---|
头文件 | 类型 |
iostream | istream,wistream 从流读取数据 |
ostream,wostream 向流写入数据 | |
iostream,wiostream 读写流 | |
fstream | ifstream,wifstream 从文件读取数据 |
ofstream,wofstream 向文件写入数据 | |
fstream,wfstream 读写文件 | |
sstream | istringstream,wistringstream 从string读取数据 |
ostringtream,wostringstream 向string写入数据 | |
stringstream,wstringstream 读写string |
IO 类型之间的关系
设备类型和字符大小都不会影响IO操作,我们可以使用 >> 读取数据,不用关系是从控制台窗口,一个磁盘文件还是一个 string 对象。也不用关心使用char对象去存储还是使用wchar_t对象去存储。
因为有继承机制和模板,我们可以忽略不同流之间的差异。
IO对象无拷贝或赋值
ofstream out1, out2;
out1 = out2; //错误: 不能对流对象赋值
ofstream print(out1); //错误:不能初始化 ofstream 参数
out2 = print(out2); //错误:不能拷贝刘对象
由于不能拷贝流对象,不能将形参和返回类型设置为流类型。
进行IO操作的函数通常以引用方式传递和返回流。
读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。
条件状态
有5各个状态标志位:
- strm::iostate ------ iostate 是一种机器相关的类型,提供表达条件状态的完整功能。
- strm::badbit ------ strm::badbit 用来标识流已经崩溃。
- strm::failbit ------ strm::failbit 用来标识一个IO操作失败了。
- strm::eofbit ------ strm::eofbit 用来指出流到达了文件结束
- strm::goodbit ------ strm:goodbit 用来指出流未处于错误状态,此值保证为0。
查询流状态的接口:
- s.eof() 若流s的eofbit置位,返回true
- s.fail() 若流s的failbit 或badbit 置位,返回true
- s.bad() 若流s的badbit置位,返回true
- s.good() 若流s处于有效状态 返回true
- s.clear() 将流s中所有的条件状态位复位,将流的状态设置为有效,返回void
- s.clear(flags) 根据给定的flags标志位,将流s的对应条件状态位复位,flags的类型为strm::iostate,返回void
- s.setstate(flags) 根据给定的flags 标志位,将流s中对应的条件状态位置位,flags类型位strm::iostate 返回void
- s.rdstate() 返回流s当前的条件状态,返回值类型为 strm::iostate
举个IO错误的例子
int ival;
cin >> ival;
如果我们在标准输入上 键入abc这样的字符,读操作就会失败。因为输入运算符期望得到一个int类型的字符,实际上却得到一个字符。
举个IO状态置位
不带参数的clear 清除(复位)所有的错位标志位,执行完clear再执行good时,good会返回true。
auto old_state = cin.rdstate(); //记住cin的当前状态
cin.clear(); //复位cin的所有标志位,使其变成有效状态
process_input(cin); //使用cin
cin.setstate(old_state); //将cin置为原来的状态
带参数的clear 接受一个iostate 值,表示流的新状态,我们可以通过位操作将需要的位进行置位。
如果我们需要将failbit和badbit置位,eofbit不变的话,可以这样写代码:
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
输出缓冲区
每个流都管理一个缓冲区,用来保存程序读写的数据。
如果执行下面的语句:
os << "hello world !";
字符串有可能直接被打印出来,也有可能被保存在缓冲区里,随后再打印出来。
有了缓冲区,系统就可以将多个输出,合并成一个输出。 因为IO读写是很耗时的操作,缓冲区的出现就大大提升了读写性能。
刷新输出缓冲区
我们知道 endl 操纵符 完成换行并刷新缓冲区的工作。 IO库中还有两个类似的操纵符, flush 和 ends。 flush 刷新缓冲区,但不输出任何额外的字符。 ends 向缓冲区插入一个空字符,然后刷新缓冲区。
unitbuf 操纵符
如果我们想在每次输出操作之后,都刷新缓冲区,我们可以使用 unitbuf 操纵符。 它告诉流,在接下里的每次写操作之后,都进行一次flush操作,而 nounitbuf 操纵符可以将流的刷新缓冲机制重置,回到正常的状态。
警告: 如果程序崩溃,输出缓冲区不会被刷新
文件输入输出
头文件 fstream 定义了三个类型支持文件IO:
- ifstream 从给定的文件读取数据。
- ofstream 向给定的文件写入数据。
- stream 可以读写给定的文件。
如何使用文件流?
第一步: 创建一个文件流
定义一个文件流对象,并将对象和文件关联起来。使用流类型的open函数进行关联文件,如果你还没理解的话,下面我们时间操作一下。
创建一个只读文件流:
ifstream input(filename); //创建一个ifstream 文件流,并打开给定的文件
ofstream output; //创建一个ofstream 文件流,没有关联任何文件
创建文件流对象时,如果提供了文件名,open函数自动回被调用。要使用is_open()对文件是否打开进行验证。
如果没有提供文件名,需要手动调用open去关联一个文件。
如果open失败了,failbit会被置位,所以open之后尽量去检测一下open是否成功。
close成员函数可以关闭一个文件的关联,close之后,可以重新打开关联一个新的文件。
第二步:手动设置open
我们先看一下open成员函数的原型:
public member function
void open ( const char * filename,
ios_base::openmode mode = ios_base::in | ios_base::out );
void open(const wchar_t *_Filename,
?? ??? ?ios_base::openmode mode= ios_base::in | ios_base::out,
?? ??? ?int prot = ios_base::_Openprot);
参数:
filename 操作文件名
mode 打开文件的方式
prot 打开文件的属性 //基本很少用到,在查看资料时,发现有两种方式
打开文件的模式 在ios类(所以流式I/O的基类)中定义,有如下几种方式:
mode类型 | 描述 |
---|---|
ios::in | 为输入(读)而打开文件 |
ios::out | 为输出(写)而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 所有输出附加在文件末尾 |
ios::trunc | 如果文件已存在则先删除该文件 |
ios::binary | 二进制方式 |
这些模式是能够进行组合使用的,以“或”运算(“|”)的方式:
ofstream out; //定义一个写文件流
out.open("Hello.txt", ios::in|ios::out|ios::binary) //根据自己需要进行相应的选取
打开文件的属性同样在ios类中也有定义:
mode类型 | 描述 |
---|---|
0 | 普通文件,打开操作 |
1 | 只读文件 |
2 | 隐含文件 |
4 | 系统文件 |
对于文件的属性也可以使用“或”运算和“+”进行组合使用的。
第三步: 对文件流进行读或写
无论是默认自动调用open还是手动调用open,当我们open完成之后,就开始对文件进行操作了。
- 可以使用 << 进行对文件进行写入。
- 可以使用 >> 对文件进行读,遇到空格即结束。
- 使用getline成员函数读,遇到换行符即结束。
同时,我们结合流状态接口可以判断文件时否可读可写,是否读到文件结尾等。
指向流中读写位置的流指针
我们还可以通过使用以下成员函数来读出或配置这些指向流中读写位置的流指针:
tellg() 和 tellp()
这两个成员函数不用传入参数,返回pos_type 类型的值(根据ANSI-C++ 标准)。
代表当前get 流指针的位置 (用tellg) 或 put 流指针的位置(用tellp).
seekg() 和 seekp()
这对函数分别用来改变流指针get 和put的位置。两个函数都被重载为两种不同的原型:
seekg ( pos_type position );
seekp ( pos_type position );
使用这个原型,流指针被改变为指向从文件开始计算的一个绝对位置。
要求传入的参数类型与函数 tellg 和tellp 的返回值类型相同。
seekg ( off_type offset, seekdir direction );
seekp ( off_type offset, seekdir direction );
使用这个原型可以指定由参数direction决定的一个具体的指针开始计算的一个位移(offset)。它可以是:
参数 | 描述 |
---|---|
ios::beg | 从流开始位置计算的位移 |
ios::cur | 从流指针当前位置开始计算的位移 |
ios::end | 从流末尾处开始计算的位移 |
流指针 get 和 put 的值对文本文件(text file)和二进制文件(binary file)的计算方法都是不同的,因为文本模式的文件中某些特殊字符可能被修改。
由于这个原因,建议对以文本文件模式打开的文件总是使用seekg 和 seekp的第一种原型,而且不要对tellg 或 tellp 的返回值进行修改。
对二进制文件,你可以任意使用这些函数,应该不会有任何意外的行为产生。
string 流这里不做介绍了,如果感兴趣可以自行学习,可参考 《C++ primer》
以上的内容参考 《C++ primer》
欢迎关注公众号,新文章现在公号上发布~~~