IO类
在之前的一些用到输入与输出的程序都是用户对流的读写,也就是使用的iostream类,但是C++中的IO处理并不止通过流进行读写,为了支持不同的IO操作,标准库中还定义了其他的一些IO类,他们被包含在不同的头文件中:
| 头文件 | 类 | 作用 |
|---|---|---|
| iostream | (w)istream (w)ostream (w)iostream |
从普通流中读写数据 |
| fstream | (w)ifstream (w)ofstream (w)fstream |
从文件流中读写数据 |
| sstream | (w)istringstream (w)ostringstream (w)stringstream |
从字符串流中读写数据 |
- 在类和其函数前添加w则为正常类对象、函数等(例如wcin,wcout)的宽字符版。
IO类型间的关系
类型ifstream和istringstream都继承自istream,因此,我们如何使用istream,就如何使用其他IO类型。在之后介绍的标准库的流特性均可以无差别地使用于各个IO类型。
IO类型的特性
IO对象不能拷贝
当我们定义了一些IO对象时(通常是用于IO操作的一些函数),之前也使用过,是不能对这些对象进行拷贝或赋值的操作的,因此当我们操作流时,通常都是使用引用的方式传递和返回流,这也意味着这些IO操作函数的返回类型要定义为IO类的引用,参数中的流也需定义为IO类的引用。
1 | istream &read(istream &in, person &item) |
条件状态
当使用IO操作流时可能会发生多种错误,为了查询这些错误,标准库中定义了相关函数和标准以访问当前流的条件状态。
流分为以下几个状态:(这些状态为IO类中定义)
| badbit | 当前流已崩溃 |
|---|---|
| failbit | 当前IO操作失败了 |
| eofbit | 当前流到达了文件结束 |
| goodbit | 为0代表流正常 |
- 可以对这些状态位进行位运算
- badbit表示系统级错误,是不可恢复的错误。通常一旦被置位,流就无法再使用了。
- failbit表示可恢复的错误,修正后流可以机械使用。badbit被置位后failbit也会被置位。
- 如果文件结束,failbit和eofbit都会被置位。
- 上面三位任意一位有置位情况goodbit都会被置位。
以下是IO类的一些查询函数:
| 类的子函数 | 作用 | 返回值 |
|---|---|---|
| eof() | 查询eofbit状态 | 若对应状态位置位则返回ture |
| fail() | 查询failbit状态 | 若对应状态位置位则返回ture |
| bad() | 查询badbit状态 | 若对应状态位置位则返回ture |
| good() | 查询goodbit状态 | 若对应状态位置位则返回ture |
| clear() | 将流中所有条件状态位复位 | void |
| rdstate() | 返回当前流的状态 | 一种包含所有状态位信息的类,但类型同状态位 |
| clear(flag) | 复位指定状态位 | void |
| setstate(flag) | 置位指定状态位 | void |
管理条件状态
每进行一次流操作后恢复上一次的条件状态:
1 | auto oldstate = cin.rdstate(); |
通过位操作一次操作多个状态位:
1 | //假设当前所有状态置位 |
输出流的缓冲
刷新缓冲区
之前讲到过输出endl可以刷新缓冲区,还有以下操作也可以刷新
1 | cout<<endl; //换行 |
刷新缓冲区的作用
我其实也不是特别理解,姑且谈一下我的理解,这是网上的一个例程
1 | int main() |
如果第四行不刷新缓冲区,则4行内容会和7行在5s后一起输出,所以刷新缓冲区的作用应该是立即输出当前内容?而按照网上说法,如果不刷新缓冲区,输出是由系统每隔一段时间检测输出流是否有内容。
unitbuf操作符
如果想设置每一次输出后自动刷新缓冲区,可以:
1 | cout<<std::unitbuf; |
关联输入和输出流
关联后任何试图从输入流读取数据的操作都会先刷新关联的输出流
1 | cin>>std::ival;//默认情况下cin和cerr都关联到了cout |
文件的输入与输出
头文件fstream中定义了三个类来支持关于文件的IO操作:
- ifstream:用于文件读操作,从给定文件读取内容
- ofstream:用于文件写操作
- fstream:读写操作,可以通过不同配置实现上两类的功能
这些类都继承了iostream的相关操作,同时还定义了一些新的成员来管理与流相关的文件,以下列出了部分特有操作,这些操作都适用于上三个类,但不能对其他IO类调用
| 操作 | 功能 |
|---|---|
| fstream a; | 创建一个未绑定的文件流 |
| fstream a(s); | 创建一个文件流,并打开名为s的文件,s为string类型或C风格字符串指针 |
| a.open() | 打开一个文件,并绑定至流a |
| a.close() | 关闭绑定的文件 |
| a.is_open() | 返回一个bool值,指出文件是否成功打开 |
文件的打开
刚刚讲到了有两种方式打开一个文件,分别是
1 | fstream a("dhk.txt"); |
我们先详解一下open函数:
open()函数
根据输入的参数数量,该函数有以下几个定义
1 |
|
参数:
- filename:文件名
- open mode:打开方式
- prot:打开文件的属性,一般很少用到
这里重点讲一下打开方式,刚刚我们讲过:头文件fstream中的fstream类可以实现读和写的操作,也就是可以通过不同的配置实现ifstream和ofstream的操作,也就是通过这里的打开方式配置。
打开方式在ios类中定义:
| ios::in | 为输入(读)而打开文件 |
|---|---|
| ios::out | 为输出(写)而打开文件 |
| ios::ate | 初始位置:文件尾 |
| ios::app | 所有输出附加在文件末尾 |
| ios::trunc | 如果文件已存在则先删除该文件 |
| ios::binary | 二进制方式 |
这些方式可以通过或运算|组合使用
- ate和binary通常用于打开任何类型的文件流对象
默认打开方式
对于第二种方式fstream a("dhk.txt");其实是隐式地调用了open()函数,直接使用了默认打开方式:
1 | ofstream out("...", ios::out); |
以写模式打开一个文件会丢弃已有数据
默认情况下,当打开一个ofstream对象时,若没有指定模式,默认是隐含的使用截断(ios::trunc),即每次打开该文件时会清空之前的内容,若要阻止清空,需显式地设置为ios::app模式,同时,使用app模式则隐式地包含了ios::out。
使用fstream对象代替iostream&
由于fstream类继承自iostream,因此,接受一个iostream类型引用参数的函数也可以接收fstream类型:
1 | struct person |
注意事项
对一个已经打开的文件流再调用open函数会导致该流的failbit置位,后续操作都会失败
- 查看文件是否成功打开的快捷方式:if(a);
文件打开路径的问题
文件的默认打开路径我也没搞懂,最好使用相对路径打开相关文件
1 | ifstream in(".vscode\\fstream\\D.txt"); |
并且这里要使用双斜杠!!!
字符串流
在头文件sstream中定义了三个类来支持操作字符串IO,分别是:
- istringstream
- ostirngstream
- stringstream
这些类同样继承自iostream,用法同文件流,类特有操作有:
| stringstream a; | 创建一个未绑定string的流 |
|---|---|
| stringstream a(s); | 与string对象s绑定的一个字符串流 |
| a.str(); | 返回a中所保存的所有字符串 |
| a.str(); | 将s拷贝至a中,返回void |
| a.clear(); | 清空流 |
读取string
与文件流相同,stringstream也可以实现读取和输入的功能
下面以一个例程说明string流的用法
1 |
|
- 可以看出,流每次输出(入)的内容为一个“词”,也就是说空格和换行都会结束该次输出(入)的内容
输出到字符串流
当我们想逐步构造想要输出的内容,最后一口气打印时,就可以使用字符串的输出流,将构造好的内容分布写入到ostingstream(或stringstream的输出流)中,最后通过函数.str()输出到cout或者你想要的其他流中