✨✨欢迎大家来到Celia的博客✨✨
🎉🎉创作不易,请点赞关注,多多支持哦🎉🎉
所属专栏:C语言
目录
[2.1 流和标准流](#2.1 流和标准流)
[2.1.1 流](#2.1.1 流)
[2.1.2 标准流](#2.1.2 标准流)
[2.2 文件指针](#2.2 文件指针)
[2.3 文件的打开与关闭](#2.3 文件的打开与关闭)
[3.1 fgetc](#3.1 fgetc)
[3.2 fputc](#3.2 fputc)
[3.3 fgets](#3.3 fgets)
[3.4 fputs](#3.4 fputs)
[3.5 fprintf](#3.5 fprintf)
[3.6 fscanf](#3.6 fscanf)
[3.7 fwrite](#3.7 fwrite)
[3.8 fread](#3.8 fread)
[4.1 fseek](#4.1 fseek)
[4.2 ftell](#4.2 ftell)
[4.3 rewind](#4.3 rewind)
[5.1 feof](#5.1 feof)
[5.2 常见判断文件读取结束的方法](#5.2 常见判断文件读取结束的方法)
引言
我们知道,C语言程序是储存在电脑的内存中的,如果程序退出,内存会被回收,保存在变量中的数据就会被删除,如果我们想把程序运行时产生的数据永久性的保存(存储到在磁盘上),就会用到有关文件的操作,本篇文章将会介绍C语言中有关文件操作的内容。
一、二进制文件与文本文件
根据数据的组成形式,分为二进制文件 和文本文件。
- 数据在内存中以二进制的形式存储,如果++不加以转换++ ,直接输出到外部的文件内,就是二进制文件。
- 如果数据要求以ASCII码的形式存储,在存储之前就++需要转换++ ,以ASCII字符存储的文件就是文本文件。
以上是数据存入文件的两种方式。那么一个数据中的内容如何区分用哪种方式存储呢?
字符数据一律以ASCII码形式存储,数值型数据既可以用二进制存储,也可以用ASCII码存储。
如:数值为 10 的数据,用二进制存储为 1010(此处省略高位0),用字符存储为 '1','0'。(2个字符)。
二、文件的打开与关闭
2.1 流和标准流
2.1.1 流
我们的程序需要输出数据到外部设备,也需要从外部设备输入数据,不同的外部设备输入输出的操作和处理方式各不相同。为了方便对内外部数据的操作,C语言抽象出"流"的概念,把流想象成一条充满数据的河,外部数据可以通过流输入数据,内部数据也可以通过流输出数据。
C语言对文件,键盘,屏幕的输出、输入数据的操作都需要通过流来完成。一般情况下,我们想要向流里输入数据或者从流里读取数据,都需要先打开流,然后操作。
2.1.2 标准流
为什么我们在编写程序输入输出数据时,没有手动写出打开流的代码呢?
那是因为在C语言程序在启动的时候,默认打开了3个流:
- stdin ------ 标准输入流,在大多数情况下从键盘输入,scanf就是向流中输入数据。
- stdout ------ 标准输出流,大多数情况下输出到显示屏界面,printf就是在流中读取数据。
- stderr ------ 标准错误流,大多数情况下输出到显示屏界面。
这三个流的类型是 FILE* ,通常称为文件指针 ,C语言就是通过文件指针对文件进行操作的。
2.2 文件指针
当我们使用文件时,每个文件都会在内存中开辟一块相应的文件信息区 ,所有对文件操作所需要的信息会自动填充到这个文件信息区内,并用一个结构体变量来保存。同时系统将这个结构体重命名为 FILE。
以下是VS2013编译环境提供的 stdio.h 头文件中对于文件类型申明:
cpp
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;//重命名为FILE
不同的编译环境对于文件类型的申明有可能不同,但大致上是相同的。
每当我们打开一个文件时,系统会自动创建一个 FILE 结构的变量并自动填充相关的信息,一般情况下,对于文件的操作都是通过创建一个 FILE* 的指针来维护这个结构体变量。
创建一个文件指针:
cpp
FILE* pf;//创建一个文件指针变量pf
pf 实际上指向的是一个文件的信息区(一个结构体变量),通过文件信息区就可以访问这个文件,也就是说,通过文件指针能够间接的找到与它相关联的文件。

2.3 文件的打开与关闭
我们在读写文件之前需要先打开文件,在使用后关闭文件。
在打开一个文件时,相应的函数会返回一个文件指针(FILE*)指向该文件。
ANSI C 规定使用 fopen 来打开文件,fclose 来关闭文件。
cpp
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen(const char*_FileName,const char*_Mode);
//相应操作
//关闭文件
fclose(pf);
return 0;
}
在这其中,FileName代表文件名,Mode代表文件的打开模式:

cpp
FILE* pf = fopen("text.text","w");
//以读的形式打开名为"text.text"的文件
创建的文件会被放在程序所在的目录下。
在VS环境下,可以点击这个按钮来查看文件:


三、文件的顺序读写

3.1 fgetc
fgetc会从文件中读取一个字符,并且返回该字符的 ASCII码值,然后自动前进一位(文件指针指向文件中下一个字符),如果读取失败或者遇到文件结尾,则返回EOF。现在文件内有以下字符:
cpp#include<stdio.h> int main() { //打开文件 FILE* pf = fopen("text.text","r");//必须以读的方式打开文件!(文件已经存在) //相应操作 char ch = 0; while ((ch = fgetc(pf)) != EOF) printf("%c ", ch); //关闭文件 fclose(pf); return 0; }
运行结果:
3.2 fputc
fputc会将一个字符写入流中并保存在文件里,同时返回输入字符的ASCII码值, 并自动前进一位(文件指针指向文件中下一个将要保存的位置),如果出现错误会返回EOF。初始文件为空:
cpp#include<stdio.h> int main() { //打开文件 FILE* pf = fopen("text.text","w");//以写的方式打开文件! //相应操作 char ch = 0; for (ch = 'a'; ch <= 'z';ch++) fputc(ch, pf); //关闭文件 fclose(pf); return 0; }
运行后的文件:
3.3 fgets
fgets会从文件中读取一串字符,并且储存到字符串数组内,如果读取成功,会返回一个字符数组首地址,如果遇到文件末尾,会返回EOF,如果读取出错,会返回NULL。
- 第一个参数为字符数组的首地址。
- 第二个参数是一次读取的最大字符数,这里需要注意,fgets会从文件中依次读取字符直到读取(num-1)个字符或遇到文件结尾或遇到换行符(换行符也会被读取)。以先发生者为准。
- 第三个参数为文件指针。
- fgets会在读取结束后在字符串的末尾添加一个\0。
现在文件中有以下内容:
cpp#include<stdio.h> int main() { //打开文件 FILE* pf = fopen("text.text","r");//以读的方式打开文件! //相应操作 char arr[20] = { 0 }; fgets(arr, 20, pf); puts(arr); //关闭文件 fclose(pf); return 0; }
运行结果:
3.4 fputs
fputs会将一个字符串保存到指定的文件中,如果成功,会返回一个非负值,如果失败,会返回EOF。初始文件为空:
cpp#include<stdio.h> int main() { //打开文件 FILE* pf = fopen("text.text","w");//以写的方式打开文件 //相应操作 char arr[20] = {"CELIA~haha"}; fputs(arr, pf); //关闭文件 fclose(pf); return 0; }
运行后的文件:
3.5 fprintf
fprintf 与 printf 的参数相似,仅仅是多了一个文件指针,fprintf会将数据以格式化的方式存入文件当中,如果成功,将返回存入字符的总数,如果失败,将返回一个负数。初始文件为空:
cpp#include<stdio.h> typedef struct str { char flag[10]; char name[10]; int age; }stu;//重命名为stu int main() { stu person = { "2024","Celia",19 };//创建一个结构体变量 //打开文件 FILE* pf = fopen("text.text","w");//以写的方式打开文件 //相应操作 fprintf(pf, "%s %s %d", person.flag, person.name, person.age); //关闭文件 fclose(pf); return 0; }
运行后的文件:
3.6 fscanf
fscanf与scanf的参数也很相似,多了一个文件指针,fscanf会将文件中的数据以格式化的方式储存到变量中,如果成功,返回成功读取的项数,如果失败,则返回EOF。现在文件中有以下数据:
cpp#include<stdio.h> typedef struct str { char flag[10]; char name[10]; int age; }stu;//重命名 int main() { stu person = {0};//创建一个结构体变量,初始化为0 //打开文件 FILE* pf = fopen("text.text","r"); //相应操作 fscanf(pf, "%s %s %d", person.flag, person.name, &(person.age));//从文件中读取数据到结构体变量中 printf("%s %s %d", person.flag, person.name, person.age);//打印结构体变量中的值 //关闭文件 fclose(pf); return 0; }
运行结果:
3.7 fwrite
fwrite是以二进制的方式写文件,返回成功写入的元素总数,因此可以接收它的返回值来判断是否完全写入成功。
- 第一个参数为一个指针,指向将要写到文件中的数据
- 第二个参数为基本单元的大小(一个单独数据的大小)
- 第三个参数为基本单元的个数(比如数组的元素个数)
- 第四个参数为文件指针
初始文件为空:
cpp#include<stdio.h> int main() { //打开文件 FILE* pf = fopen("text.text","wb");//以二进制写的方式打开文件 //相应操作 char arr[] = { "Hello,Celia~" }; fwrite(arr, sizeof(char), sizeof(arr), pf); //关闭文件 fclose(pf); return 0; }
运行后的文件:
3.8 fread
fread 是以二进制的方式读文件,将文件中的二进制数据存入指定的字符串数组中,返回成功读取的元素总数。
- 第一个参数为一个指针,指向将要接收数据的数组首地址
- 第二个参数为基本单元的大小(一个单独数据的大小)
- 第三个参数为基本单元的个数(比如数组的元素个数)
- 第四个参数为文件指针
现在文件中有以下内容(二进制):
cpp#include<stdio.h> int main() { //打开文件 FILE* pf = fopen("text.text","rb");//以二进制读的方式打开文件 //相应操作 char arr[100] = {0};//初始化为0 fread(arr, sizeof(char), sizeof(arr), pf);//将文件中的数据存入数组 puts(arr);//打印数组 //关闭文件 fclose(pf); return 0; }
运行结果:
四、文件的随机读写
我们在读写文件时,文件指针默认是指向文件中的第一个位置,这就使得我们只能对文件进行顺序操作,如果想从文件中的其他位置进行读写,就需要用到文件随机读写函数:
- fseek:根据文件中指针的位置和偏移量来定位文件指针
- ftell:返回文件指针相对于起始位置的偏移量
- rewind:让文件指针的位置回到文件的起始位置
4.1 fseek
- 第一个参数为文件指针
- 第二个参数,如果是二进制文件,为偏移的字节数;如果是文本文件,为0或ftell返回的值
- 第三个参数为文件指针的位置,共有三种选择:
fseek会根据文件指针的位置和偏移量来定位文件指针(从左向右偏移量为正,从右向左偏移量为负),如果成功,该函数会返回0,如果失败,则返回非0值。
现在文件有以下内容:
cpp#include<stdio.h> int main() { //打开文件 FILE* pf = fopen("text.text","r");//以读的方式打开文件 //相应操作 char arr[100] = {0}; fseek(pf, 6, SEEK_SET);//从文件开头偏移6个位置 fscanf(pf, "%s", arr); puts(arr); //关闭文件 fclose(pf); return 0; }
运行结果:
4.2 ftell
ftell会返回当前文件指针相对于文件起始位置的 字节数现在文件中有以下内容:
cpp#include<stdio.h> int main() { //打开文件 FILE* pf = fopen("text.text","r");//以读的方式打开文件 //相应操作 char ch = 0; while ((ch = fgetc(pf)) != EOF) printf("%d ", ftell(pf));//打印每一次的相对位置(字节) //关闭文件 fclose(pf); return 0; }
运行结果:
4.3 rewind
rewind会将文件指针的位置 设置为文件的起始位置现在文件中有以下内容:
cpp#include<stdio.h> int main() { //打开文件 FILE* pf = fopen("text.text","r");//以读的方式打开文件 //相应操作 char ch = 0; int flg = 1; while ((ch = fgetc(pf)) != EOF) { if (ftell(pf) == 6 && flg)//当偏移量为6时,将文件指针设置为文件起始位置 { rewind(pf);//设置文件指针的位置为文件起始位置 flg = 0; } printf("%c", ch);//打印每一次读取到的字符 } //关闭文件 fclose(pf); return 0; }
运行结果:
五、文件读取结束的判定
5.1 feof
- feof 函数会在文件读取结束时,判断文件读取结束的原因:++是否为遇到文件结尾而结束++
- 如果是正常遇到文件结尾而结束,返回非0值,否则返回0
- 所以并不能用feof的返回值直接判断文件读取是否结束,feof仅仅能检测文件读取结束的原因
5.2 常见判断文件读取结束的方法
- 判断文本文件是否读取结束:
--> fgetc 判断返回值是否为EOF
--> fgets 判断返回值是否为NULL- 判断二进制文件是否读取结束
--> 利用fread判断返回值是否小于实际要读的个数
六、文件缓冲区

ANSIC采用**"缓冲文件系统" ,** 处理数据文件,系统会自动在内存中为正在使用的文件开辟一块文件缓冲区,内存和硬盘之间的数据交换会先在存入文件缓冲区,待文件缓冲区存满时,再一并进行数据传输。这样可以避免频繁向CPU传输指令,能让整个程序以至整个电脑更有效率的工作。