C语言:详解文件操作
C语言:详解文件操作
1.文件相关知识
2.文件的打开和关闭
3.文件的顺序读写
4.文件的随机读写
5.文件读取结束判定
6.文件缓冲区
1.文件相关知识
什么是文件?
磁盘(硬盘)上的文件是文件。
在程序设计中,我们一般谈的文件有两种:程序文件、数据文件。
程序文件包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
本期讨论的是数据文件。
一个文件要有一个唯一的文件标识,文件名包含3部分:文件路径 + 文件名主干 + 文件后缀,例如:c:\code\test.txt 。最后一个 \ 和 . 之间的是文件主干名,最后一个 \ 之前的内容(包括最后一个 \ )为文件路径,剩下的内容和点号 . 为文件后缀。
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存的文件中,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
流和标准流
程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,为了帮助程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。
C程序针对文件、画面、键盘等的数据输入输出操作都是通过流操作的。一般情况下,我们要想向流里写数据,或者从流中读取数据,都是要打开流,然后操作。
C语言程序在启动的时候,默认打开了3个流:
- stdin :标准输入流,在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。
- stdout :标准输出流,在大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出流中。
- stderr :标准错误流,在大多数环境中输出到显示器界面。
stdin、stdout、stderr 三个流的类型是: FILE* ,通常称为文件指针。C语言中,就是通过 FILE* 的文件指针来维护流的各种操作。
文件指针
每个被使用的文件都在内存中开辟了⼀个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息保存在一个结构体变量中,该结构体类型由系统声明,取名 FILE。
下面创建一个FILE*类型的指针变量pf。
FILE* pf; //文件指针变量
pf是一个指向FILE类型数据的指针变量,可以使pf指向某个文件的文件信息区,通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够间接找到与它关联的文件。
2.文件的打开和关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
fopen函数用来打开文件,函数原型如下:
FILE* fopen(const char* filename,const char* mode);
- 参数filename为文件名,用字符串表示。
- 参数mode为文件打开模式,用字符串表示。
读和写的意义如图所示:
文件打开模式如下:
注意:对于"w",若打开的文件存在且已有信息,则清空原有信息。
fclose函数用来关闭文件,函数原型如下:
int fclose(FILE* stream);
示例:打开一个文件,然后关闭文件。
#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","w");if(pf==NULL) //检查{perror("fopen"); //如果pf为NULL,就会打印 fopen:错误信息 ,perror的参数是一个字符串return 1;}fclose(pf); //关闭文件pf=NULL; //把pf置为NULL,避免成为野指针。return 0;
}
程序运行后就会自动创建test.txt文件,按如下方法找到test.txt文件。
接下来点击 “x” 退出,按下图操作。
3.文件的顺序读写
顺序读写函数介绍
上面说的适用于所有输入流一般指适用于标准输入流和其他输入流(如文件输入流);所有输出流一般指适用于标准输出流和其他输出流(如文件输出流)。
fputc函数
fputc函数可将字符输出到文件中,函数原型如下:
int fputc(int ch,FILE* stream);
- 参数ch传字符或ASCII码值
示例:在test.txt文件上写字母a~z。
#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","w");if(pf==NULL) //检查{perror("fopen");return 1;}char ch=0;for(ch='a';ch<='z';ch++) //写入字母a~zfputc(ch,pf);fclose(pf);pf=NULL;return 0;
}
程序运行结束后,我们用记事本打开文件,就能看到文件被写入字母a~z。
fputc函数的第二个参数是文件指针类型的stream,因为fputc适用于所有输出流,所以我们将上述代码中fputc的参数pf改为stdout,即可把字母a~z输出到屏幕。
#include<stdio.h>
int main()
{char ch=0;for(ch='a';ch<='z';ch++)fputc(ch,stdout)return 0;
}
fgetc函数
fgetc函数可以从文件中读取字符,函数原型如下:
int fgetc(FILE* stream);
- 若成功读取字符,返回字符的ASCII码值。
- 若读取遇到文件末尾,或读取失败,返回EOF(即为 -1)。
示例:把test.txt中的字母a~z显示到屏幕上。
#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","r");if(pf==NULL){perror("fopen");return 1;}int ch=0;while((ch=fgetc(pf))!=EOF) //如果能正常读取,循环继续,直到读到文件末尾printf("%c",ch);fclose(pf);pf=NULL;return 0;
}
fgetc也可以把参数pf改为stdin,这样就能从键盘输入字符,类似scanf。
#include<stdio.h>
int main()
{int ch=0;ch=fgetc(stdin);fputc(ch, stdout); return 0;
}
fputs函数
fputs函数可以向文件输出一个字符串,函数原型如下:
#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","w");if(pf==NULL){perror("fopen");return 1;}fputs("haha\n",pf);fputs("hehe\n",pf);fclose(pf);pf=NULL;return 0;
}
可以看到,test.txt文件被写入了haha和hehe,由于\n的作用,所以有3行。
同样地,我们把参数pf改为stdout,就能把字符串输出到屏幕。
#include<stdio.h>
int main()
{char str[]="abcdefg";fputs(str,stdout);return 0;
}
fgets函数
fgets函数能从文件中最多读取一行字符,但不会换行读,函数原型如下:
char* fgets(char* str,int num,FILE* stream);
- 参数num表示最多读num个字符。
- 读取成功返回字符串地址,读取失败或遇到文件末尾,返回NULL。
示例:将test.txt中的haha、hehe打印到屏幕上。
#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","r");if(pf==NULL){perror("fopen");return 1;}char str[100];while(fgets(str,30,pf)!=NULL)printf("%s",str);fclose(pf);pf=NULL;return 0;
}
同样的,我们把参数pf改为stdin,就能在键盘上输入。
#include<stdio.h>
int main()
{char str[100];fgets(str,5,stdin);printf("%s\n",str);return 0;
}
因为最多只读5个字符,且包含 ‘\0’ ,所以打印结果如下:
fprintf函数
fprintf函数是格式化输出函数,函数原型如下:
int fprintf(FILE* stream,const char* format,...);
示例:把结构体的数据写到文件中。
#include<stdio.h>
struct S
{char name[20];int age;float score;
};
int main()
{struct S s={"张三",20,65.5f};FILE* pf=fopen("test.txt","w");if(pf==NULL){perror("fopen");return 1;}fprintf(pf,"%s %d %f",s.name,s.age,s.score);fclose(pf);pf=NULL;return 0;
}
可以看到,fprintf函数与printf类似,只是fprintf多了一个参数,我们同样可以通过对文件类型参数的调整来使数据输出到屏幕。
#include<stdio.h>
int main()
{int arr[10]={0};for(int i=0;i<10;i++){fprintf(stdout,"%d ",arr[i]);}return 0;
}
fscanf函数
fscanf函数为格式化输入函数,函数原型如下:
int fscanf(FILE* stream,const char* format,...);
示例:把test.txt文件中的数据读入结构体变量s中。
#include<stdio.h>
struct S
{char name[20];int age;float score;
};
int main()
{struct S s={0};FILE* pf=fopen("test.txt","r");if(pf==NULL){perror("fopen");return 1;}fscanf(pf,"%s %d %f",s.name,&(s.age),&(s.score));printf("%s %d %f\n",s.name,s.age,s.score);fclose(pf);pf=NULL;return 0;
}
fscanf也与scanf类似,不同之处就在于fscanf函数参数多了一个文件类型指针,我们通过这个指针也可以实现通过键盘输入数据。
#include<stdio.h>
int main()
{int n=0;fscanf(stdin,"%d",&n);fprintf(stdout,"%d",n);return 0;
}
fwrite函数
fwrite函数为二进制输出函数,返回成功读取的元素个数,只适用于文件输出流,函数原型如下:
size_t fwrite(const void* p,size_t size,size_t count,FILE* stream);
- 例如p指向数组,每个元素占size个字节,共count个元素。
示例:把arr的数据写到文件test.txt中。
#include<stdio.h>
int main()
{int arr[]={1,2,3,4,5};int len=sizeof(arr)/sizeof(arr[0]);FILE* pf=fopen("test.txt","wb");if(pf=NULL){perror("fopen");return 1;}fwrite(arr,sizeof(int),len,pf);fclose(pf);pf=NULL;return 0;
}
由于文件的打开模式是"wb",所以test.txt文件里的内容是无法读懂的。
fread函数
fread函数是二进制输入函数,读到多少个元素就返回数字多少,只适用于文件输入流,函数原型如下:
size_t fread(void* p,size _t size,size_t count,FILE* stream);
- size为元素所占字节数,count为元素个数。
示例:把test.txt文件中的二进制数据读到arr。
#include<stdio.h>
int main()
{int arr[5]={0};FILE* pf=fopen("test.txt","rb");if(pf==NULL){perror("fopen");return 0;}int i=0;fread(arr, sizeof(int), 5, pf);for (int i = 0;i < 5;i++){printf("%d ",arr[i]);}fclose(pf);pf=NULL;return 0;
}
4.文件的随机读写
fseek函数
fseek可根据文件中的光标的位置来读写文件。函数原型如下:
int fseek(FILE* stream,long int,offset,int origin);
- 参数offset为目标位置到起始位置偏移量。
- 参数origin为起始位置,文件起始位置为SEEK_SET,光标当前位置为SEEK_CUR,文件末尾为SEEK_END。
示例:
#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","w"); //先用"w"把之前文件中的内容清空if(pf==NULL){perror("fopen");return 1;}char str[]="abcdefghij"; //往文件中写入字母a~jfputs(str,pf);fclose(pf); //关闭文件,刷新缓冲区接下来才能正常操作pf=fopen("test.txt","r"); //再次以"r"打开文件fseek(pf,4,SEEK_SET); //光标从文件起始位置(向右)偏移4,向左偏移则为负数int ch=fgetc(pf);printf("%c\n",ch);fclose(pf);pf=NULL;return 0;
}
ftell函数
ftell函数能返回光标相对起始位置的偏移量,函数原型如下:
long int ftell(FILE* stream);
示例:
#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","r");if(pf==NULL){perror("fopen");return 1;}for (int i=0;i<5;i++) //读取5个字符{fgetc(pf);}int ret=ftell(pf);printf("%d\n",ret);fclose(pf);pf=NULL;return 0;
}
rewind函数
rewind函数能让文件指针的位置回到文件的起始位置,函数原型如下:
void rewind(FILE* stream);
示例:
#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","r");if(pf==NULL){perror("fopen");return 1;}for (int i=0;i<5;i++) //移动光标{fgetc(pf);}int ret=ftell(pf);printf("此时光标与起始位置的距离为%d\n",ret);rewind(pf);ret=ftell(pf);printf("此时光标与起始位置的距离为%d\n",ret);fclose(pf);pf=NULL;return 0;
}
5.文件读取结束判定
文件读取结束的原因可能是 遇到文件末尾 或 读取出错。
文本文件读取是否结束:
- fgetc 的返回值判断是否为 EOF 。
- fgets 的返回值判断返回值是否为 NULL 。
⼆进制文件的读取是否结束:
- 判断返回值是否小于实际要读的个数,例如,fread判断返回值是否小于实际要读的个数。
其他判断方法:
- feof函数,返回值非0,则遇到文件末尾。
int feof(FILE* stream);
- ferror函数,错误标记被设置返回非0,否则返回0。
int ferror(FILE* stream);
6.文件缓冲区
ANSIC 标准采用 “缓冲文件系统” 处理的数据文件,缓冲文件系统是指:系统自动地在内存中为程序中每⼀个正在使用的文件开辟一块 “文件缓冲区”。
若从内存向磁盘(硬盘)输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。
如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。
缓冲区的大小根据C编译系统决定的。
因为有缓冲区的存在,C语言在操作文件的时候,需要做 刷新缓冲区 或者 在文件操作结束的时候关闭文件(用fclose关闭文件)。如果不做,可能导致读写文件的问题。
拙作一篇,望诸位同道不吝斧正。