1.文件指针类型(FILE*)
先用一行代码让你理解文件类型指针(FILE*):
FILE *file = fopen("test.txt","r");
文件类型指针(FILE*)和其他指针类型一样,都是某一类型加上“ * ”后就变成指针类型了,同样的FILE类型在加上“ * ”前也是单独的一个类型,更确切的说,FILE是一个结构体类型。FILE结构体中包含了文件操作所需要的各种信息。那么我们就可以这样讲,一但一个文件类型指针指向一个fopen函数打开的文件,那么操作该文件所需要的各种信息就会储存在FILE结构体类型中。
那么现在再来看这一行代码,fopen函数以某种方式打开了指定文件并被文件指针file所接收,此时我们就拥有了操作该文件的一个“把手”(句柄或变量)file。这就是文件指针类型的意义所在,往后讲解的所有对文件的操作都要利用这个“把手”来完成。
2.fopen函数及6种常用的文件打开模式
fopen函数
fopen 是 C 标准库中的一个函数,用于 打开文件并返回一个文件指针(FILE*),以便后续进行读写操作。它的函数原型如下:
#include <stdio.h>
FILE *fopen(const char *filename, const char *mode);
参数:filename以字符串形式接收文件名,mode是你要以什么样的方式来打开文件。
功能:
- 打开指定路径的文件,并返回一个指向该文件的
FILE*指针。 - 如果打开失败,返回
NULL。 - 文件指针用于后续的文件操作(如
fgetc、fputc等)。
返回值:
成功:返回指向文件的 FILE* 指针。
失败:返回 NULL(如文件不存在、权限不足、路径错误等)。
文件打开模式
| 模式 | 说明 | 文件不存在时的行为 | 文件存在时的行为 |
|---|---|---|---|
"r" | 只读模式 | 返回 NULL(失败) | 从文件开头读取 |
"w" | 只写模式 | 创建新文件 | 清空文件内容(原有数据丢失) |
"a" | 追加模式 | 创建新文件 | 在文件末尾追加数据 |
"r+" | 读写模式 | 返回 NULL(失败) | 从文件开头读写(原有数据保留) |
"w+" | 读写模式 | 创建新文件 | 清空文件内容(原有数据丢失) |
"a+" | 读写模式 | 创建新文件 | 在文件末尾追加数据(读操作从文件开头开始) |
这里的读写模式牵扯到后续fgetc、fputc 等读写函数的使用,比如fgetc是读取一个字符,那么就用"r" "r+" "a+" "w+"等牵扯到可读模式来打开文件。
注意:
w模式和w+模式会导致数据丢失,意思是以该模式打开文件会清除文件内容并从头写入,因此当我们想要续写文件时,我们可以用a或a+模式来追加文件。但是r+模式有点特殊,因为以r+模式打开文件后我们也可以进行写入,它也是从头,但奇怪的是它原有数据不会丢失,即打开文件时不会清空文件内容,似乎和它的模式特点有点互相矛盾。但实际情况是:当使用
"r+"打开文件时,操作系统不会清空文件内容。写入操作会直接覆盖指定位置的数据,但不会影响其他部分。
3.fgetc、fputc、fgets、fputs
当我们打开文件后,所有的写入和读取操作都能用这几个函数来完成。
1)fgetc
int fgetc(FILE *file);
功能:从file指针指向的文件中读取一个字符。
使用演示:
int ch;
while ((ch = fgetc(file)) != EOF) {
printf("%c", ch);
}
返回值:
成功:返回读取的字符(转换为 int,范围 0~255)。
失败或 EOF:返回 EOF(通常是 -1)。
疑问:这是fgetc的基本工作机制,可是这里有一些疑问,为什么演示中的while循环可以正确执行下去而不陷入死循环?fgetc每次只是在读取一个字符,如果演示代码可以正确读出文件所有内容,说明一定存在某些标记可以记录并更新fgetc读取文件的位置,这里我们先按下不表。
2)fputc
int fputc(int char, FILE *file);
功能:将指定的字符写入到file指针指向的文件中。
使用演示:
fputc('A', file);
返回值:
成功:返回写入的字符(0~255)。
失败:返回 EOF(通常是 -1)。
问疑:同样的,如果连续输入十次演示代码,文件中会被写入写入十个 “A” ,似乎这十个fputc函数像是商量好的一样将 “A” 连续挨着写入。和fgetc的疑问一样我们先按下不表。
3)fgets
char *fgets(char *str, int size, FILE *file);
功能:
从指定的file指向的中读取 最多 size - 1 个字符,并存储到 str 指向的缓冲区(就是str指向的存储字符串的空间)。
如果遇到 换行符 \n 或 文件结束(EOF),则停止读取,并在字符串末尾自动添加 '\0'(字符串结束符)。
演示:
char buffer[100]; // 缓冲区大小为 100
fgets(buffer, sizeof(buffer), stdin);
成功:返回 str(指向读取的字符串)。
失败或 EOF:
如果未读取到任何字符(如 size <= 0 或文件为空),返回 NULL。
如果读取到部分数据(如遇到 EOF 但已读取部分字符),仍然返回 str(但下次调用会返回 NULL)。
疑问:
当我们连续的用fgets读取文件中的内容时:
FILE* file = fopen("test.txt", "r"); char buffer[100]; fgets(buffer, sizeof(buffer), file); printf("%s", buffer); fgets(buffer, sizeof(buffer), file); printf("%s", buffer); fgets(buffer, sizeof(buffer), file); printf("%s", buffer);文件中的内容:
输出:
可以看到每次读取的内容都会覆盖原有的缓冲区buffer,且每次fgets的读取都会很默契的读取下一行,而不是重复读取第一行,那么这里的疑问和fgetc以及fgets一样,先按下不表。
4)fputs
int fputs(const char *str, FILE *stream);
功能:
将字符串 str 写入到file指向的文件中。
不包含字符 \0 也不会添加 \n (多次写入的内容在文件中是连续的)
演示:
FILE* file = fopen("test.txt", "w");
const char* buffer1 = "我是第一行";
const char* buffer2 = "我是第二行";
const char* buffer3 = "我是第三行";
fputs(buffer1,file);
fputs(buffer2,file);
fputs(buffer3,file);
文件夹显示;
![]()
可以看到内容是连续的。
返回值:
成功:返回非负整数(通常返回写入的字符数,但标准未明确规定)。
失败:返回 EOF(通常是 -1)。
疑问:
这里的疑问同上,所有的疑问将会在文件定位中解答
4.文件定位
在讲读写函数时,有一个统一的疑问。那就是为什么这些函数能够自动定位文件内容,其实答案很简单,这些读写函数都有一个共同点,它们的参数中都含有文件指针,而文件指针前面讲过,他是指向一个结构体的指针,而这个结构体中包含了文件的各种信息,虽然这个结构体的具体实现由编译器/标准库决定,但在大多数实现中,它包含了一个关键的成员——文件位置指示器。 这里我们不具体展开讲,只需通俗的理解文件指针会记录文本位置信息,即每一次使用读写函数时,它会根据该文本位置信息进行操作。
疑问:当我们对一个文本文件进行文本写入后,再对该文件进行读取,会出现什么情况?
以下代码是对一个文件进行写入操作后再进行读取:
#include <stdio.h> #include<stdlib.h> int main() { //以读写的方式打开文件 FILE* file = fopen("test.txt", "w+"); if (file == NULL) { perror("fopen"); exit(1); } //向文件中写入 hello world\n const char *buffer1 = "hello world\n"; int n = fputs(buffer1,file); if (n == -1) { perror("fputs"); exit(2); } /*int m = fseek(file, 0, SEEK_SET); if (m == -1) { perror("fseek"); exit(3); }*/ //读取文件中的一行 char buffer[100]; char* s = fgets(buffer, sizeof(buffer), file); if (s == NULL) { perror("fgets"); exit(4); } printf("%s", buffer); fclose(file); return 0; }运行效果图:
图中什么也没有打印,表明fgets并没有读取到任何字符。这是为什么呢?
不卖关子,原因就是因为fgetc函数已经更新了file文件指针中的位置信息,再使用fgets函数对文件进行读取时,读取的起始位置就是文本的末尾,导致什么也没有读到。
为了解决类似问题,我们便可以引出文件定位函数,可以帮助我们手动对文件的读写位置进行定位。下面我们利用文件定位函数fseek来解决疑问中的问题。
取消对fseek函数的注释:
#include <stdio.h>
#include<stdlib.h>
int main() {
//以读写的方式打开文件
FILE* file = fopen("test.txt", "w+");
if (file == NULL) {
perror("fopen");
exit(1);
}
//向文件中写入 hello world\n
const char *buffer1 = "hello world\n";
int n = fputs(buffer1,file);
if (n == -1) {
perror("fputs");
exit(2);
}
///////////////////////////////////////////
//取消对定位函数fseek的注释
int m = fseek(file, 0, SEEK_SET);
if (m == -1) {
perror("fseek");
exit(3);
}
////////////////////////////////////////////
//读取文件中的一行
char buffer[100];
char* s = fgets(buffer, sizeof(buffer), file);
if (s == NULL) {
perror("fgets");
exit(4);
}
printf("%s", buffer);
fclose(file);
return 0;
}
运行效果:

这里fseek的作用就是将文件指针重新定位到文件开头,使读取函数能够从文件开头读取。
文件定位常用函数:
1)fseek
int fseek(FILE *stream, long offset, int whence);
功能:移动文件流 stream 的指针到指定位置。
参数:
stream:文件指针(由 fopen() 返回)。
offset:偏移量(字节),可为正(向后)或负(向前)
whence:基准位置:
SEEK_SET:文件开头
SEEK_CUR:当前位置
SEEK_END:文件末尾
返回值:
成功:0
失败:非零(并设置 errno)
代码示例:
FILE *fp = fopen("file.txt", "r");
fseek(fp, 100, SEEK_SET); // 移动到第 100 字节
fseek(fp, -50, SEEK_CUR); // 当前位置向前移动 50 字节
fseek(fp, 0, SEEK_END); // 移动到文件末尾
fclose(fp);
2)ftell
long ftell(FILE *stream);
功能:返回文件流 stream 的当前指针位置(相对于文件开头的偏移量)。
返回值:
成功:当前偏移量(long 类型)
失败:-1L(并设置 errno)
代码示例:
FILE *fp = fopen("file.txt", "r");
fseek(fp, 0, SEEK_END);
long size = ftell(fp); // 获取文件大小
fclose(fp);
printf("File size: %ld bytes\n", size);
3)rewind
void rewind(FILE *stream);
功能:将文件指针重置到文件开头,并清除 EOF 标志和错误标志
等效于:
fseek(stream, 0, SEEK_SET);
clearerr(stream);
代码示例:
FILE *fp = fopen("file.txt", "r");
fseek(fp, 100, SEEK_SET);
rewind(fp); // 回到文件开头
fclose(fp);
6.总结
文件操作是程序与外部数据交互的核心环节,C语言层面上的文件操作,本质还是对系统文件操作函数的封装。学好 C 语言层面的文件操作,是理解 Linux 等操作系统底层机制的重要基石。它不仅能为你后续的系统级编程打下坚实基础,还能培养对程序与硬件资源交互的直觉。
“C 文件操作是打开操作系统黑盒的钥匙——你写的每一行 fread,最终都会变成内核中的一段汇编。”




被折叠的 条评论
为什么被折叠?



