一、文件的概念
在计算机中,文件 是存储在外部存储介质(硬盘、U 盘等)上的一组相关数据的集合,是操作系统管理数据的基本单位。
从编程视角看,C 语言中的文件分为两类:
-
文本文件:以字符编码(如 ASCII)存储,可直接用文本编辑器打开查看(如
.txt、.c文件),数据以 “字符” 为最小单位。 -
二进制文件:以二进制形式存储(如
.exe、.jpg、.dat文件),无法直接用文本编辑器读懂,数据以 “字节” 为最小单位,存储效率更高、格式更紧凑。
文件操作的本质是程序与外部存储介质之间的数据交互,C 语言通过标准库<stdio.h>提供的文件操作函数实现这一过程。
二、文件路径的概念
要操作文件,首先要明确文件的路径—— 即文件在存储介质中的位置,分为两种形式:
1. 绝对路径
从根目录开始的完整路径,具有唯一性。
-
Windows 示例:
D:\code\test.txt(注意:C 语言中需转义反斜杠,写为D:\\code\\test.txt) -
Linux/Mac 示例:
/home/user/code/test.txt
2. 相对路径
相对于当前程序运行目录的路径,更灵活,适合项目内文件操作。
-
./test.txt:当前目录下的test.txt(./表示当前目录,可省略) -
../data/test.txt:上级目录下data文件夹中的test.txt(../表示上级目录)
核心注意点
-
路径分隔符:Windows 用
\(C 代码中需转义为\\),Linux/Mac 用/; -
相对路径的 “当前目录”:是程序运行的目录,而非代码文件所在目录;
-
路径错误会导致文件打开失败,需做好错误处理。
三、打开关闭文件
C 语言通过FILE结构体(文件指针)管理文件,核心操作是 “打开 - 操作 - 关闭”,缺一不可(关闭文件可释放资源、确保数据写入磁盘)。
打开文件:fopen() 关闭文件:fclose()
#include <stdio.h>
#include <stdlib.h>
// 包含exit()
int main() {
// 函数原型:FILE *fopen(const char *path, const char *mode);
// 参数1:文件路径;参数2:打开模式;返回值:成功返回文件指针,失败返回NULL
FILE *fp = fopen("test.txt", "r"); // 以只读模式打开当前目录的test.txt
// 必须检查打开是否成功
if (fp == NULL) {
perror("文件打开失败"); // perror:打印系统错误信息
exit(EXIT_FAILURE); // 退出程序,释放资源
}
printf("文件打开成功\n");
// 2. 关闭文件:fclose()
// 函数原型:int fclose(FILE *stream);
// 返回值:成功返回0,失败返回EOF(-1)
if (fclose(fp) != 0) {
perror("文件关闭失败");
exit(EXIT_FAILURE);
}
printf("文件关闭成功\n");
return 0;
}
关键说明
-
FILE:是<stdio.h>定义的结构体,存储文件的状态信息(如文件位置、缓冲区等),编程中只需用指针操作,无需关心内部结构; -
fopen失败原因:文件不存在、权限不足、路径错误等; -
fclose必须调用:即使程序结束前系统会自动关闭,但主动关闭是良好编程习惯,避免数据丢失(缓冲区数据未刷入磁盘)。
四、读数据
文件读取的核心是 “从文件中读取数据到程序内存”,C 语言提供了 3 类常用函数,适配不同读取场景。
1、fgetc:按字符读取
fgetc从文件中读取单个字符,适合逐字符处理文本文件(如统计字符数、查找字符)。
函数原型:
int fgetc(FILE *stream);
返回值:成功返回读取的字符(ASCII 值),到达文件末尾 / 失败返回EOF(-1)。
代码示例:读取整个文本文件(逐字符)
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
perror("fopen failed");
exit(EXIT_FAILURE);
}
int ch; // 用int接收(兼容EOF),不能用char(EOF是-1,char可能无法存储)
// 循环读取,直到文件末尾
while ((ch = fgetc(fp)) != EOF) {
putchar(ch); // 输出读取的字符
}
printf("\n");
fclose(fp);
return 0;
}
核心注意
-
必须用
int接收fgetc的返回值:因为EOF是-1,若用char(8 位),当读取到 ASCII 为0xFF的字符时,会被误判为EOF; -
fgetc读取后,文件指针会自动后移 1 个字符,直到文件末尾。
2、fgets:按行读取
fgets从文件中读取一行字符,适合处理文本文件的行数据(如配置文件、日志文件)。
函数原型:
char *fgets(char *str, int n, FILE *stream);
-
参数 1:存储读取数据的字符数组;
-
参数 2:读取的最大字符数(
n-1个有效字符,最后自动加\0); -
参数 3:文件指针;
-
返回值:成功返回
str地址,到达文件末尾 / 失败返回NULL。
代码示例:逐行读取文本文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
perror("fopen failed");
exit(EXIT_FAILURE);
}
char buffer[1024]; // 定义缓冲区,存储每行数据
// 循环逐行读取
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
// 去除fgets读取的换行符(可选)
buffer[strcspn(buffer, "\n")] = '\0';
printf("读取到行:%s\n", buffer);
}
fclose(fp);
return 0;
}
核心注意
-
fgets会读取换行符\n(如果行长度未超过n-1),需手动去除; -
若一行字符数超过
n-1,fgets会读取前n-1个字符,剩余部分需下次读取; -
仅适用于文本文件,二进制文件无 “行” 的概念。
3、fread:按块读取(二进制 / 文本)
fread从文件中读取指定大小的块数据,适合二进制文件(如结构体、数组),也可用于文本文件,效率最高。
函数原型:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
-
参数 1:存储数据的内存地址(如数组、结构体指针);
-
参数 2:单个数据块的大小(字节);
-
参数 3:要读取的块数;
-
参数 4:文件指针;
-
返回值:成功读取的块数(若返回值 < nmemb,可能是文件末尾或出错)。
代码示例 1:读取二进制文件(结构体数据)
#include <stdio.h>
#include <stdlib.h>
// 定义学生结构体
typedef struct {
char name[20];
int age;
float score;
} Student;
int main() {
FILE *fp = fopen("students.dat", "rb"); // 二进制只读模式:rb
if (fp == NULL) {
perror("fopen failed");
exit(EXIT_FAILURE);
}
Student s;
// 读取1个Student大小的块(sizeof(Student)是单个块大小,1是块数)
while (fread(&s, sizeof(Student), 1, fp) == 1) {
printf("姓名:%s,年龄:%d,分数:%.1f\n", s.name, s.age, s.score);
}
fclose(fp);
return 0;
}
代码示例 2:读取文本文件(按块)
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
perror("fopen failed");
exit(EXIT_FAILURE);
}
char buffer[1024];
// 每次读取1024字节(1个块,大小1024)
size_t read_size = fread(buffer, 1, sizeof(buffer), fp);
if (read_size > 0) {
buffer[read_size] = '\0'; // 手动加结束符(文本文件)
printf("读取到%d字节数据:\n%s\n", read_size, buffer);
}
fclose(fp);
return 0;
}
核心注意
-
二进制文件读取需用
rb模式(r是文本模式,Windows 下会转换换行符\n为\r\n,导致二进制数据错乱); -
fread返回的是 “块数”,而非字节数:总读取字节数 = 返回值 × size; -
适合批量读取数据,效率远高于逐字符 / 逐行读取。
五、写数据
文件写入的核心是 “将程序内存中的数据写入文件”,对应读取函数,C 语言提供了 3 类写入函数。
1、fputc:按字符写入
fputc向文件中写入单个字符,适合逐字符生成文本文件。
函数原型:
int fputc(int c, FILE *stream);
-
参数 1:要写入的字符(ASCII 值);
-
参数 2:文件指针;
-
返回值:成功返回写入的字符,失败返回
EOF。
代码示例:写入字符串到文件(逐字符)
#include <stdio.h>
#include <stdlib.h>
int main() {
// 以写入模式打开(不存在则创建,存在则清空)
FILE *fp = fopen("output.txt", "w");
if (fp == NULL) {
perror("fopen failed");
exit(EXIT_FAILURE);
}
char *str = "Hello, File Operation!";
int i = 0;
// 逐字符写入,直到字符串结束符
while (str[i] != '\0') {
fputc(str[i], fp);
i++;
}
fclose(fp);
printf("字符写入完成\n");
return 0;
}
2、fputs:按行写入
fputs向文件中写入一行字符串,适合批量写入文本行。
函数原型:
int fputs(const char *str, FILE *stream);
-
参数 1:要写入的字符串(不含
\0,可手动加\n换行); -
参数 2:文件指针;
-
返回值:成功返回非负数,失败返回
EOF。
代码示例:逐行写入多行文本
#include <stdio.h>
#include <stdlib.h>
int main() {
// 以追加模式打开(不存在则创建,存在则在末尾追加)
FILE *fp = fopen("output.txt", "a");
if (fp == NULL) {
perror("fopen failed");
exit(EXIT_FAILURE);
}
char *lines[] = {"第一行数据\n", "第二行数据\n", "第三行数据\n"};
int n = sizeof(lines) / sizeof(lines[0]);
for (int i = 0; i < n; i++) {
fputs(lines[i], fp);
}
fclose(fp);
printf("行写入完成\n");
return 0;
}
核心注意
-
fputs不会自动添加换行符,需手动写入\n; -
追加模式
a:写入的数据会追加到文件末尾,不会清空原有内容。
3、fwrite:按块写入(二进制 / 文本)
fwrite向文件中写入指定大小的块数据,适合二进制文件(如结构体、数组),效率最高。
函数原型
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
-
参数 1:要写入的数据地址;
-
参数 2:单个数据块的大小(字节);
-
参数 3:要写入的块数;
-
参数 4:文件指针;
-
返回值:成功写入的块数(若返回值 < nmemb,说明写入失败)。
代码示例:写入结构体数据到二进制文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char name[20];
int age;
float score;
} Student;
int main() {
// 二进制写入模式:wb(不存在则创建,存在则清空)
FILE *fp = fopen("students.dat", "wb");
if (fp == NULL) {
perror("fopen failed");
exit(EXIT_FAILURE);
}
// 定义要写入的学生数据
Student students[] = {
{"张三", 18, 90.5},
{"李四", 19, 88.0},
{"王五", 20, 95.5}
};
int n = sizeof(students) / sizeof(students[0]);
// 写入n个Student块
size_t write_num = fwrite(students, sizeof(Student), n, fp);
if (write_num != n) {
perror("fwrite failed");
fclose(fp);
exit(EXIT_FAILURE);
}
fclose(fp);
printf("成功写入%d个学生数据\n", write_num);
return 0;
}
核心注意
-
二进制文件写入需用
wb模式(避免文本模式的换行符转换); -
fwrite可写入任意类型数据(数组、结构体、基本类型),是二进制文件操作的首选; -
写入后建议调用
fflush(fp)强制刷新缓冲区(确保数据立即写入磁盘,避免程序崩溃导致数据丢失)。
六、文件读写模式
fopen的第二个参数(模式)决定了文件的操作方式,是文件操作的核心,需根据场景精准选择。
常用模式对照表
| 模式 | 含义 | 适用文件类型 | 核心特点 |
|---|---|---|---|
r | 只读 | 文本文件 | 文件必须存在,否则打开失败 |
w | 只写 | 文本文件 | 不存在则创建,存在则清空内容 |
a | 追加 | 文本文件 | 不存在则创建,写入内容追加到末尾 |
r+ | 读写 | 文本文件 | 文件必须存在,可读写,从开头开始 |
w+ | 读写 | 文本文件 | 不存在则创建,存在则清空,可读写 |
a+ | 读写 | 文本文件 | 不存在则创建,写入追加到末尾,可读 |
rb | 只读 | 二进制文件 | 同r,但不转换换行符 |
wb | 只写 | 二进制文件 | 同w,但不转换换行符 |
ab | 追加 | 二进制文件 | 同a,但不转换换行符 |
rb+/r+b | 读写 | 二进制文件 | 同r+,二进制模式 |
wb+/w+b | 读写 | 二进制文件 | 同w+,二进制模式 |
ab+/a+b | 读写 | 二进制文件 | 同a+,二进制模式 |
关键说明
-
文本模式 vs 二进制模式:
-
文本模式(无
b):Windows 下会将\n转换为\r\n(换行符),Linux/Mac 无转换; -
二进制模式(有
b):不做任何转换,原样读写,适合图片、视频、结构体等二进制数据。
-
-
追加模式
a的特殊性:-
无论文件指针位置如何,写入的数据始终追加到文件末尾;
-
a+模式下,读取可从任意位置,但写入仍追加到末尾。
-
-
读写模式(如
r+):-
需注意文件指针位置,避免读写覆盖;
-
读写切换时,需用
fflush、fseek、fsetpos或rewind刷新 / 重置指针。
-
代码示例:文件指针移动(fseek)+ 读写模式
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp = fopen("test.txt", "w+"); // 读写模式,创建并清空文件
if (fp == NULL) {
perror("fopen failed");
exit(EXIT_FAILURE);
}
// 写入数据
fputs("Hello, C File!", fp);
// 移动文件指针到开头(读写切换需重置指针)
fseek(fp, 0, SEEK_SET); // SEEK_SET:文件开头;SEEK_CUR:当前位置;SEEK_END:文件末尾
// 读取数据
char buffer[1024];
fgets(buffer, sizeof(buffer), fp);
printf("读取到数据:%s\n", buffer);
fclose(fp);
return 0;
}
总结
本文系统讲解了 C 语言文件操作的全流程,核心要点如下:
-
基础核心:文件操作遵循 “打开 - 操作 - 关闭” 流程,
fopen返回的FILE*是操作入口,必须检查打开 / 关闭是否成功; -
读写选择:文本文件优先用
fgetc/fputs(逐字符 / 行),二进制文件优先用fread/fwrite(按块,效率最高); -
模式关键:区分文本模式(无
b)和二进制模式(有b),w清空、a追加、r只读是最常用的基础模式。
以上便是我对于C语言中的文件管理的见解,欢迎各位一起探讨。

2万+

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



