C语言具有操作文件的能力,比如打开文件、读取和追加数据、插入和删除数据、关闭文件、删除文件等。
在操作系统中除了常见的文本文件以及二进制文件,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作。例如:
文件 | 硬件设备 |
---|---|
stdin | 标准输入文件,一般指键盘;scanf()、getchar() 等函数默认从 stdin 获取输入。 |
stdout | 标准输出文件,一般指显示器;printf()、putchar() 等函数默认向 stdout 输出数据。 |
stderr | 标准错误文件,一般指显示器;perror() 等函数默认向 stderr 输出数据。 |
stdprn | 标准打印文件,一般指打印机。 |
操作文件的正确流程为:打开文件 --> 读写文件 --> 关闭文件。文件在进行读写操作之前要先打开,使用完毕要关闭。
所谓打开文件,就是获取文件的有关信息,例如文件名、文件状态、当前读写位置等,这些信息会被保存到一个 FILE 类型的结构体变量中。关闭文件就是断开与文件之间的联系,释放结构体变量,同时禁止再对该文件进行操作。
在C语言中,文件有多种读写方式,可以一个字符一个字符地读取,也可以读取一整行,还可以读取若干个字节。文件的读写位置也非常灵活,可以从文件开头读取,也可以从中间位置读取。
所有的文件(保存在磁盘)都要载入内存才能处理,所有的数据必须写入文件(磁盘)才不会丢失。数据在文件和内存之间传递的过程叫做文件流,类似水从一个地方流动到另一个地方。数据从文件复制到内存的过程叫做输入流,从内存保存到文件的过程叫做输出流。
文件是数据源的一种,除了文件,还有数据库、网络、键盘等;数据传递到内存也就是保存到C语言的变量(例如整数、字符串、数组、缓冲区等)。我们把数据在数据源和程序(内存)之间传递的过程叫做数据流(Data Stream)。相应的,数据从数据源到程序(内存)的过程叫做输入流(Input Stream),从程序(内存)到数据源的过程叫做输出流(Output Stream)。
输入输出(Input output,IO)是指程序(内存)与外部设备(键盘、显示器、磁盘、其他计算机等)进行交互的操作。几乎所有的程序都有输入与输出操作,如从键盘上读取数据,从本地或网络上的文件读取数据或写入数据等。通过输入和输出操作可以从外界接收信息,或者是把信息传递给外界。
我们可以说,打开文件就是打开了一个流。
<stdio.h>
头文件中的fopen()
函数即可打开文件
FILE *fopen( const char * filename, const char * mode );
filename
为文件名(包括文件路径),mode
为打开方式,它们都是字符串。fopen()
会获取文件信息,包括文件名、文件状态、当前读写位置等,并将这些信息保存到一个 FILE 类型的结构体变量中,然后将该变量的地址返回。//以只写方式打开一个当前路径下test.txt文本文件
//fp 通常被称为文件指针。
FILE *fp = fopen("./test.txt","w");
打开文件出错时,fopen() 将返回一个空指针,也就是 NULL,我们可以利用这一点来判断文件是否打开成功,
int main(){
FILE *fp = NULL;
if((fp = fopne("./test.txt","w")) == NULL){
printf("File open failed");
}
return 0;
}
读写权限和读写方式可以组合使用,但是必须将读写方式放在读写权限的中间或者尾部(换句话说,不能将读写方式放在读写权限的开头)。
整体来说,文件打开方式由 r、w、a、t、b、+ 六个字符拼成,各字符的含义是:
打开方式 | 说明 |
---|---|
"r" | 以“只读”方式打开文件。只允许读取,不允许写入。文件必须存在,否则打开失败。 |
"w" | 以“写入”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。 |
"a" | 以“追加”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。 |
"r+" | 以“读写”方式打开文件。既可以读取也可以写入,也就是随意更新文件。文件必须存在,否则打开失败。 |
"w+" | 以“写入/更新”方式打开文件,相当于w 和r+ 叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。 |
"a+" | 以“追加/更新”方式打开文件,相当于a和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。 |
打开方式 | 说明 |
---|---|
"t" | 文本文件。如果不写,默认为"t" 。 |
"b" | 二进制文件。 |
fclose()
函数把文件关闭,以释放相关资源,避免数据丢失。
int fclose(FILE *fp);
以字符形式读写文件时,每次可以从文件中读取一个字符,或者向文件中写入一个字符。主要使用两个函数,分别是 fgetc() 和 fputc()。
fgetc 是 file get char 的缩写,意思是从指定的文件中读取一个字符。
int fgetc (FILE *fp);
EOF
。#include<stdio.h>
int main(){
//打开文件
FILE *fp = NULL;
if((fp = fopen("./test.txt","r")) == NULL){
printf("File open failed");
return 0;
}
//按照字符读取文件
char c;
while((c = fgetc(fp)) != EOF){
printf("%c",c);
}
//关闭文件
fclose(fp);
return 0;
}
EOF
int feof ( FILE * fp );
int ferror ( FILE *fp );
文件位置指针
fputc 是 file output char 的所以,意思是向指定的文件中写入一个字符。
int fputc ( int ch, FILE *fp );
注意:
#include<stdio.h>
int main(){
//打开文件
FILE *fp = NULL;
if((fp = fopen("./test.txt","w")) == NULL){
printf("File open failed");
return 0;
}
//从键盘获取字符,并写入文件,回车结束
char c;
while((c = getchar()) != '\n'){
fputc(c,fp);
}
fclose(fp);
return 0;
}
fgetc() 和 fputc() 函数每次只能读写一个字符,速度较慢;实际开发中往往是每次读写一个字符串或者一个数据块,这样能明显提高效率,以字符串读主要是用两个函数:fgets、fputs
fgets() 函数用来从指定的文件中读取一个字符串,并保存到字符数组中
char *fgets ( char *str, int n, FILE *fp );
注意:
#include<stdio.h>
#define NUM 100
int main(){
FILE *fp = NULL;
if((fp = fopen("./test.txt","r")) == NULL){
printf("File open failed");
return 0;
}
char c[NUM];
//逐行读取
while(fgets(c,999,fp) != NULL){
printf("%s",c);
}
fclose(fp);
return 0;
}
fputs() 函数用来向指定的文件写入一个字符串
int fputs( char *str, FILE *fp );
#include<stdio.h>
int main(){
FILE *fp = NULL;
if((fp = fopen("./test.txt","w")) == NULL){
printf("File open failed");
return 0;
}
char str[] = "Hello World\nHello C Language";
//写入文件
fputs(str,fp);
fclose(fp);
return 0;
}
以二进制读取数据,通常是数组或结构体。如果希望读取多行内容,需要使用 fread() 函数;相应地写入函数为 fwrite()。
fread() 函数用来从指定文件中读取块数据。所谓块数据,也就是若干个字节的数据,可以是一个字符,可以是一个字符串,可以是多行数据,并没有什么限制。
原型:size_t fread ( void *ptr, size_t size, size_t count, FILE *fp );
fwrite() 函数用来向文件中写入块数据
原型:size_t fwrite ( void * ptr, size_t size, size_t count, FILE *fp );
参数
返回值
#include<stdio.h>
int main(){
int age[] = {18,22,55,89};
FILE *fp = NULL;
if((fp = fopen("./age","wb")) == NULL){
printf("File Open Failed");
return 0;
}
//将数组写入文件,每个数据块为sizeof(int)字节,一共sizeof(age) / sizeof(int)个数据块
fwrite(age,sizeof(int),sizeof(age) / sizeof(int),fp);
fclose(fp);
return 0;
}
#include<stdio.h>
int main(){
FILE *fp = NULL;
if((fp = fopen("./age","rb")) == NULL){
printf("File Open Failed");
return 0;
}
int age[4];
fread(age,sizeof(int),4,fp);
for(int i = 0;i < 4;i++){
printf("%d\n",age[i]);
}
fclose(fp);
return 0;
}
#include<stdio.h>
typedef struct Stu{
int age;
char *name;
}Stu;
int main(){
FILE *fp = NULL;
if((fp = fopen("./stu","wb")) == NULL){
printf("File Open Failed");
return 0;
}
Stu s = {18,"lucy"};
//将一个结构体变量的值写入文件,数据块的大小就是一个结构体的大小,只写入一个数据块
//如果是结构体数组,那么第三个参数就是数组长度
fwrite(&s,sizeof(Stu),1,fp);
fclose(fp);
return 0;
}
#include<stdio.h>
typedef struct Stu{
int age;
char name[50];
}Stu;
int main(){
FILE *fp = NULL;
if((fp = fopen("./stu","rb")) == NULL){
printf("File Open Failed");
return 0;
}
Stu s;
int i = fread(&s,sizeof(Stu),1,fp);
printf("姓名:%s,年龄:%d",s.name,s.age);
fclose(fp);
return 0;
}
fscanf() 和 fprintf() 函数与前面使用的 scanf() 和 printf() 功能相似,都是格式化读写函数,两者的区别在于 fscanf() 和 fprintf() 的读写对象不是键盘和显示器,而是磁盘文件。
函数原型
int fscanf ( FILE *fp, char * format, ... );
int fprintf ( FILE *fp, char * format, ... );
fprintf() 返回成功写入的字符的个数,失败则返回负数。
fscanf() 返回参数列表中被成功赋值的参数个数。
例如,将Stu结构体变量写入文件中
#include<stdio.h>
typedef struct Stu{
char name[20];
int age;
}Stu;
int main(){
Stu s = {"lucy",18};
//打开文件
FILE *fp;
if((fp = fopen("./stu.txt","w")) == NULL){
printf("File opne failed");
}
//以规定格式写入
fprintf(fp, "%s %d\n",s.name,s.age);
fclose(fp);
return 0;
}
此时,stu.txt文件内容为
lucy 18
然后,按照格式读取就可以了
#include<stdio.h>
typedef struct Stu{
char name[20];
int age;
}Stu;
int main(){
FILE *fp;
if((fp = fopen("./stu.txt","r")) == NULL){
printf("File open failed");
}
Stu s;
//从文件中按照固定格式读取
int i = fscanf(fp,"%s %d\n",&s.name,&s.age);
//打印
printf("name->%s||age->%d",s.name,s.age);
fclose(fp);
return 0;
}
name->lucy||age->18
在实际开发中经常需要读写文件的中间部分,要解决这个问题,就得先移动文件内部的位置指针,再进行读写。这种读写方式称为随机读写,也就是说从文件的任意位置开始读写。
实现随机读写的关键是要按要求移动位置指针,这称为文件的定位。
函数原型:void rewind ( FILE *fp );
该函数的作用相当于重置文件位置指针,将指针移到文件开头
函数原型:int fseek ( FILE *fp, long offset, int origin );
起始点 | 常量名 | 常量值 |
---|---|---|
文件开头 | SEEK_SET | 0 |
当前位置 | SEEK_CUR | 1 |
文件末尾 | SEEK_END | 2 |
例如,把位置指针移动到离文件开头100个字节处
fseek(fp, 100, 0);
注意:值得说明的是,fseek() 一般用于二进制文件,在文本文件中由于要进行字节和字符转换,计算的位置有时会出错。