一、标准 IO 概述
标准 IO(Standard I/O)是 C 语言提供的一套文件操作接口,封装了底层的系统调用(如 read/write),并引入缓存机制提升 IO 效率。相比系统调用,标准 IO 具有跨平台性强、使用简单、缓存优化等优势,是 Linux C 编程中文件操作的首选方式。
1.1 核心头文件
所有标准 IO 接口均定义在<stdio.h>头文件中,使用前需引入:
c
运行
#include <stdio.h>
1.2 文件类型基础
在 Linux 系统中,文件可分为多种类型,标准 IO 可操作的核心类型包括:
表格
| 文件类型 | 标识 | 说明 | 典型场景 |
|---|---|---|---|
| 普通文件 | - | 存储数据的基础文件 | 文本、图片、音视频 |
| 字符设备文件 | c | 按字符交互的设备 | 键盘、终端、鼠标 |
| 块设备文件 | b | 按块读写的存储设备 | 硬盘、U 盘 |
| 目录文件 | d | 存放文件信息的文件夹 | 文件夹目录 |
| 链接文件 | l | 指向其他文件的引用 | 软链接 / 硬链接 |
| 套接字文件 | s | 进程间网络通信 | 网络编程 |
| 管道文件 | p | 进程间本地通信 | 匿名管道 / 命名管道 |
1.3 普通文件的两种形式
- ASCII 码文件:内容为可显示的 ASCII 字符(如代码、文本文件),本质是特殊的二进制文件;
- 二进制文件:存放二进制数据(如图片、音视频、压缩包),无法直接通过终端解析显示。
二、标准 IO 的缓存机制
标准 IO 通过缓存减少系统调用次数,提升效率,缓存分为三类:
表格
| 缓存类型 | 大小 | 刷新条件 | 适用场景 |
|---|---|---|---|
| 全缓存 | 4096 字节(4K) | 1. 缓存区满;2. 程序结束 /fclose;3. fflush 强制刷新 | 普通文件(如 txt、jpg) |
| 行缓存 | 1024 字节(1K) | 1. 缓存区满;2. 程序结束 /fclose;3. fflush 强制刷新;4. 遇到 \n | 终端(stdin、stdout) |
| 不缓存 | 0 字节 | 无缓存,直接写入 / 读取 | 错误输出(stderr)、人机交互 |
核心说明:
- 与文件关联的流默认是全缓存;
- 与终端关联的流(stdin/stdout)默认是行缓存;
- 标准错误流 stderr 默认不缓存,保证错误信息即时输出。
三、标准 IO 核心接口详解
3.1 文件打开与关闭
(1)fopen:打开文件并创建流
c
运行
FILE *fopen(const char *pathname, const char *mode);
- 功能:打开指定路径的文件,建立文件与流的关联;
- 参数 :
pathname:文件路径(绝对 / 相对路径);mode:打开模式(核心模式如下):
表格
| 模式 | 权限 | 文件不存在 | 文件存在 |
|---|---|---|---|
| r | 只读 | 报错(返回 NULL) | 只读打开 |
| r+ | 读写 | 报错(返回 NULL) | 读写打开 |
| w | 只写 | 创建新文件 | 清空内容后只写 |
| w+ | 写读 | 创建新文件 | 清空内容后写读 |
| a | 追加只写 | 创建新文件 | 从文件末尾追加写入 |
| a+ | 追加读写 | 创建新文件 | 读:任意位置;写:末尾追加 |
- 返回值:成功返回文件流指针(FILE*),失败返回 NULL(需检查 errno)。
(2)fclose:关闭文件流
c
运行
int fclose(FILE *stream);
- 功能:关闭文件流,刷新缓存并释放资源;
- 返回值:成功返回 0,失败返回 EOF(-1)。
注意:打开文件后必须关闭,否则会导致资源泄漏;多次关闭同一流会触发未定义行为。
3.2 字符级读写
(1)fputc:写入单个字符
c
运行
int fputc(int c, FILE *stream);
- 功能:向流中写入一个字符(c 为字符的 ASCII 码);
- 返回值:成功返回写入字符的 ASCII 码,失败返回 EOF。
(2)fgetc:读取单个字符
c
运行
int fgetc(FILE *stream);
- 功能:从流中读取一个字符;
- 返回值:成功返回字符 ASCII 码,失败 / 文件末尾返回 EOF。
等价简化:
c
运行
// 从标准输入读字符
ch = getchar(); // 等价于 ch = fgetc(stdin);
// 向标准输出写字符
putchar(ch); // 等价于 fputc(ch, stdout);
3.3 字符串级读写
(1)fputs:写入字符串
c
运行
int fputs(const char *s, FILE *stream);
- 功能 :向流中写入以
\0结尾的字符串(不自动加 \n); - 返回值:成功返回非负数,失败返回 EOF。
(2)fgets:读取字符串
c
运行
char *fgets(char *s, int size, FILE *stream);
- 功能 :从流中读取最多
size-1个字符(留 1 位存\0),遇到\n或 EOF 停止; - 参数 :
s:存储字符串的缓冲区;size:缓冲区大小;
- 返回值:成功返回缓冲区地址,失败 / 文件末尾返回 NULL。
fgets 与 gets 的区别:
- gets 会丢弃读取到的
\n,且无缓冲区大小限制(易缓冲区溢出); - fgets 保留读取到的
\n,且通过 size 限制读取长度(安全)。
fgets 模拟 gets 功能(去除 \n):
c
运行
fgets(str, sizeof(str), stdin);
str[strlen(str)-1] = '\0'; // 替换末尾的\n为字符串结束符
fputs 与 puts 的区别:
- puts 会自动在字符串末尾添加
\n; - fputs 仅写入字符串本身,不额外加
\n。
3.4 格式化读写
(1)fprintf:格式化写入
c
运行
int fprintf(FILE *stream, const char *format, ...);
- 功能:按指定格式向流中写入数据(类似 printf,printf 是 fprintf (stdout, ...) 的简化);
- 返回值:成功返回写入的字符数,失败返回负数。
(2)fscanf:格式化读取
c
运行
int fscanf(FILE *stream, const char *format, ...);
- 功能:按指定格式从流中读取数据(类似 scanf,scanf 是 fscanf (stdin, ...) 的简化);
- 返回值:成功返回匹配的项数,失败 / 文件末尾返回 EOF。
3.5 块级读写(二进制操作)
(1)fwrite:按块写入二进制数据
c
运行
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
- 功能:将 ptr 指向的内存数据,按 "每个对象 size 字节,共 nmemb 个对象" 写入流;
- 参数 :
ptr:待写入数据的内存首地址;size:单个对象的字节大小;nmemb:要写入的对象个数;
- 返回值:成功返回实际写入的对象个数,失败返回 0。
(2)fread:按块读取二进制数据
c
运行
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
- 功能:从流中读取 "每个对象 size 字节,共 nmemb 个对象",存入 ptr 指向的内存;
- 返回值:成功返回实际读取的对象个数,失败 / 文件末尾返回 0。
3.6 文件偏移量操作
(1)fseek:调整文件偏移量
c
运行
int fseek(FILE *stream, long offset, int whence);
- 功能:移动文件指针(偏移量)到指定位置;
- 参数 :
offset:偏移量(正数向后,负数向前);whence:偏移基准:SEEK_SET:文件开头;SEEK_CUR:当前位置;SEEK_END:文件末尾;
- 返回值:成功返回 0,失败返回 - 1。
(2)ftell:获取当前偏移量
c
运行
long ftell(FILE *stream);
- 功能:返回当前文件指针相对于文件开头的偏移量(字节数);
- 应用:结合 fseek (SEEK_END) 可获取文件大小。
(3)rewind:重置偏移量到文件开头
c
运行
void rewind(FILE *stream);
- 等价于 :
fseek(stream, 0, SEEK_SET);
3.7 状态判断
(1)feof:判断是否到达文件末尾
c
运行
int feof(FILE *stream);
- 返回值:非 0 表示到达末尾,0 表示未到达。
(2)ferror:判断流是否出错
c
运行
int ferror(FILE *stream);
- 返回值:非 0 表示流出错,0 表示正常;
- 出错后可通过
clearerr(stream)清除错误状态。
四、标准 IO 实战案例
案例 1:利用 fread/fwrite 实现图片文件拷贝
c
运行


编译运行:
bash
运行

案例 2:输入文件路径,打印文件大小
c
运行

运行:

案例 3:替换文件中指定字符串(string1→string2)
c
运行
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUF_SIZE 4096
// 替换字符串函数
void replace_str(char *buf, const char *old_str, const char *new_str) {
char temp[BUF_SIZE];
char *pos;
// 清空临时缓冲区
temp[0] = '\0';
// 查找旧字符串首次出现的位置
pos = strstr(buf, old_str);
while (pos != NULL) {
// 复制旧字符串前的内容
strncat(temp, buf, pos - buf);
// 拼接新字符串
strcat(temp, new_str);
// 移动指针到旧字符串末尾
buf = pos + strlen(old_str);
// 继续查找下一个旧字符串
pos = strstr(buf, old_str);
}
// 拼接剩余内容
strcat(temp, buf);
// 复制回原缓冲区
strcpy(buf, temp);
}
int main(int argc, char *argv[]) {
if (argc != 4) {
fprintf(stderr, "用法:%s 文件名 旧字符串 新字符串\n", argv[0]);
exit(EXIT_FAILURE);
}
FILE *fp = fopen(argv[1], "r+");
if (fp == NULL) {
perror("打开文件失败");
exit(EXIT_FAILURE);
}
char buf[BUF_SIZE];
size_t read_len = fread(buf, 1, BUF_SIZE-1, fp);
if (read_len == 0) {
perror("读取文件失败");
fclose(fp);
exit(EXIT_FAILURE);
}
buf[read_len] = '\0'; // 确保字符串结束
// 替换字符串
replace_str(buf, argv[2], argv[3]);
// 移动指针到文件开头,覆盖写入
rewind(fp);
fwrite(buf, 1, strlen(buf), fp);
printf("字符串替换完成!\n");
fclose(fp);
return 0;
}
编译运行:
bash
运行
gcc -o replace replace.c
./replace test.txt "old" "new"
五、标准 IO 使用注意事项
- 必须检查返回值:fopen、fread、fwrite 等接口的返回值是判断操作是否成功的关键,忽略返回值易导致隐藏错误;
- 二进制模式打开非文本文件 :操作图片、音视频等二进制文件时,需用
rb/wb模式,避免 Windows 下换行符(\n↔\r\n)转换导致数据损坏; - 避免缓冲区溢出:使用 fgets、fread 时严格限制缓冲区大小,禁止使用 gets(已被废弃);
- 及时关闭文件流:程序退出前必须 fclose,否则缓存中的数据可能丢失;
- 区分 EOF 的含义:EOF(-1)是宏定义,不是文件中的实际字符,仅用于标识读取结束 / 失败。
六、总结
标准 IO 是 Linux C 编程中文件操作的核心工具,其核心优势在于缓存机制和跨平台性。掌握 fopen/fclose 的打开模式、fread/fwrite 的块操作、fseek 的偏移量控制,以及缓存机制的特性,是实现高效、稳定文件操作的关键。本文通过基础讲解 + 实战案例,覆盖了标准 IO 的核心接口和常见应用场景,可作为日常开发的参考手册。
核心要点:
- 普通文件用全缓存,终端用行缓存,错误输出无缓存;
- 二进制文件操作必须用
rb/wb模式; - 块级读写(fread/fwrite)是二进制文件操作的首选;
- 所有 IO 操作必须检查返回值,避免隐藏错误。