C++ Primer 第八章 IO库

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
  • 为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵 wchar_t 类型的数据。
  • wcin wcout wcerr 是分别对应 cin cout cerr 的宽字符版对象
  • 宽字符版本的类型和对象与其对应的普通 char 版本的类型定义在同一个头文件中

类型间的转换

  • 标准库通过继承机制使我们能忽略这些不同类型的流之间的差异
  • ifstream 和 istringstream 都继承自 istream
  • ofstream 和 ostringstream 都继承自 ostream

IO 对象无拷贝或赋值

ofstream out1, out2;
out1 = out2; // 错误:不能对流对象赋值
ofstream print(ofstream); // 错误:不能初始化 ofstream 参数
out2 = print(out2); // 错误:不能拷贝流对象
  • 进行 IO 操作的函数通常以引用方式传递和返回流。
  • 读写一个 IO 对象会改变其状态,因此传递和返回的引用不能是 const 的

条件状态

帮助我们 访问 和 操作 流的条件状态的函数和标志

strm::iostate strm 是一种 IO 类型。iostate 是一种机器相关的类型,提供了表达条件状态的完整功能
strm::badbit strm::badbit 用来指出流已崩溃
strm::failbit strm::failbit 用来指出一个 IO 操作失败了
strm::eofbit strm::eofbit 用来指出流到达了文件结束
strm::goodbit 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 中所有条件状态位复位,将流的状态设置为有效。返回 void
s.clear(flags) 根据给定的 flags 标志位,将流 s 中对应条件状态位复位。flags 的类型为 strm::iostate。返回 void
s.setstate(flags) 根据给定的 flags 标志位,将流 s 中对应条件状态位置位。flags 的类型为 strm::iostate。返回 void
s.rdstate() 返回流 s 的当前条件状态,返回值类型为 strm::iostate

查询流的状态

  • badbit 表示系统级错误,如不可恢复的读写错误
  • 在发生可恢复错误后,failbit 被置位
  • 如果到达文件结束位置,eofbit 和 failbit 都会被置位
  • goodbit 的值为 0 ,表示流未发生错误。如果 badbit、failbit 和 eofbit 任一个被置位,则检测流状态的条件会失败
  • good 在所有错误位均未置位的情况下返回 true
  • bad、fail 和 eof 则在对应错误位被置位时返回 true
  • 此外 badbit 被置位时,fail 也会返回 true。这意味着,使用 good 或 fail 是确定流的总体状态的正确方法
  • 将流当作条件使用的代码就等价于 !fail(),而 eof 和 bad 操作只能表示特定的错误

管理条件状态

  • rdstate 返回一个 iostate 值,对应流的当前状态
  • setstate 操作将给定条件位置位,表示发生了对应错误
  • clear 不接受参数的版本清除(复位)所有错误标志位。执行 clear() 后,调用 good 会返回 true
auto old_state = cin.rdstate(); // 记住 cin 的当前状态
cin.clear(); // 使 cin 有效
process_input(cin); // 使用 cin
cin.setstate(old_state); // 将 cin 值为原有状态
  • 带参数的 clear 版本接受一个 iostate 值,表示流的新状态
// 复位 failbit 和 badbit,保持其他标志位不变
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);

管理输出缓冲

  • 每个输出流都管理一个缓冲区,用来保存程序读写的数据
  • 有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作

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

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

刷新输出缓冲区

cout << "hi!" << endl; // 输出 hi! 和一个换行,然后刷新缓冲区
cout << "hi!" << flush; // 输出 hi!,然后刷新缓冲区,不附加任何额外字符
cout << "hi!" << ends; // 输出 hi! 和一个空字符,然后刷新缓冲区

unitbuf

  • 如果想每次输出操作后都刷新缓冲区,我们可以使用 unitbuf 操作符
  • unitbuf:接下来每次写操作之后都进行一次 flush 操作
  • nounitbuf:重置流,使其恢复正常的系统管理的缓冲区刷新机制
cout << unitbuf;
// 任何输出都立即刷新,无缓冲
cout << nounitbuf;

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

关联输入和输出流

tie 有两个重载的版本

  • 不带参数:返回指向输出流的指针。如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针,如果对象未关联到流,则返回空指针。
  • 带参数:接受一个指向 ostream 的指针,将自己关联到此 ostream。即 x.tie(&o) 将流 x 关联到输出流 o

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

cin.tie(&cout); // 仅仅用来展示,标准库将 cin 和 cout 关联在一起
// old_tie 指向当前关联到 cin 的流(如果有的话)
std::ostream *old_tie = cin.tie(nullptr); // cin 不再与其他流关联
// 将 cin 与 cerr 关联;这不是一个好主意,因为 cin 应该 关联到 cout
cin.tie(&cerr); // 读取 cin 会刷新 cerr
cin.tie(old_tie); // 重建 cin 和 cout 间的正常关联
  • 每个流同时最多关联到一个流,但多个流可以同时关联到同一个 ostream

文件输入输出

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

  • ifstream 从一个给定文件读取数据
  • ofstream 向一个给定文件写入数据
  • fstream 可以读写给定文件
  • 可以用 IO 运算符(<< 和 >>)来读写文件,可以用 getline 从一个 ifstream 读取数据
fstream fstrm; 创建一个未绑定的文件流。fstream 是头文件 fstream 中定义的一个类型
fstream fstrm(s); 创建一个 fstream,并打开名为 s 的文件。s 可以是 string 类型,
或者是一个指向 C 风格字符串的指针。
这些构造函数都是 explicit 的。默认的文件模式 mode 依赖于 fstream 的类型
fstream fstrm(s, mode); 与前一个构造函数类似,但按指定 mode 打开文件
fstrm.open(s) 打开名为 s 的文件,并将文件与 fstrm 绑定。
s 可以是一个 string 或一个指向 C 风格字符串的指针。
默认的文件 mode 依赖于 fstream 的类型。返回 void
fstrm.close() 关闭与 fstrm 绑定的文件。返回 void
fstrm.is_open() 返回一个 bool 值,指出与 fstrm 关联的文件是否成功打开且尚未关闭

使用文件流对象

std::ifstream in(ifile); // ifile 为文件名字符串,如果提供文件名 则 open 会自动被调用
std::ofstream out; // 输出文件流并未关联到任何文件

用 fstream 代替 iostream&

// 在本例中假定输入和输出的文件名是通过传递给 main 函数的参数来指定的
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;
}

成员函数 open 和 close

  • open 可以将一个空文件流对象与文件关联起来
ifstream in(ifile);
ofstream out;
out.open(ifile + ".copy");
// 如果调用 open 失败,failbit 会被置位
if (out) // 检查 open 是否成功
  • 一旦一个文件流已经打开,它就保持与对应文件的关联。为了将文件流关联到另外一个文件,必须首先关闭已经关联的文件
in.close();
in.open(ifile + "2");

自动构造和析构

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

因为 input 是 while 循环的局部变量,它在每个循环步中都要创建和销毁一次。当一个 fstream 对象离开其作用域时,与之关联的文件会自动关闭。在下一步循环中,input 会再次被创建。

文件模式

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 模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用
  • 与 ifstream 关联的文件默认以 in 模式打开
  • 与 ofstream 关联的文件默认以 out 模式打开
  • 与 fstream 关联的文件默认以 in 和 out 模式打开

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

// 在这几条语句中,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);
  • 唯一方法是显式指定 app 或 in 模式

每次调用 open 时都会确定文件模式

  • 在每次打开文件时,都要设置文件模式,可能是显式地设置,也可能是隐式地设置。当程序未指定模式时,就是用默认值。
ofstream out; // 未指定文件打开模式
out.open("scratchpad"); // 模式隐含设置为 输出 和 截断
out.close(); // 关闭 out
out.open("precious", ofstream::app); // 模式为 输出 和 追加
out.close();

string 流

  • istringstream 从 string 读取数据
  • ostringstream 向 string 写入数据
  • stringstream 既可以 从 string 读取数据也可向 string 写入数据
sstream strm; strm 是一个未绑定的 stringstream 对象。sstream 是头文件 sstream 中定义的一个类型
sstream strm(s); strm 是一个 sstream 对象,保存 string s 的一个拷贝。此构造函数是 explicit
strm.str() 返回 strm 所保存的 string 的拷贝
strm.str(s) 将 string s 拷贝到 strm 中。返回 void

使用 istringstream

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;
}

使用 ostringstream

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;
}
posted @   HuiPuKui  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示