1
第 13章 文件
2
13.1 C文件概述到目前为止,我们所编程序中的输入都来自键盘,而输出都送到显示器,且程序所处理的数据都是在内存中进行的,程序一旦运行结束,存放在变量和数组中的数据都会消失 。
这种数据处理方式有一些缺点,
不利于大量数据复用,保存及交流;
由于手工及慢速设备的 I/O,会延长程序的完成周期 。
3
解决问题的办法就是利用文件。程序中都从文件中读入数据,而程序中输出的数据也都存放到文件中去。
程序中大量的数据处理都必须利用文件进行。
因此,必须掌握如何使用文件的基本知识。
本章将介绍C语言中文件的 I/O 处理方法及相关的 I/O 函数的使用 。
4
1,文件概念可从各种不同的角度来认识文件,
一般来说,文件是记录的集合,而一个记录由若干字段组成,一个字段则是一串有意义的字符 。
从文件的存储介质来看,
从文件的组织形式来看,
磁盘文件顺序存取文件随机存取文件磁带文件等
5
从文件的存取方式来看,顺序文件索引文件从文件的表示形式来看,二进制文件文本文件等总之,文件总是存储在外部存储介质上(例如磁盘、磁带等),有一个名字以供识别。因此,一个文件实际上就是存储介质上的一片命名的存储区域。 至于这片存储区域在哪儿,是连续的还是分散的,普通程序设计者不必关心。
文件有各种属性,基本的属性有只读、只写、
可读可写。
6
2,C文件系统为给程序提供与设备无关的,统一的,方便的文件 I/O处理界面,C语言把计算机系统中的各种设备都抽象成文件,如把键盘抽象成输入文件,把显示器抽象成输出文件等 。
设备都抽象成文件之后,程序便只与文件打交道,而不必关心具体的设备 。 因此,程序中使用的文件其实都是使用某种设备,C文件系统中的 I/O
函数都是针对文件而言的 。
7
C 语言把每一个文件都看成一个连续的、有序的谓之“流” 的字节序列,流中的每一个字节都可以单独存取。每个文件都以一个特别的结束标志 (end
of file marker 简记为 EOF) 结束。
C 文件系统支持两种流:
文本流 (text stream)
二进制流 (binary stream)
3,I/O流概念 (I/O stream)
8
因此,在 C中读写文件时,C将根据所在环境进行 行结束符的转换,读取时将 \r\n 或 \r转换成 \n ;
写入时将 \n 转换成 \r\n 或 \r 。
文本流
Windows,DOS环境中,\r\n
其他系统环境中,\r
(回车与换行组合 )
C语言环境中,\n
一种 以行为单位组织的字符序列,行 以特定字符结束 。不同系统的行结束符可能不同。
(单个回车符 )
(单个换行符 )
9
没有行概念的 一种字节序列 ( 注意区分,字符,与,字节,的不同含义 ),。
C程序能够读/写流中的每一个字节 ( 读写文件的字节数与实际存储的字节数相等 ),不存在任何字符变换 。
一个文本文件也可以按二进制流方式处理 。
反之,一个二进制文件也可以按文本流处理,但处理效果极差 。
通常文本文件用文本流处理,二进制文件用二进制流处理 。
二进制流
10
第三类 为低级 I/O函数第一类 为标准设备 I/O函数 (standard I/O)
C 语言文件系统由若干 I/O函数组成,可把 I/O函数分为三类:
第二类 为标准高级 I/O函数 (standard high-level I/O)
4,C 语言 I/O函数这是针对键盘和显示器的 I/O函数这类函数是针对磁盘文件或其他设备文件的,
它们面向用户或程序,可把这类函数看作为程序和操作系统之间的一种 I/O高级接口。
使用这类函数不必关心文件的处理细节,通常将这类函数称为 缓冲 I/O函数 (buffered I/O ) 。
(low-level I/O)
11
低级 I/O函数是面向系统的,可将它们看作为程序和操作系统之间的 一种低层次的接口 。它们是由操作系统提供的基本 I/O服务。
这类函数 对系统的依赖性很强,不同系统中提供的这类函数的种类、个数、调用形式、甚至于处理能力都可能不同,移植性要求较高的程序中应尽量避免使用它们。
使用这类函数存取文件需要知道较多的文件内部处理细节,因此将这类函数称之为 非缓冲 I/O函数
(unbuffered I/O),C99标准不再支持 这类函数。
12
5,利用高级 I/O库函数存取文件
C 中利用高级 I/O函数读写文件的过程与其他语言中读写文件的过程是类似的,通常按如下的顺序进行:

打开文件

读/写文件

关闭文件

若干次
13
问题是使用文件之前为什么要打开文件?打开与关闭文件的目的和任务是什么? 如何进行文件的打开、读/写和关闭操作?
下面的几节将详细讨论这些问题。
这个操作顺序表明:一个文件能被读写之前首先要打开它,只有文件被打开后才能进行读/写,
文件读/写完后要关闭这个文件。
14
13.2 文件打开
(1) 打开文件与文件类型指针系统中使用的文件名是 文件的外部名,通过它可以找到文件的实际存储设备、位臵、大小、属性等相关信息。这些信息由操作系统的文件管理系统掌握与管理。
这意味着C程序没有直接通过文件的外部名存取一个外部文件的能力。因此,要存取文件必须通过操作系统的文件系统。
15
程序中要读 /写文件必须与操作系统 的文件系统取得联系 !
这种联系包括把欲存取文件的有关信息和要求,
如文件的名字、读文件还是写文件、以何种方式存取等信息告诉操作系统,由操作系统的文件系统在设备中建立、寻找、定位、分配存取文件的缓冲区,
做好存取文件的一切准备工作 。
上述存取文件的有关信息和要求都由程序中通过调用 I/O库函数 fopen 告诉操作系统。
16
fopen 函数的一般调用形式是:
fopen ( 文件名,存取方式 )
其中:
,文件名,即为要读写文件的外部名 。 它可以是字符串,字符数组名,或指向文件名的一个指针变量,允许包含路径 。 文件名和路径的形式,
要求与 C所在的环境有关 。 如在 DOS和 Windows环境下:
17
“file1.c”
“d:\\cat1\\cat2\\file2.c”
“a:/file3.c”
都是正确的文件名参数。
注意,例中的路径分隔符应使用反斜杠的转义字符,\\”,亦可用单个正斜杠符,/” 替代之。
,存取方式,也以字符串的形式给出,用来指出如何存取文件 。
fopen 函数允许的文件存取方式,含义及使用说明列于下表中,
18
存取方式

r”
含 义打开且只读文本文件使用限制与要求文件必须存在,只能用于读文本文件
,w” 打开且只写文本文件若文件不存在,则自动建立之,若文件存在,则先删除再建立
,a” 打开仅追加文本文件若文件不存在,则自动建立,总是把数据追加在文件的尾部
,r
+”
打开读 /写文本文件文件必须存在,且允许写。 不删除文件现有内容,可从文件中读、更新及写入。
19
存取方式 含 义 使用限制与要求
,w+” 打开读 /写文本文件若文件不存在,则自动建立之,若文件存在,则先删除再建立可从文件中读,更新及写入。
,a+” 打开读 /写文本文件若文件不存在,则自动建立之。可从文件中读出内容,也可向文件写入内容,
但写入内容总是追加在文件的尾部
20
存取方式 含 义 使用限制与要求
,r
b”
打开且只读二进制文件 同,r”
,wb” 打开且只写二进制文件 同,w”
,ab” 打开仅追加二进制文件 同,a”
,rb+”
或,r+ b” 打开读 /写二进制文件 同,r+”
,wb+”
或,w+b” 打开读 /写二进制文件 同,w+”
,ab+”
或,a+b” 打开读 /写二进制文件 同,a+”
21
上述存取方式是 C89标准定义的,不同的 C 语言版本可能有差别。因此,在使用具体的 C 语言编译系统时应查阅相关手册。
fopen 函数的 功能 是根据调用它给出的实在参数 (文件的外部名和存取方式 )与操作系统取得联系,
由操作系统做一些关于文件操作的准备工作:如文件访问许可权的检查、文件存在与否的检查、设备和文件存储空间的可用性检查及分配、文件的建立与定位、分配文件缓冲区等等。
22
如果操作系统确认文件能存取,则给 fopen函数返回一个整型值,该值称为 文件描述符,它是该程序已打开的第几个文件的序号。
如果操作系统发现要求打开的文件不符合存取要求的处理,则给 fopen 函数返回 -1 的返值。
fopen函数根据操作系统传递过来的返值,再做某些适当处理后,确定给调用者的返回值。
23
fopen函数返回给调用者的返值是一个 FILE 型指针,称为,文件指针,,这以后程序中便用这个指针来代替文件的外部名存取文件。 若操作系统的返值为 -1,则 fopen函数给调用者返回一个 NULL指针。
fopen函数返回给调用者的 FILE型指针 指向处理这个外部文件的一个内部的结构型对象 ( 在 stdio.h中定义) 。
该结构对象中包含处理该文件所使用的 I/O 缓冲区的位臵、当前读/写位臵,文件的名字、文件的存取方式、文件状态 (如错误标志、结束标志 ) 等信息。程序设计者不必了解这些内容。
fopen,操作系统、文件指针之间的关系是:
24
由此可见,
FILE 是自定义的 struct型的类型名
25
26
因为 fopen返回的是指向 FILE型对象的指针,
因此在调用 fopen 的程序中必须定义一个对应的
FILE型的变量,用来接 收 来自 fopen的返回值 。 正确的 fopen 调用应按如下过程进行,

FILE *fp ;

fp = fopen ( filename,mode ) ;

定义了一个用于指向 FILE数据类型对象的指针变量 fp ; 程序中实际调用 fopen函数的一般调用形式,
这里把 fopen的返值赋给 FILE型指针变量 fp
27
因为各种原因有可能不能成功打开文件 (如文件名不正确,路径名不对,存取权限不符合,存储空间不够,设备问题等等,所以在调用程序中应该检查 fopen函数的返值,判断打开是否成功 。 这样调用
fopen函数的程序段最好写成为:

FILE *fp ;

if ( (fp = fopen ( filename,mode ) ) == NULL) {
puts ( "Can?t open file,\n" ) ;
exit (1) ;
}

28
操作系统允许同时打开的文件数是有限的 。 所以暂时用不到的文件不要预先打开 。
同一文件在未关闭之前不能以不同的文件指针变量再次打开 。
若对同一文件以同一存取方式,同一文件指针变量再次打开,相当于把文件的当前读写位臵定位到文件开头;
若对同一文件以不同的存取方式再次打开,则将取消前一次打开的存取方式;
已用于某打开文件的指针变量,若再次被用于打开不同的文件,则意味着关闭前一次打开的文件 。
对文件打开时需注意:
29
因键盘和显示器也对应于文件,它们被称之为标准 I/O设备文件 。 因此要从键盘上输入数据,以及把输出的数据显示到显示器上,也必须对它们进行打开操作 。 而这类设备使用频繁,为此当一个程序开始运行时,C文件系统将自动为这个程序打开至少三个文件:
标准设备键盘显示器错误输出设备文件指针名 文件描述符
stdin
stdout
stderr
0
1
3
它们都是 FILE
型的指针,且是指针常量 !
是 系统定义的
30
13.3 关闭文件文件读/写完之后应及时关闭。关闭文件是打开文件的逆操作,它切断文件描述符、文件指针变量、文件外部名之间的联系。
文件使用完后也可以不立即关闭,甚至可以不关闭,当程序运行结束时系统会自动关闭它们。
但文件使用完后不关闭是不好的做法,主要有两个原因:
31
第一,因为操作系统允许程序同时打开的文件个数是有限的,为了能同时打开更多的文件,
不再使用的文件应及时关闭 。
第二,在写入文件的情况下,数据不是直接写到文件上去的,而是先写到文件读/写缓冲区中,当缓冲区写满时再送到盘上去,如果写入内容已处理完,但缓冲区还未满,且没有关闭该文件,此时缓冲区的内容要等到程序运行结束时由系统自动关闭该文件后才能写出 。 在这样的情况下,若系统发生非正常情况 ( 死机或断电 ),当前缓冲区中的未写到盘上的内容就可能丢失掉了 。
32
关闭文件是通过调用 fclose函数进行的,它的调用形式是:
fclose ( fp ) ;
其中:
fp是要关闭文件的文件指针变量 。 该函数的返值是一个 int型数,关闭成功返回 0值,关闭失败返回 EOF。
若要再使用已关闭的文件必须重新打开这个文件 。
33
13.4 文件的读 /写文件一旦被成功打开,就可以按打开方式存取该文件。对已打开文件的读/写操作可以进行若干次,直到读/写完成为止。
下面介绍几种常用的读/写文件的 I/O库函数。
( 1) 读/写字符函数 putc 和 getc
getc 函数用来从已打开文件的当前位臵读出一个字符。它的调用形式是:
getc ( fp ) ;
先前调用 fopen函数返回的与打开文件相关联的一个 FILE
型指针变量
34
getc 函数的返值是读取字符的编码值。 遇到是
,文件末端,或 读操作出错 时,返回 EOF 。
测试 getc 读操作是否成功时,对于 读操作出错,
可以再调用 ferror 函数找出确切的错误原因。
例如:
getc ( stdin ) ;
putc函数向文件中写一个字符,调用形式是:
putc ( ch,fp ) ;
把字符 ch写入文件 fp中执行成功时返回字符 ch的编码,不成功时返回
EOF 。
考虑出与早期 C版本的兼容性,程序中还可以使用与 getc,putc功能等价的函数 fgetc,fputc。
35
例 1,putc和 getc函数的应用
#include <stdio.h>
int filecopy ( FILE *fp )
{
int c ;
while ( (c = getc (fp) ) != EOF )
putc ( c,stdout ) ;
}
36
main ( int argc,char *argv[ ] )
{
FILE *fp ;
int filecopy (FILE *fp) ;
if (argc==1)
filecopy ( stdin ) ;
else
while(--argc > 0)
if((fp=fopen(*++argv,"r"))==NULL){
printf("can't open %s\n",*argv) ;
exit(1) ;
}
else {
filecopy ( fp ) ;
fclose ( fp ) ;
}
}
37
(2) 读/写字符串函数 fgets 与 fputs
① fgets函数
fgets函数用来从指定的文件中 读出一行或一个指定长度的字符串,其调用形式是:
fgets ( line,n,fp ) ;
读出的字符串存放处,可以是字符数组名、
字符指针变量指出从文件中读出的字符个数,实际读出最多 n-1个字符,因要自动在其后加上 NULL
字符。
fgets 函数读入时,若 遇到 EOF,指定的字符个数已读完、或遇到行结束符,则读操作停止。
在正常情况下,函数返回读出字符串的存放地址;
当遇到 EOF或读出错误 时,该函数将返回 NULL。
38
fgets函数类似于 gets函数,二者之间的区别在于:
gets遇到一个新行字符时,gets函数将把其转换成 NULL字符;
而 fgets不进行这种转换,它把遇到的行结束符作为一个普通字符来处理,并作为读入字符串的内容 。
39
char *fgets ( char *line,int n,FILE *fp )
{
int ch ;
char *str ;
str = line ;
while (--n > 0 && (ch = getc(fp) ) != EOF)
if ( (*str++ = ch ) == '\n' )
break ;
*str = '\0' ;
return ( (ch == EOF&&str == line)? NULL,line)
}
fgets函数的处理过程如下:
40
#include <stdio.h>
main ( int argc,char *argv[ ] )
{
FILE *fp ;
char str[128] ;
if( (fp = fopen (argv[1],“r”) ) == NULL){
printf (,Can not open file.\n” ) ;
exit (1) ;
}
while ( !feof(fp) ) {
if ( fgets ( str,126,fp ) ) /* 思考,为什么用 126 */
printf (,%s”,str ) ;
}
fclose ( fp ) ;
}
例,利用 fgets函数显示指定的文本文件的内容(文件名由命令行参数给定)。 读入字符串存放处,每行最多为
128个字符文件结束标志为 1返回非 0,
否则返回 0
41
fputs 函数用来向指定的文件中写入一串字符 。
它的一般调用形式是:
fputs ( line,fp )
写入文件的字符串,
可以是字符串的指针,字符数组名,
字符串常量 。
写入文件时写入字符串后的 NULL不写到文件中去 (丢弃)。注意:在这点上它与 puts函数有区别,puts 函数将把 NULL字符换成新行字符输出!。
写入成功 fputs返回 0值,不成功时返回非 0值。
② fputs 函数
42
例,利用 fgets 函数和 fputs 函数实现的一个简单的回显程序 。
#include <stdio.h>
int main(void)
{
char str[22];
while(fgets(str,20,stdin)!=NULL&&str[0]!=?\n?)
fputs (str,stdout);
return 0;
}
43
下面是该程序实际运行的一个示例:
First line↙ /* 读入的第一行,↙ 表示回车键 */
First line /* 输出的第一行 */
Second line more than 20 characters↙ /*读入的第二行 */
Second line more than 20 characters /*输出的第二行 */

该程序看起来似乎比较简单,其实它是一个非常精巧的程序 。 它充分利用了 fgets函数保留换行符,
而 fputs函数不会添加换行符的特点 。
44
(3) 格式化读写函数 fscanf与 fprintf
fscanf函数与 scanf函数的功能完全相同,只不过 fscanf是针对磁盘等设备文件的,而 scanf只能从
stdin读入;
同理,fprintf 与 printf的 功能完全相同,
fprintf 将数据送到指定的文件中去,而 printf仅把输出数据送到 stdout上 。
fscanf和 fprintf 的调用形式是:
fscanf ( fp,format,input-list )
fprintf ( fp,format,output-list )
45
其中,
fp为读/写文件的指针 ;
format是 I/O格式说明,input-list和 output-list
分别是输入项表和输出项表 。 它们 printf函数和
scanf函数完全相同 。
用这两个函数对 文本文件 的读 /写非常方便,因此在程序设计中使用比较频繁,但由于它们在读入
/写出处理的过程中要对数据进行格式转换,所以处理速度较慢 。
例 fscanf函数,fprintf函数的应用 。
46
#include <stdio.h>
#include <string.h>
#include <stdlib.h> /* 使用 exit函数需要该头文件 */
int main ( void )
{
FILE *fp ;
char string[80] ;
int v;
if( ( fp = fopen ( "a:\\test","w" ) ) == NULL ) {
puts ( "Cannot open file a:\\test !,) ;
exit (1) ;
}
puts ( "Enter a string and a number:" ) ;
该程序先从键盘上读入一个字符串和一个整数,
然后将它们以文本的形式写入到软盘的 test的文件中,最后再从软盘的 test文件中读出并显示到屏幕上 。
47
scanf ( "%s%d",string,&v ) ; /*读入字符串和整数 */
fprintf ( fp,"%s\t%d\n",string,v ) ;/* 写入文件 */
fclose (fp) ; /* 关闭以写方式打开的文件 test */
/* 以读方式重新打开的文件 test */
if ( ( fp = fopen ( "a:\\test","r " ) ) == NULL ) {
puts ( "Cannot open file a:\\test !" ) ;
exit (1) ;
}
/* 从文件中读出刚才写入的一个字符串和一个整数 */
fscanf ( fp,"%s%d ",string,&v ) ;
printf ( "%s\t%d\n ",string,v ) ; /* 显示到屏幕上 */
return 0 ;
}
48
(4) 二进制读写函数 fread与 fwrite
把数据在内存中的存储形式不进行任何转换直接写入文件;反之,把原先直接写入文件中的内存存储形式的数据再读回内存,那么这种形式的数据
I/O便称之为二进制 I/O 。
C文件系统支持对文件的二进制读 /写操作,使用 fwrite函数向文件中一次写入若干个具有指定长度的数据项 ;用 fread函数从一个文件中一次读出若干个指定长度的数据项 。 这两个函数的调用形式是:
fwrite ( buf,size,count,fp )
fread ( buf,size,count,fp )
读/写数据在内存中的存放处,通常为数组名或指针变量或对象的地址读 / 写 数据项 的 长 度
( 以 字 节为单位 ) 。
读/写数据项的个数 。 读 /写文件
49
fread 和 fwrite分别用来一次从/向指定文件中读/写 size× count个字节 。
它们的返回值是实际读/写的数据项个数 。 返回的值可能比 count指出的值小,因为读/写过程中可能出错,或还没读/写完 count个数据项时便遇到了 EOF。 一旦发现读/写错误便立即停止读/
写操作 。
为了判别读/写出错还是遇到 EOF,可借助于
feof函数和 ferror函数进行判别 。
50
例 fread 和 fwrite 函数的应用该例先使用 fwrite以二进制方式一次向文件中写入 8个数据,然后用 fread以二进制方式从文件 中一次读出多个数据项的处理情况。
#include <stdio.h>
#include <stdlib.h>
int main ( void )
{
FILE *fp ;
float f[ ]={ 1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8 } ;
float bal[10] ;
int i ;
51
/* 以二进制写方式打开文件 test */
if ( ( fp = fopen ("test","wb" ) ) == NULL ) {
printf ( "Cannot open file test!\n" ) ;
exit (1) ;
}
/*把存储在数组 f中的 8个 float型数一次性写入文件 test */
fwrite ( f,sizeof (float),8,fp ) ;
fclose (fp) ; /*关闭以二进制写方式打开的文件 test */
/* 以二进制读方式重新打开文件 test,以便从文件中读
*/
if ( ( fp = fopen ( "test","rb" ) ) == NULL ) {
printf ( "Cannot open file test!\n" ) ;
exit (1) ;
}
52
/*从文件中一次读出 8个 float型数,存储到数组 bal中 */
if ( fread ( bal,sizeof(float),8,fp ) != 8 )
if ( feof (fp) ) /* 判别是遇到文件尾,还是读出错误 */
printf ( "End of file\n" ) ;
else
printf ( "Read file error\n" ) ;
for ( i = 0 ; i < 8 ; i++ )
printf ( "%f\n ",bal[i] ) ;
}
53
关于文件的二进制读 /写,应记住:
按二进制读写的原理,只要文件按二进制读 /
写方式打开,用 fread和 fwrite函数可以读 /写文件上的各种形式的数据 。
但为避免不必要的麻烦,减少错误,建议程序中以二进制方式写入文件的数据仍应以二进制方式读出;
同理,以文本方式写入文件中的数据仍应以文本方式读出 。
二进制文件与文本文件,文件的格式化读 /写与二进制读 /写的概念常被混淆,下图也许能帮助你弄清楚它们的区别 。
54
int num=12345;
12345被转换成内存中的存储形式
00110000 00111001
把 12345的 内存存储形式转换成字符形式
fprintf ( fp,“%d”,num ) ;
1?,?2?,?3?,?4?,?5?
00110001 00110010 00110011 00110100 01000101
转换成 ASCII码形式
00110001 00110010 00110011 00110100 01000101
以 ASCII码形式写入文件 fp中去
fp对应的 文件
55
int num=12345;
00110000 00111001
fwrite (&num,sizeof(int),1,fp ) ;
不进行转换直接写入文件 fp中去
12345被转换成内存中的存储形式
fp对应的 文件
00110000 00111001
56
13.5 文件定位文件总是从文件当前读写位臵开始读/写,读
/写后把当前读/写位臵后移到下一个可读/写的位臵。
C语言提供了几种定位函数,使文件读/写位臵按要求定位到指定的位臵。
利用 定位函数,就能够实现文件中数据的重复使用、修改、文件的随机存取等处理操作。
57
(1) rewind函数
rewind函数的功能是使文件的当前读 /写位臵定位于文件的开头,同时它将清除该打开文件的结束标志 和 错误标志 。
rewind函数的一般调用形式是:
rewind ( fp ) ;
文件指针下面的程序段使一个文件中的数据被 重复使用两次。
58
注意,指的是这里文件结束标志,而不是文件内容中的
EOF标志。
59

while ( !feof ( fp ) )
putchar ( getc ( fp ) ) ;
rewind ( fp ) ;
while ( !feof ( fp ) )
putchar ( getc( fp ) ) ;

60
(2) fseek函数
fseek 函数用来将文件的当前读写位臵定位到相对于文件某个位臵起的第 n个字节处。它的一般调用形式是:
fseek ( fp,offset,origin ) ;
fp 为文件指针。
其中:
offset 称之为位移量,是一个 long int 数,
用于指出相对于文件某个位臵的字节数。
61
当 offset为负值时,表示将文件读写位臵从当前位臵向文件开始的方向移动 fabs(offset)个字节;
当 offset的值为正数时,表示使文件 读写位臵 从当前位臵处向文件尾的方向移动 offset个字节 。
如果 offset 超出了文件始端位臵或尾端位臵,
将作出错处理 。
origin =
0 相对于文件开始位臵
1 相对于文件当前读写位臵
2 相对于文件 尾当文件定位成功返值为 0,否则返值为非 0。
origin是一个 int数,其值仅能为:
62
#include <stdio.h>
#include <stdlib.h>
#define file "randomfile.dat"
int main ( void )
{
FILE *fp ;
int i,info,newnumber,oldnumber =125,
int array[5],resul[21] ;
if ( ( fp = fopen ( file,"wb+" ) ) == NULL ) {
fprintf(stderr,"Cannot open file %s.\n",file);
exit (1) ;
}
例 使用 fseek函数、实现 文件的随机存取 程序例。
63
/* 以二进制方式向文件 randomfile.dat中写入 20个整型数 */
for ( i = 0; i < 20; i++)
fwrite ( &i,sizeof(int),1,fp ) ;
/* 把文件读写位臵定位到第 7个整型数的存储位臵 */
fseek ( fp,6*sizeof(int),0 ) ;
/*从当前读写位臵读出一个整型数 ( 即 6),赋给变量 info */
fread ( &info,sizeof ( int ),1,fp ) ;
/* 把第 10个数改为 125 */
fseek ( fp,2*sizeof ( int ),1 ) ;
fwrite ( &oldnumber,sizeof(int),1,fp ) ;
/*把刚写入的第 10个数 ( 即 125) 读出,赋给变量 newnumber */
fseek ( fp,-1*sizeof(int),1 ) ;
fread ( &newnumber,sizeof(int),1,fp ) ;
64
/* 把从第 11个数起的 5个数,读入到数组 array中 */
fread ( array,sizeof(int),5,fp ) ;
/* 在第 15个数后插入一个新的数 6 */
fread ( result,sizeof(int),6,fp) ;
fseek ( fp,-5 * sizeof(int),2 ) ;
fwrite ( &info,sizeof(int),1,fp ) ;
fwrite ( result,sizeof(int),6,fp ) ;
/*把处理后的文件内容读入到数组 result中,并显示出来 */
rewind ( fp ) ;
fread ( result,sizeof(int),21,fp ) ;
for ( i = 0; i <= 20; i++ )
fprintf ( stdout,"%d\n",result[i] ) ;
fclose ( fp ) ;
return 0 ;
}
65
(3) ftell函数
ftell函数返回文件的当前读 /写位臵值 。 该值是一个 long 整型数 。
对二进制流是从文件开始计算的字节数;
对文本流给出的位臵值可能不准确,因为可能发生文件行结束符的转换,会影响文件的大小 。
因此,ftell函数只适用于以二进制方式打开的文件 。
66
13.6 文件末端与出错检测及其他 文件操作函数通过检查读 /写函数的返回值了解文件是否已到了文件末尾,对读二进制文件不是保险的办法,
因为读二进制文件时,可能会遇到与 EOF等值的二进制数据,当然该值也由读函数返回给调用者,
将会导致判断错误 。 因此,程序判断文件是否已到了文件末尾一般通过调用 f e of函数进行 。
67
(1) feof函数当读文件遇到文件结束符时,被调用函数给调用者返回 EOF( 其值为 -1)。同时将文件指针变量指向的结构中的文件状态标志 flag成员中的 文件结束标志位 臵 1。 feof 函数用来检测 flag中的文件结束标志位是否为 1。这种检测方法是准确的。
feof 函数的一般调用形式是:
feof ( fp ) ;
被检测的文件指针若 feof函数检测到文件结束标志位为1则返回非
0值,否则返回 0值。 用 feof函数判断文件结尾的例
68
#include <stdio.h>
#include <stdlib.h>
int main ( int argc,char *argv[ ] )
{
FILE *in,*out ;
char ch ;
if ( ( in = fopen ( argv[1],“rb” ) ) == NULL ) {
printf(,Cannot open %s file!\n”,argv[1] ) ;
exit (1) ;
}
if ( ( out = fopen ( argv[2],“wb”) ) == NULL ) {
printf (,Cannot open %s file!\n”,argv[2] ) ;
exit (1) ;
}
69
while ( !feof ( in ) ) {
ch = getc ( in ) ;
if ( !feof ( in ) )
putc ( ch,out ) ;
}
fclose ( in ) ;
fclose ( out );
return 0 ;
}
70
(2) ferror 函数
ferror函数用来检查文件操作是否出错,若有错返回非 0值,否则返回 0值 。
对于文件的每种操作,如果操作出错都将臵文件状态标志 flag中的错误标志位 。
为及时发现及处理错误,保证文件操作的正确性,程序中每次调用文件操作函数后应立即调用
ferror函数检查本次文件操作是否有错,并执行相应错误处理程序 。
例 本例是在上例的基础上增加了 ferror函数检查文件操作是否出错,并进行适当处理的程序例 。
71
#include <stdio.h>
#include <stdlib.h>
int main ( int argc,char *argv[ ] )
{
FILE *in,*out ;
char ch ;
if ( ( in = fopen (argv[1],“rb” ) ) == NULL ) {
printf (,Cannot open %s file!\n”,argv[1] ) ;
exit (1) ;
}
if ( ( out = fopen ( argv[2],“wb” ) ) == NULL ) {
printf (,Cannot open %s file!\n”,argv[2] ) ;
exit (1) ;
}
72
do {
ch = getc ( in ) ;
if ( ferror ( in ) ) {
puts (,Find a reading error !” ) ;
exit (1) ;
}
else {
putc ( ch,out ) ;
if ( ferror ( out ) )
puts (,Find a writting error !” ) ;
exit (1) ;
}
} while ( !feof ( in ) ) ;
fclose ( in ) ;
fclose ( out ) ;
return 0 ;
}
73
(3) fflush 函数
fflush函数的功能是刷新缓冲区 (flushing buffer)。
对于写打开的文件,把输出缓冲区的信息写入文件;
对于读打开的文件,则清除输入缓冲区中的内容。
fflush函数的一般使用形式是:
fflush( fp) ;
其中 fp是一个打开文件的指针。若 fp是一个空指针将刷新所有的缓冲区。操作成功返回 0值,否则返回 EOF。
74
在 C 语言程序设计中 fflush 函数是很有用的,
例如在用 getchar 等函数输入数据时,常常由于滞留在输入流中的像新行这样的字符会作为
getchar 函数的有效输入数据,导致输入错误,
而如果先用 fflush函数清除 stdin,便能获得正确的输入数据。
例如:
75
#include <stdio.h>
int main(void)
{
char c ;
int a ;
scanf (“%d”,&a) ;
fflush ( stdin ) ;
c=getchar ( ) ;
printf (,a=%d\nc = %c\n”,a,c ) ;
return 0 ;
}
76
运行该程序时,若输入如下数据:
123↙ /* 注,↙ 表示键盘上的 Enter键 */#↙
则 123赋给 a,而第一个 ↙ 将作为变量 的有效输入字符赋给 c,这样便不会获得正确的输出结果 。
因此在程序中语句 scanf (“%d”,&a); 后加上一条
fflush(stdin); 语句,便可以解决问题 。
(4) remove函数
remove函数用来删除指定的文件 。 其一般使用形式是:
remove ( 文件名 );
77
#include <stdio.h>
int main ( void )
{
char filename[80] ;
printf (“Enter filename:” ) ;
gets ( filename ) ;
remove ( filename ) ;
return 0 ;
}
其中“文件名” 指出要删除的文件名。它可以是字符串常量、指向字符串的指针变量及字符型数组名等形式。串中可含有路径。删除成功返回 0,
否则返回非 0。
例如:
78
作为对本章内容的总结,下面给出一个文件应用实例。
该例在运行结束前把在内存中处理的链表保存到文件中; 下一次再运行该程序时,首先把先前保存在文件中的链表读入内存,以便继续处理。
例,一个简单的车辆违章记录处理程序。
79
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct drive_record {
char number[20] ;
int accident_count ;
char *accident_reason ;
struct drive_record *next ;
}CAR ;
CAR *head=NULL ;
FILE *fp ;
int flag=0 ; /*是否结束的标志,即是否继续显示菜单 */
80
int main ( void )
{
int s,numberlen,reasonlen ;
CAR *p ;
void add(void),inquire(void),list(void),quit(void) ;
if((fp=fopen(“logfile”,“rb+”)) != NULL)
{ /* logfile存在 */
/*读入车号长度 */
fread ( &numberlen,sizeof(int),1,fp ) ;
if ( !feof ( fp ) ) {
if((p=(CAR*)malloc(sizeof(CAR)))==NULL)
exit(1) ;
head=p ;
81
while( !feof (fp) ) {
fread(p->number,numberlen,1,fp);
/*填写字符串结束标志 */
p->number[numberlen]=0;
fread(&p->accident_count,sizeof(int),1,fp);
/*读事故原因长度 */
fread(&reasonlen,sizeof(int),1,fp);
/*分配节点存储空间 */
p->accident_reason=(char*)malloc(reasonlen+1);
fread(p->accident_reason,reasonlen,1,fp);
/*填写字符串结束标志 */
p->accident_reason[reasonlen]=0;
/*读下一车号长度 */
fread(&numberlen,sizeof(int),1,fp);
82
if ( feof(fp) )
p->next=NULL;
else{
if((p->next=(CAR*)malloc(sizeof(CAR)))==NULL)
exit(1);
p=p->next;
}
}
}
}
else /* logfile不存在,以前没有写入过 logfile,首次工作 */
if((fp=fopen("logfile","wb+"))==NULL){
printf("logfile could not be opened!");
exit(1);
}
83
while(1){
system(“cls”); /*清屏 */
fflush(stdin); /*刷新输入缓冲区 */
printf( "\n"
"\t_____________________\n\n"
"\t 1,添加 \n"
"\t 2,查询 \n"
"\t 3,清单 \n"
"\t 4,退出 \n"
"\t_____________________\n"
“\n\t 请选择 1/2/3/4:” );
scanf("%d",&s);
84
switch(s){
case 1,add( ); break;
case 2,inquire( ); break;
case 3,list( ); break;
case 4,quit( );
}
if ( flag ) { /*结束吗?*/
fclose ( fp ) ;
return 0 ;
}
}
}
85
void list ( void )
{
CAR *p ;
system ( "cls,) ;
p=head ;
printf ( "\n\n肇事汽车车号 事故次数 事故原因 \n\n" ) ;
if ( p==NULL) {
printf("\n 无任何记录 ! 按任意键返回主菜单,.......\n") ;
fflush ( stdin ) ;
getchar() ;
return ;
}
86
do{
printf("%-20s%-8d\t%s\n",p->number,
p->accident_count,p->accident_reason);
}while(p=p->next);
fflush(stdin);
printf("\n\n\n按任意键返回主菜单,.....\n");
getchar();
return;
}
87
void inquire(void)
{ char number[20];
CAR *p;
p=head;
printf("\n\n请输入要查询的汽车牌号,");
scanf("%s“,number);
printf("\n\n肇事汽车车号 事故次数 事故原因 \n\n");
if(p==NULL){
printf("\n "
"无关于车牌号为 %s的任何记录!按任意键返回主菜单 "
"…....,\n ",number);
fflush(stdin);
getchar();
return;
}
88
while(p){
if(!strcmp(p->number,number)){
printf("%-20s%-8d\t %s\n",p->number,
p->accident_count,p->accident_reason);
printf("\n\n 按任意键返回主菜单,.......\n");
fflush(stdin);
getchar();
return;
}
else
p=p->next;
}
printf("\n\ 无关于车牌号为 %s的任何记录 ! "
"按任意键返回主菜单,.......\n”,number);
89
fflush(stdin);
getchar();
return;
}
void add(void)
{ struct drive_record *p,*p1,*front;
char number[20];
char accident_reason[100];
printf("\n请输入车号,");
scanf("%s",number);
printf("\n请输入事故原因,");
scanf("%s",accident_reason);
90
if((p=(CAR*)malloc(sizeof(CAR)))==NULL)
exit(1);
strcpy(p->number,number);
p->accident_count=1;
p->accident_reason=(char*)malloc(strlen(accident_reason)+1);
strcpy(p->accident_reason,accident_reason);
p->next=NULL;
if(head==NULL) {
head=p;
return;
}
p1=head;
91
while(p1){
if(!strcmp(p1->number,p->number)){
p1->accident_count++;
p1->accident_reason=(char*)realloc(p1->accident_reason,
strlen(p1->accident_reason)+strlen(p->accident_reason)+1);
strcat(p1->accident_reason,p->accident_reason);
free(p);
return ;
}
else {
front=p1;
p1=p1->next;
}
}
front->next=p;
return;
}
92
void quit(void)
{ CAR *p=head;
int numberlen,reasonlen;
rewind(fp); /* 请思考:这儿为什么要对文件 fp做 rewind操作? */
while(p){
numberlen=strlen(p->number);
reasonlen=strlen(p->accident_reason);
fwrite(&numberlen,sizeof(int),1,fp);
fwrite(p->number,numberlen,1,fp);
fwrite(&p->accident_count,sizeof(int),1,fp);
fwrite(&reasonlen,sizeof(int),1,fp);
fwrite(p->accident_reason,reasonlen,1,fp);
p=p->next;
}
flag=1;
return;
}