1、IO编程
1.1 IO概念
I: Input(输入)
O:Output(输出)
是指计算机与外部设备之间的数据交互过程,是程序与外部环境交换数据的核心方式。
IO操作的对象是文件
1.2 一切皆是文件
文件类型:
b block 块设备文件 代表块存储设备,如硬盘、U 盘
系统以数据块为单位读写数据,按块扫描信息的文件
c character 字符设备文件 代表字符流设备,如键盘、鼠标、串口、终端系统以单个字符为单位流式读写,按字符扫描信息的文件
d directory 目录文件
存放文件或子目录信息的文件夹
– regular 普通文件
用于存储各类数据,无固定结构,可存放文本、二进制、压缩包、源码等任意数据
l link 链接文件
将一个文件链接到另一个文件,分为 软链接(符号链接) 和硬链接
软链接 = 快捷方式,硬链接 = 文件的别名 / 分身
限制:软链接可跨文件系统、可链接目录;硬链接不可跨文件系统、不可链接目录
- 软链接(l):独立文件,存储目标路径,删除原文件后软链接失效;
- 硬链接:无独立标识(显示为-),和原文件共用 inode,删除原文件不影响访问
- inode:系统分配的唯一数字编号,存储文件的元数据(权限、大小、存储位置、引用计数等),相当于文件的身份证号;
- 引用计数机制:inode 中有个链接计数,每创建一个硬链接,计数 + 1;删除一个链接(文件名),计数 – 1;只有计数归 0 时,系统才会真正删除文件数据
- 文件名:只是指向 inode 的标签 / 别名,用户通过文件名找到 inode,进而访问文件数据
- 系统判断两个文件是不是同一个文件,不看名字,只看 inode 号是否相同
- # 查看inode和详情 ls -li
s socket 套接字文件
用于本地进程间通信(IPC),也可实现网络通信,支持双向数据传输,效率高于管道,常见于系统服务(如 MySQL)
p pipe 管道文件
用于本地进程间单向通信,分为匿名管道和命名管道,数据单向流动,先进先出(FIFO),读写阻塞式传输
1.3 IO的接口分类
| 标准 IO(stdio) | 普通磁盘文件 | 有缓存的IO;用户态缓存,减少系统调用次数,效率高,跨平台性好 | C 标准库(stdio.h) |
| 文件 IO(系统调用) | 设备文件、网络通信、特殊文件 | 没有缓存的IO;无用户态缓存,直接与内核交互(直接调用内核 syscall),实时性强,平台相关 | Linux 系统调用(open/read/write) |
| 目录 IO | 目录文件 | 专用接口,仅用于遍历 / 操作目录结构 | dirent.h 系统头文件 |
2、标准IO
标准 IO 是C 语言标准库提供的 IO 操作接口,封装了底层系统调用,自带用户态缓冲区,适配所有支持 C 标准库的操作系统,是操作普通文件的首选方案。
2.1 标准IO头文件
所有标准 IO 函数均依赖该头文件,无其他额外依赖
#include <stdio.h>
2.2 核心接口分类
| 文件开关 | fopen(打开)、fclose(关闭) |
| 单字符读写 | fgetc(读单个字符)、fputc(写单个字符) |
| 字符串读写 | fgets(读一行字符串)、fputs(写字符串) |
| 格式化读写 | fscanf(格式化读)、fprintf(格式化写) |
| 二进制数据读写 | fread(块读)、fwrite(块写) |
| 文件定位 | fseek(偏移定位)、rewind(回到文件头)、ftell(获取当前偏移) |
| 缓冲区控制 | fflush(刷新缓冲区)、setvbuf(设置缓冲区模式) |
2.3 普通文件类型分类
(1)文本文件(ASCII 码文件):
- 存储内容:文件中存放的内容均为能够在终端显示的ASCII码字符
- 典型场景:代码源文件、配置文件、文本文件;
- 特点:可直接用文本编辑器打开查看。
(2)二进制文件
- 存储内容:存放二进制数据本身的文件(无字符编码转换);
- 典型场景:图片、音视频、可执行程序、压缩包;
- 特点:无法直接用文本编辑器正常解析,存储效率更高。
注意:ASCII码文件是一种特殊的二进制文件,二者本质都是二进制存储,区别仅在于数据编码规则和解析方式。
2.4 标准IO核心操作流程
打开文件:fopen 获取文件流指针(FILE*),建立程序与文件的关联;
数据交互:通过读写接口完成文件的读 / 写操作;
关闭文件:fclose 释放资源,刷新缓冲区,断开关联。
2.5 Linux/C 语言默认打开的流(与终端建立的流)
-
stdin(标准输入流):文件描述符 0,默认从键盘/终端读取
-
stdout(标准输出流):文件描述符 1,默认输出到终端
-
stderr(标准错误流):文件描述符 2,默认输出到终端
2.6 三种缓存类型
缓存的核心作用:减少磁盘 I/O/ 系统调用次数,提升程序运行效率,不同缓存策略适配不同使用场景。
(1)全缓存:(Full Buffering)4096(4k)
- 缓存区大小:Linux/glibc (通用库)默认约4096 字节(磁盘 IO 块大小常用值)
- 核心特性:缓存区写满后才统一刷新,是效率最高的缓存策略
- 刷新触发条件:
- 缓存区写满,自动刷新;
- 程序正常退出;
- 调用fclose()关闭流;
- 调用fflush()手动强制刷新;
- 无换行符触发机制(和行缓存核心区别)
- 默认适用对象:stdin/stdout 重定向到普通文件时、所有通过fopen打开的普通文件流
- 典型场景:文件读写、批量写入减少 I/O 开销等
(2)行缓存:(Line Buffering)
- 缓存区大小:Linux/glibc 默认约1024 字节(非标准强制规定)
- 核心特性:以换行符 \\n 为触发刷新的核心标识
- 刷新触发条件:
- 缓存区写满,自动刷新;
- 程序正常退出(main执行完毕);
- 调用fclose()关闭流;
- 调用fflush()手动强制刷新;
- 遇到换行符 \\n,立即刷新缓存区。
- 默认适用对象:stdin/stdout 连接终端时
- 典型场景:终端交互(命令行输出、用户输入),保证信息逐行实时显示
(3) 无缓存(No Buffering)
- 缓存区大小:0 字节,无临时存储区域
- 刷新规则:数据立即写入目标设备 / 文件,不做任何缓冲
- 强制刷新条件:无需手动刷新,写入即输出
- 默认适用对象:stderr(标准错误流,固定无缓存)
- 典型场景: 用于错误处理或者人机交互
3、核心函数接口
3.1 fopen—-打开文件,建立文件流
3.1.1 函数原型
FILE *fopen(const char *pathname, const char *mode);
3.1.2 功能
打开指定路径的文件,创建并返回文件流指针,建立程序与文件之间的数据通道
3.1.3 参数
| pathname | 文件路径(相对路径 / 绝对路径) 「文件所在的位置」 + 「文件名」 |
| mode | 文件打开模式,决定读写权限、文件创建 / 截断行为 |
3.1.4 打开模式详解
| r | 只读打开(文本模式) | 报错 | 正常打开 | 只读,从文件头部开始读取 |
| w | 只写打开,清空原有内容 | 创建新文件 | 清空重写 | 只写,从头部写入 |
| a | 追加只写(末尾添加) | 创建新文件 | 保留内容 | 只写,写入位置固定在文件末尾,不会覆盖原有内容 |
| r+ | 读写打开,从文件开头操作 | 报错 | 正常打开 | 可读可写 |
| w+ | 写读打开,文件存在则清空原有内容 | 创建新文件 | 清空重写 | 可读可写 |
| a+ | 读写打开,仅支持末尾追加 | 创建新文件 | 保留内容 | 可读可写,写入固定在末尾,读取可任意位置 |
补充:模式后加b(如rb/wb)表示二进制模式,Linux 系统下文本 / 二进制模式无差异,Windows 系统下会自动转换换行符。
3.1.5 返回值
- 成功:返回指向文件流的FILE*指针(流的首地址)
- 失败:返回NULL
3.2 fclose—关闭文件流
3.2.1 函数原型
int fclose(FILE *stream);
3.2.2 功能
关闭文件流,刷新缓冲区剩余数据,释放系统资源,断开程序与文件的关联。
重要:文件操作完成后必须调用fclose,避免数据丢失、文件句柄泄漏
3.2.3 参数
stream:通过fopen获取的文件流指针
3.2.4 返回值
- 成功:返回0
- 失败:返回EOF(宏定义,值为-1)
3.3 fputc—写入单个字符
3.3.1 函数原型
int fputc(int c, FILE *stream);
3.3.2 功能
向指定文件流中写入一个字符,写入后文件位置指针自动后移一位。
3.3.3 参数
| c | 待写入的字符(会被转换为unsigned char) |
| stream | 目标文件流指针 |
3.3.4 返回值
- 成功:返回写入字符的ASCII码值(无符号字符形式)
- 失败:返回EOF(-1)
// 向标准输出(终端)打印字符,二者完全等价
putchar(c); // 标准库宏/函数
fputc(c, stdout);
3.4 fgetc—读取单个字符
3.4.1 函数原型
int fgetc(FILE *stream);
3.4.2 功能
从指定文件流中读取一个字符,读取后文件位置指针自动后移一位。
3.4.3 参数
stream:源文件流指针
3.4.4 返回值
- 成功:返回写入字符的ASCII码值(无符号字符形式)
- 失败:返回EOF(-1)
注意:返回值用int接收,用于区分有效字符和EOF(-1)
// 从标准输入(键盘)读取字符,二者完全等价
getchar();
fgetc(stdin);
3.5 fputs—写入字符串(无换行)
3.5.1 函数原型
int fputs(const char *s, FILE *stream);
3.5.2 功能
将以\\0结尾的字符串写入指定文件流,不会自动写入字符串结束符\\0,也不会自动添加换行符。
3.5.3 参数
| s | 待写入的字符串首地址 |
| stream | 目标文件流指针 |
3.5.4 返回值
- 成功:返回非负整数(通常为0)
- 失败:返回EOF(-1)
// 向终端输出字符串,二者不完全等价
puts(s); // 会自动在末尾添加换行符
fputs(s, stdout);// 不会自动添加换行符
3.6 fgets—读取指定长度字符串
3.6.1 函数原型
char *fgets(char *s, int size, FILE *stream);
3.6.2 功能
从指定文件流中读取最多size-1个字符,存储到s指向的缓冲区,自动在末尾添加字符串结束符\\0;遇到换行符\\n 或文件末尾时提前停止读取。
参数
表格
| s | 存储读取数据的缓冲区首地址 |
| size | 缓冲区最大容量(读取长度 = size-1) |
| stream | 源文件流指针 |
返回值
- 成功:返回缓冲区指针s
- 读到文件末尾 / 失败:返回NULL
3.7 fprintf—格式化写入文件流
3.7.1 函数原型
int fprintf(FILE *stream, const char *format, …);
3.7.2 功能
按指定格式向文件流中写入字符串 / 数据(类似printf,但输出目标是文件流而非终端)。
3.7.3 参数
| stream | 目标文件流指针(如stdout输出到终端,fp输出到文件) |
| format | 格式控制字符串(和printf完全一致,如%d/%s/%f) |
| … | 可变参数,对应格式字符串中的占位符(如fprintf(fp, "%d", 10)中的10) |
3.7.4 返回值
- 成功:返回实际写入的字符总数
- 失败:返回负数(通常为-1)
示例:
// 向文件写入格式化数据
fprintf(fp, "姓名:%s,年龄:%d\\n", "张三", 20);
// 向终端输出(等价于printf)
fprintf(stdout, "计算结果:%f\\n", 3.1415);
3.8 fscanf—格式化读取文件流
3.8.1 函数原型
int fscanf(FILE *stream, const char *format, …);
3.8.2 功能
按指定格式从文件流中读取数据(类似scanf,但读取源是文件流而非键盘)。
3.8.3 参数
| stream | 源文件流指针(如stdin从键盘读取,fp从文件读取) |
| format | 格式控制字符串(和scanf完全一致,如%d/%s/%f) |
| … | 可变参数,接收数据的变量地址(如fscanf(fp, "%d", &age)中的&age) |
3.8.4 返回值
- 成功:返回成功匹配并读取的参数个数
- 失败 / 读到文件末尾:返回EOF(-1)
示例:
char name[20];
int age;
// 从文件读取姓名和年龄
fscanf(fp, "%s %d", name, &age);
// 从键盘读取(等价于scanf)
fscanf(stdin, "%d", &age);

![第5章,[Win32 章节] :绘制填充区域-171主机测评](https://www.171host.com/wp-content/uploads/2026/08/20260828173251-6a91c6434b552-220x150.png)


