在实际应用中,有时会遇到这样一些问题:比如需要我们重复读入大量的数据,如果每次运行程序都要从键盘输入,就太麻烦了;又比如在程序运行结束后,我们还想查看程序运行的结果,如果程序比较大,再跑一次也不方便……
为了解决这些问题,C引入了对文件的操作。文件是实现数据持久化的一种方式。
目录
4) fseek:使位置指针指向origin+offset的位置
一、文件概述
1.文件指针
在以往的编程中,我们使用变量名来标识具体的变量,使用数组名来标识具体的数组。在读写文件时,我们使用文件指针来标识一个具体的文件。文件指针的类型是FILE * ,其定义通常如下:
FILE * fp = NULL;
FILE类型的定义位于stdio.h头文件中,此外,stdio.h还包含了文件标准输入输出等函数
2.文件路径
1)首先我们需要知道什么是文件名。文件名是同一目录下文件的唯一标识,一般文件名的结构为“主文件名.拓展名”。例如一个C语言源程序文件hello.c,hello即是主文件名,c是拓展名。
主文件名相同、拓展名不同的文件名标识的是两个不同的文件。例如hello.c和hello.exe是两个不同的文件,他们可以在同一目录下共存
常见的文件拓展名有:txt、jpg、h、c、cpp、exe等
2)文件路径分为绝对路径和相对路径。
绝对路径直接指明了文件在硬盘上真正存在的位置,也可以理解为文件相对于根目录的位置。相对路径是指从某个基准目录开始,能找到文件位置的一条路径。
举个例子,在Windows系统下,对于VScodes文件夹下的文件p1.cpp,我们可以通过文件的属性查看其位置:
这个“位置”再加上文件名就是文件的绝对路径,即C:\C_program\VScode\p1.cpp
而VScode\p1.cpp就是文件p1.cpp相对于目录VScode的相对路径
3.字节流
所有文件在计算机中都是以二进制形式存储的,但不同类别的文件编码和解码的方式有所不同。C语言的文件操作不区分文件类别,C把文件同一看作“字节流”,以字节为单位进行处理。
二、文件的读写
1.文件的打开、关闭
1) 文件输入输出缓冲区
为了提高文件的读写速度,一般情况下我们不会直接读写文件,而是会先把要读写的数据复制到缓冲区中。
对文件进行读操作时,我们会先读取文件的字节流,将其复制到输入文件缓冲区中,程序读取数据的时候从缓冲区中读入;对文件进行写操作时,我们也会把要存储的数据先写入输出文件缓冲区,等文件关闭时再把数据从缓冲区写入文件。文件标准输入输出的函数都是从/往缓冲区中读取/写数据的,这些函数被定义在stdio.h头文件中。
2) 打开文件fopen
在读写文件之前,我们需要打开文件。打开文件函数fopen完成了什么事呢?它将文件中的数据复制到了缓冲区,并返回了指向FILE对象的文件指针。fopen函数的原型:
FILE * fopen(char * filename,char * mode);
文件打开成功时,fopen返回FILE * 类型的指针;打开失败,则返回NULL。
filename为文件路径,为了使程序具有较好的可移植性,我们通常使用相对路径,当文件与程序处于同一目录下时,filename填文件名就可以了。此外在C语言中' \ '属于转义字符,如果要在filename中使用的话,需要多加一个反斜杠:\\,例如C:\\C_program\\VScode\\p1.cpp
mode是文件的打开模式,常见的文件打开模式有:
模式 | 含义 |
r | 以只读方式打开ASCII文件,该文件必须存在 |
r+ | 以可读可写方式打开ASCII文件,该文件必须存在 |
w | 以只写方式打开ASCII文件,原有内容全部清空,若文件不存在会建立该文件 |
w+ | 以可读可写方式打开ASCII文件,原有内容全部清空,若文件不存在会建立该文件 |
a | 以只写附加方式打开ASCII文件,新写入的数据追加到文件尾,若文件不存在会建立该文件 |
a+ | 以可读写附加方式打开ASCII文件,新写入的数据会被追加到文件尾,若文件不存在会建立该文件 |
rb | 以只读方式打开二进制文件,该文件必须存在 |
rb+ | 以可读可写方式打开二进制文件,该文件必须存在 |
wb | 以只写方式打开二进制文件,原有内容全部清空,若文件不存在会建立该文件 |
wb+ | 以可读可写方式打开二进制文件,原有内容全部清空,若文件不存在会建立该文件 |
ab | 以只写附加方式打开二进制文件,新写入的数据追加到文件尾,若文件不存在会建立该文件 |
ab+ | 以可读写附加方式打开二进制文件,新写入的数据会被追加到文件尾,若文件不存在会建立该文件 |
它的调用方式通常如下:
FILE * fp=NULL;
if( (fp=fopen("text.txt","r")) == NULL ){
printf("File open error!\n");//文件打开失败
exit(0);//退出程序
}
3) 关闭文件fclose
关闭文件使用fclose函数,close执行后会将缓冲区中的数据写入文件。其函数原型为:
int fclose(FILE *);
文件关闭成功时返回0,否则返回EOF
它的调用方式比较简单,例如:fclose(fp);
尽管在C程序结束时会自动关闭文件,但是由于文件打开过多会使系统运行缓慢,所以对于不再使用的文件要及时关闭。
2.文件定位
C语言把文件看做是字节流,使用文件读写位置指针来定位文件中正要操作的字节。为了方便对位置指针进行操作,stdio.h定义了下面几个函数:
1) ftell:返回位置指针当前所处的位置
long int ftell(FILE * fp);
//若操作成功则返回位置指针值,否则返回EOF(-1)
2) feof:标志位置指针是否指向文件末尾
int feof(FILE * fp);
//若位置指针位于文件末尾则返回EOF(-1),否则返回0
3) rewind:使位置指针重新指向文件的开头
void rewind(FILE * fp);
//无返回值
4) fseek:使位置指针指向origin+offset的位置
int fseek(FILE * fp,long offset,int origin);
//若操作成功返回0,否则返回非0值
//feek一般用于二进制文件
3.文件的读操作
1) 字符读入fgetc
int fgetc(FILE * fp);
//操作成功返回读取字符的ASCII码;出错或文件结束返回EOF
#include <stdio.h>
#include <stdlib.h>
int main()
{
char ch=0;
FILE * fp=NULL;
if( (fp=fopen("text.txt","r")) == NULL ){
printf("File open error!\n");
exit(0);
}
while(!feof(fp)){
ch=fgetc(fp);
printf("%c",ch);
}
fclose(fp);
return 0;
}
2) 字符串读入fgets
char * fgets(char * str,int num,FILE * fp);
//从文件中读取num-1个字符(在字符串结尾添加\0)到str中
//操作成功返回字符串str的地址,否则返回NULL
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str[100]={0};
FILE * fp=NULL;
if( (fp=fopen("text.txt","r")) == NULL ){
printf("File open error!\n");
exit(0);
}
fgets(str,10,fp);//从fp指向的文件中读取9个字符
printf("%s\n",str);
fclose(fp);
return 0;
}
3) 格式化输入fscanf
int fscanf(FILE * fp,char * format,arg_list);
//将fp指向的文件按format格式转换,读取到arg_list中
//若操作成功返回实际读取的参数个数,否则返回一个负数
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str[100]={0};
FILE * fp=NULL;
if( (fp=fopen("text.txt","r")) == NULL ){
printf("File open error!\n");
exit(0);
}
fscanf(fp,"%s",str);
printf("%s\n",str);
fclose(fp);
return 0;
}
4) 数据块读fread
int fread(void * buf,int size,int count,FILE * fp);
/*从fp指向的文件读取count个size大小的字段,送入数据区buf的count个元素中,同时将读写位置指针向下移动size*count个字节*/
//若操作成功返回实际读取的字段个数,出错或文件结束返回EOF
fread是以二进制形式读入数据的,所以最好和fwrite配套使用。即fread读入的文件,在过去应该是以fwrite形式写入的,并且数据块的类型也要保持一致,否则容易出现乱码。(可以结合本文 数据块写fwrite 部分一起理解)
4.文件的写操作
1) 字符写入fputc
int fputc(int ch,FILE * fp);
//操作成功返回读取字符的ASCII码;出错或文件结束返回EOF
/*将a~z共26个字母写入文件text.txt中*/
#include <stdio.h>
#include <stdlib.h>
int main()
{
char ch=0;
FILE * fp=NULL;
if( (fp=fopen("text.txt","w")) == NULL ){
printf("File open error!\n");
exit(0);
}
for(ch='a';ch<='z';ch++)
fputc(ch,fp);
fclose(fp);
return 0;
}
2) 字符串写入fputs
int * fputs(char * str,FILE * fp);
//把字符串str写入fp指向的文件中(不包括\0)
//操作成功返回非负值,否则返回EOF
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str1[10]="Hello",str2[10]=" World!";
FILE * fp=NULL;
if( (fp=fopen("text.txt","w")) == NULL ){
printf("File open error!\n");
exit(0);
}
fputs(str1,fp);
fputs(str2,fp);
fclose(fp);
return 0;
}
3) 格式化输出fprintf
int fprintf(FILE * fp,char * format,arg_list);
//将arg_list中各参数按format格式转换,输出到fp指向的文件中
//若操作成功返回实际输出的参数个数,否则返回一个负数
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str[100]="Hello World!";
FILE * fp=NULL;
if( (fp=fopen("text.txt","w")) == NULL ){
printf("File open error!\n");
exit(0);
}
for(int i=0;i<100;i++){
fprintf(fp,"Day%d %s\n",i,str);
}
fclose(fp);
return 0;
}
4) 数据块写fwrite
int fwrite(void * buf,int size,int count,FILE * fp);
/*从数据区的buf中读取count个size大小的字段,送入fp指向的文件中,同时将读写位置指针向下移动size*count个字节*/
//若操作成功返回实际写入的字段个数,出错或文件结束返回EOF
如果使用fwrite写入文件,查看文件时发现有很多乱码,不用慌,这是正常情况。因为fwrite是以二进制形式写入文件的,如果你想查看这些数据,使用fread读入就好,注意数据块的类型最好保持一致。
例如,我们用fwrite往文件里写一个账单结构数组:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct PayList{
int year,month,day; //日期:年、月、日
char dish_name[30]; //菜名
float money; //价钱
}plist;
int main()
{
plist paylist[31]={0};
FILE * fp=NULL,* fp2=NULL;
if( (fp=fopen("paylist.txt","w")) == NULL ){
printf("File open error!\n");
exit(0);
}
for(int i=0;i<31;i++){
paylist[i].year=2021;
paylist[i].month=10;
paylist[i].day=i+1;
if(i%7==0){
strcpy(paylist[i].dish_name,"黄焖鸡米饭\0");
paylist[i].money=20;
}else if(i%6==0){
strcpy(paylist[i].dish_name,"水煮肉片\0");
paylist[i].money=15;
}else{
strcpy(paylist[i].dish_name,"小碗菜\0");
paylist[i].money=16;
}
}
fwrite(paylist,sizeof(plist),31,fp);
fclose(fp);
return 0;
}
我们发现paylist.txt文件中有很多乱码:
接下来我们再写一个程序,从 paylist.txt 中读入10月1~10日的账单,将其打印在屏幕上;并且将这十天的账单分别用 fwrite 和 fprintf 拷贝到 copy1.txt 和 copy2.txt 文件中:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct PayList{
int year,month,day; //日期:年、月、日
char dish_name[30]; //菜名
float money; //价钱
}plist;
int main()
{
int i=0;
plist paylist[31]={0};
FILE * fp=NULL,* fp1=NULL,* fp2=NULL;
//打开文件:
if( (fp=fopen("paylist.txt","r")) == NULL ){
printf("File open error!\n");
exit(0);
}
if( (fp1=fopen("copy1.txt","w")) == NULL ){
printf("File open error!\n");
exit(0);
}
if( (fp2=fopen("copy2.txt","w")) == NULL ){
printf("File open error!\n");
exit(0);
}
//读入十天的账单:
fread(paylist,sizeof(plist),10,fp);
//输出账单:
for(i=0;i<10;i++)
printf("%4d.%2d.%2d %10s %.2f\n",paylist[i].year,paylist[i].month,paylist[i].day,paylist[i].dish_name,paylist[i].money);
//保存账单到文件中:
fwrite(paylist,sizeof(plist),10,fp1);
for(i=0;i<10;i++)
fprintf(fp2,"%4d.%2d.%2d %10s %.2f\n",paylist[i].year,paylist[i].month,paylist[i].day,paylist[i].dish_name,paylist[i].money);
fclose(fp);
return 0;
}