8.1 IO类
- C++使用标准库类来处理面向流的输入和输出:
iostream处理控制台IO。
fstream处理命名文件IO。
stringstream完成内存string的IO。
8.1.1 IO对象无拷贝或赋值
- 由于不能拷贝IO对象,因此也不能将形参或返回类型设置为流类型。
- 进行IO操作的函数通常以引用方式传递和返回流。
- 读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。
ofstream out1, out2;
out1 = out2; //错误:不能对流对象赋值
ofstream print(ofstream); //错误:不能将形参或是返回类型设为流类型
out2 = print(out2); //错误:out2不是可修改的左值
8.1.2 条件状态
- IO类所定义的一些函数和标志,可以帮助我们访问和操纵流的条件状态。
- 一个流一旦发生错误,其上后续的IO操作都会失败。
- 确定一个流对象的状态的最简单的方法是将它当作一个条件来使用。
- IO库定义了4个iostate类型的constexpr值表示特定的位模式。
- 使用good或fail是确定流的总体状态的正确方法。
- iostate类型提供了表达流状态的完整功能,这个类型应作为一个位集合来使用。
auto old_state = cin.rdstate(); //记住cin的当前状态,istream::iostate
cin.clear(); //使cin有效
process_input(cin); //使用cin
cin.setstate(old_state); //将cin置为原有状态
//复位failbit和badbit,保持其他标志位不变
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
8.1.3 管理输出缓冲
- 每个输出流都管理一个缓冲区,用来保存程序读写的数据。
- 缓冲刷新:数据真正写到输出设备或文件。
- 刷新输出缓冲区:
flush:刷新缓冲区,但不输出任何额外的字符。
ends:向缓冲区插入一个空字符,然后刷新缓冲区。
endl:换行并刷新缓冲区。
unitbuf:每次输出操作后都刷新缓冲区。 - 如果程序崩溃,输出缓冲区不会被刷新。
- 有了缓冲机制,操作系统就可以在需要时将程序的多个输出操作合并,提升性能。
//刷新输出缓冲区
cout<< "hi!" <<endl; //输出hi和一个换行,然后刷新缓冲区
cout<< "hi!" <<flush; //输出hi,然后刷新缓冲区,不附加任何额外字符
cout<< "hi!" <<ends; //输出hi和一个空字符,然后刷新缓冲区
//通过设置unitbuf操纵符来控制是否立即刷新
cout<<unitbuf; //所有输出操作后都会立即刷新缓冲区
cout<<nounitbuf; //回到正常的缓冲方式
- 交互式系统通常应该关联输入流和输出流。这意味着所有输出,包括用户提示信息,都会在读操作之前被打印出来。
- 当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。
- 每个流同时最多关联到一个流,但多个流可以同时关联到同一个ostream。
cin.tie(&cout); //仅仅是用来展示:标准库内置
//old_tie指向当前关联到cin的流(如果有的话)
ostream *old_tie = cin.tie(nullptr); //cin不再与其他流关联
//将cin与cerr关联;这不是一个好主意,因为cin应该关联到cout
cin.tie(&cerr); //读取cin会刷新cerr而不是cout
cin.tie(old_tie); //重建cin和cout间的正常关联
8.2 文件输入输出
8.2.1 使用文件流对象
- 一旦一个文件流已经打开,它就保持与对应文件的关联。
ifstream in(ifile); //构造一个ifstream并打开指定文件
ofstream out; //输出文件流,未关联到任何文件
out.open(ifile+".copy"); //打开指定文件
if(out) //检查open是否成功
...;
in.close(); //关闭文件
in.open(ifile+"2"); //打开另一个文件
- 当一个fstream对象被销毁时,close会自动被调用。
8.2.2 文件模式
-
每个文件流类型都定义了一个默认的文件模式,当未指定文件模式时,就使用此默认模式。
-
以out(写方式)模式打开文件会丢弃已有数据,保留被ofstream打开的文件中已有数据的唯一方法是显式指定app(追加方式)或in(读方式)模式。
-
每次调用open时都会确定文件模式。
-
通常情况下,out模式意味着同时使用trunc(截断文件)模式。
//在这几条语句中,file1都被截断
ofstream out("file1"); //隐含以输出模式打开文件并截断文件
ofstream out2("file1", ofstream::out); //隐含地截断文件
ofstream out3("file1", ofstream::out | ofstream::trunc);
//为了保留文件内容,我们必须显示指定app模式
ofstream app("file2", ofstream::app); //隐含为输出模式
ofstream app2("file2", ofstream::out | ofstream::app);
ofstream out; //未指定文件打开模式
out.open("scratchpad"); //模式隐含设置为输出和截断
out.close();
out.open("preious", ofstream::app);//模式为输出和追加
out.close();
8.3 string流
- 从string读写数据,就像string是一个IO流一样。
- string代表的是内存,istringstrem是从内存读取数据,ostringstream是将数据写入内存。
8.3.1 使用istringstream
- 当需要处理行内单词的时候,通常可以使用istringstream。
struct PersonInfo{
string name;
vector<string> phones;
};
string line, word; //分别保存来自输入的一行和单词
vector<PersonInfo> people; //保存来自输入的所有记录
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末尾
}
8.3.2 使用ostringstream
for(const auto &entry:people){//对people中每一项
ostringstream formatted,badNums; //每个循环步创建的对象
for(const auto &nums:entry.phones){//对每个数
if(!valid(nums)){
badNums<<" "<<nums;//将数的字符串形式存入badNums
}else
formatted<<" "<<format(nums);//将格式化的字符串“入”formatted
}
if(badNums.str().empty()) //没有错误的数
os<<entry.name<<" "<<formatted.str()<<endl;//打印名字和格式化的数
else//否则,打印名字和错误的数
cerr<<"input error:"<<entry.name<<"invalid numbers(s) “
<<badNums.str()<<endl;
}