IO库定义了读写内置类型值的操作。一些类,如string通常也会定义类似的IO操作来读写自己的对象。
istream(输入流)类型,提供输入操作。例如cin,一个istream对象,从标准输入读取数据。
ostream(输出流)类型,提供输出操作。例如cout,一个ostream对象,向标准输出写入数据。
为了支持不同类型的IO处理操作,在istream和ostream之外,标准库还定义了其他一些IO类型。
fstream定义了读写命名文件的类型,其中:
1.ifstream类型,从文件读取数据;
2.ofstream类型,向文件写入数据。
sstream定义了读写内存对象的类型,其中:
1.istringstream类型,从string读取数据;
2.ostringstream类型,向string写入数据。
1.IO类型之间的关系
概念上,设备类型和字符大小都不会影响我们要执行的IO操作。例如,我们可以用(>>)读取数据,而不用管是从一个控制台窗口,一个磁盘文件,还是一个string读取。标准库使我们能忽略这些不同类型的流之间的差异,这是通过继承机制实现的。类型ifstream和istringstream都继承自istream。因此,可以像使用istream对象一样来使用ifstream和istringstream对象。
我们不能拷贝或对IO对象赋值,如下所示:
ofstream out1,out2;
out1=out2; //错误,不能对流对象赋值
ofstream print(ofstram); //错误,不能初始化ofstream参数
out2=print(out2); //错误,不能拷贝流对象
由于不能拷贝IO对象,因此也不能将形参或返回类型设置为流类型。进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。
IO操作一个与生俱来的问题是可能发生错误。一些错误是可恢复的,而其他错误则发生在系统深处,已经超过了应用程序可以修正的范围。下面列出了IO类所定义的一些函数和标志,可以帮助我们访问和操纵流的状态条件。
函数 | 含义 |
---|---|
strm::iostate | strm是一种IO类型,iostate是一种机器相关的类型,提供了表达条件状态的完整功能 |
strm::badbit | 指流已崩溃 |
strm::failbit | 指出一个IO操作失败了 |
strm::eofbit | 指出流到达了文件结束 |
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 |
1.查询流的状态
将流作为条件使用,只能告诉我们流是否有效,而无法告诉我们具体发生了什么。有时我们也需要知道流为什么失败。IO库定义了一个与机器无关的iostate类型,它提供了表达流状态的完整功能。这个类型应作为一个位集合使用,使用方式与第三章中使用quizl的方式一样。IO库定义了4个iostate类型的constexpr值,表示特定的位模式。这些值用来表示特定类型的IO条件,可以与位运算符一起使用来一次性检测或设置多个标志位。badbit表示系统级错误,如不可恢复的读写错误。通常情况下,一旦badbit被置位,流就无法再使用了。在发生可恢复错误后,failbit被置位,如期望读取数值却读出一个字符等错误。这种问题通常可以修正,流还可以继续使用。如果到达文件结束位置,eofbit和failbit都会被置位。goodbit的值为0,表示流未发生错误。如果badbit、failbit和eofbit任一个被置位,则检测流状态的条件会失败。
2.管理条件状态
流对象的rdstate成员返回一个iostate值,对应流的当前状态。setstate操作将给定条件位置位,表示发生了对应错误。clear成员是一个重载的成员:它有一个不接受参数的版本,而另外一个版本接受一个iostate类型的参数。
每个输出流都管理一个缓冲区,用来保存程序读写的数据。例如:
os<<"please enter a value";
文本串可能立即打出来,但也有可能被操作系统保存在缓冲区内,随后再打印。有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很耗时,允许操作系统将多个输出操作组合为单一的设备写操作,这样可以带来很大的性能提升。导致缓冲刷新的原因很多,如下:
1.程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行。
2.使用操纵符如endl来显式刷新缓冲区。
1.刷新输出缓冲区
endl: 完成换行并刷新缓冲区的工作;
flush: 刷新缓冲区但不输出任何额外的字符;
ends: 向缓冲区插入一个空字符然后刷新缓冲区;
2.unitbuf操纵符
如果想在每次输出操作后都刷新缓冲区,我们可以使用unitbuf操纵符,它告诉流在接下来的每次写操作后都进行一次flush操作,而nounitbuf操作符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制。
cout<<unitbuf; //任何输出都立即刷新
cout<<nounitbuf; //回到正常的缓冲方式
3.关联输入和输出流
当一个输入流被关联到一个输出流时,任何试图从输出流读取数据的操作都会先刷新关联的输出流。标准库将cout和cin关联在一起。tie有2个重载的版本,一个版本不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针,如果对象未关联到流,则返回空指针。tie的第二个版本接受一个指向ostream的指针,将自己关联到此ostream。即X.tie(&0)将流X关联到输出流0。我们既可以将一个istream对象关联到另一个ostream,也可以将一个ostream关联到另一个ostream。
fstream中定义的类型还增加一些新的成员来管理与流关联的文件。
函数 | 含义 |
---|---|
fstream fstrm | 创建一个未绑定的文件流 |
fstream fstrm(s) | 创建一个fstream,并打开名为s的文件 |
fstream fstrm(s,mode) | 与前一个函数类似,但按指定类型mode打开文件 |
fstrm.open(s) | 打开名为s的文件,并将文件与fstrm绑定 |
fstream.close() | 关闭与fstrm绑定的文件 |
fstrm.is_open() | 返回一个bool值,指出与fstrm关联的文件是否成功打开且尚未关闭 |
当我们想要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来。每个文件流类都定义了一个名为open的成员函数,它完成一些系统相关的操作,定位给定的文件,并视情况打开为读或写模式。创建文件流对象时,我们可以提供文件名。如果提供了一个文件名,则open会自动调用。
ifstream in(ifile); //构造一个ifstream并打开文件
1.用fstream代替iostream&
在要求使用基类型对象的地方,我们可以用继承类型的对象来替代。这意味着接受一个iostream类型引用(或指针)参数的函数,可以用一个对应的fstream(或sstream)类型来调用。
2.成员函数open和close
如果我们定义了一个空文件流对象,可以随时调用open来将它与文件关联起来。如果调用open失败,failbit会被置位。因为调用open可能会失败,进行open是否成功的检测通常是一个好习惯。一旦一个文件流打开,它就保持与对应文件的关联。实际上对一个已经打开的文件流调用open会失败,并会导致failbit被置位。随后的试图使用文件流的操作都会失败。为了将文件流关联到另外一个文件,必须首先关闭已经关联的文件。
3.自动构造和析构
当一个fstream对象被销毁时,close会自动被调用。
每个流都有一个关联的文件模式。用来指出如何使用文件。
文件模式 | 含义 |
---|---|
in | 以读方式打开 |
out | 以写方式打开 |
app | 每次写操作前均定位到文件末尾 |
ate | 打开文件后立即定位到文件末尾 |
trunc | 截断文件 |
binary | 以二进制方式进行IO |
指定文件模式有如下限制:
1.只可以对ofstream或fstream对象设定out模式;
2.只可以对istream或fstream对象设定in模式;
3.只有当out也被设定时才可设定trunc模式;
4.只要trunc没被设定,就可以设定app模式。
在app模式下,即使没有显式指定out模式,文件也总是以输出方式被打开。默认情况下,即使没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须指定app模式,这样只会将数据追加写到文件末尾;或者同时指定in模式,即打开文件的同时进行读写操作。每个文件流类型都指定了一个默认的文件模式,当我们未指定文件模式时,就使用此默认模式。与istream关联的文件默认以in模式打开;与ofstream关联的文件默认以out模式打开;与fstream关联的文件默认以in和out模式打开。默认情况下,当我们打开一个ofstream时,文件的内容会被丢弃,阻止一个ofstream清空给定文件内容的方法是同时指定app模式。
ofstream out("file", ofstream:: app );
1.每次调用open时都会确定文件模式
在每次打开文件时,都要设置文件模式,可能是显式地设置,也可能是隐式的设置。
sstream头文件定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读取数据。istringstream从string读取数据,ostringstream向string写入数据,stringstream既可以从string读数据也可向string写数据。
函数 | 含义 |
---|---|
sstream strm | strm是一个未绑定的stringstream对象。sstream是头文件sstream中定义的一个类型 |
sstream strm(s) | strm是一个sstream对象,保存string s的一个拷贝。此构造函数是explicit的 |
strm.str() | 返回strm所保存的string拷贝 |
strm.str(s) | 将string s拷贝到strm中 |
当某些工作是对整行文本进行处理,而其他的一些工作是处理行内的单个单词时,通常可以使用istringstream。
struct Person{
string name;
vector<string>phones;
};
string line,word;
vector<Person> poeple;
while(getline(cin,line)){
Person info;
istringstream record(line);
record>>info.name;
while(record>>word)
info.phones.push_back(word);
people.push_back(info);
}
当我们逐步构造输出,希望最后一起打印时,ostringstream是很有用的。例如,对上一节的例子,我们可能想逐个验证电话号码并改变其格式。如果所有号码都是有效的我们希望输出一个新文件,包含改变格式后的号码。对于那些无效的号码,我们不会将它们输出到新文件中,而是打印一条包含人名和无效号码的错误信息。
for(const auto&enter:people)
{
ostringstream formatted,badNums;
for(const auto &nums:enter.phones){
if(!valid(nums)){
badNums<<" "<<nums;
}
else
formatted<<" "<<format(nums);
}
if(badNums.str().empty())
os<<enter.name<<" "<<formatted.str()<<endl;
else
cerr<<"input error:"<<entry.name<<"invalid number(s)"<<badNums.str()<<endl;
}