I/O进程(Day26)

一、学习内容

  1. I/O进程

    1. 标准IO

      1. 概念
        1. 针对文件的读写操作 文件IO最终达成的目的:将一个临时存在于内存中的数据,永久性的存放于磁盘当中

      2. 操作
        1. 文件IO的操作,需要这样的2个指针 一个指针:指向源数据,提供读取操作的指针 另一个指针:指向用来接受数据的文件的地址 该指针需要提前准备:并且确定好该指针到底指向哪个文件

    2. 创建一个文件指针

      1. fopen
        1. 函数原型
          1. FILE *fopen(const char *pathname, const char *mode);

        2. 函数描述
          1. 打开一个文件,成功打开之后,返回指向该文件的文件指针

        3. 调用形式
          1.  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. 注意:文件打开后,读光标位于文件的开头,并随着文件读取慢慢的向后移动。写光标总是位于文件的末尾,并不会随着读光标的移动而移动

    3. 关闭一个文件

      1. fclose
        1. 函数原型
          1. int fclose(FILE *stream);

        2. 函数描述
          1. 通过给定的文件指针stream关闭文件

        3. 函数调用
          1. fclose(fp)

    4. 文件指针FILE*类型

      1. FILE本质上是一个typedef的结构体
        1. 查看系统自带的结构体类型
          1. cd /usr/include    专门用来存放头文件的目录
            sudo ctags -R      在存放头文件的目录里面,创建一个搜索列表
            vim -t 想要查看的数据名    前两步是一次性操作,之后想要查看其他内容,直接执行第三步即可
            1. 输入 vim -t FILE 显示如下画面,我们要的是 红框中的内容,所以键入1,按回车
            2. 查看后得知,FILE typedef 自 _IO_FILE 类型,继续追踪,鼠标点到 _IO_FILE 上面,键入 ctrl+ ]
            3. 此时又会找到2个搜索目标,应该是第一个,键入1,按回车
        2. FILE的定义如
          1. 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; 最核心的,最终通过该数据定位文件的                                                                 
            
            };

    5. 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输出因为什么原因发生了错误

    6. 针对文件的第一组读写函数

      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

    7. 针对文件的第二组读写函数

      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.  FILE* fp = fopen(文件名,"w")
                fprintf(fp,"%d",123)
                fprintf(fp,"%c",'c')
                fprintf(fp,"%s","abc")
                fprintf(fp,"%s","123")
                ....
          2. 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.  char str[32] = "123abc"
                  int a = 0;
                  char b[20] = {0};
                  sscanf(str,"%d%s",&a,b);
                  最终 a == 123,b=="abc"

  2. 脑图

二、作业

题目1、

有如下结构体:
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个学生的所有信息,读取到一个新的结构体数组中,并输出所有学生的信息

代码解答:

#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性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值