8.1IO类
下表列出的是一些IO类型:
为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵wchar_t类型的数据。宽字符版本的类型和函数的名字以一个w开始,例如,wcin、wcout、wcerr是分别对应cin、cout、cerr的宽字符类型。
注:IO对象无拷贝或赋值
ofstream out1,out2;
out1 = out2;//错误,不能对流对象赋值
ofstream print(ofstream);//错误,不能初始化ofstream参数
out2 = print(out2);//错误,不能拷贝流对象
条件状态
IO操作一个与生俱来的问题是可能发生错误。一些错误是可恢复的,而其他错误则发生在系统深处,已经超出了应用程序可以修正的范围。下表列出了IO类所定义的一些函数和标志,可以帮助我们访问和操纵流的条件状态。
下面是一个IO错误的例子:
int ival;
cin>>ival;
如果我们在标准输入上键入Boo,读操作就会失败。代码中的输入运算符期待读取一个int,但却得到了一个字符B。这样,cin会进入错误状态。类似的,如果我们输入一个文件结束标识,cin也会进入错误状态。
流对象的rdstate成员返回一个iostate值,对应流的当前状态。setstate操作将给定条件位置位,表示发生了对应错误。clear成员是一个重载成员,它有两个版本:
clear不接受参数的版本清除(复位)所有错误标志位:
//记住cin的当前状态
auto old_state = cin.rdstate();//记住cin的当前状态
cin.clear();//使cin有效
process_input(cin);//使用cin
cin.setstate(old_state);//将cin置为原有状态
带参数的clear版本接受一个iostate值,表示流的新状态:
//复位failbit和badbit,保持其他标志位不变
cin.clear(cin.rdstate()&~cin.failbit&~cin.badbit);
管理缓冲输出
每个输出流管理一个缓冲区,用来保存程序读写的数据。例如,执行下面代码:
os <<"Please enter a value:";
文本串可能立即打印出来,但也有可能被操作系统保存到缓冲区中,随后再打印。由于设备的写操作可能很耗时,允许操作系统将多个输出组合为单一的设备写操作可以带来很大的性能提升。
导致缓冲刷新(即,数据真正写到输出设备或文件)的原因有很多:
- 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行
- 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区
- 我们可以使用操作符如endl来显示刷新缓冲区
- 在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的
- 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,默认情况下,cin和cerr都关联到cout。因此,读cin或写cerr都会导致cout的缓冲区被刷新
我们已经使用过操纵符endl,IO库还有两个类似的操纵符:flush和ends。
cout<<"hi"<<endl;//输出hi和一个换行,然后刷新缓冲区
cout<<"hi"<<flush;//输出hi,然后刷新缓冲区,不附加任何字符
cout<<"hi"<<ends;//输出hi和一个空字符,然后刷新缓冲区
如果想在每次输出操作后都刷新缓冲区,我们可以使用unitbuf操纵符。它告诉流在接下来的每次写操作后都进行一次flush操作。
cout<<unitbuf;//所有输出操作后都会立即刷新缓冲区
//任何输出都立即刷新,无缓冲
cout<<nounitbuf;//回到正常的缓冲方式
8.2文件输入输出
下表列出了一些操作,我们可以对fstream、ifstream和ofstream对象调用这些操作,但不能对其他IO类型调用这些操作
创建文件流对象时,我们可以提供文件名(可选的)。如果提供了一个文件名,则open会被自动调用:
ifstream in(ifile);//构造一个ifstream并打开给定文件
ofstream out;//输出文件流未关联到任何文件
文件模式
每个流都有一个关联的文件模式,用来指出如何使用文件,下表列出了文件模式和他们的含义:
无论用哪种方式打开文件,我们都可以指定文件模式,指定文件模式有如下限制:
- 只可以对ofstream或fstream对象设定out模式
- 只可以对ifstream或fstream对象设定in模式
- 只有当out也被设定时才可设定trunc模式
- 只要trunc没被设定,就可以设定app模式。在app模式下,即使没有显示指定out模式,文件也总是以输出方式被打开
- 默认情况下,即使我们没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须同时指定app模式,这样只会将数据追加写到文件末尾;或者同时指定in模式,即打开文件同时进行读写操作
注意:以out模式打开文件会丢弃已有数据。默认情况下,当我们打开一个ofstream时,文件的内容会被丢弃。阻止一个ofstream清空给定文件内容的方法是同时指定app模式。
8.3string流
sstream头文件定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读取数据,就像string是一个IO流一样。下表列出了这些操作,可以对stringstream对象调用这些操作,但不能对其他IO类型调用这些操作。
使用istringstream
当我们某些工作是对整行文本进行处理,而其他一些工作是处理行内的单个单词时,通常可以使用istringstrea,例如:
struct PersonInfo
{
string name;
vector<string> phones;
}
string line,word;//分别保存来自输入的一行和单词
vector<PersonInfo> people;//保存来自输入的所有记录
//逐行从输入读取数据,直至cin遇到文件尾
while(getline(cin,line))
{
PersonInfo info;//创建一个保存此纪录数据的对象
istringstream record(line);//将记录绑定到刚记录的行
record>>info.name;//读取名字
while(record>>word)//读取电话号码
info.phones.push_back(word);//保持他们
people.push_back(info);//将此纪录追加到people末尾
}
使用ostringstream
当我们逐步构造输出,希望最后一起打印时,ostringstream是很有用的,由于我们不希望输出电话无效的人,对于上面的例子:
for(const auto &entry:peple)//对people中每一项
{
ostringstream formatted ,badNums;//每个循环步创建的对象
for(cosnt auto &nums:entry.phones)//对每个数
{
if(!valid(num))
{
badNums<<" "<<nums;//将数的字符串形式存入badNums
}
else
//将格式化的字符串“写入”formatted
formatted<<" "<<format(nums);
}
if(badNums.str().empty())//没有错误的数
os<<entry.name<<" "//打印名字
<<formatted.str()<<endl;//和格式化的数
else //否则打印名字和错误的数
cerr<<"input error:"<<entry.name
<<" invalid numbers(s)"<<badNum.str()<<endl;
}
在此程序中,我们假设已有两个函数,valid和format,分别完成电话号验证和改变格式的功能。