内容正文:
第12章 文件
内容概述
文件分类
缓冲文件系统
文件的指针
文件打开与关闭
文件应用
教学目标
1.概述文件的概念,非缓冲文件系统和缓冲文件系统;
2.描述文件类型指针的概念及定义方法;
3.正确进行文件的打开和关闭操作,及对打开和关闭操作的错误处理;
4.正确使用文件的读写操作,包括基于字节,数据项等的读写操作;
5.牢记文件的随机读写方法;
6.正确使用文件访问的错误检测及处理方法;
12.1 文件概述
“文件”是指存储在外部设备上的、以唯一的名字作为标识的数据集合。
12.1.1 文本文件和二进制文件
文本文件
由一个一个字符组成,每一个字符用一个ASCII代码表示,如123.45以ASCII代码形式存储到磁盘需要6个字节。
特点:可通过显示器或打印机显示,即可读,比较直观。
二进制文件
是指以数据在内存中的存储形式原样输出存储到磁盘上的文件。如123.45在内存中以浮点数形式存储占4个字节,不管一个实数多大都占4个字节。
特点:节省存储空间,输入输出无需转换,速度快,不便阅读。
整数10 000以ASCII形式和二进制形式存储的差别
ASCII形式
00110001
00110000
00110000
00110000
00110000
00000000
00000000
二进制形式
00100111
00010000
文本文件一般占用存储空间较多,而且要花费转换时间(二进制与ASCII码之间的转换),但它便于对字符进行逐个处理。二进制文件的存储形式与其在内存中的存储形式相同,这样可以节省存储空间和转换时间,但1个字节并不对应1个字符,不能直接输出字符形式。
12.1.2 缓冲文件系统和非缓冲文件系统
缓冲文件系统(又称标准或高层文件系统)
特点:对程序中的每一个文件都在内存中开辟一个
“缓冲区”。
作用:从磁盘文件读入的数据先到“输入缓冲区”中,
然后再从缓冲区依次将数据送给接收变量。
变量
a
磁盘文件
内存
程序数据区
输入文件缓
冲区
(
512
k
)
输出文件缓
冲区
(
512
k
)
读
写
非缓冲文件系统
不由系统自动设置所需缓冲区,而由用户根据需要设置。
新的ANSI标准只建议使用缓冲系统,并对缓冲文件系统的功能进行了扩充,使既能用于处理字符代码文件,也能处理二进制文件。
12.2 文件类型指针
在缓冲文件系统中,系统自动地在内存区为每一个正在使用的文件开辟一个缓冲区,该缓冲区用于存放文件正文。对文件的操作是通过文件指针实现的。
调用一个文件需要的信息:
文件当前读写的位置;
与该文件对应的内存缓冲区的地址;
缓冲区未被处理的字符数(字符指针);
文件的操作方式。
12.2.1 文件类型指针的定义
FILE数据结构定义在stdio.h头文件中,内容如下:
typedef struct
{
int _fd; /*文件号*/
int _cleft; /*缓冲区中剩下的字符数*/
int _mode; /*文件操作方式*/
char *_next; /*文件当前读/写位置*/
char *_buff; /*文件缓冲区位置*/
}FILE;
例如,下面定义了一个文件类型指针:
FILE *fp;
12.2.2 设备文件
C语言中把所有的外部设备都作为文件看待,这样的文件称为设备文件。
在程序开始运行时,系统自动打开三个标准文件,并分别定义了文件指针:
stdin标准输入文件(键盘);
stdout标准输出文件(显示器);
stderr标准错误输出文件(显示器)。
12.3 文件的打开和关闭
打开文件:是在用户程序和操作系统之间建立联系,程序把所要操作的文件的有关信息,如文件名、文件操作方式(读、写或读/写)等通知给系统,系统为文件分配一个文件缓冲区。
调用形式:
FILE *fp;
fp=fopen("文件名","文件使用方式");
功能:返回一个指向该文件的指针或错误标志NULL。
例如,
fp=fopen("data.dat","r");
文件的打开模式
打开模式 描 述
r 只读,打开已有文件,不能写
w 只写,创建或打开,覆盖已有文件
a 追加,创建或打开,在已有文件末尾追加
r+ 读写,打开已有文件
w+ 读写,创建或打开,覆盖已有文件
a+ 读写,创建或打开,在已有文件末尾追加
t 按文本方式打开 (缺省)
b 按二进制方式打开
说明:
(1)用"r"方式打开的文件只能在该文件已经存在的情况下向计算机输入数据,若文件不存在,则出错。
(2)用"w"方式打开的文件只能向该文件写数据,在该文件不存在的情况下,则建立以指定名字命名的文件,如果已经存在该文件,则在打开时删去该文件,然后重新建立一个新文件。
(3)用"a"方式向文件尾追加新的数据而不删除原有数据,要求文件必须首先存在。打开文件时,位置指针移到文件末尾。
(4)用“r+”、“w+”、“a+”方式打开的文件即可以用来输入数据,也可以用来输出数据。
(5)如果打开文件失败,则返回一个空指针NULL。
为增强程序的可靠性,常用下面的方法打开一个文件:
if((fp=fopen("文件名","操作方式"))==NULL)
{ printf("can not open this file
");
exit(0);
}
12.3.2 文件的关闭fclose()
所谓关闭文件,即切断文件与程序的联系,释放文件缓冲区。
调用形式:
fclose(文件指针)
例如:
fclose(fp);
含义为关闭文件指针fp所指向的文件(即fp不再指向该文件)。如果正常关闭了文件,则函数返回值为0,否则,返回非零值。
12.4 文件读/写操作
字符读/写函数:fgetc()和fputc()
字符串读/写函数:fgets()和fputs()
数据块读/写函数:fread()和fwrite()
格式化读/写函数:fscanf()和fprintf()
使用这些函数都要求包含头文件stdio.h。
在C语言中提供了多种文件读/写的函数,常用的文件读/写函数如下:
12.4.1 字符读/写函数:fgetc()和fputc()
字符输出函数fputc()
调用形式:
fputc(ch,fp);
作用: 将ch的值输出到fp所指向的文件中去,同
时将读/写位置指针向下移动1个字节(即
指向下一个写入位置)。
返回值:如果输出成功,则函数返回值就是输出的
字符数据,否则,返回一个符号常量EOF。
【例12.1】从键盘输入一些字符,写入到磁盘文件data.txt中,直到输入一个“#”为止。
#include <stdio.h>
#include <stdlib.h>
main( )
{
FILE *fp;
char str;
if((fp=fopen("data.txt","w"))==NULL) /* 创建并打开data.txt文件 */
{
printf("cannot open file
");
exit(0); /* 打开不成功退出 */
}
printf("请输入字符,以‘#’结束
");
while((str=getchar())!='#') /* 从键盘输入并判断是否为“#” */
fputc(str,fp); /* 写入到磁盘文件data.txt中 */
fclose(fp); /* 关闭文件 */
}
字符输入函数fgetc()
调用形式:
ch=fgetc(fp);
作用:从指定的磁盘文件中读取一个字符。
对于文本文件,可以用文件结束标识EOF(-1)来判断文件结束;在对二进制文件执行读入操作时,必须使用库函数feof()来判断是否遇到文件尾。 feof()也适用于文本文件。
【例12.2】从例12.1建立的文件data.txt中读出所有的字符并显示在屏幕上。
#include <stdio.h>
#include <stdlib.h>
main()
{ FILE *fp;
char ch;
if((fp=fopen("data.txt","r"))==NULL) /*以读方式打开文件,且测试*/
{
printf("cannot open file
");
exit(0); /*打开不成功退出*/
}
printf("文件data.txt中的内容为:
");
while((ch=fgetc(fp))!=EOF)
putchar(ch); /*当读取字符不为EOF时,把字符输出到显示器*/
fclose(fp);
}
12.4.2 字符串读/写函数:fgets()和fputs()
字符串输出函数fputs()
调用形式:
fputs(字符串,文件型指针);
作用: 向指定的文件写入一个字符串。
返回值:如果输出成功,返回值为0;否则,为非0值。
字符串输入函数fgets()
调用形式:
fgets(str,n,fp);
作用: 从fp所指的文件中读出n1个字符送入字
符数组str中。如果在读入n1个字符结束
前遇到换行符‘
’或EOF,读入结束。字符
读入结束最后加一个'\0'字符。
返回值:成功返回输入串的首地址,遇到文件结束
或出错返回NULL。
【例12.3】复制文本文件file1.txt的内容到file2.txt中。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
main()
{ FILE *fp1,*fp2;
char str [80];
if((fp1=fopen("file1.txt","r"))==NULL) /*以读方式打开文件,且测试*/
{
printf("cann't open file");
exit(0);
}
if((fp2=fopen("file2.txt","w"))==NULL) /*以写方式打开文件,且测试*/
{
printf("cann't open file");
exit(0);
}
while((fgets(str,80,fp1))!=NULL) /* 从文件file1.txt读取字符串 */
fputs(str,fp2); /* 一行字符串写入文件file2.txt */
fclose(fp1);
fclose(fp2);
}
12.4.3 数据块读/写函数:fread()和fwrite()
ANSIC标准提出设置两个函数(fread()和fwrite())用来读/写一个数据块。它们的一般调用形式为
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
其中:
buffer表示存放读/写数据的首地址;
size表示要读/写数据块的字节数;
count表示要读/写多少个size字节的数据项;
fp表示文件类型指针。
fread()和fwrite()函数,一般用于二进制文件的处理。例如:
fwrite(buffer,50,32,fp);
【例12.4】从键盘输入4个学生的数据,写入文件stu.dat中,再读出这4个学生的数据显示在屏幕上。
〖程序分析〗
这是一个以数据块组织信息的实例,可以从以下几方面着手:
定义一个结构体类型,其中包括姓名、学号、年龄和地址几个数据项。定义一个结构体数组,把4个学生的数据输入结构体数组中。
定义save()函数,功能是将从键盘读入到结构体数组中的数据写到磁盘文件中。
定义display()函数,功能是读取文件的内容到结构体数组,然后输出到显示器。
在main()函数中输入学生的数据,调用以上两个函数实现学生数据的存储及显示功能。
#include <stdio.h>
struct student
{ char name[10];
int num;
int age;
char addr[15];
}stud[4];
main()
{
void save(); /*函数声明*/
void display();
int i;
printf("请输入学生数据
");
for(i=0;i<4;i++) /*输入4个学生的数据*/
scanf("%s%d%d%s",
stud[i].name,&stud[i].num,&stud[i].age,stud[i].addr);
save(); /*将从键盘读入到结构体数组中的数据写到磁盘文件中*/
printf("读取文件中的数据,并显示输出
");
display(); /*读取文件的内容到结构体数组,然后输出到显示器*/
}
void save()
/*将从键盘读入到结构体数组中的数据写到磁盘文件中*/
{
FILE *fp;
int i;
if((fp=fopen("stu.dat","wb"))==NULL)
{
printf("cannot open file
");
return;
}
for(i=0;i<4;i++)
if(fwrite(&stud[i],sizeof(struct student),1,fp)!=1)
printf("file write error
");
fclose(fp);
}
void display()
/*读取文件的内容到结构体数组,然后输出到显示器*/
{ FILE *fp;
int i;
if((fp=fopen("stu.dat","rb"))==NULL)
{
printf("cannot open file
");
return;
}
for(i=0;i<4;i++)
{
fread(&stud[i],sizeof(struct student),1,fp);
printf("%8s %4d %5d %8s
",
stud[i].name,stud[i].num,stud[i].age,stud[i].addr);
}
fclose(fp);
}
程序运行情况如下:
请输入学生数据:
Zhang 001 18 4-101↙
Wang 002 19 4-101↙
Li 003 18 4-102↙
Zhao 004 19 4-103↙
读取文件中的数据并显示输出:
Zhang 1 18 4-101
Wang 2 19 4-101
Li 3 18 4-102
Zhao 4 19 4-103
12.4.4 格式化读/写函数fscanf()和fprintf()
格式化读/写函数的调用格式如下:
fscanf(文件指针,"格式符",输入变量首地址表);
fprintf(文件指针,"格式符",输出参量表);
printf()函数将输出按格式串规定输出到指定文件;scanf()函数从fp所指文件按格式串读入数据。函数执行成功返回读得的数据项个数;若在读第1项前已到达文件结束处,则返回EOF。
例如:
int i=3;float f=9.80;
fprintf(fp,"%2d,%6.2f",i,f);
fscanf(fp,"%s%d%f",&va1,&var2,&var3);
【例12.5】从键盘读入学生信息后,用格式化方式写入磁盘文件中。
#include <stdio.h>
#include <stdlib.h>
main( )
{
FILE *fp;
char str[20];
int num;
float score;
if((fp=fopen("score.txt","w"))==NULL)
/*以写方式打开文件并测试*/
{
printf("File connot be opened
");
exit(0);
}
printf("Please enter name number and score:
");
scanf("%s%d%f",str,&num,&score);
/*输入学生姓名、学号和成绩*/
while(score!=-1)
{
fprintf(fp,"%s%d%f",str,num,score);
/*写入磁盘文件*/
scanf("%s%d%f",str,&num,&score);
/*输入姓名、学号和成绩*/
}
fclose(fp);
}
12.5 文件定位函数
文件指针的重置函数rewind()
功能:将文件读/写指针移到文件开始位置,并将文件结束指示器和错误指示器清0。此函数无返回值。该函数适用于二进制文件和文本文件。
取文件的当前位置函数ftell()
功能:用于得到文件指针离开文件起点的偏移量(即偏移的字节数),如果函数调用出错(例如该文件不存在),则函数的返回值是-lL。例如:
i=ftell(fp);
if(i==-1L)
printf("error
");
12.5.3 文件读/写指针定位函数fseek()
功能:用来将文件的读/写指针移动到指定的位置。
调用形式:
fseek(fp,位移量,起始点);
其中:
fp是已定义过的文件指针;
“位移量”是相对于起始点的偏移量,要求是long型数据;
“起始点”指明以什么地方为基准移动指针,可有3种取值。
起 始 点 名 字 数字表示
文件首 SEEK—SET 0
当前位置 SEEK—CUR 1
文件末尾 SEEK—END 2
若fseek()函数调用成功,返回值为0;否则返回一个非零值。
【例12.6】文件data1.txt的内容为This is a test.,分析以下程序的执行结果。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main( )
{ FILE *fp;
int loc1,loc2;
long d=81;
char w;
fp=fopen("data1.txt","r");
if (fp==NULL)
printf("error
");
else
{ loc1=fseek(fp,d,0);
if (loc1==0)
{
w=fgetc(fp);
printf("w=%c
",w);
}
}
loc2=ftell(fp);
rewind(fp);
printf("loc2=%d
",loc2);
loc2=ftell(fp);
fclose(fp);
printf("loc2=%d
",loc2);
}
程序执行结果:
w=a
loc2=9
loc2=0
【例12.7】磁盘文件stu.dat上有4个学生数据,要求读出任意学生数据并显示。
#include <stdio.h>
#include <stdlib.h>
struct student
{ char name[10];
int num;
int age;
char addr[15];
}stud;
main()
{
int n;
FILE *fp;
if((fp=fopen("stu.dat","rb"))==NULL)
{
printf("can't open file
");
exit(0);
}
do
{
printf("Enter record number(1-4):");
scanf("%d",&n);
}while(n<1||n>4);
fseek(fp,(n-1)*sizeof(struct student),0);
fread(&stud,sizeof(struct student),1,fp);
printf("%s %d %d %s
",stud.name,stud.num,stud.age,stud.addr);
fclose(fp);
}
程序运行情况如下:
Enter record number(1-4):2↙
wang 2 19 4-101
12.6 出错检测函数
ferror()函数
在各种输入/输出函数(如fputc()、fgetc()、fscanf()、fprintf()等)被调用时,如果出现错误,除了被调函数返回值有所反映外,还可以用ferror函数检查。
调用形式:
ferror(fp);
返回值为0,表示未出错;否则,出错。
注意:ferror函数的值是最后一次调用时的出错状态。
clearerr()函数
将文件错误标志(即ferror函数的值)和文件结束标志(即feof函数的值)置为0。
调用形式:
clearerr(文件指针);
练 习
程序设计题:统计满足条件x*x+y*y+z*z==2000的所有解的个数,并将统计结果以格式"%d"写入到新建的文件design.dat。
说明:若a、b、c是1个解,则a、c、b也是1个解,等等。
#include <stdio.h>
void main()
{ FILE *p; int x,y,z,k=0;
//
//
//
//
//
//
//
}
p=fopen("design.dat","w");
for(x=1;x<=sqrt(2000);x++)
for(y=1;y<=sqrt(2000);y++)
for(z=1;z<=sqrt(2000);z++)
if((x*x+y*y+z*z)==2000) k++;
fprintf(p,"%d", k);
fclose(p);
练 习
程序设计题:在正整数中找出1个最小的、被3、5、7、9除余数分别为1、 3、5、7的数,将该数以格式"%d"写到新建文件design.dat。
#include <stdio.h>
#include <math.h>
void main()
{
//
//
//
//
//
//
//
//
}
FILE *p; int x=0;
p=fopen("design.dat","w");
do
{
x++;
} while( !(x%3==1&& x%5==3&& x%7==5 && x%9==7));
fprintf(p,"%d",x);
fclose(p);
$$