
文章目录
-
- 一、文件的核心概念
-
- [1. 什么是文件](#1. 什么是文件)
- [2. 文件名的组成](#2. 文件名的组成)
- 二、文件的打开与关闭
-
- [1. 文件指针](#1. 文件指针)
- [2. 打开与关闭函数](#2. 打开与关闭函数)
- 三、文件的读写操作
-
- [1 顺序读写:按文件顺序依次操作](#1 顺序读写:按文件顺序依次操作)
-
- [3.1 字符级读写(fputc/fgetc)](#3.1 字符级读写(fputc/fgetc))
- [3.2 格式化读写(fprintf/fscanf)](#3.2 格式化读写(fprintf/fscanf))
- [3.3 二进制读写(fwrite/fread)](#3.3 二进制读写(fwrite/fread))
- [2. 随机读写:自由定位读写位置](#2. 随机读写:自由定位读写位置)
- 三、文本文件与二进制文件的区别
- 四、文件拷贝实战:综合应用
- 五、文件缓冲区机制
- 🚩总结
一、文件的核心概念
1. 什么是文件
文件是存储在外部存储介质(如磁盘、U盘)上的一组相关数据的集合。我们日常的文档编辑、程序运行、数据备份等操作,本质上都是对文件的读写和修改。在C语言中,文件主要分为两类:
- 程序文件:与程序运行相关的文件,包括源程序文件(.c后缀)、编译后的目标文件(Windows下为.obj)、可执行文件(Windows下为.exe)。
- 数据文件:程序运行时需要读取或写入的数据载体,比如存储用户信息的文本文件、保存图片的二进制文件等,这也是本文的重点讨论对象。
2. 文件名的组成
一个完整的文件名是文件的唯一标识,由三部分组成:文件路径 + 文件名 + 文件后缀,例如:
- 绝对路径:
D:\project\student.txt - 相对路径:
../data/config.bin(../表示上一级目录) - 当前路径:
log.txt(直接写文件名,默认在程序运行目录下)
二、文件的打开与关闭
文件操作的第一步是建立程序与文件的连接,这就需要通过"打开文件"操作实现;操作完成后必须"关闭文件",释放系统资源,避免内存泄漏。
1. 文件指针
缓冲文件系统中,系统会为每个打开的文件分配一块内存区域(文件信息区),用于存储文件名、状态、当前读写位置等关键信息。这个信息区被封装为FILE结构体(定义在<stdio.h>中),而我们通过FILE*类型的指针来操作这个结构体,这个指针就是文件指针。
c
#include <stdio.h>
// 定义文件指针变量,用于指向文件信息区
FILE* pf;
2. 打开与关闭函数
C语言标准规定使用fopen函数打开文件,fclose函数关闭文件,函数原型如下:
c
// 打开文件:返回指向文件的指针,失败则返回NULL
FILE* fopen(const char* filename, const char* mode);
// 关闭文件:成功返回0,失败返回EOF(-1)
int fclose(FILE* stream);
关键:文件打开模式
文件打开模式决定了程序对文件的操作权限,不同模式对应不同的功能和行为,下表汇总了常用模式的核心信息:
| 打开模式 | 核心功能 | 适用文件类型 | 若文件不存在 | 注意事项 |
|---|---|---|---|---|
| "r" | 只读 | 文本文件 | 打开失败(返回NULL) | 只能读取已有文件 |
| "w" | 只写 | 文本文件 | 创建新文件 | 会覆盖原有文件内容 |
| "a" | 追加 | 文本文件 | 打开失败 | 仅在文件末尾添加数据 |
| "rb" | 只读 | 二进制文件 | 打开失败 | 用于读取图片、音频等资源 |
| "wb" | 只写 | 二进制文件 | 创建新文件 | 二进制形式存储数据 |
| "r+" | 读写 | 文本文件 | 打开失败 | 可同时读写,不创建新文件 |
| "w+" | 读写 | 文本文件 | 创建新文件 | 覆盖原有内容,支持读写 |
| "a+" | 读写 | 文本文件 | 创建新文件 | 读写均从文件末尾开始 |
实战:文件打开与关闭示例
c
#include <stdio.h>
#include <stdlib.h>
int main() {
// 以只写模式打开当前目录下的data.txt,不存在则创建
FILE* pf = fopen("data.txt", "w");
// 关键:检查文件是否打开成功(路径错误、权限不足等会导致失败)
if (pf == NULL) {
// perror函数会自动输出错误原因
perror("fopen failed");
// 打开失败直接退出程序
return EXIT_FAILURE;
}
// 后续文件操作...
// 关闭文件:必须执行,避免资源泄露
fclose(pf);
// 置空指针:防止野指针操作
pf = NULL;
return 0;
}
三、文件的读写操作
文件读写是文件操作的核心,C语言提供了多种读写函数,适用于不同的数据类型和场景,主要分为顺序读写 和随机读写两类。
1 顺序读写:按文件顺序依次操作
顺序读写是最常用的方式,数据从文件开头到结尾依次读取或写入,常用函数如下:
| 功能类型 | 函数名 | 函数原型 | 适用场景 |
|---|---|---|---|
| 字符输入 | fgetc | int fgetc(FILE* stream) | 读取单个字符 |
| 字符输出 | fputc | int fputc(int c, FILE* stream) | 写入单个字符 |
| 行输入 | fgets | char* fgets(char* str, int num, FILE* stream) | 读取一行字符串 |
| 行输出 | fputs | int fputs(const char* str, FILE* stream) | 写入一行字符串 |
| 格式化输入 | fscanf | int fscanf(FILE* stream, const char* format, ...) | 按格式读取数据 |
| 格式化输出 | fprintf | int fprintf(FILE* stream, const char* format, ...) | 按格式写入数据 |
| 二进制输入 | fread | size_t fread(void* ptr, size_t size, size_t count, FILE* stream) | 读取二进制数据 |
| 二进制输出 | fwrite | size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream) | 写入二进制数据 |
3.1 字符级读写(fputc/fgetc)
需求:向文件写入26个大写字母,再读取并打印。
c
#include <stdio.h>
int main() {
FILE* pf = fopen("letter.txt", "w");
if (pf == NULL) {
perror("fopen write failed");
return 1;
}
// 写入A-Z
for (char c = 'A'; c <= 'Z'; c++) {
// 逐个字符写入文件
fputc(c, pf);
}
fclose(pf);
pf = NULL;
// 读取并打印
pf = fopen("letter.txt", "r");
if (pf == NULL) {
perror("fopen read failed");
return 1;
}
int ch;
// EOF是文件结束标志(值为-1)
while ((ch = fgetc(pf)) != EOF) {
printf("%c ", ch);
}
fclose(pf);
pf = NULL;
return 0;
}
3.2 格式化读写(fprintf/fscanf)
需求:将用户信息(学号、成绩)写入文件,再读取并显示。
c
#include <stdio.h>
typedef struct {
int id;
float score;
} Student;
int main() {
Student stu = {101, 92.5f};
FILE* pf = fopen("student.txt", "w");
if (pf == NULL) {
perror("fopen failed");
return 1;
}
// 格式化写入结构体数据
fprintf(pf, "学号:%d 成绩:%.1f", stu.id, stu.score);
fclose(pf);
pf = NULL;
// 读取数据
Student tmp;
pf = fopen("student.txt", "r");
if (pf == NULL) {
perror("fopen failed");
return 1;
}
// 按对应格式读取
fscanf(pf, "学号:%d 成绩:%.1f", &tmp.id, &tmp.score);
printf("读取到的信息:学号=%d,成绩=%.1f\n", tmp.id, tmp.score);
fclose(pf);
pf = NULL;
return 0;
}
3.3 二进制读写(fwrite/fread)
二进制读写适用于存储结构体、图片、音频等数据,直接以内存中的二进制形式存储,效率更高。
需求:将结构体数据以二进制形式存储,再读取恢复。
c
#include <stdio.h>
typedef struct {
int id;
char name[20];
float score;
} Student;
int main() {
Student stu = {102, "Zhang San", 88.0f};
FILE* pf = fopen("student.bin", "wb");
if (pf == NULL) {
perror("fopen failed");
return 1;
}
// 二进制写入:1个大小为Student的结构体数据
fwrite(&stu, sizeof(Student), 1, pf);
fclose(pf);
pf = NULL;
// 二进制读取
Student tmp = {0};
pf = fopen("student.bin", "rb");
if (pf == NULL) {
perror("fopen failed");
return 1;
}
fread(&tmp, sizeof(Student), 1, pf);
printf("ID:%d,姓名:%s,成绩:%.1f\n", tmp.id, tmp.name, tmp.score);
fclose(pf);
pf = NULL;
return 0;
}
注意:二进制文件用文本编辑器打开会显示乱码,这是正常现象,需通过程序按二进制方式读取才能恢复数据。
2. 随机读写:自由定位读写位置
有时我们需要直接操作文件中间的数据(如修改文件第10个字符、读取第5条记录),这就需要随机读写功能。C语言提供了三个核心函数实现位置定位:
| 函数名 | 函数原型 | 功能描述 |
|---|---|---|
| fseek | int fseek(FILE* stream, long offset, int origin) | 定位文件指针位置 |
| ftell | long ftell(FILE* stream) | 获取文件指针当前偏移量 |
| rewind | void rewind(FILE* stream) | 将文件指针重置到文件开头 |
关键参数说明
offset:偏移量(正数表示向后偏移,负数表示向前偏移)origin:基准位置,有三个可选值:SEEK_SET:以文件开头为基准(值为0)SEEK_CUR:以当前指针位置为基准(值为1)SEEK_END:以文件末尾为基准(值为2)
随机读写示例
需求:修改文件中指定位置的字符,读取文件中间数据。
c
#include <stdio.h>
int main() {
// 先写入初始数据:abcdefghi
FILE* pf = fopen("test.txt", "w");
if (pf == NULL) {
perror("fopen failed");
return 1;
}
fputs("abcdefghi", pf);
fclose(pf);
pf = NULL;
// 随机修改:将第6个字符('f')改为'X'
pf = fopen("test.txt", "r+");
if (pf == NULL) {
perror("fopen failed");
return 1;
}
// 基准:文件开头,偏移5个位置(指向第6个字符)
fseek(pf, 5, SEEK_SET);
// 写入修改后的字符
fputc('X', pf);
// 定位到文件开头,读取修改后的数据
rewind(pf);
int ch;
printf("修改后的数据:");
while ((ch = fgetc(pf)) != EOF) {
printf("%c", ch);
}
printf("\n");
// 获取当前指针偏移量(此时在文件末尾)
long pos = ftell(pf);
printf("文件长度:%ld 字节\n", pos);
fclose(pf);
pf = NULL;
return 0;
}
输出结果:
修改后的数据:abcdeXghi
文件长度:9 字节
三、文本文件与二进制文件的区别
数据在内存中始终以二进制形式存储,而文件的存储形式由我们的操作决定:
- 文本文件:数据存储前会转换为ASCII码形式,例如整数10000会转换为字符'1'、'0'、'0'、'0'、'0'存储,可被文本编辑器直接读取。
- 二进制文件 :数据直接以内存中的二进制形式存储,例如整数10000在32位系统中存储为
0x00002710,文本编辑器打开会显示乱码,但程序读取效率更高。
关键区别对比
| 特性 | 文本文件 | 二进制文件 |
|---|---|---|
| 存储形式 | ASCII码 | 二进制数据 |
| 可读性 | 可直接用记事本打开 | 需专用程序读取 |
| 存储效率 | 较低(如10000占5字节) | 较高(如10000占4字节) |
| 适用场景 | 配置文件、日志文件 | 结构体数据、图片、音频 |
四、文件拷贝实战:综合应用
文件拷贝是文件操作的经典场景,核心逻辑是"读取源文件数据 → 写入目标文件",下面实现一个通用的文件拷贝工具:
c
#include <stdio.h>
#include <stdlib.h>
// 拷贝函数:src_path-源文件路径,dest_path-目标文件路径
int copy_file(const char* src_path, const char* dest_path) {
// 打开源文件(只读)和目标文件(只写)
FILE* src = fopen(src_path, "rb");
if (src == NULL) {
perror("open source file failed");
return 1;
}
FILE* dest = fopen(dest_path, "wb");
if (dest == NULL) {
perror("open destination file failed");
// 关闭已打开的源文件,避免资源泄露
fclose(src);
return 1;
}
// 用缓冲区提高拷贝效率(一次读取1024字节)
char buf[1024];
size_t read_len;
// 循环读取源文件,直到文件结束
while ((read_len = fread(buf, 1, sizeof(buf), src)) > 0) {
// 将读取到的数据写入目标文件
fwrite(buf, 1, read_len, dest);
}
// 关闭文件
fclose(src);
fclose(dest);
printf("文件拷贝成功!\n");
return 0;
}
int main() {
// 拷贝文本文件或二进制文件均可
copy_file("source.jpg", "target.jpg");
return 0;
}
五、文件缓冲区机制
C语言采用"缓冲文件系统",系统会为每个打开的文件在内存中分配一块缓冲区:
- 写入数据时:数据先存入缓冲区,缓冲区满后才会一次性写入磁盘;
- 读取数据时:先从磁盘读取数据填充缓冲区,程序再从缓冲区获取数据。
缓冲区的刷新机制
- 缓冲区满时自动刷新;
- 调用
fclose关闭文件时,会自动刷新缓冲区; - 调用
fflush(FILE* stream)函数强制刷新缓冲区(注意:高版本VS中fflush仅对输出流有效); - 程序正常结束时,所有打开的文件缓冲区会自动刷新。
缓冲区演示
c
#include <stdio.h>
#include <windows.h>
int main() {
FILE* pf = fopen("buffer.txt", "w");
if (pf == NULL) {
perror("fopen failed");
return 1;
}
// 写入数据(此时数据在缓冲区,文件中无内容)
fputs("hello buffer", pf);
printf("数据已写入缓冲区,睡眠10秒...\n");
// 睡眠10秒,期间打开buffer.txt会发现无内容
Sleep(10000);
// 强制刷新缓冲区,数据写入磁盘
fflush(pf);
printf("缓冲区已刷新,再睡眠10秒...\n");
// 此时打开buffer.txt可看到内容
Sleep(10000);
fclose(pf);
pf = NULL;
return 0;
}

🚩总结
