0%

C++_IO库

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
2
3
4
5
istream &read(istream &in, person &item)
{
in>>item.name>>item.addr;
return in;
}//上一章讲到的read函数就是一个IO操作函数

条件状态

当使用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
2
3
4
auto oldstate = cin.rdstate();
cin.clear();
cin>>....//一次输入流操作
cin.setstate(oldstate);

通过位操作一次操作多个状态位:

1
2
//假设当前所有状态置位
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit)//只复位failbit,badbit

输出流的缓冲

刷新缓冲区

之前讲到过输出endl可以刷新缓冲区,还有以下操作也可以刷新

1
2
3
cout<<endl; //换行
cout<<flush; //不添加任何额外字符
cout<<ends; //添加一个空字符

刷新缓冲区的作用

我其实也不是特别理解,姑且谈一下我的理解,这是网上的一个例程

1
2
3
4
5
6
7
8
9
int main()
{
setvbuf( stdout, NULL, _IOLBF, 1024 ); //设置控制台输出为行缓存模式,把缓冲区与流相关
cout <<"hello world\n"<<endl;
Sleep(5000);
cout <<"leeboy" <<endl;
system("pause");
return 0;
}

如果第四行不刷新缓冲区,则4行内容会和7行在5s后一起输出,所以刷新缓冲区的作用应该是立即输出当前内容?而按照网上说法,如果不刷新缓冲区,输出是由系统每隔一段时间检测输出流是否有内容。

unitbuf操作符

如果想设置每一次输出后自动刷新缓冲区,可以:

1
2
cout<<std::unitbuf;
cout<<std::nounitbuf;//恢复正常缓冲模式

关联输入和输出流

关联后任何试图从输入流读取数据的操作都会先刷新关联的输出流

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
2
3
4
fstream a("dhk.txt");
///////////////////////
fstream b;
b.open("dhk.txt");

我们先详解一下open函数:

open()函数

根据输入的参数数量,该函数有以下几个定义

1
2
3
4
5
6
7
8
9
10
 
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:文件名
  • 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
2
3
ofstream out("...", ios::out);
ifstream in("...", ios::in);
fstream foi("...", ios::in|ios::out);

以写模式打开一个文件会丢弃已有数据

默认情况下,当打开一个ofstream对象时,若没有指定模式,默认是隐含的使用截断(ios::trunc),即每次打开该文件时会清空之前的内容,若要阻止清空,需显式地设置为ios::app模式,同时,使用app模式则隐式地包含了ios::out。

使用fstream对象代替iostream&

由于fstream类继承自iostream,因此,接受一个iostream类型引用参数的函数也可以接收fstream类型:

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
29
 struct person
{
string name;
string addr;
};

istream &read(istream &in, person &item)
{
in>>item.name>>item.addr;
return in;
}

ostream &print(ostream &out, person &item)
{
out<<"NAME: "<<item.name<<endl;
out<<"ADDR: "<<item.addr<<endl;
return out;
}

int main()
{
person man;
ifstream in("D.txt");
ofstream out("dhk.txt");
read(in,man); //使用ifstream代替istream
print(out,man); //使用ofstream代替ostream
out.close();
return 0;
}

注意事项

对一个已经打开的文件流再调用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
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<iostream>
#include<fstream>
#include<string>
#include<windows.h>
#include<sstream>
using namespace std;
struct person
{
string name;
string addr;
};
istream &read(istream &in, person &item)
{
in>>item.name>>item.addr;
return in;
}
ostream &print(ostream &out, person &item)
{
out<<"NAME: "<<item.name<<endl;
out<<"ADDR: "<<item.addr<<endl;
return out;
}

int main()
{
person man;
string line;
ifstream in("sstream\\D.txt"); //打开一个txt文件并绑定一个输入文件流in
/*D.txt的内容:
dhk 12
aca 34
*/
while(getline(in,line)) //分别处理文件中每一行的数据
{
stringstream s(line);//将该行内容与字符串流s绑定,s的寿命为当次循环,当次循环结束s自动销毁
s>>man.name;//将该行第一个词输入进类成员
s>>man.addr;//该行第二个词输入进类成员
print(cout,man);//通过iostream输出类成员
}
in.close();//关闭打开的文件
system("pause");
return 0;
}
  • 可以看出,流每次输出(入)的内容为一个“词”,也就是说空格和换行都会结束该次输出(入)的内容

输出到字符串流

当我们想逐步构造想要输出的内容,最后一口气打印时,就可以使用字符串的输出流,将构造好的内容分布写入到ostingstream(或stringstream的输出流)中,最后通过函数.str()输出到cout或者你想要的其他流中