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

30. 文件IO (1)

我们从 最底层原理 → C语言标准接口 → 嵌入式理解 逐层展开,让你彻底理解 “文件” 在系统和程序中的含义。


🧭 一、文件 I/O 基础概念(全面讲解)


1️⃣ 什么是文件(File)

文件(File) 是操作系统用来 组织和管理数据的基本单位
无论是:

  • 一张图片(.jpg)
  • 一个程序(.elf)
  • 还是一个串口设备(/dev/ttyUSB0)

在操作系统看来,它们都是“文件”。

👉 换句话说:在 Linux / Unix / 嵌入式系统中,“一切皆文件”。


📦 文件的本质

在底层,文件只是一个字节序列(Byte Stream)

+-------------------------------+
| 0x41 | 0x42 | 0x43 | 0x44 ... |
+-------------------------------+

程序只是通过文件接口(read/write)把数据写入这个字节序列,或者读出来。


2️⃣ 文件在操作系统中的定义

在操作系统(OS)层面,文件包含两类信息:

分类内容
文件数据区实际内容,例如文本、二进制数据
文件控制块(FCB)存储文件的元信息,如文件名、权限、大小、时间戳等

在 C 程序中,当你打开一个文件时,操作系统并不是直接给你文件的内容,而是返回一个文件描述符(File Descriptor) 或者一个 FILE结构体指针


3️⃣ 文件描述符(File Descriptor)

文件描述符是一个 整数(int),是内核用来标识一个已打开文件的编号。

每个进程在内核中都有一个文件描述符表

文件描述符含义默认流
0标准输入stdin
1标准输出stdout
2标准错误stderr

示意图:

+-----------------------+
| FD=0 -> 键盘输入流(stdin)
| FD=1 -> 屏幕输出流(stdout)
| FD=2 -> 屏幕错误流(stderr)
| FD=3 -> test.txt
| FD=4 -> /dev/ttyUSB0
+-----------------------+

📌 在底层系统调用中,我们使用这些文件描述符来读写文件:

read(fd, buf, size);
write(fd, buf, size);
close(fd);

4️⃣ 文件流(Stream)与缓冲(Buffer)概念

C语言的标准库(stdio.h)在文件描述符之上封装了一层更高层的抽象:
文件流(FILE Stream)

🔹 Stream(流)

流是文件输入输出的抽象概念。
C 程序中所有的 I/O 操作都通过流来完成,比如:

  • 从键盘读取(输入流)
  • 向显示器输出(输出流)
  • 从磁盘读取文件(输入流)
  • 向磁盘写入文件(输出流)

🔹 Buffer(缓冲区)

I/O 操作通常速度慢(比如磁盘/串口),
因此 C 标准库会在用户空间开一个“缓冲区”,
只有当缓冲区满了或手动刷新(fflush)时,才真正写入设备。

类型:

类型行为示例
全缓冲(Full Buffered)缓冲区满时才写入文件写入(磁盘)
行缓冲(Line Buffered)每行或遇到换行符时写入stdout(终端)
无缓冲(Unbuffered)立即输出stderr(错误输出)

例如:

printf("Hello");   // 可能还在缓冲区
fflush(stdout);    // 强制刷新到屏幕

5️⃣ 标准输入/输出/错误流:stdin, stdout, stderr

stdio.h 中定义了三个全局流:

extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;

默认映射:

文件描述符默认设备
stdin0键盘
stdout1屏幕
stderr2屏幕(不带缓冲)

示例:

fprintf(stdout, "普通输出\n");
fprintf(stderr, "错误输出!\n");

⚙️ 嵌入式中:

  • 这些标准流常常被重定向到串口、LCD 或日志系统
  • 比如 printf() 实际是通过 UART 发送。

6️⃣ C语言中的文件操作接口(stdio.h)

C语言标准库为文件操作提供了完整接口:

操作函数说明
打开文件fopen()以指定模式打开文件
关闭文件fclose()关闭文件并释放资源
读写操作fgetc(), fgets(), fread() / fputc(), fputs(), fwrite()读取/写入文件内容
文件位置fseek(), ftell(), rewind()控制文件指针
检查状态feof(), ferror()检查是否到文件末尾或出错

7️⃣ FILE 结构体与 fopen() / fclose()

打开文件:

FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {perror("文件打开失败");return -1;
}

关闭文件:

fclose(fp);

💡 FILE 是一个结构体,内部保存了:

  • 文件描述符
  • 缓冲区指针
  • 当前读写位置
  • 文件状态标志

8️⃣ 文件模式(r, w, a, r+, w+, a+)

模式含义
"r"只读打开文件,文件必须存在
"w"只写打开文件,不存在则创建,存在则清空
"a"追加写入文件,不存在则创建
"r+"可读可写,文件必须存在
"w+"可读可写,不存在则创建,存在则清空
"a+"可读可写,写入追加到末尾,不存在则创建

示例:

FILE *fp1 = fopen("log.txt", "a+"); // 读写模式,追加写入
FILE *fp2 = fopen("data.bin", "wb"); // 写二进制文件

9️⃣ 二进制模式(rb, wb, ab)

文本文件(text mode)和二进制文件(binary mode)的区别:

  • 文本模式\n 可能在不同平台被转换(如 Windows 为 \r\n
  • 二进制模式:原样读写字节流,不做转换
fopen("data.bin", "wb"); // 写入二进制文件
fopen("data.bin", "rb"); // 读取二进制文件

⚙️ 嵌入式开发中常用 二进制模式,例如:

  • 写入 Flash 镜像
  • 记录传感器原始数据
  • 处理图片、音频、固件文件等原始字节流

✅ 小结

概念关键点
文件数据的逻辑抽象,一切皆文件
文件描述符操作系统级别的文件编号
文件流C标准库对文件描述符的高级封装
缓冲区提高I/O性能,控制刷新
标准流stdin / stdout / stderr 三个默认通道
文件模式决定读写方式(文本/二进制、覆盖/追加等)

非常好 👍!
你现在学到的这一部分是 C 语言文件 I/O 的核心章节之一。
这章讲的是 文件的基本读写操作,也就是——如何把数据读进内存写进文件

我们一步一步讲清楚每一类函数的机制、应用场景和示例,
学完这一章,你能轻松地自己实现日志系统、配置文件、数据保存与读取。


📘 第二章:文件的基本读写操作


一、C 文件 I/O 模型回顾

在 C 语言中,文件操作都是通过一个结构体 FILE 来完成的:

FILE *fp = fopen("data.txt", "r");

fp 是一个文件指针(文件流),它内部记录了文件缓冲区、文件位置等信息。
所有的读写函数 (fgetc, fputc, fgets, fputs, fread, fwrite, fprintf, fscanf)
都通过这个 FILE * 来操作文件。


二、📂 文件的基本读写操作

文件操作可以分为四种层次:
字符级 → 行级 → 块级(二进制) → 格式化文本。


✅ 1. 逐字符操作

字符操作最简单,适合文本文件的逐字读写

🔹 函数:
int fgetc(FILE *fp);
int fputc(int ch, FILE *fp);
📘 示例:
#include <stdio.h>int main() {FILE *fp = fopen("test.txt", "w");if (!fp) return -1;char *str = "Hello C!";for (int i = 0; str[i] != '\0'; i++)fputc(str[i], fp);  // 写一个字符fclose(fp);// 读回文件fp = fopen("test.txt", "r");int ch;while ((ch = fgetc(fp)) != EOF)putchar(ch);         // 输出到屏幕fclose(fp);return 0;
}

📤 输出:

Hello C!
💡 知识点:
  • 每次读写一个字符;
  • fgetc() 返回 int,以便能识别 EOF
  • 适合小文件或字符流解析。

✅ 2. 按行操作

按行操作是最常用的文本读取方式,比如读取配置文件、日志文件等。

🔹 函数:
char *fgets(char *buf, int size, FILE *fp);
int fputs(const char *str, FILE *fp);
📘 示例:
#include <stdio.h>int main() {FILE *fp = fopen("config.txt", "w");fputs("name=Sensor\n", fp);fputs("threshold=25.5\n", fp);fclose(fp);fp = fopen("config.txt", "r");char line[64];while (fgets(line, sizeof(line), fp)) {printf("读取到一行:%s", line);}fclose(fp);return 0;
}

📤 输出:

读取到一行:name=Sensor
读取到一行:threshold=25.5
💡 知识点:
  • fgets() 会读取换行符 \n
  • 如果行太长,会分段读取;
  • fputs() 不会自动添加换行。

✅ 3. 按块操作(二进制文件)

适合读取结构体、数组、图片、音频等二进制数据
文件中存储的内容不是字符,而是原始字节数据

🔹 函数:
size_t fread(void *ptr, size_t size, size_t count, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *fp);
📘 示例:
#include <stdio.h>typedef struct {int id;float temp;
} Sensor;int main() {Sensor s1 = {1001, 28.5};FILE *fp = fopen("sensor.bin", "wb"); // 二进制写fwrite(&s1, sizeof(Sensor), 1, fp);fclose(fp);Sensor s2;fp = fopen("sensor.bin", "rb");       // 二进制读fread(&s2, sizeof(Sensor), 1, fp);fclose(fp);printf("ID=%d, 温度=%.1f\n", s2.id, s2.temp);return 0;
}

📤 输出:

ID=1001, 温度=28.5
💡 知识点:
  • fwrite()/fread() 是原始字节操作;
  • 不做格式化,不会处理文本换行;
  • 读写结构体或数组时非常高效;
  • 嵌入式中常用于保存 Flash 数据或 EEPROM 数据。

✅ 4. 文件格式化读写

这是最灵活、最常见的文件操作方式。
它让你可以直接像打印一样读写文本文件。

🔹 函数:
int fprintf(FILE *fp, const char *fmt, ...);
int fscanf(FILE *fp, const char *fmt, ...);
📘 示例:
#include <stdio.h>int main() {FILE *fp = fopen("data.txt", "w");int id = 101;float voltage = 3.3;const char *name = "SensorA";fprintf(fp, "ID=%d, 电压=%.2f, 名称=%s\n", id, voltage, name);fclose(fp);fp = fopen("data.txt", "r");int rid;float rv;char rname[16];fscanf(fp, "ID=%d, 电压=%f, 名称=%s", &rid, &rv, rname);fclose(fp);printf("读取: ID=%d, 电压=%.2f, 名称=%s\n", rid, rv, rname);return 0;
}

📤 输出:

读取: ID=101, 电压=3.30, 名称=SensorA

✅ 5. printf / fprintf / scanf / fscanf 区别总结

函数用途目标
printf()输出到标准输出屏幕
scanf()从标准输入读取键盘
fprintf()输出到指定文件文件
fscanf()从文件按格式读取文件

🧠 小结:

fprintf(fp, ...) = 文件版的 printf()
fscanf(fp, ...) = 文件版的 scanf()


三、文件读写函数选择建议

场景推荐函数原因
读写文本日志fgets(), fputs()行级处理方便
保存/读取配置文件fprintf(), fscanf()格式清晰
存储结构体或二进制数据fwrite(), fread()效率高
简单字符流fgetc(), fputc()控制细粒度

四、⚙️ 嵌入式开发中的应用建议

应用推荐方式示例
Flash 参数存储fwrite() / fread()保存结构体
传感器日志fprintf()记录时间与数据
配置加载fgets() / sscanf()解析配置文件
通信模拟(串口日志)fputc() / fgetc()模拟字节流

http://www.dtcms.com/a/544434.html

相关文章:

  • 技术深析:衡石 Agentic BI 的架构革命与核心技术突破
  • UVa 12333 Revenge of Fibonacci
  • rank(A+E) >= rank(A)证明
  • 未来之窗昭和仙君(四十三)开发布草管理系统修仙版——东方仙盟筑基期
  • VMware 虚拟机网络故障
  • 河南省建设厅举报网站建网站需要多少资金
  • 网站开发常用的谷歌插件企业首次建设网站的策划流程
  • 计算机3D视觉:Pytorch3d的环境配置与初步使用
  • 国产化转型实战:制造业供应链物流系统从MongoDB至金仓数据库迁移全指南
  • 从零开始学 Rust:环境搭建、基础语法到实战项目全流程
  • S11e Protocol 完整白皮书
  • CUDA:通往大规模并行计算的桥梁
  • AR智能眼镜:变电站巡检误操作的“电子安全员”
  • Rust 中的内存对齐与缓存友好设计:性能优化的隐秘战场
  • Springboot3+mqttV5集成(Emqx 5.8.3版本)
  • 东莞网站建设设技术支持网站
  • 高州网站建设公司欧洲vodafonewifi18mmpcc
  • 第二章、Docker+Ollama封神!2步装Qwen+Deepseek小型模型
  • Rust——Trait 定义与实现:从抽象到实践的深度解析
  • Spring AI加DeepSeek实现一个Prompt聊天机器人
  • 怎么判断我的电脑是否支持PCIe 5.0 SSD?Kingston FURY Renegade G5
  • Kotlin Map扩展函数使用指南
  • 批量地址解析坐标,支持WPS、EXCEL软件,支持导出SHP、GEOJSON、DXF等文件格式
  • 【Docker】【2.docker 安装 ubuntu 桌面版】
  • 单片机上的动态数码管
  • 怎么创建网站相册甘肃网站建设项目
  • 前端三剑客之一 CSS~
  • 仓颉语言运算符使用方法详解
  • 视频编码原理
  • 房管局网站建设网站备案要求