0%

C++ Primer - 第 8 章 IO 库

  1. 继承机制使我们可以声明一个特定的类继承自另一个类。我们通常可以将一个派生类(继承类)对象当作其基类(所继承的类)对象来使用。类型 ifstreamistringstream 都继承自 istream。因此,我们可以像使用 istream 对象一样来使用 ifstreamistringstream 对象。也就是说,我们是如何使用 cin 的,就可以同样地使用这些类型的对象。例如,可以对一个 ifstreamistringstream 对象调用 getline,也可以使用 >> 从一个 ifstreamistringstream 对象中读取数据。类似的,类型 ofstreamostringstream 都继承自 ostream。因此,我们是如何使用 cout 的,就可以同样地使用这些类型的对象。

  2. 不能拷贝或对 IO 对象赋值。由于不能拷贝 IO 对象,因此我们也不能将形参或返回类型设置为流类型。进行 IO 操作的函数通常以引用方式传递和返回流。读写一个 IO 对象会改变其状态,因此传递和返回的引用不能是 const 的。

  3. 一个流一旦发生错误,其上后续的 IO 操作都会失败。只有当一个流处于无错状态时,我们才可以从它读取数据,向它写入数据。由于流可能处于错误状态,因此代码通常应该在使用一个流之前检查它是否处于良好状态。确定一个流对象的状态的最简单的方法是将它当作一个条件来使用:

    1
    2
    while (cin >> word)
    // ok:读操作成功.....
  4. 操作 good 在所有错误位均未置位的情况下返回 true,而 badfaileof 则在对应错误位被置位时返回 true。此外,在 badbit 被置位时,fail 也会返回 true。这意味着,使用 goodfail 是确定流的总体状态的正确方法。实际上,我们将流当作条件使用的代码就等价于!fail()

  5. 每个输出流都管理一个缓冲区,用来保存程序读写的数据。导致缓冲刷新(即,数据真正写到输出设备或文件)的原因有很多:

    • 程序正常结束,作为 main 函数的 return 操作的一部分,缓冲刷新被执行。
    • 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区。
    • 可以使用操纵符如 endl 来显式刷新缓冲区。
    • 在每个输出操作之后,可以用操纵符 unitbuf 设置流的内部状态,来清空缓冲区。默认情况下,对 cerr 是设置 unitbuf 的,因此写到 cerr 的内容都是立即刷新的。
    • 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,默认情况下,cincerr 都关联到 cout。因此,读 cin 或写 cerr 都会导致 cout 的缓冲区被刷新

  6. 操纵符 endl,它完成换行并刷新缓冲区的工作。IO 库中还有两个类似的操纵符:flushendsflush 刷新缓冲区,但不输出任何额外的字符;ends 向缓冲区插入一个空字符,然后刷新缓冲区。

  7. 如果想在每次输出操作后都刷新缓冲区,我们可以使用 unitbuf 操纵符。它告诉流在接下来的每次写操作之后都进行一次 flush 操作。而 nounitbuf 操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制:

    1
    2
    3
    cout << unitbuf;  // 所有输出操作后都会立即刷新缓冲区
    // 任何输出都立即刷新,无缓冲
    cout << nounitbuf; // 回到正常的缓冲方式

    如果程序异常终止,输出缓冲区是不会被刷新的。当一个程序崩溃后,它所输出的数据很可能停留在输出缓冲区中等待打印。

  8. 当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。 既可以将一个 istream 对象关联到另一个 ostream,也可以将一个 ostream 关联到另一个 ostream。每个流同时最多关联到一个流,但多个流可以同时关联到同一个 ostream

  9. 头文件 fstream 定义了三个类型来支持文件 IO:ifstream 从一个给定文件读取数据,ofstream 向一个给定文件写入数据,以及 fstream 可以读写给定文件。

  10. 创建文件流对象时,我们可以提供文件名(可选的)。如果提供了一个文件名,则 open 会自动被调用。在新 C++ 标准中,文件名既可以是库类型 string 对象,也可以是 C 风格字符数组。

  11. 在要求使用基类型对象的地方,我们可以用继承类型的对象来替代。这意味着,接受一个 iostream 类型引用(或指针)参数的函数,可以用一个对应的 fstream(或 sstream)类型来调用。也就是说,如果有一个函数接受一个 ostream& 参数,我们在调用这个函数时,可以传递给它一个 ofstream 对象,对 istream&ifstream 也是类似的。

  12. 如果我们定义了一个空文件流对象,可以随后调用 open 来将它与文件关联起来。如果调用 open 失败,failbit 会被置位。因为调用 open 可能失败,进行 oppen 是否成功的检测通常是一个好习惯。一旦一个文件流已经打开,它就保持与对应文件的关联。实际上,对一个已经打开的文件流调用 open 会失败,并会导致 failbit 被置位。随后的试图使用文件流的操作都会失败。为了将文件流关联到另外一个文件,必须首先关闭已经关联的文件。一旦文件成功关闭,我们可以打开新的文件。

  13. 当一个 fstream 对象离开其作用域时,与之关联的文件会自动关闭。

  14. 编写函数,以读模式打开一个文件,将其内容读入到一个 stringvector 中,将每一行作为一个独立的元素存于 vector 中,将每个单词作为一个独立的元素存储在另一个 vector 中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    #include <iostream>
    #include <string>
    #include <vector>
    #include <iterator>
    #include <fstream>

    using namespace std;

    int main()
    {
    ifstream input("license.txt");
    vector<string> vs_line, vs_word;
    string s;

    if (input)
    {
    while (getline(input, s))
    vs_line.push_back(s);

    input.clear(); // 复位输入文件流状态为 true(输入文件流遇到文件尾状态会被置为 false)
    input.seekg(0); // 返回输入文件流首位置

    while (input >> s)
    vs_word.push_back(s);
    }
    else
    {
    cerr << "Wrong input file stream." << endl;
    return -1;
    }

    cout << "Output by line: " << endl;
    cout << "" << endl;
    for (auto iter = vs_line.begin(); iter != vs_line.end(); ++iter)
    cout << *iter << endl;

    cout << "" << endl;
    cout << "" << endl;

    cout << "Output by word: " << endl;
    cout << "" << endl;
    for (auto iter = vs_word.begin(); iter != vs_word.end(); ++iter)
    cout << *iter << endl;

    return 0;
    }
  15. 每个流都有一个关联的文件模式(file mode)。如下表所示:

    文件模式
    in 以读方式打开
    out 以写方式打开
    app 每次写操作均定位到文件末尾
    ate 打开文件后立即定位到文件末尾
    trunc 截断文件
    binary 以二进制方式进行 IO

    无论用哪种方式打开文件,我们都可以指定文件模式,调用 open 打开文件时可以,用一个文件名初始化流来隐式打开文件时也可以。指定文件模式有如下限制:

    • 只可以对 ofstreamfstream 对象设定 out 模式。
    • 只可以对 ifstreamfstream 对象设定 in 模式。
    • 只有当 out 也被设定时才可设定 trunc 模式。
    • 只要 trunc 没被设定,就可以设定 app 模式。在 app 模式下,即使没有显式指定 out 模式,文件也总是以输出方式被打开。
    • 默认情况下,即使我们没有指定 trunc,以 out 模式打开的文件也会被截断。为了保留以 out 模式打开的文件的内容,我们必须同时指定 app 模式,这样只会将数据追加写到文件末尾;或者同时指定 in 模式,即打开文件同时进行读写操作。
    • atebinary 模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用。

    每个文件流类型都定义了一个默认的文件模式,当我们未指定文件模式时,就使用此默认模式。与 ifstream 关联的文件默认以 in 模式打开;与 ofstream 关联的文件默认以 out 模式打开;与 fstream 关联的文件默认以 inout 模式打开。

  16. 默认情况下,当我们打开一个 ofstream 时,文件的内容会被丢弃。阻止一个 ofstream 清空给定文件内容的方法是同时指定 app 模式:

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

    WARNING:保留被 ofstream 打开的文件中已有数据的唯一方法是显式指定 app 或 in 模式。

  17. sstream 头文件定义了三个类型来支持内存 IO,这些类型可以向 string 写入数据,从 string 读取数据,就像 string 是一个 IO 流一样。istringstreamstring 读取数据,ostringstreamstring 写入数据,而头文件 stringstream 既可从 string 读数据也可向 string 写数据。stringstream 特有的一些操作如下表所示:

    stringstream特有的操作
    sstream strm; strm 是一个未绑定的 stringstream 对象。sstream 是头文件 sstream 中定义的一个类型
    sstream strm(s); strm 是一个 sstream 对象,保存 strings 的一个拷贝。此构造函数是 explicit 的
    strm.str() 返回 strm 所保存的 string 的拷贝
    strm.str(s) 将 string s 拷贝到 strm 中,返回 void
  18. 每个 IO 对象都维护一组条件状态,用来指出此对象上是否可以进行 IO 操作。如果遇到了错误——例如在输入流上遇到了文件末尾,则对象的状态变为失效,所有后续输入操作都不能执行,直至错误被纠正。标准库提供了一组函数,用来设置和检测这些状态。


Thank you for your donate!