C++prime十万字笔记 第八章 IO类
io库
之前已经介绍了一些IO库中的内容:
- istream(输入流)类型,提供输入操作
- ostream(输出流)类型,提供输出操作
- cin,一个istream对象,从标准输入读取数据
- cout,一个ostream对象,向标准输出写入数据
- cerr,一个ostream对象,用于输出程序错误信息,写入到标准错误
- >>运算符,用来从一个istream对象读取输入数据
- <<运算符,用来向一个ostream对象写入输出数据
- getline函数,从一个给定的istream读取一行数据,存入一个给定的string对象中
IO类
一些常用的IO类型如下表所列:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kHeLDtkF-1641812023257)(C:\Users\15401\AppData\Roaming\Typora\typora-user-images\image-20210829133043690.png)]
为了支持宽字符语言,标准库定义了一组类型和对象来操纵wchar_t类型的数据。宽字符版本的类型和函数的名字以一个w开头,例如wcin,wcout和wcerr分别对应cin,cout,cerr的宽字符版本。宽字符版本的类型和对象和其对应的char类型的定义在同一个头文件中。例如头文件fstream中定义了ifstream和wfstream类型。
我们可以用>>读取数据而不用管是从string还是磁盘文件还是控制台窗口。标准库能让我们忽略这些不同类型的流之间的差异,这是通过继承机制来实现的。ifstream和istringstream都继承自istream。因此可以像使用istream对象一样来使用ifstream和istringstream。当然istream可以使用getline和>>,它在子类也可以。
IO对象无拷贝或者复制
进行IO操作的函数通常以引用的方式传递和返回信息流,读写一个IO对象会改变其状态,因此传递和返回的引用不能是cosnt的。
ofstream out1,out2;
out1=out2; // 不能对流对象赋值
ofstream print(ofstream); //不能初始化ofstream参数
out2 = print(out2); //不能拷贝流对象
条件状态
IO操作的一个与生俱来的问题是发生错误,一些错误是可恢复的,而其他错误发生在系统深处已经超出了应用程序可以修复的范围。下表列出了IO类定义的一些函数和标志,可以帮助我们访问和操纵流的状态。
名字 | 意思 |
---|---|
strm::iostate | strm,我试了一下好像现在都在std::ios_base::badbit |
strm::badbit | strm::badbit用来指出流已经崩溃 系统级错误,一旦被置位一般这个流就不能用了 |
strm::failbit | 用来指出一个IO操作失败了 |
strm::eofbit | 用来指出流到达了文件结束 |
strm::goobit | 用来指出流未处于错误状态,此值保证为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的类型也为strm::iostate。返回void |
s.rdstate() | 返回流s的当前条件状态,返回值类型为strm::iostate |
流出错,后续所有的操作都会失败,只有无错才能读取数据。确定的方法是将其当一个条件来使用`if(cin>>word){cout<<“sucess”<<endl;}
查询流的状态
将流作为条件使用只能告诉我们流是否有效,而无法告诉我们发生了什么。IO库定义了一个与极其无关的iostate类型,这个类型作为一个位集合来使用,定义了四4个iostate的conseexpr值来表示特定的位模式。这些值用来表示特定类型的IO条件可以和位运算符一起使用来一次性检测或设置多个标志位。
实际上我们把流当做条件使用的等价代码就是(!s.fail()),使用good和fail是确定流整体状态的方法。
管理条件状态
流对象的rdstate返回一个iostate值对应当前流的状态,setstate操作将给定条件位置位表示发生了错误。clear成员是一重载的成员有接受参数的版本和不接受参数的版本。其中一个版本接受iostate类型的参数。
old_state = cin.rdstate(); //用一个iostate接收当前的状态
cin.clear(); //使io有效
process_input(cin);
cin.setstate(old_state); //将cin置为原有的状态
//带参数的clear接受一个iostate值作为参数
cin.clear(cin.rdsate()&~cin.failbit&~cin.badbit());//这句话让failbit和badbit复位,但是保持eofbit clear让0的位进行复位,1的不变
管理输出缓冲
每个输出流都管理一个缓冲区,用来保存程序读写的数据。例如执行如下的代码文本串可能立刻打印出来:os<<"please enter a value"
但也有可能被存到缓冲区,随后在打印。有了缓冲机制可以将程序的多个输出合成单一的系统级别写操作带来性能提升。
何时刷新?
- 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行
- 缓冲区满时需要刷新缓冲区,而后心的数据才能继续写入缓冲区
- 使用操作符来显示的刷新缓冲区
- 每个输出操作之后可以通过操纵符unibuf设置流的内部状态,来清空缓冲区,默认情况下对cerr是设置unibuf的,因此写入cerr的内容都是立刻刷新的
- 一个输出流可能关联到另外一个流,在这种情况下当读写被关联的流时,关联到的流的缓冲区会被刷新。默认情况下cin和cerr都被关联到cout因此读cin或者写cerr都会导致cout的缓冲区立刻刷新。
刷新输出缓冲区
除了endl,io库中还有两个类似的操纵符:flush和ends。flush刷新缓冲区但不输出额外的字符,ends向缓冲区插入一个空字符然后刷新缓冲区:
cout <<"hi"<<endl; //输出hi和换行然后刷新缓冲区
cout<<"hi"<<flush; //输出hi然后刷新缓冲区,不附加任何的额外字符
cout<<"hi"<<ends; //输出hi和一个空字符,然后刷新缓冲区
如果想在每次输出操作后都刷新缓冲区,可以使用unibuf操纵符,其告诉流下次每次写操作都执行一次flush操作。而nounibuf就会恢复正常的缓冲区机制。
cout<<unibuf; //不使用缓冲区立马进行flush操作
cout<<nounibuf; //回到正常的 缓冲方式
当程序崩溃它的输出数据坑你在输出缓冲区等待打印
关联输入和输出流
当一个输入流被关联到一个输出流时,任何试图从输入流中读取数据的操作都会先刷新关联的输出流。标准库将cout和cin关联在一起因此下面语句:cin>>ival
;会导致cout的缓冲区被刷新。交互式系统一般会关联输入和输出,因此会,这样用户的输入信息会直观的显式。
我们既可以将一个istream关联到一个ostream,也可以将一个ostream关联到另外一个ostream中。每个流最多关联到一个流,但是可以让多个流关联到同一个ostream
cin.tie(&cout); //标准库将cin和cout关联在一起 可以知道tie的参数是ostream的指针 cin.tie(无参数)它关联的输出的指针
ostream * old_tie cin.tie(nullptr);
cin.tie(&cerr);
cin.tie(old_tie);
文件输入输出
头文件定义了三个类型来支持IO:ifstream从一个给定文件中读取数据,ofstream向一个给定的文件写入数据,以及fstream可以读写给定文件。并且这些类型也可以使用<<>>以及getline。
还有一些fstream特有的操作如下:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tuw1Dv8W-1641812023258)(C:\Users\15401\AppData\Roaming\Typora\typora-user-images\image-20210829161217909.png)]
使用文件流对象
通过文件流对象读写文件,每个文件流都定义了open的函数完成一些系统相关的工作。
ifstream in(ifile); //构造一个ifstream并打开给定的文件 文件名既可以是string对象也可以是c风格字符串
ofstream out; //输出文件流未关联到任何文件
用fstream代替iostream&
在需要用基类的时候可以用子类代替
成员函数open和close
定义了空文件流对象可以用open将其和文件关联起来
ifstream in(file);
ofstream out;
ofstream.open(ifile+".copy");
//如果调用open失败 failbit会被置位
//因为调用open可能失败我们可以对其进行检测
if (out):巴拉巴拉;
一旦一个文件流被打开,它就保持与对应文件的关联。实际上对一个已经打开的文件流调用open会失败并会导致failbit被置位。随后的试图使用该文件流的操作会失败,我们需要先close才能打开open,如果open文件成功则open会置流的状态使成员函数good()为true。
自动构造和解析
当一个fstream对象被销毁时,会自动调用close()成员函数。
文件模式
每一个流都有一个关联的文件模式,用来指出如何使用文件。如下表:
模式 | 意义 |
---|---|
in | 以读的方式打开 |
out | 以写的方式打开 |
app | 每次写操作前均定位到文件末尾 |
ate | 打开文件后立刻定位到文件末尾 |
trunc | 截断文件 |
binary | 以二进制的方式进行IO |
指定文件模式有如下的限制:
- 只可以对ofstream或者fstream对象设定out模式
- 只可以对ifstream或者fstream对象设定in模式
- 只有当out被设定时才能设定trunc模式
- 只要trunc没被设定就可以设定app模式,在app模式下,即使没有显式的指定out模式,文件也总是以输出模式被打开
- 默认情况下,即使没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须同时指定app模式,这样只会将数据追加到文件末尾;或者同时指定in模式,即打开文件同时进行读写操作。
- ate和binary模式可以用于任何类型的文件流对象,且可以和其他任何文件模式组合使用
每一个文件流类型都定义了一个默认的文件模式,当我们未指定文件模式时,就是用此默认模式。与ifstream关联的文件默认以in模式打开,与ofstream关联的文件默认以out模式打开,与fstream关联的文件默认以in和out模式打开。
默认下,以out情况打开的文件其中的内容会丢失。组织一个ofstream清空给定文件内容的方式是同时指定app模式。
//下面几条语句都会被截断
ofsteram out("file1");
ofstream out2("file1",ofstream::out);
ofstreawm out3("file1",ofstream::out|ofstream::trunc);
//为了保留文件我们需要指定app模式
ofstream app("file2",ofstream::app);
ofstream app2("file2",ofstream::out|ofsteram::app);
//保留被ofstream打开的文件中已有的数据的唯一方法是指定app或者in模式
每次调用open是都会确定一个打开模式。即使你没有显式的指定,如果想要以ofstream流打开文件并保存内容一定要小心。
string流
sstream头文件中定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读数据就像是一个IO流一样。
istringstream从string中读取数据,ostringstream写数据,stringstream读写都可以。头文件sstream定义的类型都是继承了iostream的类型,sstream增加的有:
sstream strm | strm是一个未绑定的stringstream对象。sstream是头文件sstream中定义的一个类型。 |
---|---|
sstream strm(s) | strm是一个sstream对象保存string s的一个拷贝,此构造函数是explicit的 |
strm.str() | 返回strm所保存的string的拷贝 |
strm.str(s) | 将string s拷贝到strm中返回void |
使用istringstream
struct PersonInfo{
string name;
vector<string> phones;
};
string line,word;
vector<PersonInfo> people;
//逐行从输入读取数据,直到遇到明见末尾
while(getline(cin,line)){
PersonInfo info;
istringstream record(lint);
record>>info.name;
while(record>>word)
info.phones.push_bach(word);
people.push_back(info);
}