8. IO类
8.1 其他IO类型
-
为支持使用宽字符的语言,标准库定义了一组类型和对象来操纵
wchar_t
类型的数据,宽字符版本的类型和函数的名字以一个w开始。
如wcin、wcout、wcerrcin就是分别对应cout、cerr的宽字符版本。
宽字符版本与普通版本都在同一个头文件中。头文件 类型 iostream istream, wistream, ostream, wostream, iostream, wiotream ftream ifstream, wifstream, ofstream, wofstream, fstream, wftream sstream istringstream, wistringstream, ostringstream, wostringstream, stringstream, wstringtream -
继承机制 使我们可以声明一个特定的类继承自另一个类,通常可以把派生类对象当作基类对象来使用。
iftream和istringstream都继承自istream。
8.1.1 IO对象无拷贝或赋值
-
IO操作的函数通常是以引用的方式传递和返回流。
-
读写一个IO会改变其状态,因此传递和返回的宁死不能是const的。
8.1.2 条件状态
-
IO类定义了一些函数和标志:
注:strm在下表中代表某种特定的IO类型,这个类型应作为一个位集合来使用。标志或函数 说明 strm::iostate 类型,提供了表达条件状态的完整功能 strm::badbit 用来指出流已崩溃 strm::failbit 用来指出一个IO操作失败了 strm::eofbit 用来指出流到达了文件结束 strm::goodbit 用来指出流未处于错误状态,此值保证为零 s.eof() 若流s的eofbit置位,则返回true s.fail() 若流s的failbit或badbit置位,则返回true s.bad() 若流s的badbit置位,则返回true s.good() 若流s处于有效状态,则返回true s.clear() 将s中的所有条件状态位复位,将流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 -
一个流一旦发生错误,其上后续的IO操作都会失败。
-
查询流的状态,即通过iostate类型的状态位来查看,其使用方式类似于quiz1的位操作的使用方式。
-
badbit:表示系统级错误,一般情况下,一旦被置位,流就无法使用了。
failbit:表示可恢复错误,如期望读取数值却读出了一个字符。
eofbit:表示到达文件结束,被置位时failbit也会跟着被置位。
goodbit:值为0,表示流未发生错误。
badbit、failbit、eofbit任一个被置位,则检测流状态的条件会失败。 -
good():在所有错误位均未置位的情况下返回true。
bad()、fail()、eof()在对应的错误位被置位时返回true。
badbit被置位时,fail()也会返回true。
总结:good()和fail()是确定是流总体状态的正确方法。
而eof()和bad()操作只能表示特定的错误。 -
使用示例:
auto old_state = cin.rdstate(); // 记住cin当前状态 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 管理输出缓冲
-
缓冲区的存在,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。
导致缓冲刷新的原因:- 程序正常结束。
- 缓冲区满时。
- 使用操纵符显式刷新缓冲区。
- 在每个输出操作后,可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。默认情况下对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的。
- 一个输出流可能被关联到另一个操作流,这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。默认情况下,cin和cerr都是关联到cout,因此,读cin或写cerr都会导致cout的缓冲区被刷新。
-
刷新输出缓冲区:
endl:换行并刷新缓冲区。
flush:刷新缓冲区但不输出任何字符。
ends:向缓冲区插入一个空字符,然后刷新缓冲区。 -
unibuf操纵符:
如果想每次输出操作后都刷新缓冲区,可以使用unitbuf操纵符。
而nounitbuf操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制。 -
警告:如果程序崩溃,输出缓冲区不会被刷新。
当调试一个已崩溃的程序时,需要确认那些你认为已经输出的数据确实已经刷新了。否则,可能将大量时间浪费在追踪代码为什么没有执行,而实际上代码已经执行了,只是程序崩溃后缓冲区没有刷新,输出数据被挂起没有打印而已。 -
交互式系统通常应该关联输入流和输出流,这将使得所有输出,包括用户提示信息,都会在读操作之前被打印出来。
-
tie()
有两个重载的版本:
(1)不带参数,返回指向输出流的指针。
如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针。
如果未关联到流,则返回空指针。
(2)接受一个指向ostream的指针,将自己关联到此ostream。
x.tie(&o)
将x关联到输出流o。 -
在下面这段代码中,为了将一个给定的流关联到一个新的输出流,我们将新流的指针传递给了tie。
为了彻底开放流的关联,我们传递了一个空指针。
每个流最多关联到一个流,但多个流可以同时关联到同一个ostream。cin.tie(&cout); // 仅仅是用来展示:标准库将cin和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 文件输入输出
-
ifstream、ofstream、fstream类型,所提供的操作与之前使用过的cin和cout的操作一样。
特别是,可以用IO运算符(<<和>>)来读写文件,可以用getline()从一个ifstream读取数据。 -
除了继承自iostream类型的行为之外,fstream还增加了一些新的成员来管理与流关联的文件。
我们可以对fstream、ifstream、ofstream对象调用这些操作,但不能对其他IO类型调用这些操作。
下表中,fstream是头文件fstream中定义的某一个类型。操作 说明 fstream fstrm; 创建一个未绑定的文件流 fstream fstrm(s); 创建一个fstream,并打开一个名为s的文件。s可以是string类型,也可以是一个指向C风格字符串的指针。这些构造函数都是explicit的。默认的文件模式mode依赖于fstream的类型 fstream fstrm(s, mode); 与前一个构造函数类似,但按指定mode打开文件 fstrm.open(s); 打开名为s的文件,并将文件与ftrm绑定。s可以是一个string或一个指向C风格字符串的指针。默认的文件mode依赖于fstream的类型。返回void fstrm.close() 关闭与fstrm绑定的文件。返回void fstrm.is_open() 返回一个bool值,指出与fstrm关联的文件是否成功打开且尚未关闭
8.2.1 使用文件流对象
-
创建文件流对象时,我们可以提供文件名。如果提供了一个文件名,则open函数自动被使用。
ifstream in(ifile);
:构造一个ifstream并打开给定文件。
ofstream out;
:输出文件流并未关联到任何文件。 -
自动构造和析构
如果一个程序的main函数接受一个要处理的文件列表,这种程序可能会有如下的循环:for (auto p = argv + 1; p != argv + argc; ++p){ ifstream input(*p); // 创建输出流并打开文件 if (input){ process(input); }else{ cerr << "couldn't open: " + string(*p); } } // 每个循环步input都会离开作用域,因此会被销毁
-
上面的程序中,input是while循环的局部变量,在每个循环步中都要创建和销毁一次。
当一个fstream对象被销毁时,close会自动被调用。
8.2.2 文件模式
-
每个流都有一个关联的文件模式。
模式 说明 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模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用。
-
以out模式打开文件会丢弃已有数据。
保留被ofstream打开的文件中已有数据的唯一方法是显式指定app或in模式。// 以下语句中,file1都被截断 // 隐含以输出模式打开文件并截断文件 ofstream out("file1"); // 隐含的截断文件 ofstream out2("file1", ofstream::out); ofstream out2("file1", ofstream::out | ofstream::trunc); // 为了保留文件内容,我们必须显式指定app模式 ofstream app("file2", ofstream::app); ofstream app2("file2", ofstream::out | ofstream::app);
-
每次调用open时都会确定文件模式
对于一个给定流,每当打开文件时,都可以改变其文件模式。
下面的代码中的第一个open,未显式指定输出模式,文件隐式地以out模式打开。且通常情况下,out模式意味着同时使用trunc模式。因此,当前目录名下scratchpad文件内容将被清空。
当打开precious的文件时,我们指定了append模式,文件中已有的数据都得以保留,所有写操作都在文件末尾进行。ofstream out; // 未指定文件打开模式 out.open("scratchpad"); // 模式隐含设置为out和trunc out.close(); // 关闭out,一边我们将其用于其他文件 out.open("precious", ofstream::app); // 模式为输出和追加 out.close();
8.3 string流
-
sstream头文件定义了三个类型来支持内存IO:istringstream、ostringtream、stringstream。
头文件sstream中定义的类型都继承自iostream。
stringstream增加的操作如下表,其中stream是某种特定类型:操作 说明 sstream strm strm是一个未绑定的stringstream对象 sstream strm(s) strm是一个对象,保存string s的一个拷贝,此构造函数是explicit的 strm.str() 返回strm所保存的string的拷贝
8.3.1 使用istringstream
-
当某些工作是对整行文本进行处理,而其他工作是处理行内单词时,通常使用istringstream。
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); }
8.3.2 使用ostringstream
-
使用场景:逐步构造输出,最后一起打印。
下面的程序功能:逐个验证电话号码并改变格式,若有效则保留,无效则打印一条包含人名和无效信息的错误信息。
在此程序中,我们使用标准输出运算符(<<)向这些对象写入数据,但这些写入操作实际上转换为string操作,分别向formatted和badNums中的string对象添加字符。for (const auto &entry : people) { ostringstream formatted, badNums; for (const auto &nums : entry.phones) { if (!valid(nums)) { badNums << " " << nums; } else { formatted << " " << format(nums); } } if (badNums.str().empty()) { os << entry.name << " " << formatted.str() << endl; } else { cerr << "input error: " << entry.name << " invalid number(s) " << badNums.str() << endl; } }