第十章 利用文件保存数据
文件基本概念
1)、C语言程序设计中两种文件:
- 程序文件。包括源程序文件(后缀为.c)、目标文件(后缀为.obj)、可执行文件(后缀为.exe)等。
- 数据文件;
2)、流
- 流是指数据输入输出过程,有二进制流和字节流;
- 输人输出数据流的开始和结束仅受程序控制而不受物理符号(如回车换行符)控制,这就增加了处理的灵活性。这种文件称为 流式文件 。
3)、文件名
(1)文件路径
(2)文件名主干
(3)文件后缀
PS:. dat (数据文件),.c (C语言源程序文件),.cpp (C++源程序文件),.for (FORTRAN语言源程序文件),.pas (Pascal语言源程序文件)
4)、文件分类
- 根据数据的组织形式,数据文件可分为ASCII文件和二进制文件。
- 二进制文件是把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放。
- 字符型数据只能以ASCII 形式存储,数值型数据可以用ASCII形式存储在磁盘上,也可以用二进制形式存储。
5)、文件缓冲区
C语言采用“缓冲文件系统”处理文件,所谓缓冲文件系统是指系统自动地在内存区为程序中每一一个正在使用的文件 开辟一个文件缓冲区。

文件类型指针,简称文件指针。每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息 (如文件的名字、文件状态及文件当前位置等)。这些信息保存在 FILE 这个结构体变量中的,该结构体类型由系统声明。
(1)定义一个FILE类型的变量 :
FILE f;
该结构体变量可以存放文件有关的信息;
(2)定义一个指向文件型数据的指针变量(指向文件的指针变量):
FILE *fp;
定义fp为一个指向FILE类型变量的指针变量。fp可以指向某一个文件的文件信息区 (是一个结构体变量) ,通过该文件信息区中的信息能够访问该文件。即通过文件指针变量能够找到与它相关的文件。
文件打开和关闭
1、fopen函数(打开数据文件)
fopen(文件名,使用文件方式);
fopen ("a1","r"); // r为read读入
FILE *fp; // 定义一个指向文件的指针变量fp
fp=fopen("a1","r"); // 将fopen函数的返回值赋给指针变量fp
这样就是fp指向a1文件了
使用文件的方式

(1)最基本的是最前面的"r"、"w"、"a"三种方式。在其后加“b”表示是二进制文件,不加“b”的表示是ASCII 文件(即文本文件)。加“+”表示既可读又可写。
(2)fopen函数将带回一个空指针值NULL (NULL在stdio.h文件中已被定义为0)。
常见文件打开方式:
if((fp=fopen("file1","r"))==NULL)
{
printf("cannot open this file\n");
exit(0);
}
2、fclose函数(关闭数据文件)
fclose (文件指针);
fclose(fp);
- 在程序终止之前应该关闭所有文件,如果不关闭文件可能会丢失数据。
- fclose函数也带回一个值,当顺利地执行了关闭操作则 返回值为0;否则返回 EOF(- 1)。
文件顺序读写
1、向文件读写字符

例1:从键盘输人一些字符,逐 个把它们送到磁盘上去,直到用户输人一个“#”为止。
#include <stdio.h> #include <stdlib.h> int main() { FILE *fp; char ch,filename[10]; // 用数组来存放文件名 printf("请输入所用的文件名:"); scanf("%s",filename); // 输入文件名 if((fp=fopen(filename,"w"))==NULL) // 打开输出文件 { printf("无法打开此文件\n"); // 如果打开时出错,就输出“打不开”的信息 exit(0); // 终止程序 } ch=getchar(); // ch用来接收在执行scanf语句时最后输入的回车符 printf("请输入一个准备存储到磁盘的字符串(以#结束):"); ch=getchar(); // 接收从键盘输入的第一个字符 while(ch!='#') // 当输入“#”时结束循环 { fputc(ch,fp); // 向磁盘文件输入的第一个字符 putchar(ch); // 将输出的字符显示在屏幕上 ch=getchar(); // 再接收从键盘输入的一个字符 } fclose(fp); // 关闭文件 putchar(10); // 向屏幕输出一个换行符,换行符的ASCII代码为10 return 0; }
例2:将一个磁盘文件中的信息复制到另一个磁盘文件中。现要求将上例建立的file1.dat文件中的内容复制到另一个磁盘文件file2.dat 中。
#include <stdio.h> #include <stdlib.h> int main() { FILE *in,*out; char ch,infile[10],outfile[10]; // 定义连个字符数组来分别存放文件名 printf("请输入读入文件的名字:"); scanf("%s",infile); // 输入一个输入文件的名字 printf("请输入输出文件的名字:"); scanf("%s",outfile); // 输入一个输出文件的名字 if((in=fopen(infile,"r"))==NULL) // 打开输入文件 { printf("无法打开此文件\n"); exit(0); } if((out=fopen(outfile,"w"))==NULL) // 打开输出文件 { printf("无法打开此文件\n"); exit(0); } ch=fgetc(in); // 从输入文件读入一个字符,放在变量ch中 while(!feof(in)) // 如果没遇到输入文件的结束标志 { fputc(ch,out); // 将ch写到输出文件中 putchar(ch); // 将ch显示在屏幕上 ch=fgetc(in); // 从输入文件读入一个字符,暂放在变量ch中 } putchar(10); // 显示全部字符后换行 fclose(in); // 关闭输入文件 fclose(out); // 关闭输出文件 return 0; }
- C系统用“文件读写位置标记”来表示当前所访问的位置
- 在一个文件所有有效字符的后面一字节中,系统自动设置了一个文件尾标志,用标识符EOF(End Of File) 表示,在stdio.h头文件中EOF被定义为 -1,下面两条语句等价。
while(ch!=-1)
while(ch!=EOF)
- fgetc和getc,fputs和puts等价
2、向文件写入字符串
fgets和fputs

fgets(str,n,fp) // 从fp指向的文件中读入n个字符存放在str数组中(实际是n-1个,最后加了'\n')
fputs("China",fp); // 输出一个字符串到fp指向的文件中
例子1:从键盘读人若干个字符串,对它们按字母大小的顺序排序,然后把排好序的字符串送到磁盘文件中保存。
#include <stdio.h> #include <stdlib.h> #include <string> int main() { FILE *fp; char str[3][10],temp[10]; // 定义str二维数组来存放字符串,temp字符串数组存放临时数据 int i,j,k,n=3; printf("Enter strings:\n"); for(i=0;i<n-1;i++) // 用选择排序比较字符串大小 { k=i; for(j=i+1;j<n;j++) { if(strcmp(str[k],str[j])>0) // 如果str[k]>str[j]返回值大于零 { k=j; // 记录下标 } if(k!=i) { strcpy(temp,str[i]); // 交换str[i]与str[k]的值互换 strcpy(str[i],str[k]); strcpy(str[k],temp); } } } if((fp=fopen("D:\\CC\\temp\\string.dat","w"))==NULL) // 打开磁盘文件 { printf("can't open file!\n"); exit(0); } printf("\n The new sequence:\n"); for(i=0;i<n;i++) { fputs(str[i],fp); // 向磁盘文件写数据 fputs("\n",fp); printf("%s\n",str[i]); // 在屏幕上显示 } return 0; }
注意点:(1) 在打开文件时,指定了文件路径,假设想在D盘的cc\temp子目录下建立一个名为str.dat的数据文件,用来存放已排好序的字符串。本来应该写成"D: \cc\ temp\str.dat",但由于在C语言中把"\"作为转义字符的标志,因此在字符串或字符中要表示 \ 时,应当在\之前再加一个" \ ",即"D:\\cc\\temp\\str.dat"。注意:只在双撇号(" ")或单撇号(' ')中的\才需要写成"\\" ,其他情况下则不必。
读回例子:
#include <stdio.h> #include <stdlib.h> int main() { FILE *fp; char str[3][10]; int i; if((fp=fopen("D:\\CC\\temp\\string.dat","r"))==NULL) { printf("can't open file!\n"); exit(0); } while(fgets(str[i],10,fp)!=NULL) { printf("%s",str[i]); i++; } fclose(fp); return 0; }
3、文件的格式化读写
fprintf、fscanf和printf、scanf的区别:fprintf、fscanf读写对象不是终端而是外部文件,调用方式如下:
fprintf(文件指针,格式字符串,输出列表);
fscanf(文件指针,格式字符串,输入列表);
fprintf(fp,"%d,%6.2f",i,f);
fscanf(fp,"%d,%f",i,f);
fprintf和fscanf都需要将文件中ASCII码转换为二进制形式再保存在内存变量中。
4、用二进制方式读写文件
C标准允许用fread函数从文件读一个数据块,用fwrite函数向文件写一个数据块。在进行读写时是以二进制形式进行的。
fread (buffer,size,count,fp);
fwrite (buffer,size,count,fp);
- buffer:是一个地址。对fread来说,它是读入数据的存放地址。对fwrite来说,是要输出数据的地址(以上指的是起始地址)。
- size:要读写的字节数。
- count:要进行读写多少个size字节的数据项。
- fp:文件型指针。
fread(f,4,10,fp); // f是一个实型数组名,一个实型变量占4字节,函数从fp所指向的文件读入10个4字节的数据,存储到数组f中。
文件的随机读写
1、基本概念
1)文件标记位
- 随机访问不是按数据在文件中的物理位置次序进行读写,而是可以对任何位置上的数据进行访问;
- 可以根据读写的需要,人为地移动文件位置标记的位置。文件位置标记可以向前移、向后移,移到文件头或文件尾,然后对该位置进行读写,显然这就不是顺序读写了,而是随机读写;
- 对流式文件既可以进行顺序读写,也可以进行随机读写。关键在于控制文件位置标记。如果文件位置标记是按字节位置顺序移动 的,就是顺序读写。如果能将文件位置标记按需要移动到任意位置,就可以实现随机读写;
- 可以在任何位置写入数据,在任何位置读取数据。
2)文件位置标记的定位
(1)用rewind函数 使文件位置标记指向文件头;
rewind函数 作用是使文件位置标记重新返回文件的开头,此函数没有返回值。
区分“文件位置指针”和“指向文件的指针”(FILE指针)。从概念上说,变量的指针就是变量在内存中存储单元的地址。而文件是存储在外部介质上的,不存在内存地址。因此作者认为表示文件读写位置的不宜称为“指针”,称为“文件读写位置标记”更为确切。
#include <stdio.h> int main() { FILE *fp1,*fp2; fp1=fopen("file1.dat","r"); fp2=fopen("file2.dat","w"); while(!feof(fp1)) { putchar(getc(fp1)); } putchar(10); rewind(fp1); // 使文件位置标记返回头文件 while(!feof(fp1)) { putc(getc(fp1),fp2); } fclose(fp1);fclose(fp2); return 0; }
(2)用 fseek函数 移动文件位置标记,用fseek函数可以改变文件位置标记的位置。
一般形式:fseek (文件类型指针,位移量,起始点)

“位移量”指以“起始点”为基点,向前移动的字节数。C标准要求位移量是long型数据 (在数字的末尾加一个字母L,就表示是long 型 )。
fseek(fp,100L,0); // 将文件位置标记 移到离文件头100字节处
fseek(fp,50L,1); // 将文件位置标记移到 离当前位置后面50字节处
fseek(fp,-10L, 2); // 将文件位置标记 从文件末尾处向后退10字节
(3)用ftell函数 测定文件位置标记的当前位置。
ftell函数 的作用是得到流式文件位置标记的当前位置。
i=ftell(fp);
if(i==-1L) // 如果ftell 函数返回值为一1L,表示出错。
{
printf("error\n");
}
2、随机读写文件
#include <stdio.h> #include <stdlib.h> struct student_type { char name[10]; int num; int age; char addr[15]; }stud[10]; int main() { int i; FILE *fp; if((fp=fopen("stu_dat","rb"))==NULL) // 以只读方式打开二进制文件 { printf("can not open file\n"); exit(0); } for(i=0;i<10;i+=2) { fseek(fp,i*sizeof(struct student_type),0); // 移动文件位置标记 fread(&stud[i],sizeof(struct student_type),1,fp); // 读一个数据块到结构体变量 printf("%-10s %4d %4d %-15s\n",stud[i].name,stud[i].num,stud[i].age,stud[i].addr); } fclose(fp); return 0; }
提高部分
1、ferror函数
ferror (fp);
如果ferror 返回值为0(假) ,表示未出错;如果返回一个非零值,表示出错。
2、clearerr函数
clearerr的作用是使文件错误标志和文件结束标志置为0。假设在调用一个输人输出函数时出现错误,ferror函数值为一个非零值。应该立即调用clearerr(fp),使ferror(fp)的值变成0,以便下一次的检测。

浙公网安备 33010602011771号