8.1 C文件概述
1.文件引用规则
为使计算机程序能处理大量的数据信息,常将数据存储在计算机外部存储介质中,如磁带、磁盘等。计算机操作系统将存储在外部存储介质中的数据以数据流的形式来组织。每个独立的数据流称作文件,每个文件有一个名字。为便于管理文件,操作系统维持一个呈层次状的目录结构,每个文件都被登录在某一目录下。习惯也将从键盘输入的数据流和向显示屏或打印机输出的数据流称作文件。引用文件可由以下几部分组成:
盘符:路径\文件名.扩展名
其中盘符表示文件所在存储块,系统将外部存储介质分成多个存储块,并用不同的盘符标识这些块。路径是文件所在目录层次,文件名和扩展名通常是由字母开头、字母和数字符组成。扩展名可以多至3个字符,通常用来表示文件的属性。因操作系统保留着当前盘和当前路径,若要引用当前盘或当前路径下的文件,盘符和路径可以省略。
2.文件的打开和关闭
由于文件存放在磁盘上,程序要处理文件上的数据,必须先将文件中的数据读人到内存;反之,程序要将产生的数据永久保存,就应将数据写到文件中。文件受操作系统管理,程序要使用文件,就要请求操作系统,让程序与某文件之间建立某种联系,习惯称程序与文件建立联系的过程为文件打开;反之,撤消程序与文件联系的过程为文件关闭。所以,程序要使用文件,先要打开文件;程序使用文件结束后,应及时关闭文件。
3.文件缓冲技术
在现代计算机系统中,程序读文件中的数据或写数据到文件,都在操作系统控制下完成。若程序要从文件读人一个数据,操作系统会一次性地读入一大块数据暂存于内存中,供程序已后再读入时使用。程序向文件写数据时,也不是立即将数据写到文件中,而是暂时存于某个内存块中,待内存块写满,或程序明确告知写文件结束后,再将数据写到文件中。这种文件数据读写技术称为缓冲。文件读写采用缓冲技术的系统称为缓冲文件系统。在缓冲文件系统中,暂存输入输出数据的内存块称为文件缓冲区。不采用缓冲技术,操作系统直接按程序要求完成输入输出的系统称为非缓冲系统。操作系统为了控制和完成文件读写操作,为每个正与程序相联系的文件设有一个控制块,在控制块中记录文件的名称、文件的属性、文件当前读写位置、文件缓冲区开始地址、文件当前读写位置所对应缓冲区的位置等等。文件缓冲区和文件控制块都由系统分配和受系统控制。
4.二进制文件和文本文件
文件按其数据信息的存放格式分类,文件可分二进制文件和文本文件两种。二进制文件中的数据是按二进制方式存放,即以数据在计算机内存的存放格式将数据存储在文件中。将数据转换成字符列,每个字符又以字符的代码(例如,ASCII代码)存储的文件称为文本文件。一般来说,二进制文件比文本文件更紧凑,并在数据传输时不必进行格式转换,常用于计算机与计算机之间、计算机与外部设备之间传输数据用。由于文本文件以字符的代码存储,输出内容能让人直接阅读,常用于人与计算机之间通信时使用。
5.顺序文件和随机文件
文件按读写方式分,可以把文件分为顺序文件和随机文件。顺序文件要求文件读写从文件头开始,读或写操作顺序进行。若临时要读取文件中间的某个数据,必须从头开始读,直至读人要读的数据;若在文件某位置要写入新的数据,也必须从文件的第一个数据开始顺序读取和复写,并在要改写的数据写入后,还要继续读取和复写其后的全部数据。随机文件允许随机地读取或改写文件任一位置上的数据。
C语言本身未提供有关文件操作的输入输出语句,但对文件的打开、关闭和读写操作都可用系统提供的库函数来实现。程序可用它们对文件作各种复杂的处理。
6.设备文件
系统将常规设备上的输入输出数据流称为标准文件,程序运行前,系统自动打开这些标准文件。它们是标准输入文件、标准输出文件、标准出错输出文件和标准打印输出文件。系统自动定义了这些标准文件的文件指针,它们依次是stdin、stdout、stderr和stdprn,供程序直接使用。
程序除能直接使用前面各章都使用的不带文件指针的标准输入输出库函数外,也可对它们使用下面介绍的带文件指针的一般形式的输入输出库函数。如stdin,就是指从终端输入数据;stdout,就是向终端输出数据。
7.文件类型和文件类型指针变量
为了正确地完成文件读写,操作系统为每个正被程序使用的文件在内存中开辟一个存储区,用于存放有关对文件进行操作所需的控制信息(简称控制块)。如文件名、文件读写状态。文件缓冲区大小和位置、当前读写位置等。控制块是一个结构变量,其类型由系统预定义,取名为FILE,习惯称文件类型。程序通过指向该控制块的指针调用系统提供的文件处理库函数。
程序在使用文件前,先调用文件打开函数。打开函数为将要使用的文件指定一个FILE类型的结构变量,并返回该结构的指针。系统通过指向该结构的指针来引用结构中的文件控制信息,实现正确读写对应的文件。
程序要使用文件,就要定义FILE类型的指针变量(称文件指针变量)。例如:
FILE * fp;
定义如是一个文件指针变量,它能指向前述类型为FILE的文件控制块结构变量。
8.文件打开库函数 fopen()
在读写文件之前,先得打开文件。打开文件可使用库函数fopen() 。调用函数fopen() 的一般形式为
fopen(文件名,使用方式)
其中文件名(可能还包括盘符和目录路径) 为字符串表达式。使用方式也是一个字符串,用来指明文件的读写方式。函数fopen() 将返回文件控制块结构变量指针,程序应将调用函数fopen(),返回的指针值赋给某个文件指针变量来保存。如语句
fp = fopen(“\\usr4\\smp.dat”,“r”);
以文件读方式打开根目录下的usr4子目录中的smp.dat文件。
调用函数fopen()时,可能会因某种原因不能打开文件。如读方式下打开一个不存在的文件;在写方式下,外部存储介质已无剩余的自由空间,或外设故障,或超过系统能同时打开的文件数;等等。文件不能打开时,函数fopen()将返回一个空指针值NULL。程序应考虑到文件不能正常打开的极端情况,所以常用以下形式的C代码描述打开一个文件的要求:
if((fp =fopen( filename,“r”))==NULL) {
printf(“Can not open %s file.\n”, filename);
exit(0); /* 结束程序的执行,回到环境或操作系统 */
}
以上代码以读方式打开一个文件,其中filename是某文件名字符串表达式。上述代码在调用函数fopen()后立即检查打开是否成功,如果打开不成功,就在终端上输出该文件不能打开字样,调用exit函数。exit函数是系统提供的函数,该函数的执行将释放程序的全部资源,终止程序的执行。调用该函数时需指定一个整数,该整数将作为程序终止时给系统的一个返回值。若程序使用该函数,应在程序的头写上包含stdio.h头文件的预处理命令。
关于函数fopen()的使用方式参数,说明以下几点:
(1)用“r”方式打开的文件只能用于从文件输入数据,不能用于输出;而且要求该文件已经存在,否则函数fopen() 返回NULL值。
(2)用“w”方式打开的文件只能用于向文件输出数据,不能用于输入。如打开时,原文件不存在,则新建立一个以指定名字命名的文件;如原文件已存在,则原文件上的数据被全部删除。
(3)如希望打开文件用于写,又不要删除原文件中的数据,并从原文件的末尾开始添加新的数据,应该用“a”方式打开。
(4)用“r+”、“w+”、“a+” 方式打开的文件可以输入数据,也可以输出数据。用“r+”方式只允许打开已存在的文件,以便程序能输入数据;用“w+”方式打开,则新建立一个文件,先是向文件输出数据,然后可以从该文件读人数据;用“a+”方式打开一个已存在的文件,位置指针先移到文件的末尾,准备添加数据,以后也可以输入数据。
(5)要打开二进制文件,只要在对应正文文件打开方式中接上字符b即可,如“rb”表示以输入方式打开二进制文件。
正文文件与二进制文件在使用时,还有一点不同。对于正文文件,输入时,回车符和换行符合成为一个换行符输入;输出时,换行符('\n')转换成为回车符和换行符两个字符一起输出。对于二进制文件,不进行上述这种转换。
9.文件关闭库函数fclose()
在使用完一个文件后,程序应该立即关闭它,以防止后继执行的程序语句错误或人为的误操作破坏正打开着的文件。关闭文件可调用库函数fclose()来实现。调用函数fclose()的一般形式为
fclose(文件指针);
例如:
fclose(fp);
调用函数fclose()的作用是使文件指针变量终止原先调用函数fopen()时所建立的它与文件的联系。调用函数fclose()之后,不能再通过该文件指针变量对其原先相连的文件进行读写操作,除非被再次打开。文件被关闭后,原文件指针变量又可用来打开文件,或与别的文件相联系,或重新与原先文件建立新的联系。
8.2 文件处理程序结构和文件输入输出常用库函数
1.正文文件输入处理
从正文文件逐一输入字符,对输入的字符作某种处理的程序结构有:
int c; /* 不能为char类型 */
… /*说明有关变量和设置初值等 */
fp=fopen(文件名,“r”); /* 正文文件以读方式打开 */
while(( c= fgetc(f))!= EOF) {
… /* 这里对刚读人的存于C中的字符作某种处理 */
}
fclose(fp);
.../*输出处理结果 */
其中函数 fgetC()的说明形式为
int fgetc(FILE *fp)
该函数的功能是从与中相联系的文件中读人下一个字符。在文件的控制块中,有一个当前读字符的位置信息,每读入一个字符后,在文件还未结束情况下,这个当前位置信息就移向其后一个字符,从而保证程序反复调用函数fgetc() 能顺序读人文件中的字符。函数fgetc() 的返回值就是读入字符的ASCII代码值。读八字符时,如遇到文件结束,函数返回文件结束标记EOF。对于正文文件,由于字符的ASCII代码不可能是-1,因此可用EOF(定义为-1) 作为文件结束标记。
【例8.1】 输入正文文件,统计文件中英文字母的个数,并输出。
为使程序更有一般性,设程序要统计的正文文件名在程序启动时由输入指定。
# include
FILE *fp;
int main()
{ int count, ch;
char fname[40];
printf(“输入文件名!\n”);
scanf(“%s%*c”, fname) ; /* 读入文件名和名后的回车符 */
if((fp = fopen(fname,“r”))== NULL) { /* 以读方式打开正文文件 */
printf(“Can not open%s file.\n”, fname);
return 0; /* 程序非正常结束 */
}
count= 0;
while((ch =fgetc(fp))! =EOF) {
/ * 这里对刚读人的存于ch中的字符信息作某种处理 */
if(ch>=‘a' && ch<='z' || ch>='A' && ch<='Z' )
count++;
}
fclose(fp);
/*输出处理结果 */
printf(“文件%s有英文字母%d个.\n”, fname, count)
return l; /* 程序执行正常结束 */
}
2.二进制文件输入处理
从二进制文件逐一输入字节,并作某种处理的程序结构有:
char c; /* 也可以是 int类型 */
… /* 说明有关变量和设置初值等 */
fp = fopen(文件名,“rb”);
while(! feof(fp)) {
c=fgetc(fp);
… /* 这里对存于C中的字节信息作某种处理 */
}
fclose(fp);
/* 输出处理结果 */
其中函数feof() 用来判断文件是否结束。函数调用feof(fp) 用来测试与fp相联系的文件当前状态是否为“文件结束”状态。如果是文件结束状态,函数调用feof(fp)返回非零值,否则返回零值。函数feof()也可用于测试正文文件。
对于二进制文件,一般不能以读人字节的值是否为-1来判定二进制文件是否结束,而应该用函数feof()。
3.字符(或字节)逐一生成形成新文件的程序结构
正文文件与二进制文件的生成程序除文件打开方式不同外,它们的程序结构基本相同。字符(或字节)逐一生成输出,形成新文件的程序结构有:
int c; /* 也可以是char类型 */
… /* 说明有关变量和设置初值等 */
fp = fopen(文件名,“W”); /* 或fP=fopen(文件,“wb”) */
while(还有字符(或字节)) {
… /* 生成字符(或字节)存于变量c */
fpute( c,fp); /* 将生成的字符(或字节)输出 */
}
fclose(fp);
… /* 输出程序结束报告 */
这里的函数fputc()的说明形式为
int fputc( char ch, FILE *fp)
该函数的功能是将ch中的字符输出到与fp文件指针相联系的文件中。函数fputc()返回一个整数值。如果输出成功,则返回值就是输出字符的ASCII代码值;如果输出失败,则返回EOF,即-1。
曾介绍过的函数putchar()是在stdio.h中用函数fputc()定义的宏:
# define putchar(c) fputc(c, stdout)
用宏putchar(c) 比写fputc(c,stdout) 在概念上更简单一些。从使用者来说,可以把putchar(c) 看作函数调用,不必严格地称它为宏调用。
4. 数据成块输入/输出函数
为加快程序的处理速度,程序可以成批地读人文件中的数据,也可成批地写数据到文件。它们是函数fread()和fwrite()。
成批读函数fread()的说明形式为
int fread(char *buf,int size, int count, FILE *rfp);
成批写函数fwrite() 的说明形式为
int fwrite( char *buf, int size, int count,FILE *wfp);
其中,buf是字符数组首元指针。对fread()来说,它是读人数据的存放开始地址;对bote()来说,是要输出数据的开始地址。size是读写的数据块的字节数。count为要进行读写的数据块的个数。rfp和wfp为文件指针。调用上述函数共读写size。 count个字节或字符。函数fread()和fwrite()的返回值是实际完成输入或输出的数据块的个数。一般情况下,输出调用成功,返回值为count的值。
如果是读写二进制文件,用函数fread()和fwrite()可以读写任何类型的信息。如有一个如下形式的通信录结构类型:
typedef struct {
char name[21]; /* 名字 */
char phone[15]; /* 电话 */
char zip[10]; /* 邮编 */
char addr[31]; /* 地址 */
} infoType;
利用类型infoType,可定义数组,如:
infoType info[30];
表示结构数组info[]能存放30个通信录数据。而下面的两个函数调用能分别实现20个通信录信息从某文件读出和写入某文件:
fread( info,sizeof(infoType),20,rfp);
fwrite( info,sizeo(infoType),20,wfp);
5.格式输入/输出库函数
与用函数scanf()从标准设备输入和用函数printf()向标准设备输出一样,一般文件也可进行格式输入和格式输出。
函数fscanf()和fprintf()分别能对一般文件进行格式输入和格式输出。它们的调用形式分别为
fscantf(文件指针,格式字符串,输入项地址表)
和
fprintf(文件指针,格式字符串,输出项表)
例如:
fscanf(rp,“%d%f”,&i,&r);
fprintf(wp,“i=%d, r=%6.4f\n”, i,r);
前者表示从与叩相联系的文件为变量i和r读人数据;后者表示将整型变量i和实型变量r的值按格式输出到与呷相联系的文件上。
6.字符串输入/输出库函数
函数fgets()和fputs()分别用于从正文文件输入字符串和向正文文件输出字符串。它们的说明形式分别为
char *fgets(char *str,int n, FILE *fp)
和
fputs(char *str, FILE *fp)
函数fgets() 用于从文件读取字符序列,并存于字符指针所指出的存储区域中。当连续读人n-1个字符,或遇到换行符时,读字符过程结束。
函数fgets()与函数gets()都在读人的字符序列之后自动存储字符串结束标记'\0’,使其成为字符串,并返回字符率的首字符指针。但它们有差别,函数fgets()除增加整型参数和文件指针参数之外,还在读到换行符时,存储换行符,而函数gets()不存储换行符。
函数fputs()的作用是将字符串复制到文件。其中字符串的结束标记符是不复制的,也不在复制的字符序列之后另外再添加换行符,这一点与函数puts()不同。
7.文件定位库函数
利用前面介绍的函数进行文件操作,只能顺序读写文件。然而,在有些场合,还需要能对文件作随机存取。实际上,因文件是存储在外部存储介质上的数据流,程序即刻能读写的是文件某一位置上的数据,这个位置称为文件的当前读写位置。对于顺序读写情况,每读写一个字符后,当前位置就自动向后移一个字符位置。对于允许随机读写的文件,要读写其他别的位置上的数据,就得改变文件当前读写位置。实现这样的移动可调用库函数rewind()和fseek()来实现。另设有函数ftell()用于查询文件当前读写位置。
函数rewind()的说明形式为
void rewind( FILE *fp)
函数rewind()的作用是使文件当前位置重新回到文件之首。该函数没有返回值。
函数fseek()是实现文件随机存取的最主要函数,用它可以将文件的当前位置移到文件的任意位置上。所谓文件的随机存取(或随机读写),是指读写完一个字符(或字节)之后,并不一定要读写其后继的字符(或字节),可以改变当前位置,去读写文件中其他位置上的字符(或字节)。
函数fseek()的说明形式为
fseek(FILE *fp, long offset, int ptrname)
其中ptname表示定位基准。只允许0,l或2。其中0代表以文件首为基准,1代表以当前位置为基准,2代表以文件尾为基准。0、l和2分别被定义为名称SEEK-SET、SEEK-CUR和SEEK-END。long型形参offset是偏移量,表示以prname为基准,偏移的字节数。因它是long型形参,当以整数作为它的实参调用函数 fseek()时,直在常数之后加上字母L,表示是long型常量。见下面调用函数fseek()的例子:
fseek(fp,4OL,SEEK-SET);
fseek(fp,20L,SEEK-CUR);
fseek(fp,-30L,SEEK-END);
分别表示将文件的当前位置定于离文件头4O个字节处、将文件当前位置定于离当前位置20个字节处、将文件的当前位置定于文件尾后退30个字节处。
函数fseek()一般用于二进制文件的随机读写,这是因为数据在二进制文件中的表示形式与数据在内存中的表示形式相同,各种类型的数据表示都有确定的字节数。而数据在正文文件中的表示形式与数据在内存中的表示形式不同,它们在输入输出时,数据的内外表示形式要进行转换,数值类型的数据在正文文件中的表示没有固定的字节数,计算位置时会发生混乱。
函数ftell()用于得到文件当前位置相对于文件首的偏移字节数。在随机方式存取文件时,由于文件位置频繁的前后移动,程序不容易确定文件的当前位置。调用函数ftell()就能非常容易地确定文件的当前位置。
函数ftell()的说明形式为
long ftell(FILE *fp)
利用函数ftell()也能方便地知道一个文件的长。如以下语句序列:
fseek(fp,OL,SEEK-END);
len=ftell(fp)
首先将文件的当前位置移到文件的末尾,然后调用函数ftell()获得当前位置相对于文件首的偏移,该偏移值等于文件所含字节数。
8.文件错误测式库函数
文件处理程序常用输入输出库函数的返回值来判断输入输出是否发生错误,也可以调用文件错误测试函数了解刚完成的文件输入输出函数的调用是否有错。文件错误测试函数的说明形式为
int ferror(FILE *fp)
函数的返回值是与文件指针中相联的最近一次文件库函数调用是否发生错误。若有错,函数返回非0值,若没有错误,函数返回0值。通常在这非0值,详细指出错误的类别和原因。
【编辑推荐】