12.3 二进制文卷的读写

12.3.1 二进制流

二进制流中内容是内存数据的映像,两者完全一致。

由于二进制流不涉及内存数据与格式化字符序列之间的转换,因而读写更为快捷,效率更高且没有精度损失。

然而二进制数据并不适合以人类理解的方式显示,因此,二进制数据文卷并非是供人类阅读的文卷,而是提供给程序或计算机设备阅读的。

如图12-8所示,描绘了二进制流与内存中的数据之间的关系。

12.3 二进制文卷的读写 - 图1

图12-8 二进制流是内存中数据的直接拷贝

12.3.2 用fwrite()写二进制文卷

fwrite()函数被称为直接输出函数,它在C99中的函数原型如下。

size_t fwrite (void restrict ptr, size_t size, size_t nmemb, FILE restrict stream);

该函数的功能是“ptr”这个地址开始,从内存中读取“nmemb”个长度为“size”的数据对象复制(写入)到“stream”流中。返回值为写入了的对象的数目。如果发生错误,返回值小于“nmemb”的值。

“ptr”是一个“void *”类型的指针,这是因为事先无法知道要写入的数据对象究竟是何种类型。同时可以看到,一旦使用这种类型的指针,往往还需要描述数据对象的尺寸。

下面代码是使用fwrite()函数的示例。

程序代码12-4

12.3 二进制文卷的读写 - 图2

其中对fwrite()函数的调用显然不只有这一种写法,也可以写成其他形式,比如

fwrite((void )fa, sizeof(float) sizeof(fa)/sizeof(* fa), pf);

程序运行后,如没有发生错误,会发现在“C:”盘的根目录下出现一个名字为“shuju”的文件,大小为20字节。由于这个文件是二进制文件,所以无法用“记事本”那样的程序来查看其中的内容。

12.3.3 用fread()读二进制文卷

fread()函数被称为直接输入函数,它在C99中的函数原型如下。

12.3 二进制文卷的读写 - 图3

与fwrite()函数相反,这个函数的功能是从“stream”流中读取“nmemb”个长度为“size”的数据对象,依次保存在从“ptr”这个地址开始的内存之中。返回值为成功读入的元素数目,如果出现错误或读到文件结尾,则返回值可能小于“nmemb”。

用fread()读二进制文件的前提是,知道文件中数据的类型与格式,否则无法读出其中的数据。下面程序代码12-5以程序代码12-4中的输出文件作为输入。

程序代码12-5

12.3 二进制文卷的读写 - 图4

程序运行的输出结果如图12-9所示。

12.3 二进制文卷的读写 - 图5

图12-9 图fread()读二进制文卷

可以看到,程序正确地读入了前面程序写入的数据。

12.3.4 feof()函数和ferror()函数

feof()函数的函数原型为

12.3 二进制文卷的读写 - 图6

这个函数的功能是判断是否在读入输入流时遇到了文件结尾,如果是,返回非0值,否则返回值为0。

不少人容易对这个函数产生误解,以为读完流最后一个数据之后就到了文件的结尾。但实际上这个函数只有在流结尾之后读数据,才能判断出是否到了文件的结尾。也就是说,除非是在流的最后字节以外继续读数据并返回EOF之后,这个函数才可能用来判断前面返回EOF的原因是否是因为读到了流以外而返回。

此外,

12.3 二进制文卷的读写 - 图7

的功能是判断读写失败的原因是否是因为发生了错误,如果产生错误,返回值为非0,否则返回0。

程序代码12-6是程序代码12-5的另一种写法,这段代码中假定事先不知道需要读多少数据,在无法再读入之后,将判断是因为流已经结束还是发生了错误。

程序代码12-6

12.3 二进制文卷的读写 - 图8

程序运行的输出结果如图12-10所示。

12.3 二进制文卷的读写 - 图9

图12-10 feof()函数ferror()函数

由这段代码可以看到,feof()必须在fread()的返回值为0之后才可以用来判断是否已经达到了流的结尾。

12.3.5 讨论

feof()函数和ferror()函数既可以用于二进制流也可以用于文本流。但是fread()和fwrite()函数的应用对象显然只能是二进制流。

从概念上讲,字符这种数据由于是字符的编号,不存在转换的问题,所以纯粹的字符可以看成是文本流也可以看成二进制流。也就是说,对于二进制流也可以使用fgetc()、fputc()、fgets()、fputs()这些函数。但是如果是在Windows或DOS操作系统下,有一点需要注意,文本流中的'\n'与文本文件中的'\0xd'、'\0xa'相对应,且流中的'\0xla'被视为流的结束。