# Chapter 8 The IO Library

# 前面章节已经在用的 IO 库设施

  • istream:输入流类型,提供输入操作。
  • ostream:输出流类型,提供输出操作
  • cin:一个 istream 对象,从标准输入读取数据。
  • cout:一个 ostream 对象,向标准输出写入数据。
  • cerr:一个 ostream 对象,向标准错误写入消息。
  • >> 运算符:用来从一个 istream 对象中读取输入数据。
  • << 运算符:用来向一个 ostream 对象中写入输出数据。
  • getline 函数:从一个给定的 istream 对象中读取一行数据,存入到一个给定的 string 对象中。

# The IO Classes


# IO 类

# 标准库定义的 IO 类型

  • iostream 头文件:从标准流中读写数据, istreamostream 等。
  • fstream 头文件:从文件中读写数据, ifstreamofstream 等。
  • sstream 头文件:从字符串中读写数据, istringstreamostringstream

# IO 对象不可复制或赋值

  • 1.IO 对象不能存在容器里.
  • 2. 形参和返回类型也不能是流类型。
  • 3. 形参和返回类型一般是流的引用
  • 4. 读写一个 IO 对象会改变其状态,因此传递和返回的引用不能是 const 的。

# 条件状态

状态解释
strm:iostate是一种机器无关的类型,提供了表达条件状态的完整功能
strm:badbit用来指出流已经崩溃
strm:failbit用来指出一个 IO 操作失败了
strm:eofbit用来指出流到达了文件结束
strm:goodbit用来指出流未处于错误状态,此值保证为零
s.eof()若流 seofbit 置位,则返回 true
s.fail()若流 sfailbit 置位,则返回 true
s.bad()若流 sbadbit 置位,则返回 true
s.good()若流 s 处于有效状态,则返回 true
s.clear()将流 s 中所有条件状态位复位,将流的状态设置成有效,返回 void
s.clear(flags)将流 s 中指定的条件状态位复位,返回 void
s.setstate(flags)根据给定的标志位,将流 s 中对应的条件状态位置位,返回 void
s.rdstate()返回流 s 的当前条件状态,返回值类型为 strm::iostate

上表中, strm 是一种 IO 类型,(如 istream ), s 是一个流对象。

# 管理输出缓冲

  • 每个输出流都管理一个缓冲区,执行输出的代码,文本串可能立即打印出来,也可能被操作系统保存在缓冲区内,随后再打印。
  • 刷新缓冲区,可以使用如下 IO 操纵符:
    • endl :输出一个换行符并刷新缓冲区。
    • flush :刷新流,单不添加任何字符。
    • ends :在缓冲区插入空字符 null ,然后刷新。
    • unitbuf :告诉流接下来每次操作之后都要进行一次 flush 操作。
    • nounitbuf :回到正常的缓冲方式。

# Exercise 8.1

编写函数,接受一个 istream& 参数,返回值类型也是 istream& 。此函数须从给定流中读取数据,直至遇到文件结束标识时停止。它将读取的数据打印在标准输出上。完成这些操作后,在返回流之前,对流进行复位,使其处于有效状态。

解:

std::istream& func(std::istream &is)
{
    std::string buf;
    while (is >> buf)
        std::cout << buf << std::endl;
    is.clear();
    return is;
}

# Exercise 8.2

测试函数,调用参数为 cin

解:

#include <iostream>
using std::istream;
istream& func(istream &is)
{
    std::string buf;
    while (is >> buf)
        std::cout << buf << std::endl;
    is.clear();
    return is;
}
int main()
{
    istream& is = func(std::cin);
    std::cout << is.rdstate() << std::endl;
    return 0;
}

# Exercise 8.3

什么情况下,下面的 while 循环会终止?

while (cin >> i) /*  ...    */

解:

badbitfailbiteofbit 的任一个被置位,那么检测流状态的条件会失败。

# File Input and Output


# 文件输入输出

  • 头文件 fstream 定义了三个类型来支持文件 IO:
    • ifstream 从一个给定文件读取数据。
    • ofstream 向一个给定文件写入数据。
    • fstream 可以读写给定文件。
  • 文件流:需要读写文件时,必须定义自己的文件流对象,并绑定在需要的文件上。

# fstream 特有的操作

操作解释
fstream fstrm;创建一个未绑定的文件流。
fstream fstrm(s);创建一个文件流,并打开名为 s 的文件, s 可以是 string 也可以是 char 指针
fstream fstrm(s, mode);与前一个构造函数类似,但按指定 mode 打开文件
fstrm.open(s)打开名为 s 的文件,并和 fstrm 绑定
fstrm.close()关闭和 fstrm 绑定的文件
fstrm.is_open()返回一个 bool 值,指出与 fstrm 关联的文件是否成功打开且尚未关闭

上表中, fstream 是头文件 fstream 中定义的一个类型, fstrm 是一个文件流对象。

# 文件模式

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

# Exercise 8.4

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

解:

void ReadFileToVec(const string& fileName, vector<string>& vec)
{
    ifstream ifs(fileName);
    if (ifs)
    {
        string buf;
        while (getline(ifs, buf))
            vec.push_back(buf);
    }
}

# Exercise 8.5

重写上面的程序,将每个单词作为一个独立的元素进行存储。
解:

void ReadFileToVec(const string& fileName, vector<string>& vec)
{
    ifstream ifs(fileName);
    if (ifs)
    {
        string buf;
        while (ifs >> buf)
            vec.push_back(buf);
    }
}

# Exercise 8.6

重写 7.1.1 节的书店程序,从一个文件中读取交易记录。将文件名作为一个参数传递给 main

解:

#include <fstream>
#include <iostream>
#include "../ch07/ex7_26.h"
using std::ifstream; using std::cout; using std::endl; using std::cerr;
int main(int argc, char **argv)
{
    ifstream input(argv[1]);
    
    Sales_data total;
    if (read(input, total))
    {
        Sales_data trans;
        while (read(input, trans))
        {
            if (total.isbn() == trans.isbn())
                total.combine(trans);
            else
            {
                print(cout, total) << endl;
                total = trans;
            }
        }
        print(cout, total) << endl;
    }
    else
    {
        cerr << "No data?!" << endl;
    }
    
    return 0;
}

# Exercise 8.7

修改上一节的书店程序,将结果保存到一个文件中。将输出文件名作为第二个参数传递给 main 函数。

解:

#include <fstream>
#include <iostream>
#include "../ch07/ex7_26.h"
using std::ifstream; using std::ofstream; using std::endl; using std::cerr;
int main(int argc, char **argv)
{
    ifstream input(argv[1]);
    ofstream output(argv[2]);
    
    Sales_data total;
    if (read(input, total))
    {
        Sales_data trans;
        while (read(input, trans))
        {
            if (total.isbn() == trans.isbn())
                total.combine(trans);
            else
            {
                print(output, total) << endl;
                total = trans;
            }
        }
        print(output, total) << endl;
    }
    else
    {
        cerr << "No data?!" << endl;
    }
    
    return 0;
}

# Exercise 8.8

修改上一题的程序,将结果追加到给定的文件末尾。对同一个输出文件,运行程序至少两次,检验数据是否得以保留。

解:

#include <fstream>
#include <iostream>
#include "../ch07/ex7_26.h"
using std::ifstream; using std::ofstream; using std::endl; using std::cerr;
int main(int argc, char **argv)
{
    ifstream input(argv[1]);
    ofstream output(argv[2], ofstream::app);
    
    Sales_data total;
    if (read(input, total))
    {
        Sales_data trans;
        while (read(input, trans))
        {
            if (total.isbn() == trans.isbn())
                total.combine(trans);
            else
            {
                print(output, total) << endl;
                total = trans;
            }
        }
        print(output, total) << endl;
    }
    else
    {
        cerr << "No data?!" << endl;
    }
    
    return 0;
}

# string Streams


# string 流

  • 头文件 sstream 定义了三个类型来支持内存 IO:
    • istringstreamstring 读取数据。
    • ostringstreamstring 写入数据。
    • stringstream 可以读写给定 string

# stringstream 特有的操作

操作解释
sstream strm定义一个未绑定的 stringstream 对象
sstream strm(s)s 初始化对象
strm.str()返回 strm 所保存的 string 的拷贝
strm.str(s)s 拷贝到 strm 中,返回 void

上表中 sstream 是头文件 sstream 中任意一个类型。 s 是一个 string


# Exercise 8.9

使用你为 8.1.2 节第一个练习所编写的函数打印一个 istringstream 对象的内容。

解:

#include <iostream>
#include <sstream>
using std::istream;
istream& func(istream &is)
{
    std::string buf;
    while (is >> buf)
        std::cout << buf << std::endl;
    is.clear();
    return is;
}
int main()
{
    std::istringstream iss("hello");
    func(iss);
    return 0;
}

# Exercise 8.10

编写程序,将来自一个文件中的行保存在一个 vector 中。然后使用一个 istringstreamvector 读取数据元素,每次读取一个单词。

解:

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
using std::vector; using std::string; using std::ifstream; using std::istringstream; using std::cout; using std::endl; using std::cerr;
int main()
{
    ifstream ifs("../data/book.txt");
    if (!ifs)
    {
        cerr << "No data?" << endl;
        return -1;
    }
    
    vector<string> vecLine;
    string line;
    while (getline(ifs, line))
        vecLine.push_back(line);
    
    for (auto &s : vecLine)
    {
        istringstream iss(s);
        string word;
        while (iss >> word)
            cout << word << endl;
    }
    
    return 0;
}

# Exercise 8.11

本节的程序在外层 while 循环中定义了 istringstream 对象。如果 record 对象定义在循环之外,你需要对程序进行怎样的修改?重写程序,将 record 的定义移到 while 循环之外,验证你设想的修改方法是否正确。

解:

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using std::vector; using std::string; using std::cin; using std::istringstream;
struct PersonInfo {
    string name;
    vector<string> phones;
};
int main()
{
    string line, word;
    vector<PersonInfo> people;
    istringstream record;
    while (getline(cin, line))
    {
        PersonInfo info;
        record.clear();
        record.str(line);
        record >> info.name;
        while (record >> word)
            info.phones.push_back(word);
        people.push_back(info);
    }
    
    for (auto &p : people)
    {
        std::cout << p.name << " ";
        for (auto &s : p.phones)
            std::cout << s << " ";
        std::cout << std::endl;
    }
    
    return 0;
}

# Exercise 8.12

我们为什么没有在 PersonInfo 中使用类内初始化?

解:

因为这里只需要聚合类就够了,所以没有必要在 PersionInfo 中使用类内初始化。

# Exercise 8.13

重写本节的电话号码程序,从一个命名文件而非 cin 读取数据。

解:

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
using std::vector; using std::string; using std::cin; using std::istringstream;
using std::ostringstream; using std::ifstream; using std::cerr; using std::cout; using std::endl;
using std::isdigit;
struct PersonInfo {
    string name;
    vector<string> phones;
};
bool valid(const string& str)
{
    return isdigit(str[0]);
}
string format(const string& str)
{
    return str.substr(0,3) + "-" + str.substr(3,3) + "-" + str.substr(6);
}
int main()
{
    ifstream ifs("../data/phonenumbers.txt");
    if (!ifs)
    {
        cerr << "no phone numbers?" << endl;
        return -1;
    }
    string line, word;
    vector<PersonInfo> people;
    istringstream record;
    while (getline(ifs, line))
    {
        PersonInfo info;
        record.clear();
        record.str(line);
        record >> info.name;
        while (record >> word)
            info.phones.push_back(word);
        people.push_back(info);
    }
    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())
            cout << entry.name << " " << formatted.str() << endl;
        else
            cerr << "input error: " << entry.name
                 << " invalid number(s) " << badNums.str() << endl;
    }
    return 0;
}

# Exercise 8.14

我们为什么将 entrynums 定义为 const auto&

解:

它们都是类类型,因此使用引用避免拷贝。
在循环当中不会改变它们的值,因此用 const

# Chapter Summary

🍓:)