源码: github.
主要用到两种文件:
本章主要讨论数据文件
为了使用户避免区分不同设备的区别,操作系统把各种设备都统一作为文件来处理。
文件名
文件的分类
文件缓冲区
ANSI C是美国国家标准协会(ANSI)对C语言发布的标准
ANSI C 标准采用“缓冲文件系统”处理数据文件。
缓冲文件系统:系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区
文件类型指针
在缓冲文件系统中,关键的概念是文件类型指针,简称文件指针
每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息(如文件的名字、文件的状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。
该结构体类型由系统声明,取名为FILE。
由一种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;
不同的C编译系统的FILE类型包含的内容不完全相同,但大同小异。
概述
“打开”:为文件建立相应的信息区(用来存放有关文件的信息)和 文件缓冲区(用来暂存输入输出的数据)
“关闭”:撤销文件信息区和文件缓冲区
用fopen函数打开数据文件
ANSI C规定了标准输入输出函数fopen来实现打开文件
用fclose函数关闭数据文件
撤销文件信息区 和 文件缓冲区
例:
fclose(fp);
#include
#include
int main()
{
FILE *fp;
char ch,filename[10];
printf("请输入所用的文件名:");
scanf("%s",filename);
if((fp=fopen(filename,"w"))==NULL) // 打开输出文件并使fp指向此文件
{
printf("无法打开此文件\n"); // 如果打开时出错,就输出"打不开"的信息
exit(0); // 终止程序*/
}
ch=getchar( ); // 接收在执行scanf语句时最后输入的回车符
printf("请输入一个准备存储到磁盘的字符串(以#结束):");
ch=getchar( ); // 接收从键盘输入的第一个字符
while(ch!='#') // 当输入'#'时结束循环
{
fputc(ch,fp); // 向磁盘文件输出一个字符
putchar(ch); // 将输出的字符显示在屏幕上
ch=getchar(); // 再接收从键盘输入的一个字符
}
fclose(fp); // 关闭文件
putchar(10); // 向屏幕输出一个换行符,换行符的ASCII代码为10
return 0;
}
程序分析
1. exit 是标准C的库函数,
作用:使程序终止,须包含stdlib.h头文件
2. getchar 函数 stdio.h
作用:接收用户从键盘输入的字符,每次只能接收一个字符。
#include
#include
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;
}
程序分析
1. 访问磁盘文件时,逐个字符(字节)进行的,系统用“文件读写位置标记”来表示当前所访问的位置。
2. feof函数
可以检查“文件读写位置标记”是否移动到文件末尾,即磁盘文件是否结束
feof(FILE * in) 如果结束,则为真(1);如果没有结束,则为假(0);
3. 以上程序是按照 文本 方式处理的。若改为二进制方式,将fopen函数中 “r” --> “rb”,“w” --> “wb”
4. C 系统已经将fput fgetc 函数定义为 宏putc getc
#define putc(ch,p) fputc(ch,p)
#define getc(ch,p) fgetc(ch,p)
在stdio.h中定义的,可以用作相同函数
背景:如果遇到需要读取多个字符,一个一个读取太麻烦,则引入** 字符串 **读取函数
获取函数:
- fgets函数原型
char * fgets(char * str, int n, FILE * fp);
其中,
n :要求得到的字符个数,实际获取字节数为 n-1 个字符,然后在最后加一个“\0”字符,共计 n 个字符
如果在读取 n-1 个字符之前遇到换行符“\n” 或 文件结束符EOF,读入结束,但将所遇到的换行符‘\n’,也作为一个字符读入。
若执行成功,则返回值为str数组首元素的地址,如果一开始就遇到文件尾或读数据出错,则返回NULL。
- fputs函数原型
int fputs(char * str , FILE * fp);
作用:将str所指向的字符串输出到fp所指向的文件中,调用时可写为 fputs(“China”, fp);
第一个函数变量可以是常量、字符数组名或字符型指针。字符串末尾“\0”,不输出。
若输出成功,函数值为0;失败时,函数值为EOF(-1)
fgets fputs 与 gets puts 对比
前者是以指定的文件作为读写对象,后者是以终端为读写对象
#include
#include
#include
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;i++)
gets(str[i]); // 输入字符串
for(i=0;i<n-1;i++) // 用选择法对字符串排序
{
k=i;
for(j=i+1;j<n;j++)
if(strcmp(str[k],str[j])>0) k=j;
if(k!=i)
{
strcpy(temp,str[i]);
strcpy(str[i],str[k]);
strcpy(str[k],temp);
}
}
if((fp=fopen("string.dat","w"))==NULL) /* D:\\CC\\打开磁盘文件*/
{
printf("can't open file!\n");
exit(0);
}
printf("\nThe new sequence:\n");
for(i=0;i<n;i++)
{
fputs(str[i],fp);fputs("\n",fp); // 撤号
printf("%s\n",str[i]); // 在屏幕上显示字符串
}
return 0;
}
#include
#include
int main()
{
FILE *fp;
char str[3][10];
int i=0;
if((fp=fopen("D:\\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;
}
上述fputc fputs fgetc fgets 数据对象为字符型数据,不满足其他数据类型的存取需求,故引入如下两个函数
- fprintf()
- fscanf()
调用方式:
fprintf(文件指针, 格式字符串, 输出列表);
fscanf(文件指针, 格式字符串, 输出列表);
fprintf(fp, “%d,%6.2f”, i, f); 比printf()多了个fp;
作用:将int 型变量 i 和 float型变量 f的值按照%d和%6.2f的格式控制输出到fp指向的文件中,若 i= 3 f = 4.5,则输出到磁盘文件上的是以下字符:
3, 4.50
这是和输出到屏幕的情况相似,只是它输出到的是文件,而不是屏幕
fscanf(fp,"%d, %f",&i, &j);
如果文件中有如下数据:3, 4.5 ,则从磁盘文件中读取的整数3送给i, 4.5 送给j
背景:ASCII方式:
向文件中存储数据时,需要对数据先进行ASCII转换,再存储
从文件中读取数据时,又需要将ASCII文件转换成二进制文件后在赋给内存变量
故此中花费了较多的时间在转换上,在需要频繁存取数据的时候,不建议使用fprintf fscanf函数
故:引入如下函数:fread fwrite
调用形式:
fread(buffer, size, count, fp);
fwrite(buffer, size, count, fp);
其中:
buffer:是一个地址。对于fread来说,它是用来存放从文件中读入的数据的存储区的地址
size:要读写的字节数
count:要读写多少个数据项(每个数据长度为size)
fp:FILE类型指针
fread fwrite 函数的类型为int型,如果fread fwrite 函数执行成功,则函数返回值为形参count的值(即输入输出数据项的个数)
#include
#define SIZE 10
struct student_type
{
char name[10];
int num;
int age;
char addr[15];
}stud[SIZE]; // 定义全局结构体数组stud,包含10个学生数据
void save( ) // 定义函数save,向文件输出SIZE个学生的数据
{
FILE *fp;
int i;
if((fp=fopen ("stu.dat","wb"))==NULL) // 打开输出文件atu_list
{
printf("cannot open file\n");
return;
}
for(i=0;i<SIZE;i++)
if(fwrite (&stud[i],sizeof (struct student_type),1,fp)!=1)
printf ("file write error\n");
fclose(fp);
}
int main()
{
int i;
printf("Please enter data of students:\n");
for(i=0;i<SIZE;i++) // 输入SIZE个学生的数据,存放在数组stud中
scanf("%s%d%d%s",stud[i].name,&stud[i].num,&stud[i].age,stud[i].addr);
save( );
return 0;
}
#include
#include
#define SIZE 10
struct student_type
{
char name[10];
int num;
int age;
char addr[15];
}stud[SIZE];
int main( )
{
int i;
FILE *fp;
if((fp=fopen ("stu.dat","rb"))==NULL) // 打开输入文件atu.dat
{
printf("cannot open file\n");
exit(0) ;
}
for(i=0;i<SIZE;i++)
{
fread (&stud[i],sizeof(struct student_type),1,fp); // 从fp指向的文件读入一组数据
printf ("%-10s %4d %4d %-15s\n",stud[i].name,stud[i].num,stud[i]. age,stud[i].addr);
// 在屏幕上输出这组数据
}
fclose (fp); // 关闭文件"stu.dat"
return 0;
}
向文件中存入数据:换行符 + 回车 = 换行符
从文件中读取数据:换行符 = 换行符 + 回车
#include
#define SIZE 10
struct student_type
{
char name[10];
int num;
int age;
char addr[15];
}stud[SIZE]; // 定义全局结构体数组stud,包含10个学生数据
void load( )
{
FILE *fp;
int i;
if((fp=fopen("stu.dat","rb"))==NULL) // 打开输入文件stu_list
{
printf("cannot open infile\n");
return;
}
for(i=0;i<SIZE;i++)
if(fread(&stud[i],sizeof(struct student_type),1,fp)!=1) // 从stu_ list文件中读数据
{
if(feof(fp))
{
fclose(fp);
return;
}
printf("file read error\n");
}
fclose (fp);
}
void save( ) // 定义函数save,向文件输出SIZE个学生的数据
{
FILE *fp;
int i;
if((fp=fopen ("stu_list","wb"))==NULL) // 打开输出文件stu_list
{
printf("cannot open file\n");
return;
}
for(i=0;i<SIZE;i++)
if(fwrite (&stud[i],sizeof (struct student_type),1,fp)!=1)
printf ("file write error\n");
fclose(fp);
}
int main()
{
load();
save( );
return 0;
}
随机访问:
不是按照 数据在文件中的物理位置次序进行读写,而是可以对任何位置上的数据进行访问,这种方法比顺序访问效率高得多
文件位置标记及定位
文件读写位置标记–>简称 文件位置标记 (文件标记):指示 接下来要读写的下一字符的位置
(有的教材中称之为 文件位置指针、但是为了防止与指向文件的指针混淆,常称之为 文件位置标记)
#include
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); // 从文件头重新逐个读字符,输出到file2文件
fclose(fp1);
fclose(fp2);
return 0;
}
#include
#include
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;
}