我们已经看过了一些使用 printf() 在控制台进行输入/输出(I/O)的示例。

但在本章中,我们将进一步深入探讨这些概念。

9.1 The FILE* Data Type

当我们在C语言中进行任何类型的输入/输出(I/O)时,都是通过一种你以 FILE* 类型形式获得的数据来实现的。这个 FILE* 包含了与I/O子系统通信所需的所有信息,比如你打开了哪个文件、你在文件中的位置,等等。

规范将这些称为 streams,即来自文件或任何来源的数据流。我将会交替使用“files”(文件)和“streams”(流)这两个词,但实际上你应该将“file”看作是“stream”的一种特殊情况。除了从文件中读取,还有其他方式将数据流输入程序。

稍后我们会看到如何从文件名获得一个已打开的 FILE*,但首先我想提到三种已为你打开并可用的流:

FILE* 名称 描述
stdin 标准输入 通常默认是键盘
stdout 标准输出 通常默认是屏幕
stderr 标准错误 通常默认也是屏幕

事实上,我们已经在隐式地使用这些流了。例如,下面的两个调用是等价的:

printf("Hello, world!\\n");
fprintf(stdout, "Hello, world!\\n");  // printf 输出到文件

不过后面会详细讲这个。

你还会注意到,stdout 和 stderr 都会输出到屏幕上。虽然一开始这看起来像是疏忽或多余,但实际上并非如此。典型的操作系统允许你将这两者的输出重定向到不同的文件,这样可以方便地将错误信息与普通的非错误输出分开。

例如,在类Unix系统上的POSIX shell(如sh、ksh、bash、zsh等)中,我们可以运行一个程序,将非错误输出(stdout)发送到一个文件,而将所有错误输出(stderr)发送到另一个文件。

./foo > output.txt 2> errors.txt   # 这个命令是Unix特有的

因此,你应该将严重的错误信息发送到 stderr,而不是 stdout。

后面会详细讲如何实现这一点。

9.2 Reading Text Files

数据流大致可以分为两种类型:文本(text)和二进制(binary)。

文本流允许对数据进行显著的转换,最显著的是对换行符的不同表示方式进行转换。文本文件在逻辑上是由换行符分隔的一系列行(lines)。为了保证可移植性,你的输入数据应始终以换行符结尾。

但通常的规则是:如果你可以用普通文本编辑器编辑该文件,那么它就是文本文件。否则,它就是二进制文件。关于二进制文件的内容稍后会讲。

那么我们开始动手吧——我们如何打开一个文件进行读取,并从中提取数据呢?