1 - 初探IO库

介绍

程序的基本任务是接收输入和生成输出(IO)。在C语言中,printf()scanf()可以灵活地处理IO,但它们不能很好地处理错误,处理自定义数据类型不够灵活,也不是类型安全的。

C++通过一种叫做 流(Stream) 的机制提供了更精良的IO方法,它很灵活,且面向对象,只需通过<<>>运算符就能操控数据流。

组成

C++中的IO库结构如下:

C++定义了ios这个基类,它用于定义输入输出的最基本操作,而istreamostream这两个类直接继承自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;
}

流对象的管理

  1. rdstate():获取当前流状态

  2. setstate(flag):将流对象设置为想要的状态

  3. clear():将流状态的所有标志位复位

  4. ignore():获取流中的数据并丢弃它们。

    函数原型:

    istream& ignore(streamsize n = 1, int delim = EOF)

    读取到前 n 个字符或在读这 n 个字符进程中遇到 delim 字符就停止,把读取的这些东西丢掉

参考资料

  • 飘零的落花 - 现代C++详解
  • C++20高级编程