一、学习内容
I/O进程
#### 标准IO
1.
##### 概念
1. 针对文件的读写操作 文件IO最终达成的目的:将一个临时存在于内存中的数据,永久性的存放于磁盘当中
2.
##### 操作
1. 文件IO的操作,需要这样的2个指针 一个指针:指向源数据,提供读取操作的指针 另一个指针:指向用来接受数据的文件的地址 该指针需要提前准备:并且确定好该指针到底指向哪个文件
#### 创建一个文件指针
1.
##### fopen
1.
###### 函数原型
1. FILE \*fopen(const char \*pathname, const char \*mode);
2.
###### 函数描述
1. 打开一个文件,成功打开之后,返回指向该文件的文件指针
3.
###### 调用形式
1.
```cpp
FILE* fp = fopen("文件名","r");
FILE* fp = fopen("文件名","w");
FILE* fp = fopen("文件名","a");
FILE* fp = fopen("文件名","r+");
....
if(fp == NULL){
printf("文件打开失败\n")
return 0;
}
```
4.
###### 函数返回值
1. 成功返回指向打开文件的文件指针,失败返回NULL
5.
###### 参数分析
1.
###### 参数 pathname
1. 准备打开的文件的路径名
2.
###### 参数 mode
1. 文件的打开形式
3.
###### r
1. 以只读的形式,打开文件,文件成功打开之后,光标位于文件的开头
2. 注意:如果文件不存在,r打开会失败,并且fopen函数返回一个NULL
3. 光标位于文件的开头:光标在文件中第一个数据的地址上
4. 光标位于文件的末尾:光标在文件的结束符的地址上,文件结束符的前一个数据,大概率不是文件中的最后一个数据,而是一个换行符
4.
###### r+
1. 以读写的形式打开文件,其他内容和r一样
2. 注意:r+存在读写2个光标 读写2个光标是独立管理的,但是如果不做特殊操作的话,读写两个光标是同时移动的
5.
###### w
1. 以只写的形式打完开文件,如果文件存在,则清空文件内容后打开,如果文件不存在,则创建文件后打开。文件成功打开后,光标定位在文件的开头 大概率上来说,以w打开文件,一般都会成功 只有在文件打开数量超过系统允许的上限的情况下,才会打开失败
2. 系统默认允许的文件打开上限为:1024个 也可以使用shell指令: ulimit -a 查看
6.
###### w+
1. 以读写的形式打开文件,剩下的就和w是一样了
7.
###### a
1. 以追加的形式打开文件,如果文件不存在则创建文件后打开,如果文件存在,不会清空文件内容,直接打开。文件打开后,光标位于文件的末尾
8.
###### a+
1. 以追加写和读的形式打开文件啊,如果文件不存在,则创建后打开。如果文件存在,则直接打开。
2. 注意:文件打开后,读光标位于文件的开头,并随着文件读取慢慢的向后移动。写光标总是位于文件的末尾,并不会随着读光标的移动而移动
#### 关闭一个文件
1.
##### fclose
1.
###### 函数原型
1. int fclose(FILE \*stream);
2.
###### 函数描述
1. 通过给定的文件指针stream关闭文件
3.
###### 函数调用
1. fclose(fp)
#### 文件指针FILE\*类型
1.
##### FILE本质上是一个typedef的结构体
1.
###### 查看系统自带的结构体类型
1.
```cpp
cd /usr/include 专门用来存放头文件的目录
sudo ctags -R 在存放头文件的目录里面,创建一个搜索列表
vim -t 想要查看的数据名 前两步是一次性操作,之后想要查看其他内容,直接执行第三步即可
```
1.
###### 输入 vim -t FILE 显示如下画面,我们要的是 红框中的内容,所以键入1,按回车
1. ![](https://i-blog.csdnimg.cn/direct/c1586943b6034d3785dfc9e6c9d7eb68.png)
2.
###### 查看后得知,FILE typedef 自 _IO_FILE 类型,继续追踪,鼠标点到 _IO_FILE 上面,键入 ctrl+ \]
1. ![](https://i-blog.csdnimg.cn/direct/45c0dd91f3e74bd2ad5922d6df94db2c.png)
3.
###### 此时又会找到2个搜索目标,应该是第一个,键入1,按回车
1. ![](https://i-blog.csdnimg.cn/direct/92dee89d0c11486d9ace766315ccdf56.png)
2.
###### FILE的定义如
1.
```cpp
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; 当前读光标的位置
char* _IO_read_end; 文件结束符的位置
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; 写光标的位置
char* _IO_write_end; 文件结束符为止
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
int _fileno; 最核心的,最终通过该数据定位文件的
};
```
#### 3个特殊的FILE\* 文件
1.
##### stdin
1.
###### 指向了终端标准输入流文件的指针
1. scanf,gets,getchar 等,都是默认的使用了这个文件流指针
2.
##### stdout
1.
###### 指向了终端标准输出流文件的指针
1. printf,puts,putchar 等,都是默认的使用了这个文件流指针
3.
##### stderr
1.
###### 指向了终端标准错误流的指针
1.
###### 标准错误流
1. 目前来说流向只有2种,代码流向终端 以及 终端流向代码 标准错误流:属于代码流向终端
1. stdout也属于代码流向终端,那么stderr 和 stdout的区别
1. stderr的存在目的就是为了区分stdout,从而实现 正常信息通过 stdout输出到终端 错误信息通过 stderr 可选择的输出到任意文件中 从而实现信息的划分
2. 谁在用stderr
1. perror函数
1. 函数原型
1. void perror(const char \*s);
2. 功能描述
1. perror函数,先输出s(提示信息):然后会根据一个标准的错误编号,输出该错误编号所描述的错误内容 错误代码哪来的:最后一个调用的系统函数或者库函数,因为错误调用而产生的 说人话就是:perror输出因为什么原因发生了错误
#### 针对文件的第一组读写函数
1.
##### fputc
1.
###### 函数原型
1. int fputc(int c, FILE \*stream);
2.
###### 功能描述
1. 将c的字符形式,写入到stream指向的文件中去
3.
###### 调用形式
1. FILE\* fp = fopen(文件名,"w") fputc(字符,fp)
4.
###### 参数解析
1. 参数 c:想要输出的数据
2. 参数 stream:准备接受数据的文件的地址
2.
##### fgetc
1.
###### 函数原型
1. int fgetc(FILE \*stream);
2.
###### 功能描述
1. 从stream指向的文件中,读取1个字节的数据
3.
###### 参数描述
1. 参数 stream:想要读取的文件的地址
4.
###### 返回值
1. 成功读取,会以 unsigned char的形式,返回读取到的那一个字节的数据
2. 如果读取到文件结束符,则返回 EOF (-1) 如果读取发生错误,也会返回 EOF
#### 针对文件的第二组读写函数
1.
##### fprintf / sprintf
1.
###### 函数原型
1. int printf(const char \*format, ...); int fprintf(FILE \*stream, const char \*format, ...);
2. int sprintf(char \*str, const char \*format, ...);
2.
###### 功能描述
1. printf功能:将字符串format中的内容输出到stdout指向的文件中去(stdout指向终端) fprintf功能:将字符串format中的内容输出到stream指向的文件中去(stream指向哪个文件由fopen函数决定)
2. 将format中的数据写入str指向的字符数组中去 相当于实现了把任意类型的数据,转换成字符串的功能
3.
###### 调用形式
1.
```cpp
FILE* fp = fopen(文件名,"w")
fprintf(fp,"%d",123)
fprintf(fp,"%c",'c')
fprintf(fp,"%s","abc")
fprintf(fp,"%s","123")
....
```
2.
```cpp
har str[20] = {0};
sprintf(str,"%lf",3.1416)
最终 str == "3.1416"
```
4.
###### 参数解析
1. 第一个参数 stream:指向接受数据的文件的指针
2. 第二个参数 format:想要输出到文件中的数据
3. 第三个参数 ...:format中格式占位符具体表示的数据
4. 注意:fprintf,无论将什么数据写入文件后,这些数据都会以字符串的形式进行保存
5.
###### 总结
1. fprintf/sprintf 整体上来说用法一模一样,只不过不同的printf将数据写入的地方是不一样的
2. printf:默认将数据写入终端
3. fprintf:将数据写入第一个参数stream指向的文件中去
4. sprintf:将数据写入第一个参数str指向的字符数组中去
2.
##### fscanf / sscanf
1.
###### fscanf
1.
###### 函数原型
1. int fscanf(FILE \*stream, const char \*format, ...);
2.
###### 功能描述
1. 从stream指向的文件中读取数据,写入到 format 中格式占位符所代表的变量中去
3.
###### 调用形式
1. FILE\* fp = fopen(文件名,"r") int a = 0; fscanf(fp,"%d",\&a)
4.
###### 参数解析
1. 第一个参数 stream:指向了想要读取数据的文件的指针
2. 第二个参数 format:只含有格式占位符的字符串
3. 第三个参数 ... :格式占位符代表的变量的地址
4. 注意: %s除了空格和回车不吸收以外,其他都吸收 %d只吸收int类型数据,碰到int类型以外的数据,立刻停止吸收 %lf吸收int和浮点型,遇到单独.和除.以外的其他字符,立刻停止吸收 %c吸收一切,包括空格和回车
5.
###### 返回值
1. 成功吸收,返回吸收到的数据的项数,一般就是描述符的个数
2. 如果文件吸收完毕 或者 吸收发生错误,则返回 EOF
2.
###### sscanf
1.
###### 函数原型
1. int sscanf(const char \*str, const char \*format, ...);
2.
###### 功能描述
1. 从字符串/字符数组 str中吸收数据,写入到format中的格式占位符所代表的变量的地址上面去 实际实现:将字符串类型的数据,转换成可转换的任意类型数据
3.
###### 函数调用
1.
```cpp
char str[32] = "123abc"
int a = 0;
char b[20] = {0};
sscanf(str,"%d%s",&a,b);
最终 a == 123,b=="abc"
```
脑图
二、作业
题目1、
cpp
有如下结构体:
typedef struct Student{
char name[20];
int id;
double chinese;//语文成绩
double math;
double english;
double physical;
double chemical;
double biological;
}stu_t;
有一个 stu_t的结构体数组 arr[3];随便使用任何方式初始化这个数组中的3个结构体
编写2个函数 :save_stu 和 load_stu
save_stu:通过 fprintf 将arr数组中的3个学生的所有信息,保存到文件中去
load_stu:通过 fscanf 将文件中的3个学生的所有信息,读取到一个新的结构体数组中,并输出所有学生的信息
代码解答:
cpp
#include <stdio.h>
#include <string.h>
// 定义一个学生结构体,包含学生的姓名、学号及各科成绩
typedef struct Student {
char name[20]; // 学生姓名,最多 19 个字符(+1 个 '\0' 结束符)
int id; // 学生学号
double chinese; // 语文成绩
double math; // 数学成绩
double english; // 英语成绩
double physical; // 物理成绩
double chemical; // 化学成绩
double biological; // 生物成绩
} stu_t;
// 输入学生信息
void arr_input(stu_t arr[]) {
int i; // 循环变量
for(i = 0; i < 3; i++) { // 循环 3 次,输入 3 个学生的信息
printf("请输入第%d个学生信息\n", i + 1); // 提示输入第几个学生
printf("姓名: "); // 提示输入姓名
scanf("%s", arr[i].name); // 读取学生姓名并存入结构体
printf("学号: "); // 提示输入学号
scanf("%d", &arr[i].id); // 读取学生学号并存入结构体
printf("语文成绩: "); // 提示输入语文成绩
scanf("%lf", &arr[i].chinese); // 读取语文成绩
printf("数学成绩: "); // 提示输入数学成绩
scanf("%lf", &arr[i].math); // 读取数学成绩
printf("英语成绩: "); // 提示输入英语成绩
scanf("%lf", &arr[i].english); // 读取英语成绩
printf("物理成绩: "); // 提示输入物理成绩
scanf("%lf", &arr[i].physical); // 读取物理成绩
printf("化学成绩: "); // 提示输入化学成绩
scanf("%lf", &arr[i].chemical); // 读取化学成绩
printf("生物成绩: "); // 提示输入生物成绩
scanf("%lf", &arr[i].biological); // 读取生物成绩
}
}
// 保存学生信息到文件
void save_stu(stu_t arr[]) {
FILE *fp = fopen("students.txt", "w"); // 打开文件 "students.txt",以写入模式
if (fp == NULL) { // 检查文件是否成功打开
printf("文件打开失败!\n"); // 文件打开失败时输出错误信息
return; // 返回
}
for (int i = 0; i < 3; i++) { // 循环 3 次,保存 3 个学生的信息
// 使用 fprintf 将每个学生的信息格式化写入文件
fprintf(fp, "%s %d %.2f %.2f %.2f %.2f %.2f %.2f\n",
arr[i].name, arr[i].id, arr[i].chinese, arr[i].math,
arr[i].english, arr[i].physical, arr[i].chemical, arr[i].biological);
}
fclose(fp); // 关闭文件
printf("学生信息已保存到文件\n"); // 提示信息保存成功
}
// 从文件加载学生信息
void load_stu(stu_t arr[]) {
FILE *fp = fopen("students.txt", "r"); // 打开文件 "students.txt",以读取模式
if (fp == NULL) { // 检查文件是否成功打开
printf("文件打开失败!\n"); // 文件打开失败时输出错误信息
return; // 返回
}
for (int i = 0; i < 3; i++) { // 循环 3 次,读取 3 个学生的信息
// 使用 fscanf 从文件中读取学生的信息,并存入结构体数组
fscanf(fp, "%s %d %lf %lf %lf %lf %lf %lf",
arr[i].name, &arr[i].id, &arr[i].chinese, &arr[i].math,
&arr[i].english, &arr[i].physical, &arr[i].chemical, &arr[i].biological);
}
fclose(fp); // 关闭文件
// 输出从文件加载的学生信息
printf("从文件加载的学生信息:\n");
for (int i = 0; i < 3; i++) { // 循环输出每个学生的信息
printf("姓名: %s, 学号: %d, 语文: %.2f, 数学: %.2f, 英语: %.2f, 物理: %.2f, 化学: %.2f, 生物: %.2f\n",
arr[i].name, arr[i].id, arr[i].chinese, arr[i].math,
arr[i].english, arr[i].physical, arr[i].chemical, arr[i].biological);
}
}
int main() {
stu_t arr[3]; // 定义一个学生结构体数组,包含 3 个学生
arr_input(arr); // 调用函数输入 3 个学生的信息
save_stu(arr); // 调用函数将学生信息保存到文件
load_stu(arr); // 调用函数从文件加载学生信息并输出
return 0; // 程序正常结束
}
成果展现:
三、总结
学习内容概述:
1. I/O进程基础:
I/O进程是操作系统中的一个重要部分,负责管理输入输出操作。
涉及标准输入、标准输出、文件描述符等概念。
包括阻塞、非阻塞I/O、异步I/O等不同I/O模型。
2. 具体的I/O操作方式:
`read()`和`write()`:系统调用实现读取和写入操作。
缓冲区的管理:包括内核缓冲区与用户缓冲区的交互。
文件的打开、关闭、定位等操作,使用`open()`, `close()`, `lseek()`等系统调用。
3. 进程管理与I/O的关:
进程可以通过I/O操作与外部世界进行交互。
进程阻塞和非阻塞在I/O操作中的应用,如何避免因I/O操作导致的进程停滞。
学习难点:
1. 阻塞与非阻塞I/O模型的理解与应用:
如何区分阻塞和非阻塞操作,以及这两者对进程调度和CPU利用率的影响。
在实际应用中选择合适的I/O模型。
注意事项:
1. 系统调用的返回值检查:
对每次系统调用的返回值进行检查,确保I/O操作的成功与失败能够被正确处理,避免资源泄露。
2. 缓冲区大小的设置:
使用合适大小的缓冲区,以平衡系统资源与性能,避免内存浪费或I/O频繁的上下文切换。
3. 文件描述符的管理:
在处理大量文件描述符时,确保正确管理文件描述符,防止句柄泄漏。
未来学习重点:
1. 深入理解各类I/O模型的优缺点:
继续深入学习阻塞、非阻塞、异步I/O的工作机制,掌握在不同场景下选择合适的I/O模型。
2. 多进程/多线程与I/O操作的结合:
探索如何通过多进程或多线程编程来提高I/O处理的并发性和效率,特别是在高性能计算或服务器开发中的应用。
3. 优化I/O性能:
探讨如何通过系统级优化、缓存管理、批量操作等手段提高I/O操作的效率。
4. 掌握高效的文件I/O处理方法:
针对大数据量文件的读写操作,学习使用内存映射文件(mmap)等高效手段,进一步提升I/O性能。