C语言文件操作进阶:格式化/二进制读写与随机定位详解
掌握C语言文件操作不仅是读写文本的基础,更涉及结构体序列化、二进制传输及内存映射等高级应用。本文梳理了从格式化输入输出到二进制存取的完整流程,深入剖析fseek与ftell的指针定位逻辑,并重点澄清feof函数在边界条件下的误用场景,帮助开发者编写健壮的文件处理代码。
一、文件的顺序读写
在上篇文章介绍了基础的字符和字符串读写后,本节将深入介绍针对复杂数据类型(如结构体)的处理方式,包括格式化读写和二进制读写,同时对比不同流处理函数的差异。
第1步:使用fprintf实现结构化数据的格式化写入
目的:将包含多种类型成员的结构体数据,按照指定的格式转换为可读文本写入文件。
入口:stdio.h 头文件中的 fprintf 函数。

fprintf 与 printf 类似,唯一的区别是它将格式化后的数据写入指定的文件流,而非标准输出。printf(stdout, ...) 等价于 printf(...)。
关键参数与动作:
- 第一个参数为文件指针
FILE*。 - 第二个参数为格式化字符串(如
"%s %d %f")。 - 后续参数为要写入的数据项。
#include <stdio.h>
struct S
{
char arr[10];
int num;
float sc;
};
int main()
{
// 初始化结构体数据
struct S s = { "abcdef", 20, 5.5f };
// 打开文件用于写入 (w)
FILE* pf = fopen("test.data", "w");
if (NULL == pf)
{
return 1; // 打开失败处理
}
// 执行写入:将结构体成员按格式写入文件
fprintf(pf, "%s %d %f", s.arr, s.num, s.sc);
// 关闭文件并置空指针
fclose(pf);
pf = NULL;
return 0;
}
第2步:使用fscanf实现结构化数据的格式化读取
目的:从文件中解析出特定格式的内容,并按顺序还原到结构体变量中。
入口:stdio.h 头文件中的 fscanf 函数。

重要参数与动作:
- 接收地址:
fscanf对于指针类型或需要修改变量的位置,必须传入变量的地址(如&(s.num))。 - 注意:原草稿中
s.arr本身即为数组首地址,通常无需加&,但需确保缓冲区足够大以防止溢出。
#include <stdio.h>
struct S
{
char arr[10];
int num;
float sc;
};
int main()
{
struct S s = {0};
// 以只读模式打开文件
FILE* pf = fopen("test.data", "r");
if (NULL == pf)
{
return 1;
}
// 执行读取:注意结构体成员地址传递
fscanf(pf, "%s %d %f", s.arr, &(s.num), &(s.sc));
// 验证读取结果
printf("%s %d %f\n", s.arr, s.num, s.sc);
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
第3步:使用fwrite实现二进制数据的高效写入
目的:绕过格式化转换,直接将内存中的数据块原样写入文件。这种方式体积更小且支持非文本数据(如图片、加密数据)。
入口:stdio.h 中的 write 家族函数。

核心参数:
- 指向数据块的指针
(void*)ptr。 - 每个元素的大小
size_t size(通常用sizeof获取)。 - 元素个数
size_t count。 - 目标文件流。
struct S
{
char arr[10];
int num;
float sc;
};
int main()
{
struct S s = {"abcdef", 20, 5.5f};
FILE* pf = fopen("test.data", "wb"); // 建议明确指定二进制模式
if (NULL == pf)
{
return 1;
}
// 写入整个结构体二进制内容
fwrite(&s, sizeof(struct S), 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
第4步:使用fread实现二进制数据读取
目的:将文件中的二进制内容直接加载到内存结构中。
入口:stdio.h 中的 fread 函数。

返回值判断:fread 返回的是成功读取的元素个数。如果返回值小于请求的元素个数,说明可能遇到了文件末尾或读取错误。
#include <stdio.h>
struct S
{
char arr[10];
int num;
float sc;
};
int main()
{
struct S s = {0};
FILE* pf = fopen("test.data", "rb"); // 对应二进制写入
if (NULL == pf)
{
return 1;
}
// 读取二进制数据到结构体
// 注意:这里应传入实际读取到的字节数或元素计数来验证
size_t ret = fread(&s, sizeof(struct S), 1, pf);
if(ret != 1) { /* 处理错误 */ }
printf("%s %d %f\n", s.arr, s.num, s.sc);
fclose(pf);
pf = NULL;
return 0;
}
相关函数对比总结
为了理清流操作的边界,以下是对各类型函数的定位描述:
scanf/printf:分别针对标准输入(stdin)和标准输出(stdout)。fscanf/fprintf:针对所有流(如文件)。只要第一个参数是合法的FILE*即可,功能由第二个参数的格式串决定。sscanf/sprintf:针对字符串缓冲区。一个是从字符串解析,一个是向字符串格式化。
二、文件的随机读写
顺序读写只能从头到尾遍历,当我们需要修改文件中间某处的数据,或快速跳转到特定位置时,需要使用随机定位函数。
第1步:使用fseek调整文件指针位置
目的:根据当前指针位置和偏移量,重新定位文件流内部的位置指示器。
原型:int fseek(FILE* stream, long offset, int origin);

参数说明:
offset:偏移量。正值表示向后,负值表示向前(相对于origin)。origin:起始点。通常为SEEK_SET(文件开头)、SEEK_CUR(当前位置) 或SEEK_END(文件末尾)。
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.data", "r+"); // 需要可读写模式演示fseek效果更佳
if (NULL == pf)
{
perror("open file failed");
return 1;
}
// 先读取前两个字符
char ch1 = fgetc(pf);
char ch2 = fgetc(pf);
printf("Read: %c %c\n", ch1, ch2);
// 将指针调整到距离文件末尾2个字节的位置
fseek(pf, -2, SEEK_END);
// 读取该位置的字符
char ch = fgetc(pf);
printf("Seeked Read: %c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
第2步:使用ftell获取当前偏移量
目的:查询当前文件指针距离文件开头的字节数。常用于记录进度或计算文件大小。

第3步:使用rewind重置指针
目的:将文件指针强制重置回文件开头。等效于 fseek(pf, 0, SEEK_SET),但语义更清晰。

三、被错误使用的feof()
feof 是一个非常容易被误解的函数。它并非“判断是否到达文件末尾”的直接开关,而是反映上一次读取操作的状态。
1. feof的正确应用场景
当循环读取结束时,我们需要区分:
- 读取失败(Error):发生硬件错误或权限问题。
- 遇到结尾(EOF):正常读完所有有效数据。
feof(stream) 返回非0值表示曾到达过文件尾,返回0表示未到达过。
2. 常见的while(feof)死循环陷阱
许多初学者会写出如下错误代码:
// 【错误示范】
while (!feof(pf))
{
fgetc(pf);
}
这种写法会导致最后一次读取时,指针仍在最后一个有效字符处,feof 尚未生效,下一次循环会再次尝试读取,导致重复处理或逻辑错误。
3. 正确的拷贝文件范例
在读取循环中,我们应该检查读取函数的返回值来判断是否结束,而不是依赖 feof。
#include <stdio.h>
#include <stdlib.h>
int main()
{
// 1. 准备源文件和目标文件
FILE* pfread = fopen("test.txt", "r");
FILE* pfwrite = fopen("test2.txt", "w");
if (pfread == NULL || pfwrite == NULL)
{
return 1; // 任一打开失败
}
// 2. 使用返回值判断作为循环条件
int ch;
// fgetc 读取成功返回 ASCII 码,到达 EOF 返回 EOF
while ((ch = fgetc(pfread)) != EOF)
{
fputc(ch, pfwrite);
}
// 3. 此时若需判断是出错还是正常结束,才调用 feof/ferror
/*
if (ferror(pfread)) {
printf("Read error\n");
} else if (feof(pfread)) {
printf("Normal end of file\n");
}
*/
fclose(pfread);
fclose(pfwrite);
return 0;
}
总结
C语言文件操作的核心在于理解“流”的概念以及内存数据到磁盘数据的映射关系。掌握 fprintf/fscanf 可以处理结构化文本,fwrite/fread 适用于高性能或二进制场景,而 fseek 则是解决非连续访问的关键。在处理文件结束状态时,务必优先信任读取函数的返回值,仅在事后分析状态时使用 feof。
以上就是C语言文件顺序读写、随机定位及feop误用的详细内容,更多关于C语言文件操作基础与进阶的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













