C++_Primer08.io

IO库

IO 类

为了支持不同类型的IO处理,在 istream 和 ostream 之外,标准库还定义了其他一些 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

带 w 的类型是与宽字符有关,除了上表中的对象和函数,还有 wcin, wcout, wcerr等

ifstream 和 istringstream 继承自 istream;ofstream 和 ostringstream 都继承自 ostream

IO 对象无拷贝或赋值

不能拷贝或对 IO 对象赋值:

ofstream out1, out2;
out1 = out2;                // 错误:不能对流对象赋值
ofstream print(ofstream);   // 错误:不能初始化 ofstream 参数
out2 = print(out2);         // 错误:不能拷贝流对象

使用 IO 作为参数的函数通常以引用方式传递和返回流;
读写一个 IO 对象会改变其状态,因此传递和返回的引用不能是 const 的。

条件状态

condition state

IO 类所定义的一些函数和标志,可以帮助我们访问和操纵流的条件状态

条件状态 说明
strm::iostate strm 是一种 IO 类型;iostate 是一种机器相关的类型,提供了表达条件状态的完整功能
strm::badbit 流已崩溃
strm::failbit 表示一个 IO 操作失败了
strm::eofbit 表示流到达了文件结束
strm::goodbit 表示流未处于错误状态,此值保证为0
s.eof() 若流 s 的 eofbit 置位,则返回 true
s.fail() 若流 s 的 failbit 或 badbit 置位,则返回 true
s.bad() 若流 s 的 batbit 置位,则返回 true
s.good() 若流 s 处于有效状态,则返回 true
s.clear() 若流 s 中所有状态复位,将流的状态设置为有效,返回 void
s.clear(flags) 根据给定的 flags 标志位,将流 s 中对应条件状态位置空。flags 的类型为 strm::iostate. 返回 void
s.rdstate() 返回流 s 的当前条件状态,返回值类型为 strm::iostate

strm:: 是泛指,代表: istream, ifstream 等

一个流一旦发生错误,其后续 IO 操作都会失败;
使用一个流之前通常需要检查它是否处于良好状态,确定一个流对象状态的最简单方法是将它当做一个条件来使用:

string word;
while (cin >> word){
    // ...
}

流的状态

badbit 表示系统级错误,一旦 badbit 被置位,流就无法再使用了
failbit 被置位的错误可以被恢复,流还可以继续使用,比如期望读入一个 int 时读入了一个字符的错误
如果文件结束,eofbit 和 failbit 都会被置位
goodbit 值为 0,表示流未发生错误
badbit, failbit 和 eofbit 任一个被置位,则检测流状态的条件会失败

管理条件状态

  • rdstate: 返回流的当前状态
  • setstate: 置位
  • clear: 清除错误标志位;调用 clear() 后调用 good 会返回 true
    • clear 函数有两个重载函数
auto old_state = cin.rdstate();     // 记住当前状态
cin.clear();                        // 使 cin 有效
process_input(cin);                 // 使用 cin
cin.setstate(old_state);            // 还原状态

使用 clear 函数复位指定条件状态:

// 复位 failbit 和 badbit,保持其他标志位不变
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);

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

#include <iostream>
#include <string>

using namespace std;

istream& read(istream&);

int main(int argc, char** argv){
    read(cin);

    return 0;
}

istream& read(istream& is) {
    istream::iostate state = is.rdstate();
    is.clear();
    string s;
    while (is >> s) {
        cout << s << endl;
    }
    is.setstate(state);

    return is;
}

管理输出缓冲区

os << "please enter a value: ";

在向控制台打印的时候,文本串可能会立即被打印,也可能被操作系统保存在缓冲区中随后打印。有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很好时,允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升。

导致缓冲刷新的原因有很多:

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

刷新输出缓冲区

  • endl:换行并刷新缓冲区
  • flush:刷新缓冲区,但不输出任何额外字符
  • ends:向缓冲区插入一个空字符,然后刷新缓冲区
cout << "hi!" << endl;
cout << "hi!" << flush;
cout << "hi!" << ends;

unitbuf 操作符

// 设置刷新方式,所有输出操作都会立即刷新缓冲区
cout << uintbuf;
// 回到正常缓冲方式
cout << nouintbuf;

如果程序崩溃,输出缓冲区不会被刷新

关联输入和输出流

使用 tie 函数管理关联的输出流。有两个重载函数,不带参数的函数返回指向输出流的指针;带一个 ostream 指针参数的函数制定要关联的输出流

既可以将一个 istream 对象关联到另一个 ostream,也可以将一个 ostream 关联到另一个 ostream:

// 仅用于演示,默认情况下,标准库的 cin 和 cout 关联在一起
cin.tie(&cout);
// old_tie 指向当前关联到 cin 的流
ostream* old_tie = cin.tie(nullptr);    // cin 不再与其他流关联
// 读取 cin 会刷新 cerr 而不是 cout(不是一个好主意)
cin.tie(&cerr);
// 重建 cin 和 cout 之间的正常关联
cin.tie(old_tie);

文件输入输出

头文件 fstream 定义了三个类型来支持文件IO:

  • ifstream:读
  • ofstream:写
  • fstream: 可同时读写
接口 说明
fstream fstrm; 创建一个未绑定的文件流
fstream fstrm(s); s 可以是 string 类型,或者是一个 C 风格字符串的指针。这些构造函数都是 explicit 的
fstream fstrm(s, mode); 指定 mode:读,写,二进制模式
fstrm.open(s) 默认的文件mode 依赖于 fstream 的类型
fstrm.close()
fstrm.is_open() 判断是否成功打开且尚未关闭

自动构造和析构

for (auto p = argv + 1; p != argv + argc; ++p) {
    ifstream input(* p);
    if (input){
        process(input);
    } else {
        cerr << "couldn't open: " + string(*p) << endl;
    }
}

每次循环开始时构造一个 ifstream 对象 input,并在每次循环结束后销毁,与之关联的文件会自动关闭,close() 会被自动调用。

练习8.4:按行读文件,保存到 vector 中,并打印

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

int main(int argc, char** argv){
    std::string input_file("is.cpp");
    std::ifstream is(input_file);

    if(!is){
        std::cout << input_file << " open failed." << std::endl;
        return 1;
    }

    std::vector<std::string> lines;
    std::string line;
    while (getline(is, line)){
        lines.push_back(line);
    }

    int i, count = lines.size();
    for (i = 0; i < count; ++i){
        std::cout << lines.at(i) << std::endl;
    }

    is.close();
    return 0;
}

文件模式

模式 说明
in 读(ifstream)
out 写(ofstream)
app 每次写操作前均定位到文件末尾(ofstream)
ate 打开文件后立即定位到文件末尾
trunc 截断文件,丢弃文件打开前的内容
binary 二进制方式
  • 只有当 out 也被设定时才可设定 trunc 模式
  • 只要 trunc 没被设定,就可以设定 app 模式。app模式下,即使没有显式指定 out 模式,文件也总是以输出方式被打开
  • 默认情况下,即使没有指定 trunc,以out模式打开的文件也会被截断。为了保留文件打开前的内容,必须同时指定app模式,或同时指定in模式(fstream)
  • ate 和 binary 模式可用于任何类型的文件流对象,且可以和其他任何文件模式组合使用
  • 每个文件流都定义了一个默认的文件模式
    • ifstream 默认in模式
    • ofstream 默认 out 模式
    • fstream 默认 in 和 out 模式

string 流

  • istringstream: 从string读数据
  • ostringstream:向string写数据
  • stringstream: 既能读又能写
操作 说明
sstream strm; strm是一个未绑定的 stringstream 对象。sstream 是头文件 sstream 中定义的一个类型
sstream strm(s); strm保存了 string s 的一个拷贝。此构造函数是 explicit 的
strm.str() 返回 strm 所保存的 string 的拷贝
strm.str(s) 将 string s 拷贝到 strm 中。返回 void

例:
读入一个电话本,其中每个人一行,每个人可能有一个电话,也可能有多个电话。

morgan 1234567 7654321
drew 13579
lee 12344321 56788765
struct PersonInfo {
    string name;
    vector<string> phones;
};

int main(int argc, char** argv){
    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);
    }

    return 0;
}

小结

  • iostream: 处理控制台IO
  • fstream: 处理命名文件IO
  • stringstream: 完成内存 string 的IO

fstream 和 stringstream 都继承自类 iostream,输入类都继承自 istream,输出类都继承自 ostream.

每个IO对象都维护一组条件状态,用来指出此对象上是否可以进行IO操作。如果遇到错误,比如输入流上遇到文件末尾,则对象的状态变为失效,所有后续输入操作都不能执行,直到错误被纠正。

posted @ 2022-11-23 10:07  keep-minding  阅读(21)  评论(0)    收藏  举报