第 13章 文件
主要内容
1,文件的
2,C文件的引入及特点
3,文件类型指针
4.文件的基本操作,打开、读写、关闭文件的引入
问题:通过前面的学习,我们知道,数据的处理包括数据的输入、加工处理、结果的输出。纵观前面的 c程序,对数据的输入输出有如下特点,
1)待处理的数据要么由程序员编程时在程序中设定 (该程序只能处理固定的数据),要么在程序运行时由用户输入 (而且每次运行时都要重新输入);
2)程序处理的结果均输出到显示屏,无法实现永久性的保存以便日后使用。
这显然不能满足实际应用的需求:
比如,在学生成绩的处理系统中,任何一个成绩只需输入一次,
以后在任何时候都能对其进行处理(求平均、查找、排名次等),而且处理的结果(如排名次)可以保存起来,供以后使用,无需每次都要先执行排名次的操作,然后才能查找某人的名次。
解决以上问题的方法是 使用文件,将有用的数据永久性的保存在外存上。
文件:一般指存储在外部介质上的数据的集合。
作用:操作系统是以文件为单位对数据进行管理的,每个文件的标识是文件名。当要读取某个文件中的数据时,必须先指出文件名,按文件名找到其中的数据;当需要将一批数据永久存放在外存上时,也必须先确定文件名,然后才能向它输出数据。
本章主要介绍 C文件的特点以及文件的使用
(基本操作)
13.1C文件概述
C语言将文件看作是“数据流”,即文件是由一串连续的、无间隔的字符数据构成,这种结构称为“流式文件结构”。
特点:流式文件结构在处理时不需考虑文件中数据的性质、类型和存放格式,访问时只是以字节为单位对数据进行存取,没有记录的概念,而将对数据结构的分析、处理等工作都交给后续程序去完成。因此,该文件结构更具灵活性,对存储空间的利用率高,同时对编程者要求也高。
C文件的分类(按存储格式):
文本文件:按数据的 ASCII码方式进行存储形成的文件。
二进制文件:按数据在内存中的存储形式原样输出到外存上形成的文件。
二者的区别主要体现在对数值型数据的处理上,例如,要存储一个整数 255,按二进制文件的格式存储,在文件中存放的是 255的二进制形式 11111111,战役各自结的存储空间;而以文本文件的格式存储,在文件中保存的是‘ 2’、‘ 5’、‘ 5’三个字符的 ASCII
码,占用 3个字节,见图:
文本文件二进制文件特别提示:按以上两种方式中的某一种方式存储的文件,使用时必须以原来的方式从外存上读出,才能保证数据的正确性 。
11111111
00110010 00110101 00110101
文件缓冲区
引入:
由于 CPU与内存(即主机)的工作速度非常快,而对外存
(磁盘、光盘等)的存取速度很慢。当访问外存时,主机必须等待慢速的外存操作完成后才能继续工作,严重影响了
CPU效率的发挥。解决 二者速度不匹配的方法是采用“缓冲区”技术。如图,13.2
缓冲区:在内存的 RAM中分配的一块存储空间。
其作用是:当需要向外存文件中写入数据时,并不是每次都直接写入外存,而是先写入到缓冲区,只有当缓冲区的数据存满或文件关闭时,才自动将缓冲区的数据一次性写入外存。
读数时,也是一次将一个数据块读入缓冲区中,以后读取数据时,先到缓冲区中找,若找到,则直接读出,否则,再到外存中寻找,找到后将其所在的数据块一次读入缓冲区。
目的:有效的减少了访问外存的次数。
C语言对文件缓冲区的控制方法有两种:缓冲文件系统和非缓冲文件系统
二者的区别在于“缓冲文件系统”的缓冲区是由操作系统在每个文件被打开时自动建立并管理的,而“非缓冲文件系统”的文件缓冲区需要由编程者为每个文件设置缓冲区的位置和大小。
说明:目前已采用“缓冲文件系统”作为标准的 C文件系统。
13.2文件类型指针
文件类型的引入:
使用缓冲文件系统时,系统将自动为每一个打开的文件建立缓冲区,此后,程序对文件的读写操作实际上是对文件缓冲区的操作。
为了便于编程,C语言中将有关文件缓冲区的一些信息(如缓冲区对应的文件名、文件所允许的操作方式、缓冲区的大小以及当前读 /写数据在缓冲区的位置等)。用一结构体类型来描述,类型名为
FILE,该结构体类型的定义包含在 stdio.h文件中。
文件类型指针(简称文件指针):
有了 FILE类型后,每当打开一个文件时,系统自动为该文件建立一个 FILE类型的结构体数据,将关于被打开文件及缓冲区的各种信息都存入到这个 FILE型数据中,以后对文件的操作都可通过这个 FILE类型的结构体数据进行。
对于编程者来说,只需要定义一个 FILE类型的指针变量,并让该指针指向已打开的文件,以后就可以通过该指针来访问其对应的文件。
文件类型指针的定义方法,FILE *fp;
fp是一个指向 FILE类型数据的指针变量,未赋值以前,该指针没有指向任何文件。
特别说明:编程时,一个文件只能对应一个文件指针。
只要有了指向某个文件的文件指针,具体的文件操作都由系统提供的文件操作函数实现,而不需了解文件缓冲区的具体情况,方便了程序员关于文件操作的编程。
13.3文件的打开与关闭
文件必须先打开,后使用;文件使用完后必须将其关闭。为什么呢?
原因:打开文件时,系统为该文件建立缓冲区,并将文件和缓冲区的信息写入 FILE类型的结构体数据中,返回该结构体数据的指针,以便通过该指针访问文件。
关闭文件时,系统将缓冲区中的数据作相应处理
(如写文件时,将缓冲区中的数据写入文件,避免数据丢失);然后释放缓冲区,文件指针不再指向该文件。
例 13.1打开文件,ff.txt",写入两个字符,然后关闭文件。
#include <stdio.h>
main()
{ FILE *fp;
fp=fopen("ff.txt","w");
/*打开文件,ff.txt"并让指针 fp指向该文件 */
if(fp==NULL) /*判断文件打开是否成功 */
{ printf("\n open file error."; exit(0);}
fputc('a',fp); fputc('b',fp); /*写入两个字符 */
fclose(fp); /*关闭文件 */
}
文件的打开,fopen()函数
函数原型:
FILE fopen(char *filename,char *mode)
功能:按字符串 mode指定的方式打开字符串
filename指定的文件。
参数的含义:指针 filename指向要打开的文件名字符串,指针 mode指向文件的使用方式字符串。
返回值:如打开文件成功,返回指向该文件的指针,否则,返回空指针。
如本例中语句,fp=fopen("ff.txt","w");的含义是:
按写文件的方式打开文件 ff.txt,并将文件指针赋给 fp,使得 fp指向问件 ff.txt.
特别提示:文件打开后,应检查此操作是否成功,即判断文件指针是否为空( NULL),然后才能决定能否对文件继续访问。
如本例中语句:
if(fp==NULL) {printf("\n open file error"); exit(0);}
通常将打开与检查操作合为一个语句:
if( (fp=fopen("ff.txt","w"))==NULL)
{printf("\n open file error"); exit(0);}
关于文件使用方式的说明:
见表 13.1
文件的关闭,fclose( )函数
函数原型,int fclose(FILE *fp)
功能:关闭 fp指向的文件,将缓冲区数据作相应处理后释放缓冲区。
返回值:若成功关闭,则返回值 0;否则,返回非 0值。
如本例中语句,fclose(fp); 的含义:
关闭 fp指向的文件 ff.txt。
特别提示:使用完文件后应及时关闭否则可能会丢时数据(因为写文件时,只有当缓冲区满时才将数据真正写入文件,若当缓冲区未满是结束程序运行,缓冲区中的数据将会丢失。
13.4文件的读写
文件的使用包括:读,写、文件指针的定位
文件允许的操作由打开文件时的 mode参数决定。
特别提示,文件的读写都是对文件的 当前位置 进行的。
所谓当前位置,是指文件的数据读写指针指示的的位置。文件打开时,该指针指向文件的开头;一次读写完成后,该指针自动后移(移至本次读写数据的下一个字节)。
常用的读写函数有以下几组:
字符读写函数,fgetc() fputc()
数据块读写函数,fread() fwrite()
格式化读写函数,fscanf() fprintf()
文件的定位函数,rewind() fseek()
出错检测函数,ferror()
下面分别举例介绍其应用:
一、字符读写函数
函数原型,int fgetc(FILE *fp)
功能:从 fp指向的文件的当前位置读取一个字节,
返回值为该字节数据转换成的整数。
常见调用格式,ch=fgetc(fp);
函数原型,int fputc(char ch,FILE *fp)
功能,把一个字符写入 fp指向的文件。
一般调用格式,fputc(ch,fp);
应用:适用于字符数据的读写。
例 13.2输入一段字符,(以‘ #’结束),将其存入磁盘,文件名为 aa.dat。
L13_2.c:
#include <stdio.h>
main()
{FILE *fp; char ch;
if((fp=fopen("aa.dat","w"))==NULL)
{printf("\n open file error."); exit(0);}
while((ch=getchar())!='#')
fputc(ch,fp);
fclose(fp);
}
文件 "aa.dat"是文本文件,可以察看其文件内容 。
例 13.3编程读出 ASCII文本文件,aa.dat"并显示到屏幕上。
( L13_3.c)
#include <stdio.h>
main()
{FILE *fp; char ch;
if((fp=fopen("aa.dat","r"))==NULL)
{printf("\n open file error."); exit(0);}
while(!feof(fp))
{ch=fgetc(fp); putchar(ch); }
if(fclose(fp)) printf("\n file close error.");
}
feof(fp)函数:检测读写数据指针是否指向了文件末尾。 通常用作读数据的结束条件。
二、数据块读写函数
应用:适用于任何类型的数据读写,特别适合复杂的数据类型,如数组、浮点数、结构体数据等的读写。
读函数原型:
int fread(void *buf,int size,int count,FILE *fp)
功能:从 fp指向的文件连续读出 count个大小为 size字节的数据块,存放在以 buf为起始地址的内存中。
返回值:成功读出,则返回读出数据块的个数;否则,返回值小于函数参数指定的数据块个数,说明出错。
数据块写函数:
int fwrite(void *buf,int size,int count,FILE *fp)
功能:将内存中从 buf地址开始的 count个大小为 size字节的数据块写入 fp指向的文件中。
返回值:正常写入数据后返回数据块个数。若返回的数小于函数参数指定的数据块个数 count,则表明写出错。
例 13.4建立一个学生成绩文件,包含 30名学生的学号、姓名、三门课成绩。(文件名自定,设输入文件名 score.dat)
分析,先定义数据结构,后设计算法
1)数据结构:
每个学生的成绩有 3项,可用一维数组 score[3]保存; 每个学生的信息有 3项,而且类型不相同,可定义一结构体类型
sc_list描述,该类型包括 3个成员:学号、姓名、成绩;
30人的信息可用结构体数组保存。
2)算法:
step1:输入 30个学生的信息,可用循环结构实现。
step2:输入文件名,以二进制写文件方式打开该文件。 step3:将已输入的学生信息写入文件,可以一次写入 30个结构体数据块,也可以用循环结构,每次写入一个结构体数据块。
step4:关闭文件。
程序,l13_4.c
struct sc_list
{ char no[8];
char name[10];
float score[3];
};
#define N 3
#include <stdio.h>
main()
{ struct sc_list stu[N];
FILE *fp;
int i,j; char filename[10];
printf("\n input information of 30 students\n");
for(i=0;i<N;i++)
{printf("no:"); gets(stu[i].no);
printf("nmae:");gets(stu[i].name);
printf("score:");
for(j=0;j<3;j++)
scanf("%d",&stu[i].score[j]);
getchar();
}
printf("\n input file name for save:");
gets(filename);
if((fp=fopen(filename,"wb"))==NULL)
{printf("\n open file error."); exit(0);}
for(i=0;i<N;i++)
fwrite(&stu[i],sizeof(struct sc_list),1,fp);
fclose(fp);
}
调试时,将 N的值设为 3,调试成功后该为 30即可。
运行结果:
例 13.5将上例的成绩表读出并列表显示。
分析:先定义数据结构,再设计算法。
数据结构:与上例相同。
算法:
S1:输入文件名,打开文件。
S2:从打开的文件读 30个学生的信息:可以用循环实现,每次读出一个数据块存入一个数组元素;也可以一次读出 30个数据块,存入结构体数组。
S3:输出每个学生的信息,用循环结构实现。
S4:关闭文件。
struct sc_list
{char no[8]; char name[10]; int score[3];};
#define N 3
#include <stdio.h>
main()
{ struct sc_list stu[N];
FILE *fp;
int i,j; char filename[10];
printf("\n input file name for open:");
gets(filename);
if((fp=fopen(filename,"rb"))==NULL)
{printf("\n open file error."); exit(0);}
for(i=0;i<N;i++)
if(fread(&stu[i],sizeof(struct sc_list),1,fp)!=1)
{ printf("\n read file error."); break;}
for(i=0;i<N;i++)
{printf("\n no name score1 score2 score3");
printf("\n%8s %10s ",stu[i].no,stu[i].name);
printf(" %6d %6d %6d
",stu[i].score[0],stu[i].score[1],stu[i].score[2]); }
fclose(fp);
}
以上读数和输出两个循环可以合并为一个,即循环体内用一结构体变量接收读出的一个数据块,然后输出。程序将更简练。
三、格式读写函数
fprintf()和 fscanf()函数根据写入和读取时所规定的数据格式来存取数据。其作用与 printf函数,scanf函数类似,只有一点不同,fprintf 和 fscanf函数的读写对象不时标准的终端设备而是指定的文件。
一般格式:
fprintf(文件指针,格式字符串,输出表列)
fscanf(文件指针,格式字符串,地址表列)
应用:可用于任意数据的读写,特别适合读写整型、实型等数值数据文件。
例 13.6用文本编辑软件输入一批实验数据,数据间用空格或回车分开,校正无误后,存入文件 "aa.dat",以后需要时可直接从该文件读出数据并处理。编程从该文件读出数据并列表输出(每行 8个数)。
#include <stdio.h> /*程序 l13_6.c*/
main()
{ float data; int n=0;
FILE *fp;
if((fp=fopen("aa.dat","r"))==NULL)
{printf("\n open file error."); exit(0);}
printf("\n output data list:\n");
while(!feof(fp))
{fscanf(fp,"%f",&data);
n++;
printf("%10.3f",data);
if(n%8==0) printf("\n");
}
fclose(fp);
}
说明:本例中的数据文件使用文本编辑软件输入,便于对数据修改和检查。
用 fscanf函数时,同 scanf一样,要注意格式控制字符串规定的格式与所读取数据的格式的匹配,否则,很容易出错。因此,对于整型、字符型等多种类型数据的输入,一般不用该函数。
特别提示:原文件是文本文件,因此打开文件时必须使用 "r"(文本文件读 )方式。若使用
"rb"(二进制文件读 )方式,读入数据会出错。
四、文件的定位
文件的读写都是对位置指针指向的当前位置的读写,文件打开时,位置指针指向文件的第一个字符,每次读写一个字符后,位置指针自动向后移动,指向下一个字符位置。
前面例题中,都是对文件进行顺序读写,而实际应用中,经常需要随机读写,即直接读取文件中指定位置的数据,这就需要位置指针按读写数据的需要移动,而不仅仅是顺序移动。
文件定位函数 rewind()和 fseek()可以强制使位置指针指向其他指定的位置 。
常用文件定位函数的一般格式,
rewind(文件指针)
fseek(文件指针,位移量,起始点)
ftell(文件指针)
功能,rewind函数使文件的位置指针移至文件的开头。
fseek函使文件的位置指针以“起始点”为基点,向前移动“位移量”指定的字节数。起始点的取值有 3个,0表示文件头,
1表示当前位置,2表示文件尾。
ftell函数用于检测文件的当前位置。
例 13.7编程实现,输入 6本教材的信息(书名、价格、出版社),
输出第 1,3,5本书的信息。
分析:定义结构体类型 struct book来描述教材的信息。使用
,wb+”方式打开文件,循环输入每一本书的信息并写入文件;
此时位置指针指向文件尾,使用 rewind函数使指针指向文件开头,然后循环使用 fseek函数使位置指针定位到指定的位置、
读出一本书的信息并输出。
程序 13_7.c:
#include <stdlib.h>
#include <stdio.h>
struct book
{ char name[30];
float price;
char publisher[20];
};
main()
{ struct book x;
FILE *fp; int i;
if((fp=fopen("book.dat","wb+"))==NULL)
{ printf("\n open file error,");exit(0);}
for(i=0;i<6;i++)
{ printf("\n book%d:",i);
gets(x.name); scanf("%f",&x.price);getchar();
gets(x.publisher);
fwrite(&x,sizeof(struct book),1,fp);
}
rewind(fp);
for(i=0;i<6;i+=2)
{ fseek(fp,i*sizeof(struct book),SEEK_SET);
fread(&x,sizeof(struct book),1,fp);
printf("\n %-10s %6.1f %-
20s",x.name,x.price,x.publisher);
}
fclose(fp); }
文件小结
本章主要介绍以下几点:
1,C语言中的文件是一“数据流”,文件结构为“流式文件结构”,采用缓冲文件系统,通过文件类型的指针访问文件。
2、文件使用使必须先打开,后使用。
3、常用读写文件的函数有:字符读写函数 fgetc和 fputc,数据块读写函数 fwrite和 fread,格式读写函数 fscanf和 fprintf。
其中字符读写函数适合对 ASCII码文件的存取;数据块读写函数适合任何数据的存取,特别是结构体数据,因此也是最常用的一对文件读写函数;格式读写函数原则上适合任何类型数据的存取,但同数存取多种类型数据是易出错,因此用的较少。
4、实现随机读写文件的方法是使用文件定位函数 rewind和
fseek及 ftell函数,使位置指针移到指定位置。
练习
1、将从终端读入的 10个整数以二进制方式写入一个名为 "bi.dat"
的新文件中。请填空:
#include <stdio.h>
#include <stdlib.h>
main()
{FILE *fp;
int i,j;
if((fp=fopen("bi.dat","wb"))==NULL)exit(0);
for(i=0;i<10;i++)
{scanf("%d",&j);
fwrite(&j,sizeof(int),1,fp); }
fclose(fp);
}
2、编程实现:用户由键盘输入一个文件名,然后输入一串字符(用 #结束)存放到此文件中,形成文本文件,并将字符的个数写到文件尾部。
#include <string.h>
#include <stdio.h>
main()
{ FILE *fp;
char ch,fname[32];int count=0;
printf("input the filename:");
gets(fname);
if((fp=fopen(fname,"w+"))==NULL)
{printf("\ncan't open file:%s",fname); exit(0);}
printf("\n enter data:");
while((ch=getchar())!='#') {fputc(ch,fp);count++;}
fprintf(fp,"\n%d\n",count); fclose(fp); }