1、第第4章章 文文 件件 操操 作作4.1 文件的概念4.2 文件处理的基本过程4.3 字节级文件读写4.4 字符串级文件读写4.5 记录级文件读写4.6 格式化文件读写4.7 文件位置指针的移动4.8 出错的检测4.9 综合实例习题4实验4 我们已经学习了C语言的基本输入和输出函数,通过它们,可以很方便地进行信息的输入与输出,解决一些基本的输入/输出问题。但在实际的应用中,数据量可能比较大,而且输入数据、中间结果和输出结果三类数据需要被保存起来,这样就不能每次都从键盘输入或者只从屏幕上观看结果,需要存储起来便于以后的重复使用和查询。目前存储数据主要采用两种方式:一种是数据库管理方式,此时计算机
2、中需要安装数据库管理系统;另一种是文件方式,大量的信息一般都是以文件的形式存储在外存上的。本章主要讲述用文件方式保存数据的方法,正确的理解和使用文件将有助于我们进行复杂应用软件的开发。4.1 文文件件的的概概念念所谓“文件”,一般指存储在外部存储介质上的有组织的数据的集合。它具有一个唯一的名字,通过名字可以对文件进行存取、修改、删除等操作。一般地,我们把放在外部存储介质,如软盘、硬盘、磁带、光盘这样的设备上的数据称为文件,它们有不同的名字和后缀(或叫扩展名),除这类文件外,从广义上来看,许多外部设备也可看做是一种文件,因为也可给它们取一个唯一的名字,对它们的操作也可用对磁盘文件相同的操作来实现
3、。例如,在DOS中,定义打印机为名字是PRN的文件,向该文件写信息时,实际上就是打印输出;定义键盘为名字是CON的文件,当从该文件读信息时,实际上就是从键盘接收键入的字符数字。将物理设备看做是一种逻辑文件来操作,可以简化设计、方便用户。C语言中也采取了类似的作法,因而从广义上说,文件是指信息输入和输出的对象,磁盘文件是文件,打印机、键盘、显示器也是文件。不过本章的内容不涉及广义上的文件操作。以前我们学习到的输入和输出,都是暂时性的输入与输出(当应用程序终止时,这些数据由于应用程序的结束而全部消亡),即在应用程序运行时,从键盘输入数据,运行结果在显示器上显示。但在实际的应用中,常常需要将一些程序
4、的数据(运行的最终结果或中间数据)输出到外存上存储起来,以后需要时再从外存输入到计算机内存之中。所有这些都需要通过磁盘文件来完成。以前我们学习到的输入和输出,都是暂时性的输入与输出(当应用程序终止时,这些数据由于应用程序的结束而全部消亡),即在应用程序运行时,从键盘输入数据,运行结果在显示器上显示。但在实际的应用中,常常需要将一些程序的数据(运行的最终结果或中间数据)输出到外存上存储起来,以后需要时再从外存输入到计算机内存之中。所有这些都需要通过磁盘文件来完成。在C中引入了流(stream)的概念。它将数据的输入/输出看做是数据的流入/流出,这样不管是磁盘文件还是物理设备(打印机、显示器、键盘
5、等),都被看做是一种流的源或目的,即为同一种东西,而不管其具体的物理结构。对它们的操作,就是数据的流入和流出。这种把数据的输入/输出操作对象抽象化为一种流,而不管它的源或目的的具体结构的方法很有利于编程。而涉及流的输出操作函数可用于各种对象,与其具体的实体无关,即具有通用性。读者可以想象一下水流,流的概念就来源于此。在C中流可分成两类,即文本流(Text Stream)(文件)和二进制流(Binary Stream)(文件)。所谓文本流,是指在流中流动的数据以字符形式出现。由于文本有行的限制,因而一行流完后,必须有行结束符(C规定为“n”),它代表了回车换行。因而在文本流中,当流入(文件)时“
6、n”被换成回车CR和换行LF(代码分别是0dH和0aH);而当流出(文件)时,0dH和0aH被换成“n”符号(即符合C中规定的一行的行结束符)。例如下面的简单程序:main()printf(I love C!);存盘时,以文本流方式存储,其流的形式如下:磁盘上的格式如下:二进制流是指流动的是二进制数字序列,它把数据按其在内存中的存储形式存放在磁盘上。若流中有字符,则用一个字节的二进制ASCII码表示;若是数字,则用若干个字节的二进制数表示。在流入/流出时,对“n”符号不进行变换,因而流中写入的字节数与读出的字节数相同。下面是信息分别在内存中、ASCII文件中和二进制文件中的具体表现形式。(1)
7、ABC的存储。内存中的形式:0 1 0 0 0 0 0 1 0 1 0 0 0 0 1 0 0 1 0 0 0 0 1 1 A 的 ASCII 码值 B 的 ASCII 码值 C 的 ASCII 码值 ASCII文件形式:二进制文件形式:(2)257的存储。内存中的形式:ASCII文件形式:二进制文件形式:用ASCII码形式输出,一个字节代表一个字符,便于对字符进行逐个处理,也便于输出字符,但要占用较多存储空间,而且要花费一些时间进行转换(二进制形式与ASCII码间的转换)。用二进制形式输出,可以节省外存空间和转换时间,但字节并不与字符对应,不能直接输出字符形式。在实际的软件开发中,中间结果数
8、据一般都需要暂时保存在外存上以便以后使用,在这种情况下,常用二进制文件方式对其进行保存。现在我们已经知道,在C语言中,一个文件是一个字节流或二进制流,即数据被看做是一连串不考虑记录界限的字符(字节)。因此,C语言中文件并不是由记录(record)组成的(这是和PASCAL或其他高级语言不同的)。在C语言中对文件的存取是以字符(字节)为单位的,数据输入/输出的开始和结束仅受程序控制而不受物理符号(如回车换行符)控制,这是流式文件的主要特点。4.1.2 C语言支持的文件处理方法语言支持的文件处理方法在过去使用的C版本中,对文件的处理方法有两种:一种叫“缓冲文件系统”;一种叫“非缓冲文件系统”。所谓
9、缓冲文件系统,是指系统自动地在内存中为每一个正在使用的文件开辟一个缓冲区。从内存向磁盘输出数据,必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘。如果从磁盘向内存读取数据,则一次从磁盘文件将一批数据输入到内存缓冲区(填满缓冲区),然后从缓冲区逐个地将数据送到程序数据区(赋给程序变量)。缓冲区的大小由各个具体的C版本确定,一般为512字节,如图4.1所示。图4.1 缓冲文件系统所谓“非缓冲文件系统”,是指系统不自动地开辟确定大小的缓冲区,而由程序为每个文件设定缓冲区。1983年,ANSI C标准决定不再采用非缓冲文件系统,而只采用缓冲文件系统。在文件处理时有两级方法,即标准级和系统级。所谓标
10、准级,就是这些函数更符合C语言的普通标准,与其他机器上的C 语言更兼容,更容易移植到其他机器、其他操作系统上。对程序员来说标准级函数也更容易使用,更高级,功能更强。另外,由于其内部带有缓冲能力,故磁盘存取次数较少,效率更高。标准级函数有时也称为流式输入/输出函数,因为在使用这些函数时,经常把被操作的文件看做是一个字符流。所谓系统级,是指这些函数往往直接调用操作系统。正因为如此,系统级函数速度更快,内存占用更少,但它们的兼容性更差。对程序员来说,系统级函数使用起来比较困难,对操作系统的依赖性较强,功能较差,没有自动缓冲能力,也没有格式输入/输出的能力,而且移植性较差。标准级函数有时被错误地理解为
11、只提供流式输入/输出,即把文件看做字符流或字符序列。虽然大部分情况下是这样用的,但这些函数的功能却不只是这些。例如,可以通过标准级进行格式化的输入/输出,可以对文件进行随机存取,也可以按块或按记录对文件进行存取。4.2 文件处理的基本过程文件处理的基本过程标准级函数添加的许多功能都是由于它使用了FILE这个数据结构。每当打开一个文件进行标准输入/输出时,系统就建立了一个FILE结构,并返回一个指向这个结构的指针。对于随后所有的操作,都是以这个结构指针(下面称为文件指针,有些教材称为流指针)为基础进行的。FILE数据结构如下:typedef struct short level;/*缓冲区满空程
12、度*/unsigned flags;/*文件状态标志*/char fd;/*文件描述符*/unsigned char hold;/*无缓冲则不读取字符*/short bsize;/*缓冲区大小*/unsigned char*buffer;/*数据缓冲区*/unsigned char*curp;/*当前位置指针*/unsigned istemp;/*临时文件指示器*/short token;/*用于有效性检查*/FILE;程序员不应直接存取这个结构中的任何一个数据域,因为它们仅供内部使用。这里列出这个结构只是为了方便读者理解C是怎样维护一个文件的。不同的C编译器可能会定义一个完全不同的结构,但其
13、结构的名字一般都是FILE。提醒读者注意,不要把文件位置指针和FILE结构指针(文件指针)混为一谈,它们代表两个不同的地址。文件位置指针指出了对文件当前要读写的数据的位置,也可称为位置指针;而文件指针指出了打开的文件所对应的FILE结构在内存的地址,实际上它本身也包含了文件位置指针的信息。文件指针中的各字段是供C语言内部使用的,用户不应该存取它的任何字段。注:有些资料上称FILE结构指针为流指针,称指示当前读写位置的指针为文件指针,这样很容易让读者产生误解。4.2.1 文件指针变量的定义文件指针变量的定义文件指针变量的定义如下:FILE*fp;其中,fp是一个指向FILE类型结构体的指针变量。
14、可以使fp指向某一个文件的结构体变量,从而通过该结构体变量中的文件信息访问该文件。通过文件指针变量能够找到与它相关的文件。如果有n个文件,一般应设n个指针变量(指向FILE类型结构体的指针变量),使它们分别指向n个文件(确切地说,指向该文件的信息结构体),以实现对文件的访问。4.2.2 文件的打开与关闭文件的打开与关闭文件是信息的有组织的集合。在使用文件时,我们应遵守文件使用的基本规则:先打开,再使用,最后关闭。即:在对文件读写之前首先应该打开该文件,然后再对其进行相应的读写操作,当所需的操作完成后,关闭该文件。就好像我们要用水,首先要打开水龙头,然后才能用,不用时应关闭水龙头一样。另外,标准
15、文件是不需要打开和关闭的,可直接使用。文件的打开为文件的使用做准备工作,即对使用的文件创建相应的数据结构,并和相应的存储空间发生联系。文件的关闭则是对文件的一些相关信息进行保存。在文件操作时,如果没有进行文件的关闭操作,会造成文件相应信息的丢失,导致文件的损坏。因此我们在使用文件时应对其进行正确的打开和关闭操作。1.文件的打开文件的打开(fopen函数函数)函数原型:FILE*fopen(const char*filename,const char*mode);其中,filename表示将要打开的文件的名字,mode表示文件的使用方式。文件名可以带路径。如果是已经存在的文件,则文件应该用全名方
16、式表示,即包括其主文件名和扩展名。在书写路径时注意路径分隔符“”的正确表示。在书写路径时使用双斜线“/”和斜线“”作为路径分隔符,是程序员经常犯的一个错误。例如:FILE *fp1,*fp2,*fp3;fp1=fopen(abc,r);fp2=fopen(d:mydoc.dat,r);fp3=fopen(a1.txt,r);以上语句分别表示:以只读方式打开当前路径下的abc文件、d盘的mydoc.dat当前路径下的a1.txt文件。其中fp1、fp2、fp3为文件指针。文件的常见使用方式如表4.1所示。表表4.1 文件的使用方式文件的使用方式说明:(1)用r方式打开的文件只能读而不能写,且该文
17、件应该已经存在。不能打开一个并不存在的文件,否则出错。(2)用w方式打开的文件只能用于向该文件写数据,而不能用来向计算机输入。如果原来不存在该文件,系统会新建立一个以指定名字为文件名的新文件,并打开它。如果文件存在,则在打开时将该文件内容删去,然后重新建立一个新文件。(3)如果希望向文件末尾添加新的数据(不删除原有数据),则应该用a方式打开。如果文件不存在,就将创建一个文件并打开它。打开时,文件位置指针移到文件末尾。(4)用r+、w+、a+方式打开文件时既可以读也可以写。用r+方式打开时该文件必须已经存在,以便能向计算机输入数据,否则出错;用w+方式打开一个文件时,可以向文件中写入数据,也可以
18、从文件中读出数据,但会删除文件原有的内容;用a+方式打开的文件不删除原有内容,位置指针移到文件末尾,可以添加也可以读,但只能进行追加写,不能对已有内容(包括本次写入的)修改。(5)如果无法打开一个文件,fopen函数将会返回一个NULL(NULL在stdio.h文件中已被定义为0)。原因可能是:用r方式打开一个并不存在的文件;文件名书写错误;磁盘出故障;磁盘已满无法建立新文件等。常用下面的方法打开一个文件:if(fp=fopen(d:xs.txt,r)=NULL)printf(error!cannot open this filen);exit(1);先检查打开有否出错,如果有错就在终端上输出
19、“cannot open this file”。exit函数的作用是关闭所有文件,终止程序的运行。6)用表4.1中所列方式可以打开文本文件或二进制文件,这是ANSI 的规定。但目前使用的有些C编译系统可能不完全提供所有这些功能(例如有的只能用、方式),有的C版本不用r+、w+、a+而用rw、wr、ar等,请读者注意所用系统的规定。2.文件的关闭文件的关闭(fclose函数函数)当一个文件使用结束时应该关闭它,以防止它再被误用,造成对文件信息的破坏和文件信息的丢失。“关闭”从本质上讲,就是让文件指针变量不再指向该文件的结构体,也就是将文件指针变量与文件的联系断开,此后不能再通过该指针对其相连的文
20、件进行读写操作,除非再次打开,使该指针变量重新指向该文件。函数原型:int fclose(FILE*stream);它表示该函数将关闭FILE指针(文件指针)对应的文件,并返回一个整数值。若成功地关闭了文件,则返回一个0值,否则返回一个非零值。例如:if(fclose(fp)!=0)printf(error:file cannot be closed!);exit(1);else printf(file is closed successful!);一般文件的关闭很少出错,所以经常直接使用fclose(fp);进行关闭,不作测试。当打开了多个文件进行操作,而又要同时关闭时,可采用fclosea
21、ll函数,它将关闭所有在程序中已打开的文件(stdin、stdout、stdaux、stdprn、stderr除外)。函数原型:int fcloseall(void);该函数将关闭所有已打开的文件,将各文件缓冲区的内容写到相应的文件中去,接着释放这些缓冲区,并返回成功关闭文件的数目。在程序终止之前关闭所有使用的文件是我们应该遵守的基本规则,如果不关闭文件将会丢失数据。因为,标准C支持缓冲文件系统,在文件操作时,系统自动地在内存区为每一个正在使用的文件开辟一个缓冲区。从内存向磁盘输出数据时,先将数据送到内存中的缓冲区中,装满缓冲区后才一起送到磁盘。这样操作可以提高操作效率。如果从磁盘向内存读取数
22、据,则一次从磁盘文件将一批数据输入到内存缓冲区,然后从缓冲区逐个地将数据送到程序数据区(给程序变量)。缓冲区的大小一般为512字节。如果当数据未填满缓冲区而程序结束运行,系统会自动释放其文件缓冲区,从而导致缓冲区中数据的丢失。用fclose函数关闭,可以很好地解决和避免这个问题,它先把缓冲区中的数据输出到磁盘文件,然后才释放文件指针变量和缓冲区空间。4.3 字节级文件读写字节级文件读写所谓字节级的文件读写,是指文件的读写单位是字节。文件在打开之后,就可以进行信息的读取与保存。常用的字节级读写函数有fputc()和fgetc()。4.3.1 fputc函数函数函数原型:int fputc(int
23、 ch,FILE*fp);功能:将字符ch输出到文件fp中。其中,ch可以是一个字符常量,也可以是一个字符变量,还可以是一个整数(对应字符的ASCII码值,不能超过128)。返回值:如果输出成功则返回值就是输出的字符;如果输出失败,则返回一个EOF。EOF是在stdio.h文件中定义的合法整数,值为-1。【程序程序4.1】从键盘读入字符存入文件,直到用户输入一个句号为止。#include stdio.h main()FILE*fp;char ch;if(fp=fopen(f:test.txt,w)=NULL)printf(cant open the filen);exit(2);ch=getc
24、har();while(ch!=.)/*输入以.结束*/fputc(ch,fp);/*写入fp指定的文件中*/ch=getchar();/*从标准文件stdin(键盘)读入字符*/fclose(fp);/*关闭文件fp,清空文件缓冲区*/运行时,从键盘输入字符,直到输入.为止,所输入的字符就构成新的文件内容。4.3.2 fgetc函数函数函数原型:ch=fgetc(fp);功能:从指定文件fp读入一个字符,该文件必须是以读或读写方式打开的。返回值:返回读入的字符,ch也可以是整型变量,此时将返回的字符的ASCII码值赋予ch。如果遇到文件结束符,则函数返回一个文件结束标志EOF。【程序程序4.
25、2】从一个磁盘文件顺序读入字符并在屏幕上显示出来。#include stdio.hmain()FILE *fp;char ch;*在此处也可以这样定义int ch;*if(fp=fopen(d:my.dat,r)=NULL)printf(n this file does not exit n);exit(1);ch=fgetc(fp);while(ch!=EOF)/*文件没有结束*/putchar(ch);/*输出到屏幕*/ch=fgetc(fp);fclose(fp);但在这里大家需要注意,EOF是不可输出字符,因此不能在屏幕上显示。由于字符的ASCII码不可能出现-1,因此EOF定义为-1
26、是合适的。当读入的字符值等于EOF时,表示读入的已不是正常的字符而是文件结束符。但这只适用于对文本文件的读写。ANSI C已经允许用缓冲文件系统对二进制文件进行处理。在二进制文件中,信息都以数值方式存在,EOF的值可能就是所要处理的二进制文件中的信息。这就出现了需要读入有用数据而却被处理为“文件结束”的情况。为了解决这个问题,ANSI C提供一个feof函数,我们可以用它来判断文件是否真的结束。feof(fp)用来测试fp所指向的文件的当前状态是否为“文件结束”。如果是文件结束,函数feof(fp)的值为1(真),否则为0(假)。【程序程序4.3】利用feof函数控制文件读入结束。#inclu
27、de stdio.hmain()FILE *fp;int ch;if(fp=fopen(d:my.dat,rb)=NULL)printf(n this file does not exit n);exit(1);int ch;if(fp=fopen(d:my.dat,rb)=NULL)printf(n this file does not exit n);exit(1);在使用上述函数时,要特别注意接收字符的变量ch要定义为int型的,而不能定义为char型的。因为用到该变量的函数自动将其转换为无符号字符,而将其整型数高位字节忽略,即得到的仍是一个字符;另外一个原因是,当函数返回文件尾的信息E
28、OF时,它并不是一个字符,而是代表-1,因此,如果定义为char型,这个值便和字符代码不同(因为没有一个字符的ASCII码会取-1)。也就是说,如果定义char ch;当从文件中循环读取字符并检查是否到文件尾,即执行以下语句时:while(ch=fgerc(fp)!=EOF)该循环将是个死循环,因为永远不会有EOF出现。C语言的基本输入/输出函数putchar()和getchar()实质上是fputc()和fgetc()的宏,在stdio.h文件中有如下定义:#define putchar(ch)fputc(ch,stdout)#define getchar()fgetc(stdin)其中,s
29、tdout是系统定义的文件指针变量,它与终端输出相连,默认是屏幕,可以重定向;stdin表示键盘。4.4 字符串级文件读写字符串级文件读写所谓字符串级的文件读写,指文件的读写单位是字符串。字符串级的文件读写函数主要有fgets()、fputs()、fprintf()、fscanf()。4.4.1 fgets函数函数函数原型:char*fgets(char*str,int n,FILE*fp);功能:从fp指定的文件中读字符串并将其存储到以str为首地址的内存中,n为读取的字符串的总长。在读取字符时,当读取了n-1个字符或遇到换行符时,函数将停止字符的读取,但保留换行符。当读完n-1个字符后在字
30、符串str的最后加一个0字符(字符串结束符)。返回值:若成功,返回str的首地址;出错或遇文件结束时返回NULL。4.4.2 fputs函数函数函数原型:int fputs(char*str,FILE*fp);功能:把以str为首地址的字符串输出到fp所指向的文件。其中第一个参数可以是字符串常量,也可以是字符数组名或字符型指针。返回值:若输出成功,函数返回最后写入的字符;失败时,返回EOF。fgets和fputs函数类似于我们以前学习过的gets和puts函数,只是gets和puts函数已指定标准输出流(stdout)和标准输入流(stdin)作为读写对象。同理,fprintf函数、fscan
31、f函数与printf函数和scanf函数作用相仿,都是信息的格式化输入与输出。只有一点不同:fprintf函数和fscanf函数的读写对象是磁盘文件,而printf函数和scanf函数的读写对象是标准输出流(stdout)和标准输入流(stdin)。fprintf与fscanf在4.6节详细介绍。【程序4.4】按字符串读入文本文件,并输出在屏幕上。同时将该文件保存为c:4_4.c。#include stdio.hmain()FILE *fp;char buffer64;if(fp2=fopen(c:4_4.c,r)=NULL)printf(cant open file n);exit(1);w
32、hile(!feof(fp)/*测试文件是否结束*/if(fgets(buffer,64,fp)!=NULL)/*读一行字符并测试是否为空*/printf(%s,buffer);/*显示该行字符*/fclose(fp);/*关闭文件 */4.5 记录级文件读写记录级文件读写所谓记录级的文件读写,指文件的读写单位是记录。所谓记录,从本质上讲,它不过是一个没有格式的数据块。记录有两种:一种是定长的,另一种是不定长的。值得注意的是,从函数的角度来讲,ANSI C只提供了定长记录的支持,所以这里我们主要介绍定长记录文件的读写。用fgetc函数和fputc函数可以读写文件中的一个字符。用fgets函数和
33、fputs函数可以读写文件中的一个字符串。但在现实的数据处理过程中,问题本身的复杂性和我们对处理的要求,使得我们在进行信息处理时往往需要将某些信息作为一个整体进行处理,即常常要求一次读入一组数据(例如一个实数或一个结构体变量的值),对于此类问题,ANSI C标准提供两个函数来读写一个数据块,即fread函数和fwrite函数。它们的一般调用形式为:int fread(void *ptr,int size,int nitems,FILE *fp);int fwrite(void *ptr,int size,int nitems,FILE *fp);其中,ptr是指向内存缓冲区的指针,对fread
34、来说,它是读入数据的存放地址,对fwrite来说,它是输出数据的地址(以上指的是起始地址);size是一个记录的字节数(记录大小);nitems是读写记录的个数。fread函数从指定的输入流fp中读取nitems项数据,每一项数据长度为size字节,将读取的数据存放到ptr所指定的块中。fwrite函数向指定的输出流fp中写入数据,所写入的数据项的个数为nitems,每个数据项长size个字节。所写入的数据的存放首地址为ptr。对于这两个函数而言,所读写的字节总数为nitems*size。当调用成功时,两函数返回实际读或写的数据项数,而非实际的字节数。在遇到文件结束或出错时,则返回一个计数值。
35、【程序程序4.5】从键盘输入两个学生数据,并写入一个文件中,再读出这两个学生的数据并显示在屏幕上。#include stdio.hstruct student char name10;int num;int age;char addr15;main()FILE*fp;char ch;struct student stu2,temp;int i;if(fp=fopen(d:stu_list,wb+)=NULL)printf(Cannot open file strike any key exit!);getch();exit(1);printf(ninput datan);for(i=0;i2;
36、i+)scanf(%s%d%d%s,stui.name,&stui.num,&stui.age,stui.addr);if(fwrite(stu,sizeof(struct student),2,fp)!=2)printf(write file error!);return;fclose(fp);/*关闭文件*/if(fp=fopen(d:stu_list,rb+)=NULL)printf(Cannot open file strike any key exit!);getch();exit(1);printf(nnnametnumber age addrn);while(fread(qq,s
37、izeof(struct stu),2,fp)!=1)printf(%st%5d%7d%sn,temp.name,temp.num,temp.age,temp.addr);fclose(fp);这个例子比较简单,但值得注意。一个记录的字节数应该通过sizeof操作符求得,而不应该由程序员自己计算。这是因为,在C中记录一般都是通过结构实现的,而许多C编译器具体实现一个结构时,为了边界对齐,往往添加了一些字节,但各个C编译器添加的方法和字节数又不一致。在Turbo C编译中,缺省的原则是不添加任何字节,以使结构最紧凑,少占内存。但是,如果程序员确实想以整数为边界对齐,则在编译时可以加一个“_a”选
38、择项。加了这个选择项会进行如下三项对齐:(1)结构将总是从偶地址开始。(2)结构内的非字符字段也总是从偶地址开始。(3)整个结构总是占偶数个字节。这种对齐方式使得运行速度加快,但增加了存储量。如果我们以二进制形式打开一个文件,用fread和fwrite函数就可以读写任何类型和长度的数据信息。如:fread(d_f,4,5,fp);其中d_f是一个实型数组名。这个函数的功能是从fp所指向的文件读取5条数据(每条4个字节),并将其存储到数组d_f中。fread(d_str,15,4,fp1);其中,d_str是一个字符型数组名。这个函数从fp1所指向的文件读取4条数据(每条15个字节),并将其存储
39、到数组d_str中。假设有一个如下的结构体类型:typedef struct stu_typechar name 8;int code;char birthday10;char addr30;stu;stu d_stu20;结构体数组d_stu由20个元素组成,每一个元素用来存放一个学生的信息(包括姓名、学号、出生日期、住址四部分内容)。可以用以下语句将内存中的20个学生数据存储到磁盘文件中去:for (i=0;i20,i+)fwrite(&d_stui,sizeof(struct stu_type),1,fp);或者用以下语句:fwrite(d_stu,sizeof(struct stu_t
40、ype),20,fp);假设学生的数据已存放在磁盘文件中,可以用下面的语句读入20个学生的数据:for(i=0;i20;i+)fread(&d_stui,sizeof(struct stu_type),1,fp);或者用以下语句:fread(d_stu,sizeof(struct stu_type),20,fp);如果fread或fwrite调用成功时,函数返回值为输入或输出数据项的完整个数。ANSI C提供的fread和fwrite函数具有强大功能,我们可以根据自己的实际要求编写信息的读写程序,这样就可以十分方便地读写任何类型的数据。4.6 格式化文件读写格式化文件读写4.6.1 fprin
41、tf函数函数函数原型:int fprintf(FILE*fp,char*format,argument,);功能:按照指定的格式将输出列表中的内容写入指定的文件中。其中,fp用于指明所要操作的文件,format(格式字符串)用于指明信息的写入格式,argument用于指明所要写入的信息。例如:若j=10 ch=A,经过下面语句后:fprintf(fp,%d%c,j,ch);fp所指向的文件中会有数据10A。4.6.2 fscanf函数函数函数原型:int fscanf(FILE*fp,char*format,argument,)功能:从文件中读取数据,并将其按照格式字符串所指定的格式写入到地址
42、参数&argument所指定的地址中。其中,fp用于指明所要操作的文件,format(格式字符串)用于指明信息的写入格式。fscanf返回成功扫描、转换、存储的输入字段数。被扫描但未被存储的字段不计算在内。如果该函数试图在文件末尾进行读操作,则返回EOF;如果没有字段被存储,则返回0。例如,若磁盘文件上如果有以下字符:3,4.5若采用下面的语句从文件中输入数据:fscanf(fp,%d,%j,&i,&j);则将磁盘文件中的数据3送给变量i,4.5送给变量j。用fprintf函数和fscanf函数对磁盘文件进行读和写,使用方便,容易理解。但由于在输入时要将ASCII码转换为二进制形式,在输出时又
43、要将二进制形式转换成字符,花费时间比较多,因此,在内存与磁盘频繁交换数据的情况下,fprintf函数和fscanf函数的效率较低。【程序4.6】假设一个文本文件data.txt中有如下数据,编写程序将其读入一个二维数组中。2 4 6 8 1 3 5 7#include stdio.hmain()FILE*fp;int data23;for(i=0;i2;i+)for(j=0;j3;j+)fscanf(%d,&dataij);fclose(fp);4.7 文件位置指针的移动文件位置指针的移动在文件的读写过程中,为了能够正确地完成输入与输出,系统需要有一个指示标志,来指明当前正在读写的位置,我们称
44、这一指示标志为文件位置指针,即FILE结构体中的unsigned char*curp。也就是说,在文件的读写过程中,系统设置了一个表示位置的指针,指向当前读写的位置。在顺序地读写一个文件时,假定每次读写一个字符,则读写完一个字符后,该指针自动指向下一个字符位置;同样,如果每次读写一个记录,则读写完成后,该指针自动指向下一个记录的位置。在实际的文件读写过程中,往往需要根据自己的实际要求进行文件操作,也就是说,需要将文件位置指针移动到我们所需要的位置,可能向前移动,也可能向后移动。ANSI C中提供了许多移动文件指针的函数,最常用的有rewind()、ftell()、fseek()。4.7.1 r
45、ewind函数函数函数原型:int rewind(FILE*fp);功能:使文件位置指针重新返回文件的开头。如果移动成功,返回值为0;如果移动失败,返回一非零值。对于一个新打开的文件,文件位置指针指向文件开始。当我们对其进行了读操作以后,文件位置指针会发生变动,如果现在我们需要从文件开始进行新的操作,此时就需将位置指针移到文件开始。【程序程序4.7】将一个文件的内容在显示器上重复显示两次。#include stdio.hmain()FILE *d_fp;if (d_fp=fopen(d:my.dat,r)=NULL)printf(n open file error n);exit(0);whi
46、le(!feof(d_fp)putchar(fgetc(d_fp);rewind(d_fp);while(!feof(d_fp)putchar(fgetc(d_fp);4.7.2 ftell函数函数函数原型:long ftell(FILE*fp);功能:用来获取文件位置指针当前位置相对于文件起点的偏移量,以字节为单位。4.7.3 fseek函数函数对流式文件可以进行顺序读写,也可以进行随机读写,关键在于控制文件的位置指针。如果位置指针是按字节位置顺序移动的,就是顺序读写。如果可以将位置指针按需要移动到任意位置,就可以实现随机读写。也就是说,读写完上一个字符(字节)后,并不一定要读写其后续的字符
47、(字节),而可以读写文件中任意位置的字符(字节)。用fseek函数可以改变文件的位置指针。函数原型:int fseek(FILE*fp,long offset,int origin);其中,fp为所操作的文件指针;offset为从指定位置移动指针的偏移量(所需移动的大小),必须为长整型;Origin为指针移动的开始位置(起始点)。起始点必须是0、1、2中的一个。0代表“文件开始”,1为“当前位置”,2为“文件末尾”,如表4.2所示。表表4.2 offset函数中的指针移动的开始位置函数中的指针移动的开始位置 偏移量指以起始点为基点,向前移动的字节数。ANSI C和大多数C版本要求偏移量是lon
48、g型数据。这样当文件的长度大于64K时不致出现问题。ANSI C标准规定在数字的末尾加一个字母L,就表示是long型。下面是feek函数调用的几个例子:fseek(fp,500L,0);*/将文件指针从文件头向后移动500个字节*/fseek(fp,100L,1);*/将文件指针从当前位置向后移动100个字节*/fseek(fp,-100L,2);*/将文件指针从文件末尾处向前移动10个字节*/【程序程序4.8】编写一个程序,求取文件位置指针以及文件长度。#include stdio.hmain()long len;FILE *fp;long length();if(fp=fopen(d:my
49、.dat,r)=NULL)printf(n open file error n);exit(0);len=length(fp);printf(the length of d:my.dat is%Ld bytes,d_len);long length(FILE *fp)longcurpos,length;curpos=ftell(fp);/*求取文件指针相对于文件开始的相对位置*/printf(n the begin of d:my.dat is%Ld n,curpos);fseek(fp,0L,SEEK_END);/*文件指针指向文件末尾*/length=ftell(fp);printf(n
50、the end of d:my.dat is%Ld n,length);fseek(fp,curpos,SEEK_SET);/*恢复文件指针的初始值*/return(length);如果指针成功移动,返回值为零;如果移动失败,返回一非零值。注意问题:(1)通过fseek函数可以把位置指针移到超过文件当前末尾(超过原来的文件长度)的位置,这样做很容易扩充文件的长度。(2)原来的文件末尾和新位置之间的区域是未被初始化的(未写入内容)。(3)如果文件打开方式允许,可以越过文件末尾进行数据的写操作,但若试图读则会返回一个错误信息。(4)ftell函数用来读取文件当前位置相对于文件起点的偏移量。当一个文