1 - 初探IO库
介绍
程序的基本任务是接收输入和生成输出(IO)。在C语言中,printf()和scanf()可以灵活地处理IO,但它们不能很好地处理错误,处理自定义数据类型不够灵活,也不是类型安全的。
C++通过一种叫做 流(Stream) 的机制提供了更精良的IO方法,它很灵活,且面向对象,只需通过<<或>>运算符就能操控数据流。
IO库的组成
C++中的IO库结构如下:
C++定义了ios这个基类,它用于定义输入输出的最基本操作,而istream和ostream这两个类直接继承自ios类。
首先是用户流/控制台流:
-
ostream类定义了数据从内存到输出设备流动的功能,例如cout -
istream类定义了数据从输入设备到内存流动的功能,例如cin -
iostream则综合上边的功能,定义了上边的输入输出流,我们引入这个头文件就能用用户流了:流 说明 cin输入流,从“输入控制台”读取数据 cout缓冲的输出流,向“输出控制台”写入数据 cerr非缓冲的输出流,向“错误控制台”写入数据
(通常等同于“输出控制台”)clogcerr的缓冲版本此外,这些流还有宽字符版本(
wcin等),适用于中文等语言。
然后是文件流:
ifstream继承自istream,定义了数据从磁盘到内存流动的功能。ofstream继承自ostream,定义了数据从内存到磁盘流动的功能。fstream则综合上边的功能,我们引入这个头文件就能用文件流了。
最后是 字符串流,可以让我们像处理流一样处理字符串:
istringstream继承自istream,定义了数据从指定字符串到特定内存流动的功能。ostringstream继承自ostream,定义了数据从特定内存到指定字符串流动的功能。sstream则综合上边的功能,我们引入这个头文件就能用字符串流了。
注意事项
流对象的传递
形如
istream myCin(cin);
ifstream if1, if2;
if2 = if1;
的代码将无法通过编译。因此使用流对象时 无法使用值传递,一般使用引用传递,防止内存泄漏。一般的例子就是重载<</>>运算符。
流对象的状态
错误的状态
在IO操作过程中,可能会出现一些错误。有的错误可以被修复,而有的则发生在系统底层,已经超出了用户程序可以修正的范围。为了避免在IO过程中出错导致程序崩溃,我们在使用流对象时应判断流对象的状态。
可以通过rdstate()来查看流的状态,它返回iostate类型,在VS中是int,用不同的位代表不同的状态。常见的状态如下:
| 状态 | 说明 |
|---|---|
badbit | 系统级致命错误,此位为1说明流对象无法使用 |
failbit | 可修复的错误。当badbit为1时,此位也是1 |
eofbit | 数据流当前位置为文件尾。当此位是1时,failbit也是1 |
goodbit | 表示流对象没有任何错误 |
错误的检测
标准库还定义了一组成员函数用来查询流的状态:
.good():所有错误位均为0时,返回true.bad(),.fail(),.eof():对应位为1时返回true,用.fail()较准确
错误的处理
一段标准的输入处理代码如下:
int item;
// 确认cin状态良好, 且没有到达文件尾
// 可用if, while
while(cin >> item, !cin.eof())
{
// cin发生致命错误, 无法修复
if (cin.bad())
{
throw runtime_error("cin is corrupted");
}
// 本次cin操作失败
else if (cin.fail())
{
// 重置cin的状态, 并丢弃输入内容
cin.clear();
cin.ignore(numeric_limits<std::streamsize>::max(), '\n');
// 这里知道是格式错误, 直接输出错误信息
cerr << "Data format error, please try again" << endl;
}
// cin成功, 正常业务处理
cout << item << endl;
}
流对象的管理
-
rdstate():获取当前流状态 -
setstate(flag):将流对象设置为想要的状态 -
clear():将流状态的所有标志位复位 -
ignore():获取流中的数据并丢弃它们。函数原型:
istream& ignore(streamsize n = 1, int delim = EOF)读取到前 n 个字符或在读这 n 个字符进程中遇到 delim 字符就停止,把读取的这些东西丢掉
参考资料
- 飘零的落花 - 现代C++详解
- C++20高级编程
