CPPprimer第八章IO库

C++语言不直接处理输入输出,而是通过一组定义在标准库中的class类型,即 流类类型(stream class type) 来处理IO操作。这些类支持从设备读取数据、向设备写入数据的IO操作,设备可以是文件、控制台窗口等,还有一些类允许内存IO,即,从内存读取数据,向内存写入数据。

IO 库定义了读写内置类型值的操作。除此之外,一些类,比如 string ,通常也会定义类似的IO操作,来读写class类型自己的对象。

istream 输入流类型,提供输入操作。

ostream 输出流类型,提供输出操作。

cin 一个 istream 对象,从标准输入读取数据。

cout 一个 ostream 对象,向标准输出写入数据。

cerr 一个 ostream 对象,用户输出程序错误消息,写入到标准错误。

>> 运算符,用来从一个 istream 对象读取输入数据。

\<< 运算符,用来从一个 ostream 对象写入输出数据。

getline函数,从一个给定的 istream 读取一行数据,存入一个给定的 string 对象中。

IO类

头文件 类型
iostream istream,wistream 从流中读取数据
ostream,wostream 向流中写入数据
iostream,wiostream 读写流
fstream ifstream,wifstream 从文件中读取数据
ofstream,wofstream 向文件中写入数据
fstream,wfstream 读写文件
sstream istringstream,wistringstream 从 string 对象中读取数据 ostringstream,wostringstream 向 string 对象中写入数据 stringstream,wstringstream 读写 string 对象

文件输入输出

头文件 fstream 定义了三个类型来支持文件IO:ifstream从一个给定文件读取数据,ofstream向一个给定文件写入数据,以及fstream可以读写给定文件。在17.5.3节中将介绍如何对同一个文件流既读又写。

这些类型提供的操作与之前已经使用过的对象cin和cout的操作一样。特别是可以用IO运算符(<<和>>)来读写文件,可以用getline函数从一个ifstream读取数据。也就是说,包括8.1节中介绍的操作也都适用于这些类型。

除了继承自iostream类型的行为之外,头文件 fstream 中定义的类型还增加了一些新的成员来管理与流关联的文件。在下面列出了这些操作,我们可以对fstream,ifstream和ofstream对象调用这些操作,但不能对其他IO类型调用这些操作。

使用文件流对象

1
2
ifstream in(ifile);	// 构造一个 ifstream 对象,打开给定文件并将双方绑定
ofstream out; // 输出文件流out未关联到任何文件

这段代码定义了一个输入流 in ,它被初始化为从文件读取数据,文件名由参数 ifile 指定。第二条语句定义了一个输出流out,未与任何文件关联。在新C++标准中,文件名既可以是库类型string对象,也可以是C风格字符数组(参见3.5.4节)。旧版本的标准库只允许C风格字符数组。

  • open和close函数

    1
    2
    3
    ifstream in(ifile);			// 构筑一个ifstream并打开给定文件
    ofstream out; // 输出文件流未与任何文件相关联
    out.open(ifile + ".copy"); // 打开指定文件

    这个条件判断与我们之前将cin用作条件相似。如果open失败,条件会为假,我们就不会去使用out了。

    一旦一个文件流已经打开,它就保持与对应文件的关联。实际上,对一个已经打开的文件流调用open会失败,并会导致 failbit 被置位。而随后的试图使用该文件流的操作自然都会失败。 为了将文件流关联到另外一个文件,必须首先关闭已经关联的文件。一旦文件成功关闭,我们可以打开新的文件:

    1
    2
    in.close();				// 关闭文件
    in.open(ifile + "2"); // 打开另一个文件

文件模式

每个流都有一个关联的文件模式(file mode),用来指出如何使用文件。下面列出了文件模式和它们的含义。

文件模式 含义
in 以读方式打开
out 以写方式打开
app 每次写之前定位到文件末尾(追加)
ate 打开文件后立即定位到文件末尾
trunc 截断文件
binary 以二进制方式进行 IO 操作

有的文件模式可以同时指定。

通过|可以组合文件模式,比如:ofstream::out | ofstream::trunc

以out模式打开文件会丢弃已有数据

1
2
3
4
5
6
7
8
// 在这几条语句中,filel都被截断
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);

string流

头文件 sstream 定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读取数据,就像string是一个IO流一样。
istringstream从string读取数据,ostringstream向string写入数据,而stringstream既可从string读数据也可向string写数据。与头文件fstream类似,头文件sstream中定义的类型都继承自iostream头文件定义的类型。除了继承得来的操作,sstream中定义的类型还增加了一些成员来管理与流相关联的string。

头文件sstream中定义的这些类型的单参数构造函数都是explicit的,,即不能通过隐式转换参数。

istringstream

应用举例:

1
2
3
4
5
6
7
8
9
10
11
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的末尾
}

这里用 getline 从标准输入读取整条记录。如果 getline 函数调用成功,那 么 line 中将保存着从输入文件而来的一条记录。在 while 中定义了一个局部Personlnfo对象,来保存当前记录中的数据。

接下来将一个 istringstream 与刚刚读取的文本行进行绑定,这样就可以在 此 istringstream 上使用输入运算符来读取当前记录中的每个元素。首先读取人名,随后用一个 while 循环读取此人的电话号码。

当读取完line中所有数据后,内层 while 循环就结束了。此循环的工作方式与前面章节中读取 cin 的循环很相似,不同之处是,此循环从一个 string 而不是标准输入 读取数据。当 string 中的数据全部读出后,同样会触发“文件结束”信号,在 record 上的下一个输入操作会失败。

将刚刚处理好的Personinfo追加到 vector 中,外层 while 循环的一个循环步就随之结束了。外层 while 循环会持续执行,直至遇到 cin 的文件结束标识。

ostringstream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for (const auto &entry : people)			// 对people中每一项
{
ostringstream formatted, badNums; // 每个循环步创建一个对象
for (const auto &nums : entry.phones) // 对每个数
{
if (!validTel(nums))
badNums << " " << nums; // 将数的字符串形式存入badNums
else
// 将格式化的字符串"写入"formatted
formatted << " " << (nums);
}
if (badNums.str().empty()) // 没有错误的数
os << entry.name << " " // 打印名字到os绑定的string流中
<< formatted.str() << endl; // 与格式化的数
else
// 否则,打印名字和错误的数
cerr << "input error: " << entry.name
<< " invalid number(s) " << badNums.str() << endl;
}