当前位置: 首页 > news >正文

彻底搞懂 C 语言 `fread` 函数:从入门到精通

文章目录

  • 彻底搞懂 C 语言 `fread` 函数:从入门到精通
    • 📌 目录
    • 1. 函数原型与头文件
    • 2. 四个参数详细拆解
    • 3. 返回值的秘密
    • 4. 为什么这样设计?(设计哲学)
      • 4.1 面向数据块,而非原始字节
      • 4.2 原子性保证
      • 4.3 与 `fwrite` 完全对称
    • 5. 完整代码示例(8 个场景)
      • 示例 1:按字节读取文件(最常用)
      • 示例 2:读取整数数组
      • 示例 3:读取结构体数组(核心应用)
      • 示例 4:处理不完整读取(文件提前结束)
      • 示例 5:循环读取直到文件结束(处理任意剩余字节)
      • 示例 6:使用 `fread` 读取网络数据(模拟)
      • 示例 7:错误处理与 `ferror` / `feof` 结合
      • 示例 8:大文件块读取性能对比(示意)
    • 6. 常见错误与最佳实践
      • ❌ 错误 1:误解返回值
      • ❌ 错误 2:忘记检查文件结束或错误
      • ❌ 错误 3:混用 `fread` 和 `read` 于同一描述符
      • ✅ 最佳实践
    • 7. 总结速查表
    • 最后的话

彻底搞懂 C 语言fread函数:从入门到精通

一篇博客带你掌握fread的设计思想、参数细节、完整示例和避坑指南

📌 目录

  1. 函数原型与头文件
  2. 四个参数详细拆解
  3. 返回值的秘密
  4. 为什么这样设计?(设计哲学)
  5. 完整代码示例(8 个场景)
  6. 常见错误与最佳实践
  7. 总结速查表

1. 函数原型与头文件

#include<stdio.h>size_tfread(void*ptr,size_tsize,size_tnmemb,FILE*stream);
  • 所属库:标准输入输出库<stdio.h>
  • 功能:从文件流中读取数据到内存缓冲区
  • 返回值类型size_t(无符号整数,通常为unsigned long

2. 四个参数详细拆解

参数类型名称含义比喻
ptrvoid *缓冲区指针数据读到哪里去一个空篮子
sizesize_t每个元素的大小(字节)每个物品占多大空间每个鸡蛋的大小
nmembsize_t要读取的元素个数想读多少个物品想拿多少个鸡蛋
streamFILE *文件流指针从哪里读从哪个鸡窝拿

关键理解

  • sizenmemb共同决定要读的总字节数:总字节数 = size × nmemb
  • 但返回值不是总字节数,而是成功读到的完整元素个数

3. 返回值的秘密

size_tn=fread(ptr,size,nmemb,stream);
  • 成功时n等于实际读到的完整元素个数
    • 如果n == nmemb,说明全部读完了。
    • 如果0 < n < nmemb,说明文件提前结束(或出错),但至少读到了n个完整元素。
  • 失败或文件尾
    • 若文件一开始就到达末尾,则n == 0
    • 若发生读取错误,n可能小于nmemb,需要配合ferror(stream)判断。

重要:不要用返回值去判断读了多少字节,除非你明确知道size == 1


4. 为什么这样设计?(设计哲学)

4.1 面向数据块,而非原始字节

C 语言经常用来处理结构体、数组等复合数据类型。如果fread只返回字节数,你读到 29 个字节时,很难判断这是完整的 7 个int(28 字节)+ 1 个多余字节,还是 9 个int少了 7 字节?
通过sizenmemb分离,你可以直接知道“我读到了3 个完整结构体”。

4.2 原子性保证

fread保证:要么读到一个完整的元素,要么完全不读这个元素。绝不会出现“半个元素”的情况。
例如size = sizeof(Student) = 64,文件还剩 100 字节,你请求nmemb = 10

  • fread会先计算64 × 10 = 640字节,然后尝试读取。
  • 实际只读到 100 字节 → 只能提供 1 个完整结构体(64 字节)和 36 字节残留。
  • 返回值n = 1,那 36 字节被丢弃(文件指针移动到 64 字节位置)。
  • 这样你永远不会拿到一个残缺的Student

4.3 与fwrite完全对称

fwrite(ptr,size,nmemb,stream);// 写入 nmemb 个元素fread(ptr,size,nmemb,stream);// 读取 nmemb 个元素

对称的接口让代码易于理解和维护。


5. 完整代码示例(8 个场景)

以下所有示例均可在 GCC / Clang 下编译运行。

示例 1:按字节读取文件(最常用)

#include<stdio.h>intmain(){FILE*fp=fopen("hello.txt","rb");if(!fp){perror("fopen");return1;}charbuf[256];size_tn=fread(buf,1,sizeof(buf)-1,fp);// 保留一个字节给 '\0'buf[n]='\0';// 手动添加字符串结束符printf("读取了 %zu 个字节\n",n);printf("内容:%s\n",buf);fclose(fp);return0;}

说明size=1时,返回的n就等于实际读取的字节数。


示例 2:读取整数数组

#include<stdio.h>intmain(){// 先写入一些整数FILE*fp=fopen("ints.bin","wb");intdata[]={100,200,300,400,500};fwrite(data,sizeof(int),5,fp);fclose(fp);// 再读取回来fp=fopen("ints.bin","rb");intarr[10]={0};size_tn=fread(arr,sizeof(int),10,fp);printf("成功读取 %zu 个整数\n",n);for(size_ti=0;i<n;i++){printf("%d ",arr[i]);}printf("\n");fclose(fp);return0;}

示例 3:读取结构体数组(核心应用)

#include<stdio.h>#include<string.h>typedefstruct{intid;charname[32];floatscore;}Student;intmain(){// 写入 3 个学生Student stu[]={{101,"Alice",88.5},{102,"Bob",92.0},{103,"Charlie",76.5}};FILE*fp=fopen("students.dat","wb");fwrite(stu,sizeof(Student),3,fp);fclose(fp);// 读取学生fp=fopen("students.dat","rb");Student buffer[10];size_tn=fread(buffer,sizeof(Student),10,fp);printf("实际读取了 %zu 个学生记录\n",n);for(size_ti=0;i<n;i++){printf("ID: %d, Name: %s, Score: %.1f\n",buffer[i].id,buffer[i].name,buffer[i].score);}fclose(fp);return0;}

示例 4:处理不完整读取(文件提前结束)

#include<stdio.h>intmain(){// 创建一个只有 5 字节的文件FILE*fp=fopen("tiny.bin","wb");fwrite("ABCDE",1,5,fp);fclose(fp);// 尝试读取 3 个 int(每个 4 字节,需要 12 字节)fp=fopen("tiny.bin","rb");intarr[3];size_tn=fread(arr,sizeof(int),3,fp);if(n!=3){if(feof(fp)){printf("文件提前结束:只读到 %zu 个完整的 int\n",n);}elseif(ferror(fp)){printf("读取过程中发生错误\n");}}fclose(fp);return0;}

输出文件提前结束:只读到 0 个完整的 int


示例 5:循环读取直到文件结束(处理任意剩余字节)

#include<stdio.h>intmain(){FILE*fp=fopen("largefile.bin","rb");if(!fp)return1;charbuf[4096];size_tn;while((n=fread(buf,1,sizeof(buf),fp))>0){// 处理读到的 n 个字节fwrite(buf,1,n,stdout);// 直接输出到屏幕}fclose(fp);return0;}

解释:当size=1时,fread返回的就是实际字节数,非常适合流式拷贝。


示例 6:使用fread读取网络数据(模拟)

#include<stdio.h>#include<string.h>intmain(){// 模拟从网络收到的数据包(内存流)charfake_network_data[]="\x01\x00\x00\x00Hello\x02\x00\x00\x00World";FILE*memstream=fmemopen(fake_network_data,sizeof(fake_network_data)-1,"rb");if(!memstream)return1;intlen1,len2;charstr1[100],str2[100];// 读取第一个字符串的长度和内容fread(&len1,sizeof(int),1,memstream);fread(str1,1,len1,memstream);str1[len1]='\0';// 读取第二个字符串fread(&len2,sizeof(int),1,memstream);fread(str2,1,len2,memstream);str2[len2]='\0';printf("str1 = %s\n",str1);printf("str2 = %s\n",str2);fclose(memstream);return0;}

示例 7:错误处理与ferror/feof结合

#include<stdio.h>#include<stdlib.h>intsafe_read(FILE*fp,void*buf,size_telem_size,size_telem_count){size_tn=fread(buf,elem_size,elem_count,fp);if(n!=elem_count){if(feof(fp)){fprintf(stderr,"警告:文件意外结束,只读了 %zu/%zu 个元素\n",n,elem_count);}elseif(ferror(fp)){fprintf(stderr,"错误:读取文件时发生 I/O 错误\n");return-1;}}return(int)n;}intmain(){FILE*fp=fopen("maybe_corrupt.bin","rb");if(!fp)return1;doubledata[100];intret=safe_read(fp,data,sizeof(double),100);if(ret<0){printf("读取失败\n");}else{printf("成功读取 %d 个 double\n",ret);}fclose(fp);return0;}

示例 8:大文件块读取性能对比(示意)

#include<stdio.h>#include<time.h>intmain(){FILE*fp=fopen("bigfile.bin","rb");if(!fp)return1;charbuf[1024*1024];// 1 MB 缓冲区clock_tstart=clock();size_ttotal=0;size_tn;while((n=fread(buf,1,sizeof(buf),fp))>0){total+=n;}clock_tend=clock();printf("读取 %zu 字节,耗时 %.2f 秒\n",total,(double)(end-start)/CLOCKS_PER_SEC);fclose(fp);return0;}

6. 常见错误与最佳实践

❌ 错误 1:误解返回值

// 错误size_tn=fread(buf,4,10,fp);if(n<40){...}// 永远不可能成立,因为 n 最大是 10

正确

if(n<10){/* 没读够 10 个 int */}

❌ 错误 2:忘记检查文件结束或错误

fread(buf,1,100,fp);// 如果只读了 50 字节,可能不知道是文件尾还是错误

正确

size_tn=fread(buf,1,100,fp);if(n<100){if(feof(fp))puts("到达文件尾");elseif(ferror(fp))puts("读取出错");}

❌ 错误 3:混用freadread于同一描述符

fread有用户态缓冲区,直接混用会导致数据错位。如果必须混用,调用fflushfileno但极其危险,不建议

✅ 最佳实践

  1. 读取二进制文件时,总是用"rb"模式打开(避免 Windows 下换行符转换问题)。
  2. size=1处理任意字节流,用size=sizeof(T)处理固定类型数组。
  3. 每次调用后检查返回值,并配合feof/ferror判断原因。
  4. 为大文件使用足够大的缓冲区(通常 4KB ~ 1MB),减少实际 I/O 次数。

7. 总结速查表

场景调用方式返回值含义
读取任意字节流(如拷贝文件)fread(buf, 1, count, fp)实际读取的字节数
读取固定大小元素(如 int)fread(arr, sizeof(int), N, fp)成功读取的 int 个数
读取结构体数组fread(stu, sizeof(Student), N, fp)成功读取的结构体个数
判断是否全部读完if (n == nmemb)全部成功
判断文件尾if (n < nmemb && feof(fp))文件提前结束
判断 I/O 错误if (n < nmemb && ferror(fp))发生错误

最后的话

fread是 C 语言中处理二进制 I/O 的核心函数。它的设计巧妙地将字节流抽象为元素流,让程序员能直接操作高级数据类型。理解sizenmemb的分离,以及返回值的真实含义,是写出健壮文件操作代码的关键。

希望这篇博客能帮你彻底掌握fread。如果在项目中遇到奇怪的读取问题,记得先检查返回值,再用feofferror诊断!

Happy Coding!🚀

http://www.cnnetsun.cn/news/1933559.html

相关文章:

  • 【A40i-Android7.1】--- 从源码到镜像:A40i Android系统构建全链路解析
  • PHP HTML转PDF终极指南:快速生成专业PDF文档的完整方法
  • 大模型赛道香吗?过来人掏心窝子告诉你4个真相!月薪30K+的AI岗,你真的能抓住吗?
  • S 型、T 型、多项式、三次样条多段轨迹规划及圆弧过渡算法研究(Matlab代码实现)
  • AI绘画商用翻车实录:为什么你买的“商用授权”根本无效?——深度拆解Stable Diffusion生态中7层隐性版权链
  • AutoGod:安卓-全兼容!一站式自动化框架,开发效率直接拉满
  • CVE-2026-5281全解析:Chrome WebGPU零日漏洞暴露的图形安全新战场
  • 从零理解归一化:BatchNorm和LayerNorm在Transformer与CNN中的不同表现(含PyTorch示例)
  • 房东网络/合租上网必看:如何用一台新路由器安全搭建自己的“子网”(华硕/腾达路由器设置详解)
  • DataX:从原理到实战,构建企业级数据同步平台的完整指南
  • 多主题领域EI会议推荐:好中、快审、稳检索
  • 【Nordic实战】nRF54L15引脚规划:从电源域到GPIO的功耗优化指南
  • SAP要关闭 SAP 科目相关的凭证拆分,优先在公司代码层面撤销激活(适用于 ECC 新总账 / S/4HANA),辅以科目分类、规则与强制字段的清理,避免过账报错
  • 告别配置烦恼:CLion + Qt + CMake 一站式开发环境实战指南
  • 移动端自动化测试:Appium 入门
  • 《AI元人文·延伸卷》拟合提纲
  • Python3 正则表达式详解
  • jEasyUI 创建学校课程表
  • 从试点到推广:政务 AI 规模化应用的实施策略
  • vi编辑器模式切换与高效操作指南
  • STM32F407 HAL库实战:用定时器中断实现步进电机精准脉冲计数(附完整代码)
  • RabbitMQ 消息持久化:如何实现消息不丢失?三重持久化完整实战
  • 如何用Bioicons解决科学插图资源碎片化难题:技术架构深度解析
  • VScode远程调试嵌入式开发板的完整指南:从SSH连接到GDB实战
  • 从依赖冲突到编译成功:Ubuntu 18.04上Ceres-Solver 1.14的完整避坑指南
  • SITS2026 AI教学助手实战手册(2026春季学期紧急部署版)
  • 若依权限不够用?手把手教你扩展RBAC模型,实现更细粒度的数据权限控制
  • 从表单提交到数据入库:Servlet+JDBC构建经典Web交互闭环
  • 从引脚到协议:JTAG接口的深度解析与实践指南
  • 概率化递推ai工程应用技术