
🏠个人主页:黎雁
🎬作者简介:C/C++/JAVA后端开发学习者
❄️个人专栏:C语言、数据结构(C语言)、EasyX、游戏、规划
✨ 从来绝巘须孤往,万里同尘即玉京

文章目录
- [C 语言文件操作进阶:格式化读写 + 二进制读写 + 随机读写进阶全解 🚀](#C 语言文件操作进阶:格式化读写 + 二进制读写 + 随机读写进阶全解 🚀)
-
- [前景回顾:文件基础速记 📝](#前景回顾:文件基础速记 📝)
- [一、格式化读写 ------ 处理复杂数据的利器 📊](#一、格式化读写 —— 处理复杂数据的利器 📊)
-
- [1. 格式化输出:fprintf函数](#1. 格式化输出:fprintf函数)
- [2. 格式化输入:fscanf函数](#2. 格式化输入:fscanf函数)
- [3. 补充:sprintf/sscanf(字符串与格式化数据互转)](#3. 补充:sprintf/sscanf(字符串与格式化数据互转))
- [二、二进制读写 ------ 高效存储数据的方式 ⚡](#二、二进制读写 —— 高效存储数据的方式 ⚡)
-
- [1. 二进制输出:fwrite函数](#1. 二进制输出:fwrite函数)
- [2. 二进制输入:fread函数](#2. 二进制输入:fread函数)
- [3. 二进制读写结构体(实战常用)](#3. 二进制读写结构体(实战常用))
- [三、随机读写 ------ 精准定位文件内容 🎯](#三、随机读写 —— 精准定位文件内容 🎯)
-
- [1. 调整文件指针:fseek函数](#1. 调整文件指针:fseek函数)
- [2. 获取偏移量:ftell函数](#2. 获取偏移量:ftell函数)
- [3. 重置文件指针:rewind函数](#3. 重置文件指针:rewind函数)
- [写在最后 📝](#写在最后 📝)
C 语言文件操作进阶:格式化读写 + 二进制读写 + 随机读写进阶全解 🚀
在上一篇中,我们掌握了文件操作的基础------文件的打开关闭、字符和字符串的顺序读写,这一篇我们进阶学习更实用的操作:格式化读写(轻松处理结构体等复杂数据)、二进制读写(高效存储数据)、随机读写(精准定位文件内容),让文件操作能力再上一个台阶!
前景回顾:文件基础速记 📝
回顾上一篇核心知识点,是进阶学习的前提:
- 文件操作核心流程:打开(fopen)→ 读写 → 关闭(fclose),必须检查fopen返回值,关闭后指针置空。
- fputc/fgetc处理单个字符,fputs/fgets处理字符串,支持文件流和标准流(stdin/stdout)。
- 文本文件存储ASCII码(易读),二进制文件直接存储二进制数据(高效)。
一、格式化读写 ------ 处理复杂数据的利器 📊
字符/字符串读写只能处理简单文本,而实际开发中常需要读写数字、结构体等复杂数据,这就需要fscanf(格式化读)和fprintf(格式化写),用法和scanf/printf几乎一致,只是多了文件指针参数。
1. 格式化输出:fprintf函数
函数原型:int fprintf(FILE* stream, const char* format, ...);
stream:文件指针(写入文件传文件指针,写入屏幕传stdout);format:格式控制符(和printf完全一致,如%d、%s、%lf等);- 剩余参数:要写入的变量/数据。
示例:向文件写入结构体数据
c
#include <stdio.h>
// 定义结构体
struct Student
{
char name[20]; // 姓名
int age; // 年龄
double score; // 分数
};
int main()
{
struct Student s = {"张三", 18, 95.5};
// 打开文件,以只写模式(文本文件)
FILE* pf = fopen("student.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
// 格式化写入结构体数据
fprintf(pf, "姓名:%s 年龄:%d 分数:%.1lf\n", s.name, s.age, s.score);
// 也可以输出到屏幕(替换pf为stdout)
fprintf(stdout, "姓名:%s 年龄:%d 分数:%.1lf\n", s.name, s.age, s.score);
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
效果:student.txt中写入姓名:张三 年龄:18 分数:95.5,同时屏幕也会输出相同内容。
2. 格式化输入:fscanf函数
函数原型:int fscanf(FILE* stream, const char* format, ...);
stream:文件指针(读取文件传文件指针,读取键盘传stdin);format:格式控制符(和scanf完全一致);- 剩余参数:存储数据的变量地址(注意取地址符&)。
示例:从文件读取结构体数据
c
#include <stdio.h>
struct Student
{
char name[20];
int age;
double score;
};
int main()
{
struct Student s = {0}; // 初始化结构体
// 打开文件,以只读模式(文本文件)
FILE* pf = fopen("student.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
// 格式化读取文件中的数据到结构体
fscanf(pf, "姓名:%s 年龄:%d 分数:%lf", s.name, &(s.age), &(s.score));
// 打印读取结果
printf("读取到的数据:\n");
printf("姓名:%s 年龄:%d 分数:%.1lf\n", s.name, s.age, s.score);
fclose(pf);
pf = NULL;
return 0;
}
输出:读取到的数据:姓名:张三 年龄:18 分数:95.5。
3. 补充:sprintf/sscanf(字符串与格式化数据互转)
这两个函数不是文件操作函数,但常和文件读写配合使用,核心作用是"格式化数据↔字符串"互转:
sprintf:将格式化数据转为字符串;sscanf:从字符串中读取格式化数据。
示例:结构体与字符串互转
c
#include <stdio.h>
struct S
{
char arr[20];
int num;
double pai;
};
int main()
{
struct S s = {"hello", 100, 3.14159};
char buf[100] = {0}; // 存储转换后的字符串
// 1. 结构体数据 → 字符串
sprintf(buf, "%s %d %.3lf", s.arr, s.num, s.pai);
printf("转换后的字符串:%s\n", buf); // 输出:hello 100 3.142
// 2. 字符串 → 结构体数据
struct S t = {0};
sscanf(buf, "%s %d %lf", t.arr, &(t.num), &(t.pai));
printf("转换后的结构体:%s %d %.3lf\n", t.arr, t.num, t.pai); // 输出:hello 100 3.142
return 0;
}
二、二进制读写 ------ 高效存储数据的方式 ⚡
格式化读写是"文本形式"存储(易读但占空间、效率低),而二进制读写直接存储内存中的二进制数据,效率更高、占用空间更小,核心函数是fwrite(二进制写)和fread(二进制读)。
1. 二进制输出:fwrite函数
函数原型:size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
ptr:指向要写入数据的指针(比如数组/结构体地址);size:单个元素的大小(字节),常用sizeof计算;count:要写入的元素个数;stream:文件指针(需以二进制模式打开,如"wb");- 返回值:成功写入的元素个数(正常等于count)。
示例:向文件写入二进制数据(数组)
c
#include <stdio.h>
int main()
{
int arr[] = {1, 2, 3, 4, 5}; // 要写入的数组
int len = sizeof(arr) / sizeof(arr[0]); // 元素个数
// 打开文件,以二进制只写模式
FILE* pf = fopen("data.bin", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
// 二进制写入数组(5个int元素,每个4字节)
size_t ret = fwrite(arr, sizeof(int), len, pf);
if (ret == len)
{
printf("二进制写入成功,共写入%d个元素\n", len);
}
else
{
printf("写入失败,仅写入%d个元素\n", ret);
}
fclose(pf);
pf = NULL;
return 0;
}
注意:用普通文本编辑器打开data.bin会显示乱码(二进制数据),需用二进制编辑器查看。
2. 二进制输入:fread函数
函数原型:size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
- 参数含义和
fwrite完全一致; stream:文件指针(需以二进制只读模式打开,如"rb");- 返回值:成功读取的元素个数(正常等于count,文件末尾/出错时小于count)。
示例:从文件读取二进制数据(数组)
c
#include <stdio.h>
int main()
{
int arr[10] = {0}; // 存储读取的数据
int read_count = 5; // 要读取的元素个数
// 打开文件,以二进制只读模式
FILE* pf = fopen("data.bin", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
// 二进制读取数据到数组
size_t ret = fread(arr, sizeof(int), read_count, pf);
if (ret == read_count)
{
printf("读取成功,共读取%d个元素:", ret);
for (int i = 0; i < ret; i++)
{
printf("%d ", arr[i]); // 输出:1 2 3 4 5
}
}
else
{
printf("读取失败,仅读取%d个元素\n", ret);
}
fclose(pf);
pf = NULL;
return 0;
}
3. 二进制读写结构体(实战常用)
c
#include <stdio.h>
struct Student
{
char name[20];
int age;
double score;
};
int main()
{
// 1. 写入结构体二进制数据
struct Student s = {"李四", 19, 98.0};
FILE* pf = fopen("stu.bin", "wb");
if (pf == NULL) { perror("fopen"); return 1; }
fwrite(&s, sizeof(struct Student), 1, pf);
fclose(pf);
pf = NULL;
// 2. 读取结构体二进制数据
struct Student t = {0};
pf = fopen("stu.bin", "rb");
if (pf == NULL) { perror("fopen"); return 1; }
fread(&t, sizeof(struct Student), 1, pf);
printf("姓名:%s 年龄:%d 分数:%.1lf\n", t.name, t.age, t.score); // 输出:李四 19 98.0
fclose(pf);
pf = NULL;
return 0;
}
三、随机读写 ------ 精准定位文件内容 🎯
顺序读写只能从文件开头到末尾依次操作,而随机读写可以通过函数调整文件指针位置,直接读写指定位置的内容,核心函数是fseek、ftell、rewind。
1. 调整文件指针:fseek函数
函数原型:int fseek(FILE* stream, long int offset, int origin);
stream:文件指针;offset:偏移量(正数向后偏移,负数向前偏移);origin:起始位置(3个可选值):SEEK_SET:文件开头位置(常用);SEEK_CUR:文件指针当前位置;SEEK_END:文件末尾位置;
- 返回值:调整成功返回0,失败返回非0。
示例:定位读取文件内容
c
#include <stdio.h>
int main()
{
// 假设data.txt内容为:abcdefghijk
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
// 1. 从文件开头偏移3个字符(定位到'd')
fseek(pf, 3, SEEK_SET);
int ch = fgetc(pf);
printf("读取到的字符:%c\n", ch); // 输出:d
// 2. 从当前位置(d)向后偏移2个字符(定位到'f')
fseek(pf, 2, SEEK_CUR);
ch = fgetc(pf);
printf("读取到的字符:%c\n", ch); // 输出:f
// 3. 从文件末尾向前偏移4个字符(定位到'h')
fseek(pf, -4, SEEK_END);
ch = fgetc(pf);
printf("读取到的字符:%c\n", ch); // 输出:h
fclose(pf);
pf = NULL;
return 0;
}
2. 获取偏移量:ftell函数
函数原型:long int ftell(FILE* stream);
- 功能:返回文件指针相对于文件开头的偏移量(字节数);
- 用途:配合fseek使用,确认指针位置。
示例:查看文件指针偏移量
c
#include <stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL) { perror("fopen"); return 1; }
// 读取第一个字符
fgetc(pf);
// 获取当前偏移量(已读取1个字符,偏移量为1)
long pos = ftell(pf);
printf("当前文件指针偏移量:%ld\n", pos); // 输出:1
// 调整指针到开头偏移5的位置
fseek(pf, 5, SEEK_SET);
pos = ftell(pf);
printf("调整后偏移量:%ld\n", pos); // 输出:5
fclose(pf);
pf = NULL;
return 0;
}
3. 重置文件指针:rewind函数
函数原型:void rewind(FILE* stream);
- 功能:将文件指针重置到文件开头位置;
- 用途:快速回到文件开头,无需手动计算偏移量。
示例:重置文件指针
c
#include <stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL) { perror("fopen"); return 1; }
// 读取前3个字符
for (int i = 0; i < 3; i++)
{
printf("%c ", fgetc(pf)); // 输出:a b c
}
// 重置指针到开头
rewind(pf);
printf("\n重置后读取第一个字符:%c\n", fgetc(pf)); // 输出:a
fclose(pf);
pf = NULL;
return 0;
}
写在最后 📝
这一篇我们掌握了文件操作的进阶技能:用fscanf/fprintf处理格式化数据(适配结构体等复杂类型),用fread/fwrite实现高效的二进制读写,用fseek/ftell/rewind完成精准的随机读写。这些是实际开发中最常用的文件操作方式,覆盖了大部分场景的需求。
核心要点总结
- 格式化读写(fscanf/fprintf):兼容复杂数据类型,用法和scanf/printf一致,多了文件指针参数。
- 二进制读写(fread/fwrite):高效存储,需以二进制模式(rb/wb)打开文件,适合批量/结构体数据。
- 随机读写核心:fseek调整指针位置,ftell查看偏移量,rewind重置到开头,摆脱顺序读写限制。
下一篇我们将解决文件操作的坑------文件读取结束的判定、文件缓冲区的原理,让你的文件操作代码更健壮!