第 10章文件本章要点
10.1 文件的基本概念概述
10.2 文件类型及其指针
10.3 文件的打开和关闭
10.4 文件的输入和输出
10.5 文件的定位函数
10.6 出错检测函数
本章要点
◆ 文件中数据的组织形式:文本文件、二进制文件
◆ 文件的打开函数和关闭函数
◆ 文件的输入和输出函数
◆ 缓冲文件操作函数的使用
10.1文件的基本概念
10.1.1文件的概念及分类
1.文件的概念
文件是程序设计中的一个重要概念。所谓“文件”是指一组相关数据的有序集合。这个数据集的名称,就叫做文件。实际上在前面的各章中我们已经多次使用了文件,例如源程序文件、目标文件、可执行文件、库文件 (头文件 )等。
2.文件的分类
1)按数据格式分类。
C语言把文件看成是一个字符 (字节 )的序列,即由一个个字符 (字节 )数据组成,按数据格式可分为二进制文件
(二进制流 )和文本文件 (字符流 ),虽然它们都是字节序列,但它们表示数据的形式和存储方式不同,所以 C语言对它们要区别处理。
2)按读写方式分类。
按文件的读写方式,可以把文件分为“顺序文件”
和“随机文件”。对顺序文件来说,读写必须从头开始。
对随机文件来说,读写的过程是随机的。
10.1.2 文件缓冲区
C 系统对文件的处理方式有两种:缓冲文件和非缓冲文件。
( 1)缓冲文件
所谓缓冲文件系统是指系统自动地在内存区为每个正在使用的文件开辟一个缓冲区。当从内存向磁盘输出数据时,
先将数据送到内存缓冲区,待缓冲区装满后,再一起送到磁盘文件保存;当从磁盘文件读入数据时,则一次从磁盘文件中将一批数据输入到内存缓冲区,然后再从缓冲区逐个地将数据送到程序数据区。
( 2)非缓冲文件
所谓非缓冲文件系统是指系统不自动开辟一个内存缓冲区,而由用户根据所处理数据量的大小在程序中设置数据缓冲区。
10.1.3 设备文件
由于计算机中的输入 /输出设备的作用是输入输出数据,
其功能和文件的读取数据 /写入数据相似,所以操作系统把输入 /输出设备也看成文件,称为设备文件。
计算机的常用输入设备是键盘,称标准输入设备;
常用输出设备是显示器,称标准输出设备;还有一个专用于输出错误信息的标准错误输出设备,也是显示器。
从输入设备上读取数据,可以看成是从输入设备文件中读数据:将数据写到输出设备上,可以看成是写到输出设备文件中。
10.2 文件类型及其指针当在 C语言程序中操作一个实际的磁盘文件时,需要一个流指针来代表这个文件。流指针其实是一个结构体类型的指针,这个结构体被定义在头文件 stdio.h中,它详细描述了一个流的性质,其形式如下:
typedef struct
{
short level; /*缓冲区“满”或者“空”的程度 */
unsigned flags; /*户文件状态标志 */
char fd; /*文件描述符 */
unsigned char hold; /*如果无缓冲区则不读取字符 */
short bsize; /*缓冲区的大小 */
unsigned char*buffer; /*数据缓冲区的位置 */
unsigned char*curp; /*指针,当前的指向 */
unsigned istemp; /*临时文件指示器 */
short token; /*用于有效性检查 */
}FILE;
10.3 文件的打开和关闭
10.3.1 文件打开函数文件打开函数 fopen()的原型是:
FILE*fopen(char*name,char*mode);
该函数是指针型函数,调用后返回文件类指针。有两个函数参数,都是字符型指针,其中 name用来接收“文件名” (字符串 ),mode用来接收“文件操作方式” (字符串 )。因此该函数调用方式是:
fp=fopen("文件名 ","文件操作方式 ");
其中,
“文件指针名” fp必须是被说明为 FILE 类型的指针变量;
“文件名”是被打开文件的文件名;
"文件操作方式 "是指文件的类型和操作要求。
mode的取值与所代表的含义对于文件使用方式有以下几点说明:
文件使用方式由 r,w,a,t,b,+六个字符拼成,各字符的含义是,r(read)读 w(write)写 a(append)-追加
t(text)-文本文件,可省略不写 b(binary)-二进制文件
+读和写
用,r”打开一个文件时,该文件必须已经存在,且只能从该文件中读出。
用,w”打开的文件只能向该文件写入。若打开的文件已经存在,则将该文件删除,重建一同名新文件;若打开的文件不存在,则以指定的文件名建一新文件。
以,a”方式打开的文件,主要用于向其尾部添加 (写 )数据。此时,该文件应存在,打开后,位置指针指向文件尾。如所指文件不存在,则创建一个新文件。
对于文件使用方式有以下几点说明:
,r+”、,w+”、,a+”方式打开的文件,既可以读入数据,
也可以输出数据。,r+”方式时,文件应存在。,w+”方式是新建文件 (同,w”方式 ),操作时,应先向其输出数据,有了数据后,也可读入数据。而,a+”方式,不同于,w+”方式,其所指文件内容不被删除,指针至文件尾,可以添加,也可以读入数据。若文件不存在,也可用其新建一文件。
打开文件操作不能正常执行时,函数 fopen()返回空指针
NULL(其值为 0),表示出错。出错原因大致为:以,r”、
,r+”方式打开一个并不存在的文件、磁盘故障、磁盘满、
无法建立新文件等。
10.3.2 文件关闭函数文件关闭函数 fclose()的原型是:
int fclose(FILE*fp);
调用该函数后的功能是断开由 fopen()函数建立的文件指针 fp与其相应文件的联系,释放它所占的内存缓冲区和相应的文件类型结构体变量所占的内存,使得原来的指针变量不再指向该文件。此后就不可以通过该指针来访问这个文件。关闭文件函数的调用形式是:
fclose(文件指针 );
如 fclose(fp);
执行该语句后,文件指针 fp所指向的文件类结构体变量被释放,文件被关闭,fp也不再指向该文件。此后,fp
可以指向其他文件。
程序
#include<stdio.h>
#define NULL 0
FILE*fp; /*定义文件型指针 */
if((fp=fopen("文件名 ","文件使用方式 "))=NULL) /*打开文件用于读写 */
{
printf("file can not open!\n"); /*打开文件出错的提示 */
exit(0); /*关闭所有文件,中止程序运行 */
}
┇ /*文件正确打开,可对文件操作 */
fclose(fp);
} /*关闭 fp所指向的文件 */
注意:其中打开文件出错时调用了系统函数,exit(0)”,该函数的作用是关闭所有文件,中止程序的运行。
10.4 文件的输入和输出
10.4.1 字符读 /写函数
1.读字符函数
fgetc()的功能是从指定的文件读取一个字符,该文件必须是以读或读取方式打开的。调用形式为 fgetc( fp),其中 fp为指向 FILE结构的指针变量,用来指向将要读取的文件,例如:
ch=fgetc(fp);
ch为字符型变量,从 fp所指的文件中读取一个字符,赋给 ch。
2.写字符函数
fputc()的功能是向指定的文件输出一个字符,调用形式为:
fputc(字符量,文件指针 );
其中:待写入的字符量可以是字符常量或变量。例如:
fputc('a',fp);
它表示向文件指针变量 fp指向的文件输出一个字符 a。
文件复制工具程序
#include<stdio.h>
void main(int argc,char *argv[])
{
nt c;FILE*fpr,*fpd;
printf("这是一个文件复制工具程序的例子 \n");
if(argc!=3)
{
puts("\7 命令行参数的格式是 exp12-6.exe 源文件名目的文件名 ");
exit(0);
}
if((fpr=fopen(argv[1],"r"))==NULL)
{
printf("\7读取的文件 %s不能打开 \n",argv[1]);
exit(0);
}
文件复制工具程序
if((fpd=fopen(argv[2],"w"))==NULL)
{
printf("\7写入的文件 %s不能打开 \n",argv[2]);
exit(0);
}
while((c=getc(fpr))!=EOF) putc(c,fpd);
fclose(fpr);
fclose(fpd);
}
注意:
① 有些 C版本为了书写方便,把函数 fgetc()和 fputc()在头文件 stdio.h中分别定义为宏名 getc()和 putc(),即
#define putc(ch,fp) fputc(ch,fp)
#define getc(fp) fgetc(fp)
这时两函数名前的,f”字母可以省写。
②程序开始执行时,操作系统自动打开三个标准文件:标准输入、标准输出和标准出错输出。并自动定义三个文件指针 stdin,stdout和 stderr,它们分别指向终端输入 (键盘 )、
终端输出 (屏幕 )和标准出错输出 (屏幕 )。因此这三者可以直接用来文件操作。如,fgetc(stdin);/*从键盘输入一个字符 */
fputc(ch,stdout);/*在屏幕上显示一个字符 */
相当于系统已经定义的 getchar()函数和 putchar(ch)函数。
10.4.2 字符串读 /写函数
1,fgets()函数
fgets()的原型是:
char *fgets(char*buf,int n,FILE*fp);
fgets()函数的调用形式是:
fgets(buf,n,fp);
从 string文件中读入一个含 10个字符的字符串
#include<stdio.h>
main()
{
FILE*fp;
char str[11];
if((fp=fopen("d:\\string","r"))==NULL) /*以只读方式打开 string文件 */
{
printf("\nCannot open file string!");
exit(1);
}
fgets(str,11,fp); /*从文件 string中读取 10个字符,放入字符串 str中 */
printf("\n%s\n",str); /*在屏幕上输出字符串 str*/
fclose(fp); /*关闭文件 */
}
程序运行结果为:
2,fputs()函数
fputs()的原型是:
int fputs(char*str,FILE*fp);
该函数把字符串 str输出到 fp指向的文件中,但不输出字符串结束符。函数调用成功,则返回值为写入的最后一个字符的值;调用不成功,则返回值为 EOF。
fputs()函数的调用形式是,fputs(str,fp);
其中 str可以是字符串常量,也可以是字符数组名,或指针变量; fp是已打开文件的指针,函数调用结果是向 fp
所指向的文件写一个字符串 str。 fputs()函数中的第一个参数可以是字符串常量、字符数组名或字符型指针。
向文件 string中追加一个字符串
#include<stdio.h>
void main()
{
FILE*fp;
char ch,st[20];
if((fp=fopen("string","a+"))==NULL) /*以读取 /追加方式打开文件 string*/
{
printf("Cannot open file string!");
exit(1);
}
向文件 string中追加一个字符串
printf("input a string,\n");
scanf("%s",st);
fputs(st,fp); /*将 st字符串写入文件 string*/
rewind(fp); /*重新定位文件指针到文件头 */
ch=fgetc(fp); /*从文件中读取一个字符 */
while(ch!=EOF) /*只要读取的字符不是回车换行,继续循环 */
{
putchar(ch); /*在屏幕上输出该字符 */
ch=fgetc(fp); /*从文件中读取一个字符 */
}
printf("\n");
fclose(fp); /*关闭文件 */
}
程序运行结果为:
10.4.3 格式化读 /写函数
格式化输入函数 fscanf()和格式化输出函数 fprintf()跟常用的 scanf()和 prinf()函数相似,都是格式化读 /写函数。
它们的不同点在于读 /写对象不一样,前者读 /写对象是磁盘文件,后者读 /写对象是终端。因此函数 fscanf()和
fprint()的函数参数多一个文件指针,其他参数与 scanf()
和 printf()函数相同。
1,fscanf 函数
fscanf 函数的一般调用形式如下:
fscanf(fp,格式控制字符串,输入表 );
2,fprintf函数
fprintf函数调用的一般格式如下:
fprintf(fp,格式控制字符串,输出表 );
其中,fp是文件指针,其他参数说明与 printf函数的参数说明相同。该函数是将输出表中各表达式的值,按格式控制字符串中指定的格式写到 fp所指的文件中。例如:
fprintf(fp,"a=%d,x=%f\n",a,x);
表示将整型变量 a和实型变量 x的值,按双引号内的格式输出到 fp所指的文件中。
从键盘上依次读取 1个字符,2个整数,3个单精度数和 1个字符串,写入 a盘当前目录下名为
,format1.dat”的二进制数据文件中。
#include<stdio.h>
#define NULL 0
main()
{
FILE *fp;
char a[81],c;
int i1,i2;
float f1,f2,f3;
if((fp=fopen("a:\\format1.dat","wb" ))==NULL)
{
printf(" file can not open !\n" );
exit(0);
} /*打开一个只写的二进制文件 */
scanf("%c",&c); /*从键盘读取 1个字符 */
scanf("%d,%d",&i1,&i2); /*从键盘读取 2个整数 */
scanf("%f,%f,%f",&f1,&f2,&f3); /*从键盘读取 3个实数 */
scanf("%s",a); /*从键盘读取 1个字符串 */
fprintf(fp,"%c\n%d,%d\n%f,%f,%f\n%s\n",c,i1,i2,f1,f2,f3,a);/
*将 1个字符,2个整数,3个实数,1个字符串写入 fp指向的文件 */
fclose(fp); /*关闭 fp所指向的文件 */
}
程序运行结果为:
10.4.4 块读 /写函数
1,fread函数
该函数是用来从指定文件中读取一组数据,如一个数组元素,一个结构变量的值等。它的说明形式如下:
int fread(char*buf,int size,int n,FILE*fp);
它的调用形式如下:
fread(buf,size,n,fp);
2,fwrite函数
该函数是将一组数据写到指定的文件中。它的说明形式如下:
int fwrite(char*buf,int size,int n,FILE*fp);
它的调用形式如下:
fwrite(buf,size,n,fp);
函数 fwrite()和 fread()的应用
# include<stdlib.h>
# include<stdio.h>
main()
{
struct student /*定义结构体类型 student及数且 s[]*/
{
char number[6];
char name[20];
char sex;
int age;
int score;
}
s[2]={{ "00001","Peter",'m',19,250},{"00002","Betty",'f',
18,268} };
struct student ss[2]; /*定义结构体类型数组 ss[],用以存放从文件中读出的数据 */
int i,j;
FILE * fp;
if((fp=fopen("c:\\myfile4","wb+") ) == NULL)
{
printf("Can't open this file!\n");
exit(0);
}
j=sizeof(struct student);
for(i=0;i<=1;i++)
if(fwrite(&s[i],j,1,fp)!= 1)
printf("File write Error!\n");
rewind(fp);
for(i=0;i<=1;i++ )
{
fread(&ss[i],j,1,fp);
printf("%s,%s,%c,%d,%d\n",ss[i].number,
ss[i].name,ss[i].sex,ss[i].age,ss[i].score);
}
fclose(fp);
}
程序运行结果为从键盘输入两个学生的数据,写入一个文件中,
再读出这两个学生的数据显示在屏幕上
#include<stdio.h>
struct stu
{
char name[10]; /*学生姓名 */
int num; /*学生编号 */
int age; /*学生年龄 */
char addr[15]; /*学生住址 */
}boya[2],boyb[2],*PP,*qq; /*定义结构体数组 boya
和 boyb,结构体指针 pp和 qq*/
void main()
{
FILE*fp;
char ch;
int i;
PP=boya; /*指针 PP指向结构体数组 boya*/
qq=boyb; /*指针 qq指向结构体数组 boyb*/
if((fp=fopen("d:\\stu_list","w+"))==NULL) /*以可写可读方式打开文件 stu_list*/
{
printf("Cannot open file stu_list!");
exit(1);
}
printf("\ninput data\n");
for(i=0;i<2;i++,PP++) /*从键盘录入 2个学生信息,存放在
boya结构体数组中 */
scanf("%s%d%d%s",PP->name,&PP->num,&PP-
>age,PP->addr);
PP=boya; /*把结构体数组 boya中 2个学生信息写入文件
stu_list中 */
fwrite(PP,sizeof(struct stu),2,fp);
rewind(fp); /*重新定位文件指针到文件头 */
/* 从文件 stu_list中读取 2个学生信息,存放到结构体数组
boyb中 */
fread(qq,sizeof(struct stu),2,fp);
printf("\n\nname\tnumber age addr\n");
for(i=0;i<2;i++,qq++) /*把结构体数组 boyb中的 2个学生信息按格式输出到屏幕 */
printf("%s\t%5d%7d %s\n",qq->name,qq-
>num,qq->age,qq->addr);
fclose(fp); /*关闭文件 Stu_list*/
}
10.5 文件的定位函数
10.5.1 rewind()函数
rewind()函数的原型是:
void rewind(FILE*fp);
该函数的功能是使位置指针重新返回文件的开头,
对文件可以重新进行读写操作。此函数无返回值。调用形式是:
rewind(fp);
其中 fp是被操作的文件指针
10.5.2 fseek()函数
fseek()函数的原型是:
int fseek(FILE*fp,long offset,int base);
该函数的功能是将 fp所指向的文件的位置指针移到以 base为基准、以 offset为位移量的位置上。因此调用该函数可以改变文件的位置指针,使用户能直接去读写文件中的某一个指定的位置。这样就可以使得 C语言的流式文件既可以顺序读 /写,也可以随机读 /写了。
调用 fseek()函数的一般形式是:
fseek(fp,offset,base);
10.6 出错检测函数
10.6.1 ftell()函数
ftell()函数的功能是了解 FILE指针变量的当前指向,
其调用格式:
ftell(文件类型指针变量名 );
其中,文件类型指针变量名为指向该文件的指针变量。
如果调用 ftell()函数成功,返回 FILE指针变量当前的指向相对于起始位置的位移量,以字节数表示,否则返回 -1。
如果仅仅只是要判断文件指针是否已经指向文件末尾,可以直接调用 feof(fp)函数。如果遇到文件结束,函数返回 1,否则返回 0。参数 fp为指向文件的指针变量。
10.6.2 ferror()函数和 Clearerr()函数
在使用前面所介绍的各种函数对文件进行读写操作时,可以借助调用函数后的返回值判断函数调用的成功与否。此外,C语言还提供两个函数 ferror()和 clearerr(),
用以对文件读写操作过程中的出错情况进行检测。
ferror()函数的原型是:
int ferror(FILE*fp);
函数功能是检测文件在用各种输入输出函数进行读写时是否正确,正确返回 0,不正确返回非 0。
该函数的调用形式是:
ferror(fp);
要求使用错误处理函数来判断文件操作中是否发生错误
#include<stdio.h>
#define NULL 0
void pro_err(FILE*fp) /*错误测试与处理的通用函数 */
{
if(ferror(fp)!=0)
{
printf("file can not open!\n");
exit(0);
} /*有错输出提示信息后中止运行 */
else return; /*无错返回继续运行 */
}
void main()
{
FILE *fp;
int i,x;
if((fp=fopen("c:\\intbl.dat","rb"))==NULL) /*打开一个只读二进制文件 */
{
printf("file can not open!\n");
exit(0);
}
for(i=0;i<3;i++) /*循环读取第 1,3,5个实数 */
{
fseek(fp,(long)(i*sizeof(int)),0); /*指向当前要读取数据的位置 */
pro_err(fp); /*进行定位后错误测试 */
fread(&x,sizeof(int),1,fp); /*从当前位置读取 1个整数 */
pro_err(fp);/*进行定位后错误测试 */
printf("%d %d\n",2*i+1,x); /*输出读取的单精度数据 */
}
fseek(fp,-2L*sizeof(int),SEEK_END); /*指向第 9个整数 */
pro_err(fp); /*进行定位后错误测试 */
fread(&x,sizeof(int),1,fp); /*从当前位置读取 1个整数 */
pro_err(fp); /*进行读数据错误测试 */
printf("%d %d\n",9,x); /*输出读取的整数 */
fseek(fp,-1L*sizeof(int),SEEK_END); /*指向第 10个整数 */
pro_err(fp); /*进行定位后错误测试 */
fread(&x,sizeof(int),1,fp); /*从当前位置读取 1个整数 */
pro_err(fp); /*进行读数据错误测试 */
printf("%d %d\n",10,x); /*输出读取的整数 */
fclose(fp); /*关闭 fp所指向的文件 */
}