I/O进程(Day26)

news2024/11/26 4:49:38

一、学习内容

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2207475.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

复杂系统学习

一、复杂网络分析在复杂性研究中的地位 1.复杂系统 系统中存在的复杂度从两个维度来看 ①系统自由度&#xff08;系统组成成分的数目&#xff09; ②相互作用&#xff08;线性到非线性的转换&#xff09; 复杂网络是复杂系统的骨架 复杂系统可以抽象成一个网络&#xff0…

大数据新视界 --大数据大厂之 Dremio:改变大数据查询方式的创新引擎

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

【JVM】如何判断对象是否可以被回收

引用计数法&#xff1a; 在对象中添加一个引用计数器&#xff0c;每当有一个地方引用它时&#xff0c;计数器值就加一&#xff1b;当引用失效时&#xff0c;计数器值就减一&#xff1b;任何时刻计数器为零的对象就是不可能再被使用的。 优点&#xff1a;实现简单&#xff0c;判…

Visual Studio--VS安装配置使用教程

Visual Studio Visual Studio 是一款功能强大的开发人员工具&#xff0c;可用于在一个位置完成整个开发周期。 它是一种全面的集成开发环境 (IDE)。对新手特别友好&#xff0c;使用方便&#xff0c;不需要复杂的去配置环境。用它学习很方便。 Studio安装教程 Visual Studio官…

从这里看BD仓储如何改变物流效率?

BD仓储物流建设成为当代物流领域的核心要素&#xff0c;推动着整个行业朝向高效性与智能化水平不断提升。在BD仓储物流的创新浪潮中&#xff0c;RFID技术犹如一颗耀眼的明珠&#xff0c;凭借其无可比拟的特性获得了业界的广泛推崇与广泛应用。该技术通过无线信号与电子标签的互…

Python剪辑视频

import os from moviepy.editor import VideoFileClipvideo_dir r"E:\学习\视频剪辑" s_video_file "1.mp4" d_video_file "剪辑片段1.mp4" s_video_path os.path.join(video_dir, s_video_file) # 原视频文件路径 d_video_path os.path…

FDTD Solutions(时域有限差分)仿真技术与应用

FDTD Solutions是一款非常好用的微纳光学设计工具。该软件提供了丰富的设计功能&#xff0c;支持CMOS图像传感器&#xff0c;OLED和液晶&#xff0c;表面计量&#xff0c;表面等离子体&#xff0c;石墨烯&#xff0c;太阳能电池&#xff0c;集成光子组件&#xff0c;超材料&…

排序|归并排序|递归|非递归|计数排序(C)

归并排序 如果数组的左半区间有序&#xff0c;右半区间有序&#xff0c;可以直接进行归并 基本思想 快排是一种前序&#xff0c;归并是后序 每次取小尾插 void _MergeSort(int* a, int* tmp, int begin, int end) {if (end < begin)return;int mid (end begin) / 2;/…

go开发环境设置-安装与交叉编译

1. 引言 Go语言&#xff0c;又称Golang&#xff0c;是Google开发的一门编程语言&#xff0c;以其高效、简洁和并发编程的优势受到广泛欢迎。作为一门静态类型、编译型语言&#xff0c;Go在构建网络服务器、微服务和命令行工具方面表现突出。 在开发过程中&#xff0c;开发者常…

PyCharm打开及配置现有工程(详细图解)

本文详细介绍了如何利用Pycharm打开一个现有的工程&#xff0c;其中包括编译器的配置。 PyCharm打开及配置现有工程 1、打开工程2、配置编译器 1、打开工程 双击PyCharm软件&#xff0c;点击左上角 文件 >> 打开(O)… 选中想要打开的项目之后点击“确定” 2、配置编译器…

STM32学习--3-5 光敏控制传感器控制蜂鸣器

接线图 Buzzer.c #include "stm32f10x.h" // Device header void Buzzer_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode GPIO_Mode_O…

Microsoft Visual Studio安装gtest

1. 参考【Windows Visual Studio下安装和使用google test&#xff08;gtest&#xff09;】 https://blog.csdn.net/Bule_Zst/article/details/78420894 2. 编译gtest使用Win32模式。 3. 配置属性&#xff0c;C/C&#xff0c;常规&#xff0c;附加包含目录 …

【画质模组】古墓丽影mod,调色并修改光影,游戏画质大提升

大家好&#xff0c;今天小编我给大家继续引入一款游戏mod&#xff0c;这次这个模组主要是针对雷神之锤4进行修改&#xff0c;如果你觉得游戏本身光影有缺陷&#xff0c;觉得游戏色彩有点失真的话&#xff0c;或者说你想让雷神之锤4这款游戏增加对光线追踪的支持的话&#xff0c…

Java | Leetcode Java题解之第474题一和零

题目&#xff1a; 题解&#xff1a; class Solution {public int findMaxForm(String[] strs, int m, int n) {int[][] dp new int[m 1][n 1];int length strs.length;for (int i 0; i < length; i) {int[] zerosOnes getZerosOnes(strs[i]);int zeros zerosOnes[0]…

【红外传感器】STM32C8T6标准库使用红外对管

好好学习&#xff0c;天天向上 前言一、了解红外二、标准库的代码1.infrared.c2.infrared.h3.main.c4 现象 总结 前言 红外线&#xff1a;频率介于微波与可见光之间的电磁波。 参考如下 【STM32】标准库与HAL库对照学习教程外设篇–红外避障传感器 光电红外传感器详解&#…

查看 Excel 应用程序中已打开的 Excel 文件的完整路径

要查看 Excel 应用程序中已打开的 Excel 文件的完整路径&#xff08;全路径&#xff09;&#xff0c;你可以通过以下几种方法获取具体路径&#xff0c;尤其是在 VSTO 应用程序中。 方法1&#xff1a;使用 VSTO Excel 外接程序代码 在 VSTO 外接程序代码中&#xff0c;您可以直接…

前端反馈弹框组件封装

一、需求背景 需要针对某个功能进行用户调查反馈&#xff0c;设计一个弹框&#xff0c;进行后端入表记录&#xff0c;以便后期进行数据分析。 二、实现UI 三、代码留存 以vue为例 <template><div class"advice-container"><van-dialogv-model"…

聚类分析 | WOA-K-means++聚类优化算法

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 (创新)WOA-K-means聚类优化算法 (WOA聚类优化&#xff0c;创新&#xff0c;独家) 鲸鱼算法优化K-means聚类优化算法 matlab语言&#xff0c;一键出图&#xff0c;直接运行 1.鲸鱼算法WOA作为群智能算法简单高效&a…

Collection-LinkedList源码解析

文章目录 概述LinkedList实现底层数据结构构造函数getFirst(), getLast()removeFirst(), removeLast(), remove(e), remove(index)add()addAll()clear()Positional Access 方法查找操作 概述 LinkedList同时实现了List接口和Deque接口&#xff0c;也就是说它既可以看作一个顺序…

【LeetCode】修炼之路-0005-Longest Palindromic Substring【python】

题目 Given a string s, return the longest palindromic substring in s. Example 1: Input: s “babad” Output: “bab” Explanation: “aba” is also a valid answer. Example 2: Input: s “cbbd” Output: “bb” 前言 首先&#xff0c;题目我们就看不懂 &…