🍎文件输入输出
2021-1-24
| 2023-8-2
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property
 

 
 

文件通信

有时,需要程序从文件中读取信息或把信息写入文件。这种程序与文件交互的形式就是文件重定向这种方法很简单,但是有一定限制。例如,假设要编写一个交互程序,询问用户书名并把完整的书名列表保存在文件中。如果使用重定向,应该类似于:
用户的输入被重定向到 bklist 中。这样做不仅会把不符合要求的文本写入 bklist,而且用户也看不到要回答什么问题。
C提供了更强大的文件通信方法,可以在程序中打开文件,然后使用特殊的I/O函数读取文件中的信息或把信息写入文件。
 
文件通常是在磁盘或固态硬盘上的一段已命名的存储区。对我们而言,stdio.h就是一个文件的名称,该文件中包含一些有用的信息。然而,对操作系统而言,文件更复杂一些。例如,大型文件会被分开储存,或者包含一些额外的数据,方便操作系统确定文件的种类。然而,这都是操作系统所关心的,程序员关心的是C程序如何处理文件(除非你正在编写操作系统)。
C把文件看作是一系列连续的字节,每个字节都能被单独读取。这与UNIX环境中(C的发源地)的文件结构相对应。由于其他环境中可能无法完全对应这个模型,C提供两种文件模式:文本模式和二进制模式。
 
文本模式和二进制模式
首先,要区分文本内容和二进制内容、文本文件格式和二进制文件格式,以及文件的文本模式和二进制模式。
所有文件的内容都以二进制形式储存。但是,如果文件最初使用二进制编码的字符(例如, ASCII或Unicode)表示文本(就像C字符串那样),该文件就是文本文件,其中包含文本内容。如果文件中的二进制值代表机器语言代码或数值数据(使用相同的内部表示,假设,用于long或double类型的值)或图片或音乐编码,该文件就是二进制文件,其中包含二进制内容。
UNIX用同一种文件格式处理文本文件和二进制文件的内容。不奇怪,鉴于C是作为开发UNIX的工具而创建的,C和UNIX在文本中都使用\n(换行符)表示换行。UNIX目录中有一个统计文件大小的计数,程序可使用该计数确定是否读到文件结尾。然而,其他系统在此之前已经有其他方法处理文件,专门用于保存文本。也就是说,其他系统已经有一种与UNIX模型不同的格式处理文本文件。例如,以前的OS X Macintosh文件用\r (回车符)表示新的一行。早期的MS-DOS文件用\r\n组合表示新的一行,用嵌入的Ctrl+Z字符表示文件结尾,即使实际文件用添加空字符的方法使其总大小是256的倍数。其他系统可能保持文本文件中的每一行长度相同,如有必要,用空字符填充每一行,使其长度保持一致。或者,系统可能在每行的开始标出每行的长度。
为了规范文本文件的处理,C 提供两种访问文件的途径:二进制模式和文本模式。在二进制模式中,程序可以访问文件的每个字节。而在文本模式中,程序所见的内容和文件的实际内容不同。程序以文本模式读取文件时,把本地环境表示的行末尾或文件结尾映射为C模式。例如,C程序在旧式Macintosh中以文本模式读取文件时,把文件中的\r转换成\n;以文本模式写入文件时,把\n转换成\r。或者,C文本模式程序在MS-DOS平台读取文件时,把\r\n转换成\n;写入文件时,把\n转换成\r\n。在其他环境中编写的文本模式程序也会做类似的转换。
除了以文本模式读写文本文件,还能以二进制模式读写文本文件。如果读写一个旧式MS-DOS文本文件,程序会看到文件中的\r 和\n 字符,不会发生映射。如果要编写旧式 Mac格式、MS-DOS格式或UNIX/Linux格式的文件模式程序,应该使用二进制模式,这样程序才能确定实际的文件内容并执行相应的动作。
notion image
虽然C提供了二进制模式和文本模式,但是这两种模式的实现可以相同。因为UNIX使用一种文件格式,这两种模式对于UNIX实现而言完全相同,Linux也是如此。
 
I/O的级别
除了选择文件的模式,大多数情况下,还可以选择I/O的两个级别(即处理文件访问的两个级别)。底层I/O(low-level I/O)使用操作系统提供的基本I/O服务。标准高级I/O(standard high-level I/O)使用C库的标准包和stdio.h头文件定义。因为无法保证所有的操作系统都使用相同的底层I/O模型,C标准只支持标准I/O包。有些实现会提供底层库,但是C标准建立了可移植的I/O模型,这里主要讨论这些I/O。
 
标准文件
C程序会自动打开3个文件,被称为标准输入(standard input)、标准输出(standard output)和标准错误输出(standard error output)。在默认情况下,标准输入是系统的普通输入设备,通常为键盘;标准输出和标准错误输出是系统的普通输出设备,通常为显示屏。
通常,标准输入为程序提供输入,它是getchar()scanf()使用的文件。程序通常输出到标准输出,它是putchar()puts()printf()使用的文件。重定向把其他文件视为标准输入或标准输出。标准错误输出提供了一个逻辑上不同的地方来发送错误消息。例如,如果使用重定向把输出发送给文件而不是屏幕,那么发送至标准错误输出的内容仍然会被发送到屏幕上。这样很好,因为如果把错误消息发送至文件,就只能打开文件才能看到。
 
 

标准I/O

与底层I/O相比,标准I/O包除了可移植以外还有两个好处。第一,标准I/O有许多专门的函数简化了处理不同I/O的问题。例如,printf()把不同形式的数据转换成与终端相适应的字符串输出。第二,输入和输出都是缓冲的。也就是说,一次转移一大块信息而不是一字节信息(通常至少512字节)。例如,当程序读取文件时,一块数据被拷贝到缓冲区(一块中介存储区域)。这种缓冲极大地提高了数据传输速率。程序可以检查缓冲区中的字节。缓冲在后台处理,所以让人有逐字符访问的错觉(如果使用底层I/O,要自己完成大部分工作)。
notion image
 

检查命令行参数

首先,程序检查argc的值,查看是否有命令行参数。如果没有,程序将打印一条消息并退出程序。字符串argv[0]是该程序的名称。显式使用argv[0]而不是程序名,错误消息的描述会随可执行文件名的改变而自动改变。这一特性在像 UNIX 这种允许单个文件具有多个文件名的环境中也很方便。但是,一些操作系统可能不识别argv[0],所以这种用法并非完全可移植。
exit()函数关闭所有打开的文件并结束程序。exit()的参数被传递给一些操作系统,包括 UNIX、Linux、Windows和MS-DOS,以供其他程序使用。通常的惯例是:正常结束的程序传递0,异常结束的程序传递非零值。不同的退出值可用于区分程序失败的不同原因,这也是UNIX和DOS编程的通常做法。但是,并不是所有的操作系统都能识别相同范围内的返回值。因此,C 标准规定了一个最小的限制范围。尤其是,标准要求0或宏EXIT_SUCCESS用于表明成功结束程序,宏EXIT_FAILURE用于表明结束程序失败。这些宏和exit()原型都位于stdlib.h头文件中。
根据ANSI C的规定,在最初调用的main()中使用return与调用exit()的效果相同。因此,在main()
但是要注意,我们说的是“最初的调用”。如果main()在一个递归程序中,exit()仍然会终止程序,但是return只会把控制权交给上一级递归,直至最初的一级。然后return结束程序。returnexit()的另一个区别是,即使在其他函数中(除main()以外)调用exit()也能结束整个程序。
 

fopen()函数

使用fopen()函数打开文件。该函数声明在stdio.h中。它的第1个参数是待打开文件的名称,更确切地说是一个包含文件名的字符串地址。第 2 个参数是一个字符串,指定待打开文件的模式。
模式字符串
含义
"r"
以读模式打开文件
"w"
以写模式打开文件,把现有文件的长度截为0,如果文件不存在,则创建一个新文件
"a"
以写模式打开文件,在现有文件末尾添加内容,如果文件不存在,则创建一个新文件
"r+"
以更新模式打开文件(即可以读写文件)
"w+"
以更新模式打开文件(即,读和写),如果文件存在,则将其长度截为0;如果文件不存在,则创建一个新文件
"a+"
以更新模式打开文件(即,读和写),在现有文件的末尾添加内容,如果文件不存在则创建一个新文件;可以读整个文件,但是只能从末尾添加内容
"rb"、"wb"、"ab"、"rb+"、"r+b"、"wb+"、"w+b"、"ab+"、"a+b"
与上一个模式类似,但是以二进制模式而不是文本模式打开文件
"wx"、"wbx"、"w+x"、"wb+x"、"w+bx"
(C11)类似非x模式,但是如果文件已存在或以独占模式打开文件,则打开文件失败
像UNIX和Linux这样只有一种文件类型的系统,带b字母的模式和不带b字母的模式相同。
新的C11新增了带x字母的写模式,与以前的写模式相比具有更多特性。第一,如果以传统的一种写模式打开一个现有文件,fopen()会把该文件的长度截为 0,这样就丢失了该文件的内容。但是使用带 x字母的写模式,即使fopen()操作失败,原文件的内容也不会被删除。第二,如果环境允许,x模式的独占特性使得其他程序或线程无法访问正在被打开的文件。
如果使用任何一种"w"模式(不带x字母)打开一个现有文件,该文件的内容会被删除,以便程序在一个空白文件中开始操作。然而,如果使用带x字母的任何一种模式,将无法打开一个现有文件。
程序成功打开文件后,fopen()将返回文件指针(file pointer),其他I/O函数可以使用这个指针指定该文件。文件指针(该例中是fp)的类型是指向FILE的指针,FILE是一个定义在stdio.h中的派生类型。文件指针fp并不指向实际的文件,它指向一个包含文件信息的数据对象,其中包含操作文件的I/O函数所用的缓冲区信息。因为标准库中的I/O函数使用缓冲区,所以它们不仅要知道缓冲区的位置,还要知道缓冲区被填充的程度以及操作哪一个文件。标准I/O函数根据这些信息在必要时决定再次填充或清空缓冲区。fp指向的数据对象包含了这些信息。
 

getc()和putc()函数

getc()putc()函数与getchar()putchar()函数类似。所不同的是,要告诉getc()putc()函数使用哪一个文件。
stdout作为与标准输出相关联的文件指针,定义在stdio.h中,所以putc(ch, stdout)putchar(ch)的作用相同。实际上,putchar()函数一般通过putc()来定义。与此类似,getchar()也通过使用标准输入的getc()来定义。
 

文件结尾

从文件中读取数据的程序在读到文件结尾时要停止。如何告诉程序已经读到文件结尾?如果 getc()函数在读取一个字符时发现是文件结尾,它将返回一个特殊值EOF。所以C程序只有在读到超过文件末尾时才会发现文件的结尾。
为了避免读到空文件,应该使用入口条件循环进行文件输入。鉴于getc()(和其他C输入函数)的设计,程序应该在进入循环体之前先尝试读取:
以上代码可简化为:
 

fclose()函数

fclose(fp)函数关闭fp指定的文件,必要时刷新缓冲区。对于较正式的程序,应该检查是否成功关闭文件。如果成功关闭,fclose()函数返回0,否则返回EOF:
如果磁盘已满、移动硬盘被移除或出现I/O错误,都会导致调用fclose()函数失败。
 

指向标准文件的指针

stdio.h头文件把3个文件指针与3个标准文件相关联,C程序会自动打开这3个标准文件
标准文件
文件指针
通常使用的设备
标准输入
stdin
键盘
标准输出
stdout
显示器
标准错误
stderr
显示器
这些文件指针都是指向FILE的指针,所以它们可用作标准I/O函数的参数,如fclose(fp)中的fp
 

一个简单的文件压缩程序

把一个文件中选定的数据拷贝到另一个文件中。该程序同时打开了两个文件,以"r"模式打开一个,以"w"模式打开另一个。以保留每3个字符中的第1个字符的方式压缩第1个文件的内容。最后,把压缩后的文本存入第2个文件。第2个文件的名称是第1个文件名加上.red后缀(此处的red代表reduced)。使用命令行参数,同时打开多个文件,以及在原文件名后面加上后缀,都是相当有用的技巧。这种压缩方式有限,但是也有它的用途(很容易把该程序改成用标准 I/O 而不是命令行参数提供文件名)。
为了构造新的输出文件名,该程序使用strncpy()把名称eddy拷贝到数组name中。参数LEN-5为.red后缀和末尾的空字符预留了空间。如果argv[2]字符串比LEN-5长,就拷贝不了空字符。出现这种情况时,程序会添加空字符。调用strncpy()后,name中的第1个空字符在调用strcat()函数时,被.red的.覆盖,生成了eddy.red
程序中还检查了是否成功打开名为eddy.red的文件。这个步骤在一些环境中相当重要,因为像strange.c.red这样的文件名可能是无效的。例如,在传统的DOS环境中,不能在后缀名后面添加后缀名(MSDOS使用的方法是用.red替换现有后缀名,所以strange.c将变成strange.red。例如,可以用strchr()函数定位(如果有的话),然后只拷贝点前面的部分即可)。 该程序同时打开了两个文件,所以要声明两个 FIFL 指针。注意,程序都是单独打开和关闭每个文件。同时打开的文件数量是有限的,这个限制取决于系统和实现,范围一般是10~20。相同的文件指针可以处理不同的文件,前提是这些文件不需要同时打开。
 

文件I/O

文件I/O函数要用FILE指针指定待处理的文件。与getc()putc()类似,这些函数都要求用指向 FILE 的指针(如,stdout)指定一个文件,或者使用fopen()的返回值。

fprintf()和fscanf()函数

文件I/O函数fprintf()fscanf()函数的工作方式与printf()scanf()类似,区别在于前者需要用第1个参数指定待处理的文件
 

fgets()和fputs()函数

fgets()函数的第1个参数和gets()函数一样,也是表示储存输入位置的地址(char * 类型);第2个参数是一个整数,表示待输入字符串的大小;最后一个参数是文件指针,指定待读取的文件。下面是一个调用该函数的例子:
fgets()函数读取输入直到第 1 个换行符的后面,或读到文件结尾,或者读取STLEN-1 个字符(以上面的 fgets()为例)。然后,fgets()在末尾添加一个空字符使之成为一个字符串。字符串的大小是其字符数加上一个空字符。如果fgets()在读到字符上限之前已读完一整行,它会把表示行结尾的换行符放在空字符前面。fgets()函数在遇到EOF时将返回NULL值,可以利用这一机制检查是否到达文件结尾;如果未遇到EOF则之前返回传给它的地址。
 
fputs()函数接受两个参数:第1个是字符串的地址,第2个是文件指针。该函数根据传入地址找到的字符串写入指定的文件中。和puts()函数不同,fputs()在打印字符串时不会在其末尾添加换行符。
由于fgets()保留了换行符,fputs()就不会再添加换行符。
 

随机访问fseek()和ftell()

有了 fseek()函数,便可把文件看作是数组,在 fopen()打开的文件中直接移动到任意字节处:
 

fseek()和ftell()的工作原理

  • fseek()的第1个参数是FILE指针,指向待查找的文件,fopen()应该已打开该文件。
  • fseek()的第二个参数是偏移量,该参数表示从起点开始要移动的距离。该参数必须是一个long类型的值,可以为正(前移)、负(后移)、0(保持不动)。
  • fseek()的第三个参数是模式,该参数确定起始点(参见下表列出的起始点模式)。根据ANSI标准,在stdio.h头文件中规定了几个表示模式的明示常量:
    • 模式
      偏移量的起始点
      数值
      SEEK_SET
      文件开始出
      0L
      SEEK_CUR
      当前位置
      1L
      SEEK_END
      文件末尾
      2L
      旧的实现可能缺少这些定义,可以使用数值0L、1L、2L分别表示这3种模式。L后缀表明其值是long类型。或者,实现可能把这些明示常量定义在别的头文件中。
 
下面是调用fseek()函数的一些示例,fp是一个文件指针:
如果一切正常,fseek()的返回值为0;如果出现错误(如试图移动的距离超出文件的范围),其返回值为-1。
 
ftell()函数返回类型是long,它返回的是参数指向文件的当前位置距文件开始处的字节数。ANSI C把它定义在stdio.h中。在最初实现的UNIX中,ftell()通过返回距文件开始处的字节数来确定文件的位置。文件的第1个字节到文件开始处的距离是0,以此类推。ANSI C规定,该定义适用于以二进制模式打开的文件,以文本模式打开文件的情况不同。
 
 

可移植性

理论上,fseek()ftell()应该符合UNIX模型。但是,不同系统存在着差异,有时确实无法做到与UNIX模型一致。因此,ANSI对这些函数降低了要求。下面是一些限制。 在二进制模式中,实现不必支持SEEK_END模式。因此无法保证程序的可移植性。移植性更高的方法是逐字节读取整个文件直到文件末尾。C 预处理器的条件编译指令提供了一种系统方法来处理这种情况。
在文本模式中,只有以下调用能保证其相应的行为。
notion image
不过,许多常见的环境都支持更多的行为。
 

fgetpos()和fsetpos()函数

fseek()ftell()潜在的问题是,他们都把文件大小都限制在long类型能表示的范围内。也许20亿字节看起来相当大,但是随着存储设备的容量迅猛增长,文件也越来越大。鉴于此,ANSI C新增了两个处理较大文件的新定位函数:fgetpos()fsetpos()
 
这两个函数不使用 long 类型的值表示位置,它们使用一种新类型:fpos_t(代表file position type,文件定位类型)。fpos_t类型不是基本类型,它根据其他类型来定义。fpos_t类型的变量或数据对象可以在文件中指定一个位置,它不能是数组类型,除此之外,没有其他限制。实现可以提供一个满足特殊平台要求的类型,例如,fpos_t可以实现为结构。
ANSI C定义了如何使用fpos_t类型。fgetpos()函数的原型如下:
调用该函数时,它把fpos_t类型的值放在pos指向的位置上,该值描述了文件中的一个位置。如果成功,fgetpos()函数返回0;如果失败,返回非0。
fsetpos()函数的原型如下:
调用该函数时,使用pos指向位置上的fpos_t类型值来设置文件指针指向该值指定的位置。如果成功,fsetpos()函数返回0;如果失败,则返回非0。fpos_t类型的值应通过之前调用fgetpos()获得。
 
 

标准I/O的机理

通常,使用标准I/O的第1步是调用fopen()打开文件(C程序会自动打开3种标准文件)。fopen()函数不仅打开一个文件,还创建了一个缓冲区(在读写模式下会创建两个缓冲区)以及一个包含文件和缓冲区数据的结构。另外,fopen()返回一个指向该结构的指针,以便其他函数知道如何找到该结构。假设把该指针赋给一个指针变量fp,fopen()函数“打开一个流”。如果以文本模式打开该文件,就获得一个文本流;如果以二进制模式打开该文件,就获得一个二进制流。
这个结构通常包含一个指定流中当前位置的文件位置指示器。除此之外,它还包含错误和文件结尾的指示器、一个指向缓冲区开始处的指针、一个文件标识符和一个计数(统计实际拷贝进缓冲区的字节数)。
我们主要考虑文件输入。通常,使用标准I/O的第2步是调用一个定义在stdio.h中的输入函数,如fscanf()getc()fgets()。一调用这些函数,文件中的数据块就被拷贝到缓冲区中。缓冲区的大小因实现而异,一般是512字节或是它的倍数,如4096或16384。最初调用函数,除了填充缓冲区外,还要设置fp所指向的结构中的值。尤其要设置流中的当前位置和拷贝进缓冲区的字节数。通常,当前位置从字节0开始。
在初始化结构和缓冲区后,输入函数按要求从缓冲区中读取数据。在它读取数据时,文件位置指示器被设置为指向刚读取字符的下一个字符。由于stdio.h系列的所有输入函数都使用相同的缓冲区,所以调用任何一个函数都将从上一次函数停止调用的位置开始。
当输入函数发现已读完缓冲区中的所有字符时,会请求把下一个缓冲大小的数据块从文件拷贝到该缓冲区中。以这种方式,输入函数可以读取文件中的所有内容,直到文件结尾。函数在读取缓冲区中的最后一个字符后,把结尾指示器设置为真。于是,下一次被调用的输入函数将返回EOF。输出函数以类似的方式把数据写入缓冲区。当缓冲区被填满时,数据将被拷贝至文件中。
 
 

其他标准I/O函数

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

int ungetc()函数把c指定的字符放回输入流中。如果把一个字符放回输入流,下次调用标准输入函数时将读取该字符。例如,假设要读取下一个冒号之前的所有字符,但是不包括冒号本身,可以使用 getchar()或getc()函数读取字符到冒号,然后使用 ungetc()函数把冒号放回输入流中。ANSI C标准保证每次只会放回一个字符。如果实现允许把一行中的多个字符放回输入流,那么下一次输入函数读入的字符顺序与放回时的顺序相反。
notion image

int fflush()函数

flush()函数的原型如下:
setvbuf()函数创建了一个供标准I/O函数替换使用的缓冲区。在打开文件后且未对流进行其他操作之前,调用该函数。指针fp识别待处理的流,buf指向待使用的存储区。如果buf的值不是NULL,则必须创建一个缓冲区。例如,声明一个内含1024个字符的数组,并传递该数组的地址。然而,如果把NULL作为buf的值,该函数会为自己分配一个缓冲区。变量size告诉setvbuf()数组的大小。mode的选择如下:
  • _IOFBF表示完全缓冲(在缓冲区满时刷新)
  • _IOLBF表示行缓冲(在缓冲区满时或写入一个换行符时)
  • _IONBF表示无缓冲
如果操作成功,函数返回0,否则返回非零值。假设一个程序要储存一种数据对象,每个数据对象的大小是3000字节。可以使用setvbuf()函数创建一个缓冲区,其大小是该数据对象大小的倍数。
 

二进制I/O: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函数用于以二进制形式处理数据。
实际上,所有的数据都是以二进制形式储存的,甚至连字符都以字符码的二进制表示来储存。如果文件中的所有数据都被解释成字符码,则称该文件包含文本数据。如果部分或所有的数据都被解释成二进制形式的数值数据,则称该文件包含二进制数据(另外,用数据表示机器语言指令的文件都是二进制文件)。
notion image
二进制和文本的用法很容易混淆。ANSI C和许多操作系统都识别两种文件格式:二进制和文本。能以二进制数据或文本数据形式存储或读取信息。可以用二进制模式打开文本格式的文件,可以把文本储存在二进制形式的文件中。可以调用 getc()拷贝包含二进制数据的文件。然而,一般而言,用二进制模式在二进制格式文件中储存二进制数据。类似地,最常用的还是以文本格式打开文本文件中的文本数据(通常文字处理器生成的文件都是二进制文件,因为这些文件中包含了大量非文本信息,如字体和格式等)。
 

size_t fwrite()函数

fwrite()函数的原型如下:
fwrite()函数把二进制数据写入文件。size_t是sizeof运算符返回的类型。指针ptr是待写入数据块的地址。size表示待写入数据块的大小(以字节为单位),nmemb表示待写入数据块的数量。和其他函数一样,fp指定待写入的文件。例如,要保存一个大小为256字节的数据对象(如数组),可以这样做:
以上调用把一块256字节的数据从buffer写入文件。另举一例,要保存一个内含10个double类型值的数组,可以这样做:
以上调用把earnings数组中的数据写入文件,数据被分成10块,每块都是double的大小。
注意fwrite()原型中的const void * restrict ptr声明。fwrite()的一个问题是,它的第1个参数不是固定的类型。例如,第1个例子中使用buffer,其类型是指向char的指针;而第2个例子中使用earnings,其类型是指向double的指针。在ANSI C函数原型中,这些实际参数都被转换成指向void的指针类型,这种指针可作为一种通用类型指针(在ANSI C之前,这些参数使用char*类型,需要把实参强制转换成char *类型)。
fwrite()函数返回成功写入项的数量。正常情况下,该返回值就是nmemb,但如果出现写入错误,返回值会比nmemb小。
 

size_t fread()函数

size_t fread()函数的原型如下:
fread()函数接受的参数和fwrite()函数相同。在fread()函数中,ptr是待读取文件数据在内存中的地址,fp指定待读取的文件。该函数用于读取被fwrite()写入文件的数据。例如,要恢复上例中保存的内含10个double类型值的数组,可以这样做:
该调用把10个double大小的值拷贝进earnings数组中。
fread()函数返回成功读取项的数量。正常情况下,该返回值就是nmemb,但如果出现读取错误或读到文件结尾,该返回值就会比nmemb小。
 

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

如果标准输入函数返回 EOF,则通常表明函数已到达文件结尾。然而,出现读取错误时,函数也会返回EOF。feof()ferror()函数用于区分这两种情况。当上一次输入调用检测到文件结尾时,feof()函数返回一个非零值,否则返回0。当读或写出现错误,ferror()函数返回一个非零值,否则返回0。
 
 
用一个程序示例说明这些函数的用法
下面列出了程序的设计方案:
询问目标文件的名称并打开它。 使用一个循环询问源文件。 以读模式依次打开每个源文件,并将其添加到目标文件的末尾。 为演示setvbuf()函数的用法,该程序将使用它指定一个不同的缓冲区大 小。下一步是细化程序打开目标文件的步骤: 1.以附加模式打开目标文件; 2.如果打开失败,则退出程序; 3.为该文件创建一个4096字节的缓冲区; 4.如果创建失败,则退出程序。 与此类似,通过以下具体步骤细化拷贝部分: 1.如果该文件与目标文件相同,则跳至下一个文件; 2.如果以读模式无法打开文件,则跳至下一个文件; 3.把文件内容添加至目标文件末尾。
最后,程序回到目标文件的开始处,显示当前整个文件的内容。
如果setvbuf()无法创建缓冲区,则返回一个非零值,然后终止程序。可以用类似的代码为正在拷贝的文件创建一块4096字节的缓冲区。把NULL作为setvbuf()的第2个参数,便可让函数分配缓冲区的存储空间。 该程序获取文件名所用的函数是 s_gets(),而不是 scanf(),因为 scanf()会跳过空白,因此无法检测到空行。该程序还用s_gets()代替fgets(),因为后者在字符串中保留换行符。
以下代码防止程序把文件附加在自身末尾:
参数file_app表示目标文件名,file_src表示正在处理的文件名。 append()函数完成拷贝任务。该函数使用fread()和fwrite()一次拷贝4096字节,而不是一次拷贝1字节:
因为是以附加模式打开由 dest 指定的文件,所以所有的源文件都被依次添加至目标文件的末尾。注意,temp数组具有静态存储期(意思是在编译时分配该数组,不是在每次调用append()函数时分配)和块作用域(意思是该数组属于它所在的函数私有)。
 
 
用二进制I/O进行随机访问
首先,该程序创建了一个数组,并在该数组中存放了一些值。然后,程序以二进制模式创建了一个名为numbers.dat的文件,并使用fwrite()把数组中的内容拷贝到文件中。内存中数组的所有double类型值的位组合(每个位组合都是64位)都被拷贝至文件中。不能用文本编辑器读取最后的二进制文件,因为无法把文件中的值转换成字符串。然而,储存在文件中的每个值都与储存在内存中的值完全相同,没有损失任何精确度。此外,每个值在文件中也同样占用64位存储空间,所以可以很容易地计算出每个值的位置。 程序的第 2 部分用于打开待读取的文件,提示用户输入一个值的索引。程序通过把索引值和 double类型值占用的字节相乘,即可得出文件中的一个位置。然后,程序调用fseek()定位到该位置,用fread()读取该位置上的数据值。注意,这里并未使用转换说明。fread()从已定位的位置开始,拷贝8字节到内存中地址为&value的位置。然后,使用printf()显示value
  • C
  • 类型限定符建立结构声明
    目录