嵌入式 - Linux软件编程:标准IO
目录
一、标准 IO 基础概念
二、标准 IO 核心接口与操作流程
1. 打开与关闭文件
fopen 函数:用于打开文件并建立流,原型为FILE *fopen(const char *pathname, const char *mode)。
核心区别对比表
详细说明与关键差异
重要注意事项
示例
fclose 函数:用于关闭已打开的流,原型为int fclose(FILE *stream)。
2. 读写操作接口
字符级读写:fputc(写字符)和fgetc(读字符)。例如,fputc('A', fp)将字符 'A' 写入流fp,fgetc(fp)从流fp中读取一个字符;
字符串级读写:fputs(写字符串)和fgets(读字符串)。fputs("hello", fp)写入字符串 "hello",fgets(buf, 100, fp)从流中最多读取 99 个字符到buf(预留一个位置存结束符 '\0');
格式化读写:fprintf(格式化输出)和fscanf(格式化输入)。例如,fprintf(fp, "name: %s", name)按格式写入数据,fscanf(fp, "%d", &num)从流中读取整数到num;
3. 定位操作
4. 操作流程示例
三、特殊流与缓存机制
1. 三个默认打开的流
2. 缓存机制
四、man 手册:查询标准 IO 的利器
五、实战
1. 文件拷贝(fgets 与 fputs 实现)
2. 带记忆功能的数字平均值计算
在 Linux 系统编程中,IO 操作是与外部设备交互的核心环节。其中,标准 IO 作为 C 语言库提供的高级 IO 接口,凭借其缓存机制和简洁的接口设计,成为处理文件和设备交互的常用选择。本文将从标准 IO 的基础概念出发,详细解析其核心接口、缓存机制,并结合实践任务帮助理解与应用。
一、标准 IO 基础概念
标准 IO(Standard I/O)是基于 C 语言库实现的 IO 接口,其设计初衷是简化用户对文件的操作,并通过缓存机制提高 IO 效率。与直接操作内核的文件 IO 不同,标准 IO 为开发者提供了更上层的抽象,屏蔽了底层硬件的细节差异。
标准 IO 的操作对象主要是普通文件,而普通文件又可分为两类:
- ASCII 码文件:所有内容均为可在屏幕上显示的 ASCII 字符,如程序源码、文本文件等,这类文件可直接通过文本编辑器阅读;
- 二进制文件:内容以数据的二进制形式存储,包含不可显示的字符,如图片、音视频、压缩包等。需要注意的是,ASCII 码文件本质上也是一种特殊的二进制文件。
无论是哪种文件,标准 IO 都通过 “流(stream)” 的概念进行操作 —— 打开文件后,程序与文件之间建立一个流,所有读写操作都通过流来完成。
二、标准 IO 核心接口与操作流程
标准 IO 的操作遵循 “打开 - 读写 - 关闭” 的基本流程,围绕这一流程提供了一系列接口函数。
标准 IO 的接口:
- fopen/fclose
- fgetc/fputc
- fgets/fputs
- fscanf/fprintf
- fread/fwrite
- fseek/rewind/ftell
- 操作步骤:
- 打开文件
- 读写文件
- 关闭文件
1. 打开与关闭文件
-
fopen 函数:用于打开文件并建立流,原型为
FILE *fopen(const char *pathname, const char *mode)
。
原型:FILE *fopen(const char *pathname, const char *mode);
功能:
打开pathname指向字符串对应的文件,并且和它建立一个流
参数:
pathname:要打开的文件路径字符串
mode:打开的方式
r 只读 文件存在只读打开,文件不存在报错
r+ 读写 文件存在读写打开,文件不存在报错
w 只写 文件存在清0,文件不存在创建,只写打开
w+ 写读 文件存在清0,文件不存在创建,写读打开
a 追加 文件存在追加,文件不存在创建,只写打开
a+ 追加读写 文件存在追加,文件不存在创建,写读打开
返回值:
成功返回FILE*指针
失败返回NULL
补充:
C语言
fopen
函数的几种主要模式及其核心区别总结如下:核心区别对比表
模式 读写权限 文件存在时 文件不存在时 文件指针初始位置 主要用途 "r"
只读 打开 打开失败 开头 安全读取已有文件 "r+"
读写 打开 打开失败 开头 读取并修改已有文件内容 "w"
只写 清空内容(长度归零) 创建新文件 开头 覆盖写入或创建新文件 "w+"
读写 清空内容(长度归零) 创建新文件 开头 清空文件后读写(从头操作) "a"
只写 保留内容 创建新文件 末尾(追加写入) 日志追加(防覆盖旧数据) "a+"
读写 保留内容 创建新文件 读-开头 / 写-末尾 🌟 读取后追加数据(如更新日志) 详细说明与关键差异
**
"r"
(只读)**
- 最严格:文件必须存在,否则失败。
- 指针在开头,适合仅读取场景(如配置文件解析)。
**
"r+"
(读写)**
- 文件必须存在,否则失败。
- 可修改任意位置(指针可自由移动),但不会自动清空文件(需手动覆盖内容)。
**
"w"
(只写)**
- 破坏性最强:立即清空文件!
- 适合覆盖写入(如生成新报告),误用会导致数据丢失⚠️。
**
"w+"
(读写)**
- 同
"w"
会先清空文件,但支持读取(清空后从头读写)。- 可用于重建文件结构(如初始化空数据库文件)。
**
"a"
(追加只写)**
- 最安全:永不覆盖原有内容,新数据总是写在末尾。
- 适合日志记录、连续数据采集(如传感器数据保存)。
**
"a+"
(追加读写)** 🌟
- 唯一分离式指针:
- 读取时从开头开始(可读历史数据)。
- 写入时强制跳到末尾(避免中间插入破坏数据)。
- 典型场景:读取日志分析后追加新条目。
重要注意事项
- **
"w"
/"w+"
的破坏性:打开即清空,务必确认文件可覆盖**!- **
"a"
/"a+"
的写入特性:
即使使用fseek()
移动指针,写入时仍会强制回到末尾**(追加是绝对操作)。- **
"r"
/"r+"
的必要性**:若文件可能不存在,需先检查(如access()
)或改用"a"
/"w"
系。- 二进制文件:在模式后加
"b"
(如"rb+"
),处理非文本数据(如图像)。示例
// 安全追加日志(文件不存在则创建) FILE *log = fopen("debug.log", "a+"); if (log) {fseek(log, 0, SEEK_SET); // 跳到开头读取历史char buf[256];while (fgets(buf, sizeof(buf), log)) { /* 分析日志 */ }fprintf(log, "[New] Event recorded.\n"); // 自动追加到末尾fclose(log); }
-
fclose 函数:用于关闭已打开的流,原型为
int fclose(FILE *stream)
。- 关闭成功返回 0,失败返回 EOF(-1);
- 关闭流的同时会刷新缓存,因此操作完成后务必关闭流,避免数据丢失。
原型:int fclose(FILE *stream);
功能:
关闭已经打开的流
参数:
stream:文件流指针
返回值:
成功返回0,失败返回EOF(-1)
2. 读写操作接口
标准 IO 提供了多种读写函数,适用于不同场景:
-
字符级读写:
fputc
(写字符)和fgetc
(读字符)。例如,fputc('A', fp)
将字符 'A' 写入流fp
,fgetc(fp)
从流fp
中读取一个字符;
fputc
原型:int fputc(int c, FILE *stream);
功能:
将字符c写入流中
参数:
c:要写入的字符
stream:文件流指针
返回值:
成功返回输出的ASCII码值
失败返回EOF
注意: putchar()等价于fputc(ch, stdout)
fgetc
原型:int fgetc(FILE *stream);
功能:
读取流中的下一个字符
参数:
stream:文件流指针
返回值:
成功返回读到字符的ASCII码值
失败或者读到文件末尾返回EOF
注意: ch = getchar()等价于 ch = fgetc(stdin)
-
字符串级读写:
fputs
(写字符串)和fgets
(读字符串)。fputs("hello", fp)
写入字符串 "hello",fgets(buf, 100, fp)
从流中最多读取 99 个字符到buf
(预留一个位置存结束符 '\0'); fputs
原型:int fputs(const char *s, FILE *stream);
功能:
向流中写入s指向的字符串
参数:
s:要写入的字符串的首地址
stream:文件流指针
返回值:
成功返回非负数
失败返回EOF
注意: fputs不会多打印\n字符 puts会多打印一个\n字符
fgets
原型:char *fgets(char *s, int size, FILE *stream);
功能:
从流中最多读size-1个字节数据放入s指向的空间中,遇到\n读取截止
参数:
s:存放数据空间的首地址
size:最多存放元素的个数
stream:文件流指针
返回值:
成功返回存放数据空间的首地址
失败返回NULL
读到文件末尾返回NULL
注意: gets会去掉从终端接收的\n字符 fgets不会去掉从终端接收的\n字符
-
格式化读写:
fprintf
(格式化输出)和fscanf
(格式化输入)。例如,fprintf(fp, "name: %s", name)
按格式写入数据,fscanf(fp, "%d", &num)
从流中读取整数到num
; fprintf
原型:int fprintf(FILE *stream, const char *format, ...);
功能:
将格式化的字符串打印在流中
参数:
stream:文件流指针
format:格式化的字符串
...:参数
返回值:
成功返回打印字符的个数
失败返回负数
fscanf
原型:int fscanf(FILE *stream, const char *format, ...);
功能:
从流中接收格式化的字符串
参数:
stream:文件流指针
format:格式化字符串
...:参数
返回值:
成功返回匹配的输入控制符的个数
失败返回EOF
读到文件末尾返回EOF
- 块级读写:
fread
和fwrite
,适用于二进制文件,可一次性读写指定大小的数据块。
3. 定位操作
fseek
:用于移动文件指针,原型int fseek(FILE *stream, long offset, int whence)
,可实现文件内的随机访问;ftell
:返回当前文件指针位置;rewind
:将文件指针重置到文件开头。
4. 操作流程示例
// 打开文件
FILE *fp = fopen("test.txt", "w+");
if (fp == NULL) {perror("fopen error");return -1;
}// 写入数据
fputs("hello standard IO", fp);// 移动指针到开头
rewind(fp);// 读取数据
char buf[100];
fgets(buf, sizeof(buf), fp);
printf("read: %s\n", buf);// 关闭文件
fclose(fp);
三、特殊流与缓存机制
1. 三个默认打开的流
程序启动时,系统会自动打开三个特殊流,无需手动 fopen:
stdin
:标准输入流,对应终端输入(如键盘);stdout
:标准输出流,对应终端输出;stderr
:标准出错流,用于输出错误信息。
-
printf、puts、putchar都是通过stdout流实现在终端信息打印
-
scanf、gets、getchar都是通过stdin流实现获得终端信息
-
perror通过stderr流实现在终端打印出错信息
2. 缓存机制
标准 IO 的高效性源于其缓存机制 —— 数据先写入缓存,满足一定条件后再批量写入内核或设备,减少系统调用次数。缓存分为三类:
.标准IO缓存:
- 标准IO是一种有缓存的IO
- 效率高
- 实时性差
- 全缓存:缓存区满(通常 4k)时刷新,或通过
fflush
强制刷新、关闭流时刷新,适用于普通文件;-
缓存区满刷新缓存
刷新条件:
缓存区满4k刷新
fflush函数强制刷新
fclose或者程序关闭刷新
与文件建立的缓存一般为全缓存
-
- 行缓存:遇到换行符
\n
或缓存区满(通常 1k)时刷新,适用于终端(stdin
、stdout
);-
遇到\n刷新缓存区
刷新条件:
缓存区满1k刷新
遇到\n刷新
fflush函数强制刷新
fclose或者程序关闭刷新
与终端建立的缓存一般为行缓存(stdin、stdout均为行缓存)
-
- 不缓存:数据立即刷新,无缓存,适用于
stderr
(保证错误信息及时输出)。-
立即刷新不缓存
刷新条件:
不缓存立即刷新
出错处理或者人机交互一般使用不缓存(stderr不缓存)
-
注意:fflush(FILE *stream)
可强制刷新指定流的缓存,在实时性要求高的场景(如日志输出)中常用。
四、man 手册:查询标准 IO 的利器
在 Linux 中,man
手册是查询函数用法的权威工具,标准 IO 相关函数主要分布在第 3 章节(库函数)。
1. 章节:
1. 标准命令
2. 系统调用
3. 库函数
4. 设备说明
5. 文件格式
6. 娱乐
7. 杂项
8. 管理员命令
- 查看
fopen
用法:man 3 fopen
;
五、实战
1. 文件拷贝(fgets 与 fputs 实现)
任务:利用 fgets 和 fputs 拷贝文件
#include<stdio.h>
// 1. 利用fgets和fputs实现文件内容的拷贝int main(void)
{FILE *fp1 = NULL;FILE *fp2 = NULL;char file1[256] = {0};char file2[256] = {0};printf("请输入源文件:\n");gets(file1);printf("请输入目的文件:\n");gets(file2);fp1 = fopen(file1,"r");if(fp1 == NULL){perror("fail to fopen");return -1;}fp2 = fopen(file2,"w");if(fp2 == NULL){perror("fail to fopen");return -1;}char tmpbuff[1024] = {0};char *pret = 0;while(1){pret = fgets(tmpbuff,sizeof(tmpbuff),fp1);if(NULL == pret){break;}fputs(tmpbuff,fp2);}fclose(fp1);fclose(fp2);return 0;
}
结果:
2. 带记忆功能的数字平均值计算
任务:从终端接收数字,实时显示所有数字的平均值;程序关闭后重启,仍能显示历史所有数字的平均值。
// 2. 从终端接收数字,将数字存放在文件中,程序实时显示当前所有数字的平均数,
//如果程序关闭,再次启动时应该能够显示之前用户输入的所有数字的平均值(具有记忆功能)
// 1
// 2
// 3
// 4
// 5// 平均值:3.00
#include<stdio.h>int main(void)
{FILE *fp = NULL;float num = 0.0;int pret = 0;float sum = 0.0;int cnt = 0;float avg = 0.0;fp = fopen("a.txt","r");if(NULL == fp){perror("fail to fopen");return -1;}if(EOF == fscanf(fp,"%f",&num)){printf("当前文档内无数据\n");}else{ sum = num;cnt = 1;while(1){pret = fscanf(fp,"%f",&num);if(EOF == pret){break;}sum += num;cnt++;}avg = sum / cnt;printf("[历史记录] %d 个数据,平均值: %.3f\n",cnt,avg);}fclose(fp);//本次输入部分fp = fopen("a.txt","a");if(NULL == fp){perror("fail to fopen");return -1;}printf("请输入数字(输字母退出):\n"); //if (scanf("%lf", &number) != 1) break; //'1' 返回成功读取的变量数量while(1){ ///fscanf 返回成功读取的变量数量//输入字母时fscanf(stdin, "%f",&num)返回0(匹配失败)pret = fscanf(stdin,"%f",&num); if(EOF == pret || 0 == pret)break;fprintf(fp,"%f\n",num);sum += num;cnt++;avg = sum / cnt;printf("此为第%d个数据,当前平均值为: %.3f\n",cnt,avg);}fclose(fp);return 0;
}
结果:
diff: