news 2026/8/26 18:12:58

C 语言文件 IO 学习Day03:从基础函数到实战踩坑(BMP 读取 + 词典查询)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C 语言文件 IO 学习Day03:从基础函数到实战踩坑(BMP 读取 + 词典查询)

本文为 C 语言软件编程学习 Day03 笔记,系统梳理标准 IO 库函数、核心易混点对比,并结合「BMP 图片宽高读取」「英汉词典查询」两个实战项目,总结实操中高频踩坑点

文章目录

  • 前言
  • 一、文件 IO 核心函数全解
    • 1. fgetc:读取单个字符
    • 2. fputs:写入字符串
    • 3. fgets:读取一行字符串
    • 4. fprintf:格式化写入文件
    • 5. fwrite:整块数据写入
    • 6. fread:整块数据读取
    • 7. fseek:设置文件偏移位置
    • 8. ftell:获取当前偏移量
    • 9. rewind:回到文件开头
  • 二、易混函数深度对比
    • 1. gets 与 fgets 核心区别
    • 2. puts 与 fputs 核心区别
  • 三、实战项目与踩坑复盘
    • 项目 1:读取 24 位 BMP 图片的宽度和高度
    • 项目 2:英汉词典查询系统
  • 四、高频易错点速查表
  • 五、课后练习:文件拷贝

前言

第三天的学习核心围绕标准文件 IO 操作展开,从单字符读写到整块数据读写,再到文件位置偏移控制,最终落地到两个实战场景。这部分知识点函数多、细节碎,坑点密集,尤其字符串与指针结合的地方非常容易出错。本文按「函数大全 → 易混对比 → 实战项目 → 易错汇总」的结构整理,方便复习和避坑。


一、文件 IO 核心函数全解

1. fgetc:读取单个字符

int fgetc(FILE *stream);
  • 功能:从文件流中读取一个字符
  • 返回值:成功返回字符的 ASCII 码值;失败或读到文件末尾返回EOF
  • 注意:返回值用int接收而不是char,因为EOF是超出 char 范围的负值

2. fputs:写入字符串

int fputs(const char *s, FILE *stream);
  • 功能:将字符串写入指定文件流
  • 返回值:成功返回非负整数;失败返回EOF
  • 特点不会自动追加换行符\n,原样输出字符串内容

3. fgets:读取一行字符串

char *fgets(char *s, int size, FILE *stream);
  • 功能:从流中读取一行字符串,最多读取size-1个字符,末尾自动补\0
  • 返回值:成功返回缓冲区首地址;失败或读到文件末尾返回NULL
  • 核心特点
    • 自带长度保护,不会缓冲区溢出
    • 会保留行尾的换行符\n,不会自动丢弃
    • 既可读取标准输入stdin,也可读取普通文件

4. fprintf:格式化写入文件

int fprintf(FILE *stream, const char *format, ...);
  • 功能:按格式化字符串将数据写入指定文件流
  • 返回值:成功返回实际写入的字符个数;失败返回负数
  • 对比printf等价于fprintf(stdin, ...),只是默认输出到终端

5. fwrite:整块数据写入

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 功能:向文件写入nmemb个对象,每个对象占size字节
  • 参数
    • ptr:待写入数据的内存首地址
    • size:单个对象的字节大小
    • nmemb:要写入的对象数量
  • 返回值:成功返回实际写入的完整对象个数;失败返回 0

6. fread:整块数据读取

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 功能:从文件中读取nmemb个对象,每个对象占size字节,存入 ptr 指向的内存
  • 返回值:成功返回实际读到的完整对象个数;失败或读到文件末尾返回 0
  • 两种常用写法
    • 读单个结构体 / 整数:fread(&var, sizeof(var), 1, fp)→ 返回值判断是否等于 1
    • 读字节缓冲区:fread(buf, 1, sizeof(buf), fp)→ 返回值就是读到的字节数

7. fseek:设置文件偏移位置

int fseek(FILE *stream, long offset, int whence);
  • 功能:重新定位文件读写光标的位置
  • 基准参数 whence
    • SEEK_SET:从文件开头计算偏移
    • SEEK_CUR:从当前光标位置计算偏移
    • SEEK_END:从文件末尾计算偏移
  • 返回值:成功返回 0;失败返回 - 1

8. ftell:获取当前偏移量

long ftell(FILE *stream);
  • 功能:获取当前光标距离文件开头的字节偏移量
  • 经典用法:配合 fseek 获取文件总大小
    fseek(fp, 0, SEEK_END); long file_size = ftell(fp);

9. rewind:回到文件开头

void rewind(FILE *stream);
  • 功能:将文件光标直接重置到文件开头
  • 等价于fseek(fp, 0, SEEK_SET);

二、易混函数深度对比

1. gets 与 fgets 核心区别

这是考试最高频考点,也是实操最容易踩坑的点。

表格

对比维度gets()fgets()
缓冲区长度无长度检查,输入多长写多长强制指定 size,最多读 size-1 个字符
溢出风险极高,C11 标准已正式删除安全,不会越界
换行符处理读到换行直接丢弃,替换为\0换行符保留在字符串中,再补\0
读取来源只能从标准输入读取可从任意文件流读取
推荐程度禁止使用标准推荐写法

踩坑提醒:用fgets从键盘读取单词后,直接和文件内单词做strcmp会永远失败,因为字符串末尾多了一个\n。必须先清除换行符:

str[strcspn(str, "\n")] = '\0';

2. puts 与 fputs 核心区别

表格

对比维度puts()fputs()
换行符自动在末尾追加一个\n不会额外添加任何字符,原样输出
输出目标只能输出到标准输出可输出到任意文件流

三、实战项目与踩坑复盘

项目 1:读取 24 位 BMP 图片的宽度和高度

需求

从 BMP 文件中读取图片分辨率,已知宽度在文件偏移 18 字节处,占 4 字节;高度在偏移 22 字节处,占 4 字节。

核心代码

#include <stdio.h> int main(void) { FILE *fp = fopen("src.bmp", "rb"); // 二进制文件必须用rb打开! if (NULL == fp) { perror("fopen failed"); return -1; } unsigned int width = 0, height = 0; fseek(fp, 18, SEEK_SET); // 定位到宽度字段 fread(&width, 4, 1, fp); fread(&height, 4, 1, fp); // 读完宽度光标自动后移4字节,正好对准高度 printf("宽度:%u\n高度:%u\n", width, height); fclose(fp); return 0; }

高频踩坑点

  1. 偏移计算错误:误以为宽度从 17 字节开始

    正确逻辑:文件头占 14 字节(0-13)+ biSize 占 4 字节(14-17)= 18,宽度从 18 号字节开始。字段占 N 字节,下一字段起始位置 = 起始偏移 + N。

  2. 用文本模式打开二进制文件:BMP 是二进制文件,必须用rb模式,否则数据会错乱。
  3. 小端序问题:BMP 采用小端存储,x86/x64CPU 原生小端,直接 fread 到 int 变量即可,无需手动翻转字节。

项目 2:英汉词典查询系统

需求

终端输入单词,从词典文件(每行格式:单词 释义,释义内部含空格)中匹配并打印对应释义。

正确实现(strtok + 指针遍历)

#include <stdio.h> #include <string.h> int main(void) { FILE *fp = fopen("dict.txt", "r"); if (NULL == fp) { perror("fopen failed"); return -1; } char str[100] = {0}; char tmpbuff[4096] = {0}; gets(str); // 仅作业演示使用,实际开发禁用gets while (fgets(tmpbuff, sizeof(tmpbuff), fp) != NULL) { // 第一步:strtok只调用一次,切出第一个单词 char *word = strtok(tmpbuff, " "); if (word == NULL) continue; // 第二步:遍历指针越过单词末尾的\0,找到释义区域 char *meaning = word; while (*meaning != '\0') meaning++; // 走到单词结尾 meaning++; // 跨过strtok写入的\0 // 第三步:跳过释义开头连续的空格 while (*meaning != '\0' && *meaning == ' ') { meaning++; } // 第四步:字符串比较必须用strcmp if (strcmp(str, word) == 0) { printf("含义为:%s", meaning); break; } } fclose(fp); return 0; }

踩坑大汇总

  1. 低级错误:文件名加双引号
    fopen("argv[1]", "r"); // ❌ 去找名叫argv[1]的文件,永远打开失败 fopen(argv[1], "r"); // ✅ 正确,访问命令行参数
  2. 致命错误:用==比较字符串

    ==比较的是指针地址,不是字符串内容。内容比较必须用strcmp,相等返回 0。

  3. 经典错误:多次调用 strtok 切碎释义

    strtok(NULL, " ")会把释义内部的空格也当分隔符切开,只能拿到半截释义。正确做法是只调用一次 strtok 切单词,剩余内容靠指针遍历获取。

  4. 隐形错误:fgets 带换行符

    输入单词和词典行尾都可能带\n,不处理会导致 strcmp 永远匹配失败。


四、高频易错点速查表

易错点错误写法正确写法
字符串比较if(str1 == str2)if(strcmp(str1, str2) == 0)
命令行参数传文件名fopen("argv[1]","r")fopen(argv[1],"r")
二进制文件打开fopen("a.bmp","r")fopen("a.bmp","rb")
gets 安全问题gets(str)fgets(str, sizeof(str), stdin)
fgets 换行符直接拿去 strcmpstr[strcspn(str,"\n")]='\0'
strtok 切释义strtok(NULL, " ")只切一次单词,剩余内容指针遍历
BMP 偏移计算宽度偏移 17宽度偏移 18
ftell 返回值%d打印%ld打印(long 类型)
指针遍历边界先判断字符再判断 \0先判断!='\0'再判断字符,利用短路保护

五、课后练习:文件拷贝

利用freadfwrite实现二进制文件拷贝(图片、文档均可),核心框架:

#include <stdio.h> int main(int argc, char *argv[]) { if (argc < 3) { printf("usage: ./a.out src_file dst_file\n"); return -1; } FILE *fp_src = fopen(argv[1], "rb"); FILE *fp_dst = fopen(argv[2], "wb"); char buf[4096] = {0}; size_t n = 0; while ((n = fread(buf, 1, sizeof(buf), fp_src)) > 0) { fwrite(buf, 1, n, fp_dst); } fclose(fp_src); fclose(fp_dst); return 0; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/26 18:09:57

Python进阶教程:Git版本控制与团队协作

目录Python进阶教程&#xff1a;Git版本控制与团队协作一、Git 是什么二、基本配置与初始化三、核心操作&#xff1a;add / commit四、回退与撤销五、分支管理六、远程仓库协作七、团队协作工作流7.1 常用工作流7.2 标准流程八、解决冲突九、.gitignore 与规范提交十、实战&…

作者头像 李华
网站建设 2026/8/26 18:08:22

配对样本t检验结果解读:前后测量差异的显著性判断

配对t检验结果解读一、分析方法概述配对样本t检验&#xff08;Paired-Samples t Test&#xff09;用于比较同一组受试者在两个不同时间点或两种不同条件下的均值差异&#xff0c;是重复测量设计中最常用的统计方法。该检验要求配对差值近似服从正态分布&#xff0c;通过计算每对…

作者头像 李华
网站建设 2026/8/26 17:56:50

java静态类使用@Value动态绑定环境变量

java不能在静态类&#xff08;即 static 字段&#xff09;上直接使用Value 绑定环境变量。直接在一个 static 字段上使用 Value 是不会生效的&#xff0c;该字段会始终为 null 或默认值。原因分析Spring 容器在启动时&#xff0c;会为每个由它管理的 Bean&#xff08;非静态类&…

作者头像 李华
网站建设 2026/8/26 17:55:30

从SEO到GEO:重塑内容流量逻辑

&#x1f50d; 1. 流量入口变天&#xff1a;从“点击蓝链”到“直达答案”在过去的互联网时代&#xff0c;内容创作者与数字营销人员早已习惯了一套固定的流量运作模式&#xff1a;寻找低竞争关键词、撰写长文、优化元标题与元描述、建立反向链接&#xff0c;然后静静等待搜索引…

作者头像 李华
网站建设 2026/8/26 17:39:19

TCP 粘包问题:产生原因分析与嵌入式场景解决方法

一、什么是 TCP 粘包问题在嵌入式 TCP 透传、串口转 WiFi 等项目开发中&#xff0c;经常会遇到这类异常&#xff1a;设备端连续发送温度采集、湿度采集两条独立指令&#xff0c;上位机却只收到一条合并的数据&#xff1b;或是消息解析到一半突然错位&#xff0c;导致后续所有业…

作者头像 李华
网站建设 2026/8/26 17:32:18

ROS2 Action通信实战

文章目录Action通信编写Action服务端编写Action客户端Action通信 Action是ROS中最强大的一种核心通信机制&#xff0c;是ROS2在底层利用多个 Topic 和 Service 组合封装而成&#xff0c;专门用于长耗时、需反馈、可取消的任务。 下面使用ROS2内置的Fibonacci Action 接口进行…

作者头像 李华