C++学习——IO库基础
IO处理通过继承封装了不同输入输出流读取的细节,其继承关系为
1、IO类
1.1、IO对象不能拷贝和赋值
ofstream o1,o2;
o1 = o2; //错--不能对流对象赋值
o2 = print(o1); //错--不能拷贝流对象
因为不能拷贝流对象,所以在io作为形参的过程中,不要将形式参数设置为流类型(也就是值传递),因为值传递是将传入的流对象拷贝一份,但是流是不支持拷贝的,所以一般通过引用传递来传入和返回流的对象,并且一般读写一个流对象会改变其状态,所以传递和返回的引用是不能为const的
1.2、IO流的条件状态
io流操作不可避免地会遇到输入错误和某些未知地系统错误,有些是可以恢复的,有些是不可以恢复的,IO类中有一些定义这些标志的函数,分别对应着流的不同状态,并且在流中定义了不同的state和函数来表示和获取该状态,大致为
状态&函数 | 描述 |
---|---|
strm::iostate | 它是一种与机器相关的功能,表述里面定义了流的各种状态 |
strm::badbit | 这个流已经崩溃,如果已经崩溃,badbit被置为0,并且无法恢复 |
strm::failbit | 表示发生了错误的读取方式,是可以恢复的,比如需要读取一个int值,你却输入了一个字符值,这会导致failbit |
strm::goodbit | 表示流是正常状态,正常的话此值为0 |
strm::eofbit | 表示流已经到了文件的结尾 |
s.eof() | 判断流是否已经到了文件的结尾 |
s.fail() | 判断流是否发生了错误的读取 |
s.bad() | 判断流是否发生了系统级的错误 |
s.good() | 判断流是否正常 |
s.clear() | 将流中所有的状态复位 |
s.clear(flags) | flags是strm::iostate类型,表示将流s中对应的条件状态复位 |
s.setstate(flags) | 将流s中对应的条件状态置位 |
s.rdstate() | 返回流当前的状态,返回一个strm::iostate类型 |
首先看一下iostate一个类型,它定义在头文件ios_bash.h,是从c语言中传承过来的头文件,可以在头文件中看到,对于iostate的定义,说白了iostate其实就是一个枚举类型,请看源码
enum _Ios_Iostate { _S_goodbit = 0, _S_badbit = 1L << 0, _S_eofbit = 1L << 1, _S_failbit = 1L << 2, _S_ios_iostate_end = 1L << 16, _S_ios_iostate_max = __INT_MAX__, _S_ios_iostate_min = ~__INT_MAX__ }; typedef _Ios_Iostate iostate; //用iostate来等价上面的枚举类型,下设四种情况 /// Indicates a loss of integrity in an input or output sequence (such /// as an irrecoverable read error from a file). static const iostate badbit = _S_badbit; /// Indicates that an input operation reached the end of an input sequence. static const iostate eofbit = _S_eofbit; /// Indicates that an input operation failed to read the expected /// characters, or that an output operation failed to generate the /// desired characters. static const iostate failbit = _S_failbit; /// Indicates all is well. static const iostate goodbit = _S_goodbit;
然后再来看几个函数的源码
//eof bool eof() const{ return (this->rdstate() & eofbit) != 0; } //fail bool fail() const{ return (this->rdstate() & (badbit | failbit)) != 0; } //bad bool bad() const{ return (this->rdstate() & badbit) != 0; } //可以看到,这些都是通过位运算来进行运算的,这三个函数是c++派生出来的,定义在头文件basic_ios.tcc中
通过这些,我们就可以管理流的条件状态
当然这里需要去分清楚clear(不带参数)和setstate的区别,clear是将复位所有的错误标志位,而setstate是将某一个未知置为某个状态,可以是错误的,也可以是正确的
auto old_state = cin.rdstate(); //记住此时的cin的当前状态
cin.clear(); //将cin的所有错误标志位复位,使其有效
....... //使用cin
cin.setstate(old_state); //将cin恢复原来的状态
带参数的clear表达的意思是,可以复位特定的标志位
比如,下面的代码是将failbit复位,而不去管badbit和eofbit
cin.clear(cin.restate()&~cin.failbit);
1.3、控制缓冲区
每一个输出流都有一个缓冲区,因为设备的写操可能十分的耗费时间,一般操作系统会将多个缓冲区整合到一起后然后合并到一起然后再进行输出,这就好比一辆卡车运砖,是当车子上的砖达到一定的数量之后车子才会走,这样的将多个缓冲区整合到一起为单一的设备进行输出可以带来很大的提升,卡车开始运送这一步对应着缓冲区的刷新,下面有很多种情况能刷新缓冲区
- 当程序正常结束和函数返回时,在作用域中调用的缓冲区都会刷新
- 当缓冲区满时,也会刷新缓冲,以便新的数据写入到缓冲区
- 使用操作符endl,flush,ends来显式的刷新缓冲区
- 使用操作符unitbuf来设置缓冲区的刷新
- 通过流的关联,即一个输出流可能会被关联到另一个流,当读写被关联的流时,这个关联的流就会刷新,比如cout和cin就是一个关联的流,当cin>>i时,cout的缓冲区就会刷新
cout<<"Hi!"<<endl; //输出一个Hi!和一个换行符,并刷新缓冲区
cout<<"Hi!"<<flush; //输出一个Hi!,并刷新缓冲区
cout<<"Hi!"<<ends; //输出一个Hi!和一个空字符,并刷新缓冲区
//unitbuf
cout<<unitbuf; //所有输出操作之后都会立即刷新缓冲区
cout<<nounitbuf; //回到正常的缓冲方式
需要注意的是:当程序异常终止时,输出缓冲区是不会被刷新的!
//关联输入和输出,通过使用tie来关联
ostream os;
istream in;
in.tie(&os); // 将输出流os与in关联起来,对in的读操作会导致os缓冲区的刷新
ostream *on = in.tie(nullptr); // in不在于os进行关联,并返回os的指针
in.tie(on); // 重建in于os的关系
在关联中,可以看到
- 每个流同时最多关联一个流
- 但是一个流可以被很多不同的流关联
2、文件输入输出
2.1、流的基本输入输出操作
ifstream、ofstream和fstream都是从IO流中继承过来的,可以进行文件的读取,在出去IO基类的基本参数定义外,还封装了一些其它的操作,如下
操作 | 描述 |
---|---|
fstream fstrm | 创建一个未绑定的文件流。为头文件fstream中定义的 |
fstream fstrm(s) | s可以是字符串类型的文件地址,或者是c风格字符串表示的文件地址 |
fstream fstrm(s,mode) | 以什么样的方式打开文件地址为s的文件 |
fstrm.open() | 打开一个文件s,并与文件绑定,默认的mode类型依赖于fstream |
fstrm.close() | 关于与fstrm关联的文件 |
fstrm.is_open() | 返回bool,指示与fstrm关联的文件是否正常打开并且正常关闭 |
ifstream in("data.txt"); //创建一个ifstream并且与文件data.txt关联起来
ofstream ou; //构造一个ofstream并没有关联任何文件
ou.open("file文件",mode); //以mode的文件打开方式打开一个文件
关闭文件
一个流一旦关联了一个文件,就无法再次关联其它的文件,必须通过close方法来关闭与对应文件的关联,如果一个流已经关联了一个文件,那么再次调用open方法时会导致failbit被置位。
int main() {
ofstream on("data.txt",ios::out);
if(!on.is_open()){
throw runtime_error("File open error");
}
int v;
while(cin>>v){
on<<v;
on<<" ";
cout<<"The value "<<v<<" are be write to the file"<<endl;
}
on.close();
on.open("data1.txt",ios::out);
if(on.is_open()||!on.fail()){
cout<<"File open success"<<endl;
}
return 0;
}
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ofstream on("data.txt",ios::out);
if(!on.is_open()){
throw runtime_error("File open error");
}
int v;
while(cin>>v){
on<<v;
on<<" ";
cout<<"The value "<<v<<" are be write to the file"<<endl;
}
on.close();
ifstream in("data.txt",ios::in);
if(!in.is_open()){
throw runtime_error("File open error");
}
while(!in.eof()){
in>>v;
in.ignore(2);
cout<<v<<" ";
}
cout<<endl;
in.close();
return 0;
}
2.2、文件打开模式
每一个流,在打开文件的时候,都有自己打开文件的模式,在c++中,文件的打开模式有这么几种
模式 | 描述 |
---|---|
in | 以读方式打开,只适用于输入流(读) |
out | 以写的方式打开,只适用于输出流(写) |
app | 每次写操作前均定位到文件末尾 |
ate | 打开文件后立即定位到文件末尾 |
trunc | 截断文件 |
binary | 以二进制的方式打开IO |
说明:
对于上面的几种文件打开模式,有如下的注意点
- 这些流可以同时设置
//比如 ofstream os; os.open("data.txt",ios::out&ios::trunc&ios::binary);
- 只有当out被设定才可以设定trunc模式,(trunc其实是,无视有没有这个现存的文件,都会新建一个文件,有的话将会将之前的文件覆盖掉)
- 只要trunc没有被指定,就可以设定app模式,app模式下,即使没有显式指定out模式,文件也总是以输出方式被打开的
- 默认情况下,即使没有设置trunc,out模式下文件也是会被截断的,如果想要文件不被阶段,必须要设置app模式
ofstream os; os.open("data.txt",ios::app&ios::out);
- ate和binary可以适用于任何的文件流对象
这个文件打开模式参数是一个openmode类型,也是一个枚举类型,它定义在头文件ios_base.h中,源码为:
enum _Ios_Openmode
{
_S_app = 1L << 0,
_S_ate = 1L << 1,
_S_bin = 1L << 2,
_S_in = 1L << 3,
_S_out = 1L << 4,
_S_trunc = 1L << 5,
_S_ios_openmode_end = 1L << 16,
_S_ios_openmode_max = __INT_MAX__,
_S_ios_openmode_min = ~__INT_MAX__
};
typedef _Ios_Openmode openmode;
/// Seek to end before each write.
static const openmode app = _S_app;
/// Open and seek to end immediately after opening.
static const openmode ate = _S_ate;
/// Perform input and output in binary mode (as opposed to text mode).
/// This is probably not what you think it is; see
/// https://gcc.gnu.org/onlinedocs/libstdc++/manual/fstreams.html#std.io.filestreams.binary
static const openmode binary = _S_bin;
/// Open for input. Default for @c ifstream and fstream.
static const openmode in = _S_in;
/// Open for output. Default for @c ofstream and fstream.
static const openmode out = _S_out;
/// Open for input. Default for @c ofstream.
static const openmode trunc = _S_trunc;
3、string流
c++STL中定义了三个输入输出流,分别是istringstream,ostringstream,stringstream,分别对字符串进行读写操作,同样它也有它的特定的定义方法
特定方法 | 描述 |
---|---|
sstream strm | strm是一个未与字符串绑定的字符串流对象 |
sstream strm(s) | 保存字符串s的一个拷贝,这个构造函数时explicit的 |
strm.str() | 返回strm所保存的字符串的拷贝 |
strm.str(s) | 将s拷贝到strm中,返回值为void |
这里explicit修饰的构造函数,只能用于直接初始化
3.1、istringstream的用法
比如对于某些需要特定的操作,像将整行文本拆分出来,比如一个通讯录,在输入的时候读取一整行字符串,然后用istringstream将其拆分出来
mon 13800320012 13233210123
bob 12233231123 //电话号码使多样的
#include <iostream>
#include<fstream>
#include<sstream>
#include<string>
#include<vector>
using namespace std;
//定义村粗通讯录的结构体
struct PersonInfo{
string name;
vector<string> phones;
};
int main(){
string line,word;
istringstream in;
vector<PersonInfo> people;
while(getline(cin,line)){
PersonInfo info;
in.str(line); //将line绑定到流中
in>>info.name;
if(info.name=="exit")
break;
while(in>>word){
info.phones.push_back(word);
}
people.push_back(info);
in.clear();
}
//输出电话簿
for(auto temp:people){
cout<<temp.name<<" ";
for(auto phone:temp.phones){
cout<<phone<<" ";
}
cout<<endl;
}
}
3.2、ostringstream的用法
ostringstream其实也就跟istringstream的用法是一样的,它可以将输入的数据写入字符串中,这里就不再赘述