13.7 其他标准I/O函数

ANSI标准库中包含超过三打的标准I/O系列函数。在这里不能覆盖其全部内容,只能简要地再描述一些其他的函数,使用户对可用的函数有一个更好的了解。这里将使用ANSI C原型列出每一个函数,以表明其参数和返回值。除了setvbuf()之外,这里讨论的函数在ANSI之前的实现中都是可用的。参考资料5的“添加了C99的标准ANSI C库”小节中列出了全部ANSI C标准I/O包。

13.7.1 int ungetc(int c, FILE * fp)函数

int ungetc(int c,FILE * fp)函数将c指定的字符放回输入流中。如果向输入流中放入了一个字符,下一次调用标准输入函数就会读入那个字符(请参见图13.2)。例如,假定需要一个函数读取下一个冒号前的全部字符,但是不包括冒号本身。可以使用getchar()或者getc()函数进行读入,直到将冒号读入为止,然后使用ungetc()函数将冒号放回输入流中。ANSI C标准保证每次只会放回一个字符。如果一个C实现允许将一行里的多个字符放回输入流,那么输入函数就会以与放回时相反的顺序来读入。

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

阅读 ‧ 电子书库

图13.2 ungetc()函数
13.7.2 int fflush()函数

fflush的原型是:

阅读 ‧ 电子书库

调用fflush()函数可以将缓冲区中任何未写的数据发送到一个由fp指定的输出文件中去。这个过程称为刷新缓冲区(flushing a buffer)。如果fp是一个空指针,将刷新掉所有的输出缓冲。对一个输入流使用fflush()函数的效果没有定义。只要最近一次使用流的操作不是输入操作,就可以对一个更新流(任何读-写模式的流)使用这个函数。

13.7.3 int setvbuf()函数

setvbuf()函数的原型是:

阅读 ‧ 电子书库

setvbuf()函数建立了一个供标准I/O函数使用的替换缓冲区。打开文件以后,在没有对流进行任何操作以前,可以调用这个函数。由指针fp来指定流,buf指向将使用的存储区。如果buf的值不是NULL,就必须创建这个缓冲区。例如,可以声明一个1024个字符的数组,然后传递该数组的地址。但是,如果buf的值为NULL,函数会自动为自己分配一个缓冲区。size变量为setvbuf()函数指定数组的大小(size_t类型是一种派生整数类型;请参见第5章“运算符、表达式和语句”)。mode将从下列选项中选取:_IOFBF表示完全缓冲(缓冲区满的时候刷新),_IOLBF表示行缓冲(缓冲区满的时候或者一个新行写入的时候刷新),_IONBF表示无缓冲。如果成功执行,函数会返回零值,否则返回一个非零值。

假定有一个处理存储数据对象(每个对象的大小为3 000字节)的程序,就可以使用setvbuf()函数创建一个缓冲区,其大小和该数据对象的大小相符。

13.7.4 二进制I/O:fread()和fwrite()函数

fread()和fwrite()函数是接下来要讨论的主题,不过首先我们需要了解一些背景知识。以前所使用的标准I/O函数都是面向文本的,用于处理字符和字符串。如果要把数字数据保存到一个文件中,该怎么办呢?的确可以使用fprintf()函数和%f格式保存一个浮点值,不过那样就将它作为字符串存储了。例如,下面的序列将num作为一个8字符的字符串0.333333存储:

阅读 ‧ 电子书库

使用%.2f说明符可以杷它存储为4字符的字符串0.33。便用%.12f说明符可以把它存储为14字符的字符串0.333333333333。改变说明符可以改变存储这一值所需的空间大小;这也会导致存储不同的值。在num的值存为0.33以后,读取文件的时候就没有办法恢复其完整的精度。总之,fprintf()函数以一种可能改变数字值的方式将其转换成为字符串。

最精确和一致的存储数字的方法就是使用与程序所使用的相同的位格式。所以,一个double值就应该存储在一个double大小的单元中。如果把数据存储在一个使用与程序具有相同表示方法的文件中,就称数据以二进制形式存储。这中间没有从数字形式到字符串形式的转换。对于标准I/O, fread()和fwrite()函数提供了这种二进制服务(请参见图13.3)。

阅读 ‧ 电子书库

图13.3 二进制输出和文本输出

实际上,所有的数据都是以二进制的方式进行存储的。甚至连字符也都是使用字符编码的二进制表示来存储。然而,如果文件中的全部数据都以字符编码的形式被解读,我们才称该文件包含文本数据。如果这些数据的部分或者全部以二进制形式的数字数据被解读,就称文件包含二进制数据(另外,包含表示机器语言指令数据的文件也是二进制文件)。

术语“二进制”和“文本”的使用可能会造成混淆。ANSI C认可两种打开文件的模式:二进制模式和文本模式。很多操作系统认可两种文件格式:二进制格式和文本格式。信息可以作为二进制数据或者文本数据存储和读取。这些都是互相关联的,但又不完全相同。可以用二进制模式打开文本格式的文件,可以将文本存储在二进制格式的文件中,也可以使用getc()函数复制包含二进制数据的文件。不过通常情况下,还是使用二进制模式将二进制数据存储到二进制格式的文件中。与之类似,用的最频繁的还是使用以文本模式打开的文本文件中的文本数据(字处理器产生的文件通常都是二进制文件,因为其中包含了很多描述字体和格式的非文本信息)。

13.7.5 size_t fwrite()函数

fwrite()函数的原型是:

阅读 ‧ 电子书库

fwrite()函数将二进制数据写入文件。size_t类型是根据标准C类型定义的。它是sizeof运算符返回的类型,通常是unsigned int类型,不过具体的实现中可以选择其他类型。指针ptr是要写入的数据块的地址。size表示要写入的数据块的大小(以字节为单位)。nmemb表示数据块的数目。像一般函数那样,fp指定要写入的文件。例如,要保存一个256字节大小的数据对象(如一个数组),可以这样做:

阅读 ‧ 电子书库

这一调用将一块256字节大小的数据块从缓冲区写入到文件。再者,要保存一个包含10个double值的数组,可以这样做:

阅读 ‧ 电子书库

这一调用将earnings数组中的数据写入文件,数据分成10块,每块都是double大小。

您也许会注意到fwrite()原型中有一个奇怪的声明void* ptr。fwrite()的一个问题就是它的第一个参数不是一个固定类型。比如,第一个例子中使用了字符指针buffer,第二个例子中使用了double指针earnings。在ANSI C函数原型下,这些实际参数都被转换成为指向void的指针,这种指针可以作为一种普通的指针类型工作(ANSI之前的C对这一参数使用char *类型,需要将实际参数的类型指派为这一类型)。

fwrite()函数返回成功写入的项目数。正常情况下,它与nmemb相等,不过如果有写入错误的话,返回值就会小于nmemb。

13.7.6 size_t fread()函数

fread()函数的原型是:

阅读 ‧ 电子书库

fread()函数与fwrite()函数的参数相同。这时,ptr为读入文件数据的内存存储地址,fp指定要读取的文件。使用这一函数来读取通过fwrite()写入的文件数据。例如,要恢复前一例子中保存的包含10个double值的数组,可以使用以下函数调用:

阅读 ‧ 电子书库

该调用将10个double值复制到earnings数组中。

fread()函数返回成功读入的项目数。正常情况下,它与nmemb相等;不过如果有读取错误的话,返回值就会小于nmemb。

13.7.7 int feof(FILE * fp)和int ferror(FILE * fp)函数

当标准输入函数返回EOF时,通常表示已经到达了文件结尾。可是,这也有可能表示发生了读取错误。使用feof()和ferror()函数可以区分这两种可能性。如果最近一次输入调用检测到文件结尾,feof()函数返回一个非零值,否则返回零值。如果发生读写错误,ferror()函数返回一个非零值,否则返回零值。

13.7.8 一个fread()和fwrite()的例子

我们在程序中使用上述一些函数将一系列文件的内容追加到另一个文件的结尾。一个问题是把文件信息传送到程序中,这可以用交互式或者用命令行参数方式完成。这里使用第一种方法,下面几行是程序设计的计划:

● 请求一个目的文件名,并打开该文件。

● 使用一个循环请求源文件。

● 依次以读取模式打开每个源文件,并将其内容追加到目的文件。

为了示范setvbuf()函数的使用,我们使用它来指定一个不同的缓冲区大小。下面详细分析了打开目的文件的具体步骤:

● 以追加模式打开最后一个命令行文件。

● 如果不能成功打开则退出。

● 为这个文件建立一个1024字节的缓冲区。

● 如果不能完成则退出。

与之类似,我们可以通过下列步骤来详细描述每个文件的复制过程:

● 如果该文件和目的文件相同,则跳到下一个文件。

● 如果不能以读取模式打开该文件,则跳到下一个文件。

● 把该文件的内容添加到目的文件中。

作为练习,我们使用fread()和fwrite()进行复制。程序清单13.6列出了结果程序。

程序清单13.6 append.c程序

阅读 ‧ 电子书库

阅读 ‧ 电子书库

下面的代码创建了一个供目的文件使用的1024字节大小的缓冲区:

阅读 ‧ 电子书库

如果setvbuf()不能创建这个缓冲区,它返回一个非零值,上面的代码将终止程序。使用同样方法可以为正被复制的文件建立一个1024字节大小的缓冲区。通过用NULL作为setvbuf()的第二个参数,我们让函数负责为缓冲区分配存储空间。

下面的代码防止程序将文件追加到它自身:

阅读 ‧ 电子书库

参数file_app代表目的文件名,file_src代表当前被处理文件的名字。

append()函数完成复制任务。这里没有一次复制一个字节,而是利用fread()和fwrite()每次复制1024个字节:

阅读 ‧ 电子书库

因为由dest指定的文件是以追加模式打开的,所以源文件逐个被添加到目的文件的结尾。注意数组temp是具有静态时期(表示它的分配发生在编译时,而不是每次调用append()函数时)和代码块作用域的,这意味着它是该函数私有的。

本例中使用文本模式打开文件;通过使用“ab”和“rb”模式,该程序也可以处理二进制文件。

13.7.9 使用二进制I/O进行随机存取

随机存取最常用于使用二进制I/O写入的二进制文件,所以我们看一个简短的例子。程序清单13.7中的程序创建了一个double类型数值的文件,然后允许您访问这些内容。

程序清单13.7 randbin.c程序

阅读 ‧ 电子书库

程序首先创建了一个数组,然后在其中存放了一些值。接着它以二进制模式创建了一个名为numbers.dat的文件,然后使用fwrite()把数组的内容复制到文件中。每个double值的64位模式从内存复制到文件中。不能通过文本编辑器来读取结果的二进制文件,因为没有把这些值翻译为字符串。但是,存储在文件中的每个值和它在内存中的存储方式是完全相同的,因此没有损失任何精度。而且每个值都在文件中精确占用64位存储空间,所以可以很容易地计算出每个值的位置。

程序的第二部分为了读取打开文件,请求用户输入一个值的索引。通过将索引和double值占用的字节数相乘就可以得到一个文件中的位置。然后程序使用fseek()定位到该位置,利用fread()读取该位置的数据值。注意这里并没有使用格式说明符。而是由fread()把从该位置开始的8个字节复制到内存中由&value指定的位置,然后使用printf()显示value的值。下面是一个运行示例:

阅读 ‧ 电子书库