470 likes | 666 Views
本章主要内容. 第十章 文 件. 文件概述 文件的打开与关闭 文件的读写 文件应用举例. 10.1 文件概述. 10.1.1 文件的存储方式 数据文件在磁盘上有两种存储方式 : 一种是按 ASCII 码存储 , 称为 ASCII 码文件 ; 一种是按二进制码存储,称为二进制文件。 1.ASCII 码文件 ASCII 码文件(又称文本文件 ) ,它的每一个字节存放一个 ASCII 代码,代表一个字符,便于字符的输入和输出处理,但占用存储空间较大。. 2. 二进制文件
E N D
本章主要内容 第十章 文 件 • 文件概述 • 文件的打开与关闭 • 文件的读写 • 文件应用举例
10.1 文件概述 10.1.1 文件的存储方式 数据文件在磁盘上有两种存储方式:一种是按ASCII码存储,称为ASCII码文件;一种是按二进制码存储,称为二进制文件。 1.ASCII码文件 ASCII码文件(又称文本文件),它的每一个字节存放一个ASCII 代码,代表一个字符,便于字符的输入和输出处理,但占用存储空间较大。
2.二进制文件 二进制文件是把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放,一个字节并不对应一个字符,但占用存储空间较小。 例如: 有一个整数 10000,在内存中占 2个字节,如果按ASCII码形式存储,则占5个字节,而按二进制形式存储,在磁盘上只占2个字节。用ASCII码形式输出与字符一一对应,一个字节代表一个字符。
10.1.2 C的文件操作 • 由前所述,一个 C文件是一个字节流或二进制流,它把数据看作是一连串的字符(字节)。 • 在C语言中,对文件的存取是以字符(字节)为单位的。输入输出的数据流的开始和结束仅受程序控制,而不受物理符号(如回车换行)控制。我们把这种文件称为流式文件。
10.1.3 缓冲文件与非缓冲文件 过去使用的C语言版本有两类文件系统:一类为缓冲文件,又称为标准I/O文件或高级文件系统;另一类为非缓冲文件,又称为系统I/O文件或低级文件系统 1.缓冲文件 缓冲文件系统是指系统自动地在内存区为每一个正在使用的文件开辟一个缓冲区。当从内存向磁盘文件输出数据时,必须先送到内存缓冲区,待缓冲区装满后再向磁盘输出。 输入数据的过程正好相反,首先将一批数据从磁盘输入到缓冲区,然后再从缓冲区将数据逐个送到程序数据区。这样做是为了减少系统读写磁盘的次数,提高处理速度。
2.非缓冲文件 非缓冲文件是指系统不能自动开辟确定大小的缓冲区,而由程序本身根据需要设定。 在UNIX系统下,缓冲文件用来处理文本文件,非缓冲文件用来处理二进制文件。1983年ANSI C标准决定不再采用非缓冲文件,而只采用缓冲文件,即用缓冲文件来处理文本文件和二进制文件。考虑到程序的可移植性,希望读者在编程时,尽可能地遵循ANSI C标准而不要使用非缓冲文件。
10.1.4 文件类型指针 在C语言中,对文件操作都是通过标准函数实现的,同时,在对文件操作时,必须定义一个文件指针变量,只有通过文件指针变量,才能找到与其相关联的文件,实现对文件的访问 C 语言的文件管理系统为每个被使用的文件在内存中开辟一个区,用来存放诸如文件的名字、文件的状态,这些信息被保存在一个由系统定义的、取名为FILE的结构体类型的变量中。
FILE定义形式如下: typedef struct { int fd ; /*文件号*/ int cleft ; /*缓冲剩下的字符*/ int mode ; /*文件操作模式*/ char*nextc; /*下一个字符位置*/ char*buff; /*文件缓冲位置*/ }FILE; 其中:大写的FILE是用typedef自定义的结构类型名。 有了FILE类型之后,就可以用它来定义若干个FILE类型的变量,以便存放若干个文件的信息。例如: FILE*fp; fp为一个指向FILE类型结构的指针变量。
这样可以使fp指向某一个文件的结构变量,从而通过该变量中的文件信息访问该文件。在缓冲文件系统中,对于文件的所有操作,均基于这样的一个FILE类型的指针变量来进行。也就是说,在文件操作之前,一定要定义这样的文件类型指针,然后,通过它来进行各种操作。这样可以使fp指向某一个文件的结构变量,从而通过该变量中的文件信息访问该文件。在缓冲文件系统中,对于文件的所有操作,均基于这样的一个FILE类型的指针变量来进行。也就是说,在文件操作之前,一定要定义这样的文件类型指针,然后,通过它来进行各种操作。 由于FILE是一个类型,所以可以定义多个这种类型的指针变量,使它们指向多个文件,以便对多个文件进行处理。 例如: FILE*fp1,*fp2; 定义了2个FILE类型的指针变量。
10.2 文件的打开与关闭 10.2.1 文件的打开(fopen函数) C 语言在其标准输入输出函数库中定义了对文件操作的若干函数,其中fopen()函数用来打开磁盘文件。 格式: FILE*fp; fp=fopen(″文件名″,″文件使用方式″); 功能: 打开文件。
其中,fp是FILE文件类型指针,用来指向被打开文件数据区(结构变量)的起始地址; fopen()函数打开一个名为“文件名”的文件,并按 “文件使用方式” 来读写文件。文件使用方式见表10.1。 表10.1 文件使用方式 文件使用方式 含义 r(只读) 为输入打开一个文本文件 w(只写) 为输出打开或建立一个文本文件 a(追加) 向一个文本文件尾部追加数据 rb(只读) 为输入打开一个二进制文件 wb(只写) 为输出打开或建立一个二进制文件 ab(追加) 向一个二进制文件尾部追加数据 r+(读写) 为读/写打开一个文本文件 w+(读写) 为读/写建立一个新的文本文件 a+(读写) 为读/写打开或建立一个文本文件 rb+(读写) 为读/写打开一个二进制文件 wb+(读写) 为读/写建立一个新的二进制文件 ab+(读写) 为读/写打开或建立一个二进制文件
说明: (1)″r″、″w″、″a″是三种基本的文件使用方式,分别表示读、写、追加;″b″是指定为二进制文件,″+″指定可以读和写。 (2)″r″方式打开的文件只能用于向计算机输入,而不能用作向该文件输出数据,所以该文件必须已经存在,否则出错. (3)″w″方式打开的文件只能用于向该文件写数据,而不能用来向计算机(内存)输入。如果原来不存在该文件,则在打开时新建立一个以指定名字命名的文件。如果原来已存在一个以该文件名命名的文件,则在打开时将该文件删去,然后重新建立一个新文件。 (4)″a″方式打开的文件,将文件指针移至文件尾部,然后在文件尾部添加新数据。 (5)″r+″表示从已存在的文件中读入数据,读完后可以向文件输出数据(更新文件);
″w+″表示建立或删除文件内容,向文件写数据;″w+″表示建立或删除文件内容,向文件写数据; • ″a+″表示不删除原文件,而文件指针移至文件尾部,可以追加和读数据,若文件不存在,则建立一个新文件,待写完成后,可以读入数据。例如: • FILE*fp; • fp=fopen(″A1″,″w″); • 其调用含义为:打开名字为A1的文件,文件使用方式为“只写”。fopen()函数返回一个指向 A1文件的指针,并赋给fp,即fp指向A1文件。 • 当文件打开失败时,fopen返回一个NULL空指针值。 • 文件打开失败的原因有: • 用″r″方式打开一个并不存在的文件; • 用″w″方式打开一个文件,而磁盘空间已满; • 磁盘出故障或写保护等。
在打开一个文件时,可判断文件是否被正常打开,常用的打开文件的方法为:在打开一个文件时,可判断文件是否被正常打开,常用的打开文件的方法为: if(fp=fopen(″filename″,″r″)==NULL) { printf(″\n Can not open this file \n″); exit(0); }
10.2.2 文件的关闭(fclose函数) 使用完一个文件后,应该及时关闭它,以防止再被误用。关闭文件就是使原来指向该文件的文件指针与文件脱离,以便再使用该文件指针时,就不会对该文件进行读写操作,除非再次打开该文件。 • 格式: fclose(文件指针); • 功能:用fclose()函数关闭文件。 例如: fclose(fp); fclose()函数带有一个返回值,当顺利地执行了关闭操作时,则返回值为0;若返回值为非0值,则表示关闭文件时有错误。这类错误一般是由于驱动器和磁盘故障或磁盘满等原因造成的,并可用ferror()函数来测试。
10.3 文件的读写 10.3.1 读写一个字符 1.fputc()函数 • 格式: fputc(ch,fp); • 功能: 把一个字符写到指定的磁盘文件中。 • 说明: (1)ch为字符变量或字符常量。 (2)fp为FILE类型的文件指针变量,它由fopen()函数赋初值。
(3)fp所指文件的打开方式必须是″w″或″w+″。(3)fp所指文件的打开方式必须是″w″或″w+″。 (4)fputc()函数有一个返回值。若写成功,则返回这个写出的字符;若写失败,则返回EOF。EOF是在stdio.h文件中定义的符号常量,值为-1。当然,EOF也是文件结束标记。 例10.1 从键盘输入若干字符,然后逐个把它们送到磁盘文件中去,直到输入″*″为止。 #include<stdio.h> #include<string.h> main() { char c; FILE*fp; if((fp=fopen(″A2″,″w″))==NULL) {
puts(″\n Can’t open the file!\n″); • exit(0); • } • while((c=getchar())!=′*′) fputc(c,fp); • fclose(fp); • } • 程序运行的结果是将从键盘随机键入的若干字符写到文件名为“A2”的磁盘文件中去。若“A2”文件已存在,则更新;若不存在,则建立。键入的内容可以用DOS的TYPE命令查看。
2.fgetc()函数 格式: ch=fgetc(fp); 功能:从指定的磁盘文件中读取一个字符。 说明: (1)ch为字符变量,用来接收从磁盘文件读入的字符。 (2)fp为FILE类型的文件指针变量,它由fopen()函数赋初值。 (3)fp所指文件的文件打开方式必须是″r″或″r+″,并且文件必须存在。 (4)fgetc()函数返回一个字符,该字符就是从磁盘文件读入的字符。如果读到文件尾,fgetc()函数会返回一个文件结束标志EOF(End Of File)。EOF是符号常量,其值已在stdio.h中定义为-1。由于 ASCII码不可能出现-1,所以 EOF不能作为ASCII字符在屏幕上显示。
例如,如果把一个指定的磁盘文件从头到尾按顺序读入并在屏幕上显示出来,可以用如下程序段实现:例如,如果把一个指定的磁盘文件从头到尾按顺序读入并在屏幕上显示出来,可以用如下程序段实现: • while((ch=fgetc(fp))!=EOF) • putchar(ch); 例如,如果把一个指定的二进制文件从头到尾按顺序读入并在屏幕上显示出来,可以用如下程序段实现: • while(!feof(fp)) • putchar(fgetc(fp)); • feof()是文件尾的测试函数,当文件指针指向文件尾,该函数返回真值(非0),否则返回值为假(0)。
例10.2 将某一指定磁盘文件的内容复制到另一个指定的磁盘文件中。 #include<stdio.h> main() { char ch,infile[10],outfile[10]; /*字符数组infile,outfile用于存放文件名*/ FILE*inf,*outf; /*定义两个文件指针*/ printf(″\n Enter source filename:″); scanf(″%s″,infile); /*读入源文件名*/ printf(″\n Enter target filename:″); scanf(″%s″,outfile); /*读入目标文件名*/ if((inf=fopen(infile,″r″))==NULL) /*以读方式打开源文件*/
{ printf(″Can’t open source file!\n″); exit(0); } if((outf=fopen(outfile,″w″))==NULL) /*以写方式打开目标文件*/ { printf(″Can’t open target file!\n″); exit(0); } while(!feof(inf)) fputc(fgetc(inf),outf); /*用fgetc()读出文件内容,同时用 fputc写入到目标文件中*/ fclose(inf); fclose(outf); }
程序的运行情况如下: Enter source filename:A1.C (假设源文件为A1.C) Enter target filename:B1.C (假设目标文件为B1.C) 程序运行后,A1.C中的内容就会原样地全部复制到B1.C中.此时,可用DOS的TYPE命令验证A1.C与B1.C中的内容是否一样。
10.3.2 读写一个字符串 1.fputs()函数 格式: fputs(str,fp); 功能:把一个字符串写到指定的磁盘文件中。说明: (1)str为字符数组或字符型指针,fp为 FILE类型的文件指针变量。 (2)fputs()函数与前面介绍过的 puts()函数类似,只是 fputs()可以把某一个字符串输出到指定的文件中,而不限于在终端上显示。 (3)fputs()函数带有返回值。若输出成功,返回值为0,否则为非零值。
例 10.3 从键盘输入两行字符,并存入指定的磁盘文件A2.TXT中。 #include<stdio.h> main() { inti; char str[81]; FILE*fp; if((fp=fopen(″A2.TXT″,″w″))==NULL) { puts(″Can’t open the file!\n″); exit(0); }
for(i=1;i<3;i++) { gets(str); fputs(str,fp); fputs(″\n″,fp); } fclose(fp); }
2.fgets()函数 • 格式: fgets(str,n,fp); • 功能: 从指定的磁盘文件中读入一个字符串。 • 说明: • (1)str为字符数组或字符型指针。fp为 FILE类型的文件指针变量。其具体操作是从fp指向的文件中读取n-1个字符,并放到字符数组str中。如果在完成读入n-1个字符之前,遇到换行符或EOF,则读入过程立即结束。字符串读入后自动在str的串尾加一个′\0′字符。 • (2)fgets()的返回值为str的首地址。若只读到文件尾或出错,则返回空指针NULL。
例10.4 从磁盘文件A3中读取一串字符,并显示在屏幕上。 #include<stdio.h> main() { char str[81]; FILE*fp; if((fp=fopen(″A3″,″r″))==NULL) { puts(″Can not open the file!\n″); exit(0); } while(fgets(str,81,fp)!=NULL) printf(″%s″,str); fclose(fp); } 当fgets()函数读到文件尾时,则返回空指针NULL,并结束读取过程。
10.3.3 读写数据字段 在编程时,经常碰到需要读写一个或多个字段的情形,而此时的字段是各种类型的数据,如整型、实型、浮点型、双精度型和结构变量型的值,以及成段输入输出各种数据。 ANSI C标准提出设置两个函数,即fread()和fwrite()来实现对上述数据字段的读写。
1.fread()函数 格式: fread(buffer,size,count,fp); 功能: 从指定的文件中读入一组数据。 说明: (1)buffer用于存放读入数据的缓冲区指针,指向读入数据的起始地址。 (2)size是读入的每个数据项的字节数。 (3)count是指要进行读多少个size字节长的字段。 (4)fp是FILE类型的文件指针变量。 例如: 利用fread函数,从fp所指定的文件读入5个整型数据,则fread函数中的参数设置如下: int x[5]; … fread(x,2,5,fp); 其中第2个参数2是指每个整型数据占2个字节。
2.fwrite()函数 格式: fwrite(buffer,size,count,fp); 功能: 将一组数据输出到指定的磁盘文件中。 说明: (1)buffer用于存放输出数据的缓冲区指针,指向输出数据的起始地址。 (2)size是输出的每个数据项的字节数。 (3)count是指要输出多少个size字节的数据项。 (4)fp是FILE类型的文件指针变量。 例如: 利用fwrite函数,将x数组中的5个浮点型数据输出到磁盘文件中,则fwrite函数中的参数设置如下: static float x[5]={1.5,2.4,3.6,4.7,6.5}; … fwrite(x,4,5,fp); 其中第2个参数4是指每个浮点型数据占4个字节。
1.fprintf()函数 格式: fprintf(文件类型指针,格式控制,输出表列); 功能: 是将“输出表列”中的相应变量中的数据,经过相应的转换后,输出到″文件类型指针″所标识的文件中。 例如:把变量a和b的值分别按%d的%f的格式输出到fp所标识的文件中。 int a=20; float b=5.2; fprintf(fp,″%d%f″,a,b); 一般来讲,由fprintf函数写入磁盘文件中的数据,应由fscanf函数以相同格式从磁盘文件读出来使用。 10.3.4 文件的格式化读写
2.fscanf(函数) 格式: fscanf(文件类型指针,格式控制,地址表列); 功能: 从文件类型指针所标识的文件读入一字符流,经过相应的格式转换后,存入地址表列中的对应变量中,其中格式控制部分的内容与scanf函数完全一样。例如: int a,b; float f; fscanf(fp,″%d,%d,%f″,&a,&b,&f); 注意: 在利用fscanf函数从文件中进行格式化输入时,一定要保证格式说明符与所对应的输入数据的一致性,否则将会出错。通常的做法是用什么格式写入的数据,就应该用什么格式来读出。
10.4 文件定位与随机读写 定位一般用rewind()和fseek()函数。 1.rewind()函数 格式: rewind(fp); 功能: 将文件位置指针重新设置到文件的开头。 说明: (1)fp为文件类型指针。 (2)rewind()函数无返回值。
2.fseek()函数 对流式文件可以进行顺序读写,也可以进行随机读写。关键在于文件的位置指针的移动方式。如果位置指针是按字节位置顺序移动,就是顺序读写。如果位置指针可以按需要移动到文件内的任意位置,就是随机读写。 在C语言中,文件位置指针是通过fseek()函数实现随意移动的。 格式: fseek(文件类型指针,位移量,起始点); 功能: 对流式文件的位置指针按字节移到指定的位置。
说明: (1)“位移量”是一个long类型的数据,是指从 “起始点”起向前或向后移动的字节数。 (2)“起始点”用0、1、2分别表示“文件开始”、“当前位置”、“文件末尾”。ANSI C 标准指定“起始点”的名字、代表数字及其含义的对应关系如表10.2。 (3)fseek() 函数一般用于二进制文件,因为文本文件要发生字符转换,计算位置时往往会发生混乱。 (4)fseek()函数的返回值为0时,表示执行正确;否则,表示执行不正确。
表10.2 起始点的表示方法 起始点含义 名 字 代表数字 文件开始 SEEK- SET 0 文件当前位置 SEEK- CUR 1 文件末尾 SEEK- END 2 例如: fseek(fp,128L,0); /*文件位置指针向前移到距文件头128字节。*/ fseek(fp,-16L,1); /*从当前位置向文件头方向后移16个字节。*/ fseek(fp,-32L,2);. /*从文件尾向文件头方向后移32个字节。*/ fseek(fp,0L,0); /*移到文件头。*/
3.ftell()函数 格式: ftell(文件类型指针); 功能: 返回文件的当前读写位置,并用相对于文件头的位移量来表示。 说明: (1)ftell()函数返回值为-1L时,表示出错(如文件不存在或指定流为终端)。 (2)ftell()函数仅对流式文件有效,对ASCII文本文件往往会出错。 例如: if((i=ftell(fp))==-1L) printf(″FILE error!\n″);
例10.5 从键盘上输入20个整数存在文件中。 #include<stdio.h> main() { FILE*fp; int data[20],i; /*data[20]用于存放键入的整数*/ for(i=0;i<20;i++) scanf(″%d″,&data[i]); /*读入20个整型数到数组data中*/ if((fp=fopen(″b1.dat″,″w+″))==NULL) printf(″Can not open file b1.dat!″); else { fwrite(data,2,20,fp); /*设data为首地址,以2个字节为单位 写入20个整型数到文件中*/ fclose(fp); /*关闭文件*/ } }
例10.6 编一程序,将例10.5中存入b1.dat文件中的20个整数读入到程序中,并显示出来。 #include<stdio.h> main() { FILE*fp; int data[20],i; /*数组data用于存放从文件中读出的整数*/ if((fp=fopen(″b1.dat″,″r″))==NULL) printf(″File b1.dat has not been found!″); else {fread(data,2,20,fp); /*将文件中连续20个整型数读入到数组data中*/ fclose(fp); for(i=0;i<20;i++) /*将读入到data中的数显示出来*/ printf(″%d″,data[i]); } }
例10.7 建立 10名学生的信息表,已知每个学生的信息包括:姓名、学号、年龄、性别及十门课成绩。要求: (1)从键盘输入10名学生的信息。 (2)把学生信息输出到sdata.dat磁盘文件中。 #include<stdio.h> struct student { /*定义用于存放学生信息的结构体*/ char name[20]; int num; int age; char sex; float score[10]; }; struct student st[10];
/*定义和10名学生对应的结构体变量st[0]…st[9]*//*定义和10名学生对应的结构体变量st[0]…st[9]*/ main() { FILE*fp; int i,j; for(i=0;i<10;i++) { scanf(″%s%d″,&st[i].name,&st[i].num); /*读入姓名,学号*/ scanf(″%d%c″,&st[i].age,&st[i].sex); /*读入年龄,性别*/ for(j=0;j<10;j++) /*依次读入每个学生的十门课成绩*/ scanf(″%f″,&st[i].score[j]); }
if((fp=fopen(″sdata.dat″,″w″))==NULL) /*打开存放学生信息的数据文件sdata.dat*/ printf(″Can not open file sdata.dat″); else { for(i=0;i<10;i++) /*将前面键入的st[0]…st[9]对应的数据写入到文件中*/ fwrite(&st[i],sizeof(struct student),1,fp); fclose(fp); /*关闭文件*/ } }
说明: 结构体作为一种数据类型同其他基本数据类型一样,也可以以变量为单位来进行输入和输出。程序中的语句: for(i=0;i<10;i++) fwrite(&st[i],sizeof(struct student),1,fp); 表示每循环一次,输出st结构体数组中的一个元素,每个元素的地址用&st[i]来表示,每个元素占用的字节数用sizeof(struct student)来计算。
例10.8 将例10.7中输出到磁盘文件sdata.dat中的数据按顺序读入到程序中,并显示出来。 • #include<stdio.h> • struct student • { • char name[20]; • int num; • int age; • char sex; • float score[10]; • }; • struct student st[10];
main() { FILE*fp; int i,j; if((fp=fopen(″sdata.dat″,″r″))==NULL) printf(″Can not open file sdata.dat″); else { for(i=0;i<10;i++) fread(&st[i],sizeof(struct student),1,fp); /*将文件中的数据以结构体为单位依次读入*/ fclose(fp); /*到st[0]…st[9]对应的结构变量中*/
for(i=0;i<10;i++) {printf(″Name=%s,Num=%d″,st[i].name,st[i].num); /*显示姓名,学号*/ printf(″Age=%d,Sex=%c\n″,st[i].age,st[i].sex); /*显示年龄,性别*/ for(j=0;j<10;j++)/*依次显示每名学生的成绩*/ printf(″%f″,st[i].score[j]); printf(″\n″); } } }