Linux - 进程间通信(中)- 管道的应用场景

news2024/9/20 0:53:35

前言

在上篇博客当中,对Linux 当中的进程通信,做了详细阐述,主要是针对父子进程的通信来阐述的同时,也进行了模拟实现。

对于管道也有了初步了解,但是这仅仅是 进程间通信的一部分,Linux 当中关于进程间通信还有很多的内容,这篇博客将会在上篇博客的基础之上,继续阐述进程间通信。

如有疑问,请看上篇博客:
Linux - 进程间通信(上) - Linux 当中的管道-CSDN博客

管道的应用场景

我们知道,在Linux 当中,有一个命令 -- "|" ,我们也把这个命令称之为管道,那么这个命令和我们上篇博客当中,对于管道的介绍有什么关系呢?

cat text.txt | head -10 | tail-5

比如上述命令,在之前来解释的话,就是把 cat text.txt 的运行结果,通过管道,传输给 head -10 ,然后 head -10 也有有一个输出结果,又把这个输出结果,通过管道,传输给 tail -5,此时得出的结果才是上述命令的最终结果。

 而上述我们所使用的 -- "|" 命令,和 Linux 当中的 pipe 管道是有关系的

 我们拿下述 sleep 这个命令来说明上述 -- "|" 的实现:
 

sleep 6666 | sleep 7777 | sleep 8888

在执行上述命令之前,我们先用 ps 命令查看一下当中 关于 sleep 的进程,发现此时只有一个 sleep 进程: 

当我们执行上述命令之后,发现系统的当中就多出了上述的  sleep 6666 ,sleep 7777 ,sleep 8888 这个三个进程:
 

 而这  sleep 6666 ,sleep 7777 ,sleep 8888 这三个进程 的 PPID 都是 18595,也就是说,这三个进程的 PPID (父进程的PID)是一样的。

说明,这三个进程 互相直接的关系是兄弟进程。也就是具有血缘关系。

 很显然,这个18595的PID 是 BASH 的PID。

 而上述的实现也就和上篇博客当中的父子通信的原理是一样的:

首先,操作系统会先创建上述的两个管道,创建之后,在创建   sleep 6666 ,sleep 7777 ,sleep 8888 这三个进程,三个进程通过程序替换的方式,执行不同的代码块。

然后,利用上篇博客当中的对于 进程之间通信的共享文件,哪一个进程是读端,哪一个进程是写端,确定好。

也就是对每一个进程的 0,1,2 号文件,所输入或者输出重定向,使得这些文件的输出和输入不再是单纯的从 键盘文件读取数据,显示器文件输出数据。而是在指定的共享文件当中进行的数据的输入和输出,实现三个进程之间的通信。

这样就建立好了这三个进程之间的链接。

所以,这个 "|" 管道,在底层实现上就是使用 PIPE()函数,实现的 进程之间的通信

 而这 "|" 管道,就是一种匿名管道

 shell 当中实现 "|" 管道 原理

 Linux - 基础IO(重定向 - 重定向模拟实现 - shell 当中的 重定向)- 下篇-CSDN博客

Linux - 实现一个简单的 shell-CSDN博客

 上述 Linux - 基础IO(重定向 - 重定向模拟实现 - shell 当中的 重定向)博客当中是对shell 加入 重定向之后的shell,其中大体流程如下所示:

显示做出与用户交互的函数,也就是打印出控制台,接受用户输入的命令,判断输入的命令是否有误等等。

然后是对接受到的命令做字符串的分割,把需要执行什么命令解析出来,也就是解析用户的输入的字符串。

然后,判断解析出来的命令,是不是内建命令,如果是,就执行内建命令;如不是,就执行普通命令。

但是,现在要想实现管道的话,管道左右两边的命令可以是内建命令,也可以是普通命令。

所以,我们在解析命令的过程当中,先要解析用户当中有没有 "|" 管道,如果有,就要以 "|" 管道为分割符,把命令一个一个的解析出来;也就是判断,命令字符串当中有多少 "|" ,从而把这个字符串打散为多个字符串。

这些字符串就是一个一个需要执行的命令,所以,需要 malloc 开辟空间来保存这个字符串(如果当中malloc 满了,好临时扩容空间)。

然后,在创建这些命令的子进程之前,需要先创建出 "|",也就是在代码当中写一个循环,在创建管道左右的子进程之时,就可以通过 pipe()函数,链接出左右两个进程之间的 共享文件的链接关系(重定向)。

之后就是循环创建出子进程,对各个子进程的 输入和输出重定向做修改,实现子进程之间的通信。(在管道的最开始就是 1->指定一个管道的写端;管道中间的进程,0标准输入重定向到上一个管道的读端,标准输出指定到下一个管道的写端;最后一个,将标准输入重定向到最后一个管道的读端) 

在让不同的子进程执行不同的命令,也就是 exec* 程序替换,程序替换不会影响到这个进程在替换之间,曾经打开的文件,也就不会影响到曾经预先设置好的重定向文件位置了

通过管道简单实现进程池 

 在 C/C++ 当中有内存池,这样可以更高效的像操作系统申请内存空间供用户使用。其实本质上也就是 像是打水一样,如果每天都需要去河边打一桶水,那么来回的路程,是相对于每一天都需要去跑的,如果,我们一天一次性打上很多桶水。比如是开车 去打上一车的水,这一车的水可以供我们一个星期来使用,那么这个一个星期都可以不同再去河边去打水了。省去了来回在路上往返的消耗了。

同样的,如果要想要操作系统为我们申请内存空间的话,是有消耗的。因为操作系统本身也是有很多事情要去做的,那么在操作系统做完当前事情之前,我们需要去等待。

而且,操作系统也是通过调用底层系统调用接口来实现的,内存属于硬件,硬件就有自己的驱动程序,操作系统在上层,只能一层一层往下去访问到内存硬件资源。这些其实都是有消耗的。

所以,在不浪费内存资源,适量的情况下,预先加载多个内存资源,就可以在一定程度上缓解,通过操作系统申请内存资源所带来的消耗。

同样的,对于申请进程而言,如果单纯的,需要一个进程就去创建一个进程的话,也是和上述一天打一桶水的结果是一样的。都是有消耗的。

创建进程需要申请内存空间,需要有进程地址空间,有进程PCB,由页表等等的内核数据结构,而且,里链接这些内核数据结构也是需要时间的。

所以,在操作系统当中,同样是有 内存池 这样的 “池”的。

我们对这种池的创建,称之为-- 池化技术

我们预先创建好 多个 进程所需的进程资源,当我们想要用到某一个进程资源之时,只需要直接指派这个进程,帮我们去完成任务。


在父进程接受到任务之前,先预先在创建出多个 子进程的资源:
 

父进程和每一个子进程之间都建立一条管道的信道, 每一个子进程只负责从管道当中读取数据;而父进程之负责,把对应数据,输入到 对应子进程的管道当中。

如果,父进程没有向一个管道当中写入任何数据,那么这个管道对应的子进程就是阻塞在这个管道当中。等待父进程向管道当中输入数据。

一旦父进程向某一个管道当中写入数据了,那么对应子进程就会读到这些数据,就可以继续执行子进程当中的代码。

我们把父进程向管道当中写入的数据,叫做一个一个的任务码

规定,父进程在写入数据之时,一次只能写 4字节的内容,子进程在读取数据之时,一次也只能读取4字节的内容。总之就是以等长的数据长度写入,以等长的数据长度读取(4字节只是假设,具体要看对应的操作系统的设计)


完整代码

// Task.cpp 
// 任务列表,text.cc 源程序当中,用户从这个 LoadTask ()函数当中选择对应的任务
#pragma once

#include <iostream>
#include <vector>

typedef void (*task_t)();

// 下述的 tesk1-4 都是对应的任务
// LoadTask()函数是选择这些任务的函数,供text.cc 源程序当中进行选择
// <---- 这里我们使用函数指针的方式来 使得 子进程能直接跳转到 下述的 tesk1-4 都是对应的任务 函数当中进行执行 ---->
void task1()
{
    std::cout << "lol 刷新日志" << std::endl;
}
void task2()
{
    std::cout << "lol 更新野区,刷新出来野怪" << std::endl;
}
void task3()
{
    std::cout << "lol 检测软件是否更新,如果需要,就提示用户" << std::endl;
}
void task4()
{
    std::cout << "lol 用户释放技能,更新用的血量和蓝量" << std::endl;
}

// 传入一个 vector<task_t> ,这个容器当中哦弄个存储的是一个一个的任务
// 下述当中的 tasks 是一个输出型参数
void LoadTask(std::vector<task_t> *tasks)
{
    tasks->push_back(task1);
    tasks->push_back(task2);
    tasks->push_back(task3);
    tasks->push_back(task4);
}


#include "Task.hpp"
#include <string>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>

const int processnum = 10;
std::vector<task_t> tasks;  // 任务列表,用于 LoadTask ()函数返回 任务清单    
                            // 这个 tasks 容器当中存储的是 一个一个 task()任务执行函数的 函数指针
                            // 子进程通过这个函数指针,就可以直接调用到 对应的函数体当中去执行代码
// 先描述
// 父进程为了能管理各个管道
// 把各个管道用结构体对象描述起来
// 一个channel 就是一个管道结构体
class channel
{
public:
    // 用于构造一个 管道类 的构造函数
    channel(int cmdfd, int slaverid, const std::string &processname)
    :_cmdfd(cmdfd), _slaverid(slaverid), _processname(processname)
    {}
public:
    int _cmdfd;               // 发送任务的文件描述符
    pid_t _slaverid;          // 子进程的PID
    std::string _processname; // 子进程的名字 -- 方便我们打印日志
    // int _cmdcnt;
};

// 子进程需要做的事情
void slaver()
{
    // read(0)
    while(true) // 循环一直做,直到做完
    {
        int cmdcode = 0;

        // read参数: (从0号文件当中读取数据, 保存到cmdcode变量当中, 一次只读取 4 字节的内容
        int n = read(0, &cmdcode, sizeof(int)); // 如果父进程不给子进程发送数据呢??阻塞等待!
        if(n == sizeof(int))  // n 是read()的返回值,返回的是 成功从文件当中读取的字节个数
        {
            //执行cmdcode对应的任务列表
            // 先 打印子进程的PID,然后打印 当前从文件当中读取的内容
            std::cout <<"slaver say@ get a command: "<< getpid() << " : cmdcode: " <<  cmdcode << std::endl;

            // 判断当前的 执行任务是否 是在安全区间当中的
            if(cmdcode >= 0 && cmdcode < tasks.size())
                tasks[cmdcode]();  // 执行任务
        }

        // 如果 read() 函数返回返回 0 ,说明读取错误,我们就跳出这个循环
        if(n == 0) break;
    }
}

// 先预先建立好管道共享文件,然后预先创建子进程,把各个子进程和父进程之间连接上管道
void InitProcessPool(std::vector<channel> *channels)
{
    // version 2: 确保每一个子进程都只有一个写端
    std::vector<int> oldfds;
    for(int i = 0; i < processnum; i++)
    {
        // pipedf[] 数组用于pipe函数当中的 输出型参数的返回值的存储
        // pipedf[0] 是共享文件的读端;pipedf[1] 是共享文件的写端
        int pipefd[2]; // 临时空间
        int n = pipe(pipefd); // 创建管道
        assert(!n);  // pipe() 创建成功返回0,否则返回 -1
        (void)n;

        pid_t id = fork();   // 创建子进程
        if(id == 0) // child 执行的代码块
        {
            std::cout << "child: " << getpid() << " close history fd: ";
            for(auto fd : oldfds) {
                std::cout << fd << " ";
                close(fd);
            }
            std::cout << "\n";

            close(pipefd[1]); // 关闭写端文件

            // 下述的 进程替换,就是把 子进程当中的 0 号文件,本来是标准输入文件,也就是键盘文件读取数据
            // 现在修改为管道文件的 来读取数据
            // 这样的好处是 以后 子进程就会不用再去管其他的 去哪里接收父进程传输的数据了
            // 只需要“无脑”的从 子进程的 0号文件当中读取数据既可以
            dup2(pipefd[0], 0);  // 进程替换 

            close(pipefd[0]); // 因为上述已经进行了 重定向,所以 pipefd[0] 号文件就可以关闭了
            slaver();         // 执行 子进程当中需要做的任务
            std::cout << "process : " << getpid() << " quit" << std::endl; // 提示子场景完成任务,即将退出
            // slaver(pipefd[0]);
            exit(0); // 子进程退出 , 所以下述代码就可以不同执行了
        }

        // father 执行的代码块
        close(pipefd[0]); // 关闭读端文件

        // 添加channel字段了
        // 往 存储 channel 对象的 vector 容器当中添加当前管道的 channel 对象
        std::string name = "process-" + std::to_string(i);
        channels->push_back(channel(pipefd[1], id, name));
        oldfds.push_back(pipefd[1]);

        sleep(1);
    }
}

void Debug(const std::vector<channel> &channels)
{
    // test
    for(const auto &c :channels)
    {
        std::cout << c._cmdfd << " " << c._slaverid << " " << c._processname << std::endl;
    }
}

void Menu()
{
    std::cout << "################################################" << std::endl;
    std::cout << "# 1. 刷新日志             2. 刷新出来野怪        #" << std::endl;
    std::cout << "# 3. 检测软件是否更新      4. 更新用的血量和蓝量  #" << std::endl;
    std::cout << "#                         0. 退出               #" << std::endl;
    std::cout << "#################################################" << std::endl;
}

void ctrlSlaver(const std::vector<channel> &channels)
{
    int which = 0;
    // int cnt = 5;
    while(true)
    {
        int select = 0;
        Menu(); // 打印菜单,供用户选择操作
        std::cout << "Please Enter@ ";
        std::cin >> select;


        // 判断用户输入是否正确
        if(select <= 0 || select >= 5) break;
        // select > 0&& select < 5
        // 1. 选择任务
        // int cmdcode = rand()%tasks.size();    // tasks 是定义的全局 vector<task_t> 容器
        int cmdcode = select - 1;  // 由用户去选择

        // 2. 选择进程
        // 使用随机数来 选择 父进程当前要选择的子进程 来执行子任务
        // 防止 父进程 一直选择 某一个进程来执行任务,那么其他的资源就浪费了
        // 除了这种方式,还可以使用 轮询的方式,也就是递增或者是递减循环选择 子进程的方式来实现
        // write(channels[processpos]._cmdfd, &cmdcode, sizeof(cmdcode)); 这样使用即可
        // int processpos = rand()%channels.size();   这种方式是使用随机数的方式来 进行 选择进程

        std::cout << "father say: " << " cmdcode: " <<
            cmdcode << " already sendto " << channels[which]._slaverid << " process name: " 
                << channels[which]._processname << std::endl;
        // 3. 发送任务
        // 这种使用 which 的方式其实就是一种 轮询的方式 来选择 进程的
        write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));

        which++;
        which %= channels.size();  // 不哟让 which 越界了 

        // cnt--;
        // sleep(1);
    }
}
    
// 让所有的进程全部退出
void QuitProcess(const std::vector<channel> &channels)
{
    for(const auto &c : channels){
        close(c._cmdfd);  // 将子进程当中的写端直接关闭,这样的话,在子进程当中的 read()函数就会返回 0 
                          // 只要检测到 read()函数返回0 ,就可以在 父进程 控制子进程的函数当中退出 子进程
        waitpid(c._slaverid, nullptr, 0); // 因为此时 QuitProcess ()函数是 父进程执行的
                                          // 子进程退出,父进程要等待子进程退出
    }
    // version1 
    // int last = channels.size()-1;
    // for(int i = last; i >= 0; i--)
    // {
    //     close(channels[i]._cmdfd);
    //     waitpid(channels[i]._slaverid, nullptr, 0);
    // }

    // for(const auto &c : channels) close(c._cmdfd);
    // // sleep(5);
    // for(const auto &c : channels) waitpid(c._slaverid, nullptr, 0);
    // // sleep(5);
}
int main()
{
    LoadTask(&tasks);  // 获取到任务 tasks 是定义的全局 vector<task_t> 容器

    srand(time(nullptr)^getpid()^1023); // 种一个随机数种子

    // 在组织
    // 用下述的 vector  数据结构把 一个一个的管道结构体管理起来
    // 从此之后,父进程管理一个一个的管道,就变成了对这个 vector 数据结构的增删查改
    std::vector<channel> channels;

    // 1. 初始化 --- bug?? -- 找一下这个问题在哪里?然后提出一些解决方案!
    InitProcessPool(&channels);
    // Debug(channels);

    // 2. 开始控制子进程
    ctrlSlaver(channels);

    // 3. 清理收尾
    QuitProcess(channels);
    return 0;
}

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

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

相关文章

散点图,盒须图,折线图混放在一个echarts

散点图&#xff0c;何须图&#xff0c;折线图混放在一个echarts option {tooltip: {trigger: axis,axisPointer: {type: cross,crossStyle: {color: #999}}},legend: {data:[盒须图1,盒须图2,折线图,散点图]},xAxis: [{type: category,data: [周一,周二,周三,周四,周五,周六…

智能优化算法应用:基于萤火虫算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于萤火虫算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于萤火虫算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.萤火虫算法4.实验参数设定5.算法结果6.参考文…

官宣 | HelpLook已入驻企业微信应用市场

HelpLook正式入驻企业微信第三方应用市场。 HelpLook支持自定义域名与AI站内搜索&#xff0c;能够帮助企业微信用户搭建所见即所得的企业知识库、产品帮助中心、用户手册、企业博客。 | 怎么找到HelpLook并开始使用 在企业微信的第三方应用就可直接搜索HelpLook&#xff0c;添…

mysql数据库损坏后重装,数据库备份

重装 先卸载 sudo apt-get remove --purge mysql-server mysql-client mysql-common sudo apt-get autoremove sudo apt-get autoclean 然后重新安装MySQL: sudo apt-get install mysql-server mysql-client 首先要先使用无密码登录数据库一定要使用 sudo mysql -uroo…

新手上路:盘点「性能测试」必须掌握的技术点

前段时间&#xff0c;有一些小伙伴提出希望我们推送点性能测试的技术干货。所以&#xff0c;小编今天通过上网查资料&#xff0c;结合项目实操过程中的一些问题&#xff0c;总结了一些关于性能测试的内容&#xff0c;希望是大家想要了解的内容哈。 1、性能测试的目的 首先&am…

分布式环境认证和授权-基于springboot+JWT+拦截器实现-实操+源码下载

1、功能概述&#xff1f; 1、当用户登录的时候&#xff0c;将用户的信息通过JWT进行加密和签名&#xff0c;并将JWT产生了token信息保存到当前浏览器的localStoragee中,即本地存储中。 2、当用户登录成功后&#xff0c;访问其他资源的时候&#xff0c;程序从localStorage中获…

linux(4):linux基础命令第三弹

在linux基础命令第二弹中http://t.csdnimg.cn/JPNYY我们讲了有关路径&#xff0c;创建目录和文件、文件夹&#xff0c;以及如何查看文件内容的问题&#xff0c;第三弹我们将学习有关文件操作和查找以及过滤关键字、展示文件字节&#xff0c;行数的命令&#xff0c;还有一个很重…

【程序员的自我修养04】目标文件生成可执行文件过程

绪论 大家好&#xff0c;欢迎来到【程序员的自我修养】专栏。正如其专栏名&#xff0c;本专栏主要分享学习《程序员的自我修养——链接、装载与库》的知识点以及结合自己的工作经验以及思考。编译原理相关知识本身就比较有难度&#xff0c;我会尽自己最大的努力&#xff0c;争…

.Net中的集合

所有的集合都是继承自IEnumerable。集合总体可以分为以下几类&#xff1a;关联/非关联型集合&#xff0c;顺序/随机访问集合&#xff0c;顺序/无序集合&#xff0c;泛型/非泛型集合&#xff0c;线程集合。 各集合类底层接口关系图 泛型与非泛型集合类的分析 泛型集合是类型安…

自动化测试基础知识:什么是自动化测试?需要学习哪些知识与工具!

1、自动化测试概念 自动化测试是把以人为驱动的测试行为转化为机器执行的一种过程。通常&#xff0c; 在设计了测试用例并通过评审之后&#xff0c;由测 试人员根据测试用例中描述的规程一步步执行测试&#xff0c;得到实际结果与期望结果的比较。简言之&#xff0c;自动化测试…

【操作系统导论】比例份额调度

本文介绍一种 比例份额&#xff08;proportional-share&#xff09; 调度程序&#xff0c;也称为 公平份额&#xff08;fair-share&#xff09;。 彩票调度 简介 彩票调度 的基本思想&#xff1a; 每隔一段时间&#xff0c;都会举行一次彩票抽奖&#xff0c;以确定接下来应该…

【上海大学数字逻辑实验报告】六、时序电路

一、 实验目的 掌握同步二进制计数器和移位寄存器的原理。学会用分立元件构成2位同步二进制加计数器。学会在Quartus II上设计单向移位寄存器。学会在Quartus II上设计环形计数器。 二、 实验原理 同步计数器是指计数器中的各触发器的时钟脉冲输入端连接在一起&#xff0c;接…

做题总结 707. 设计链表

做题总结 707. 设计链表 leetcode中单链表节点的默认定义我的尝试正确运行的代码&#xff08;java&#xff09; leetcode中单链表节点的默认定义 class ListNode {int val;ListNode next;//无参public ListNode() {}//有参:1public ListNode(int val) {this.val val;}//有参:…

【项目小结】优点分析

一、 个人博客系统 一&#xff09;限制强制登录 问题&#xff1a;限制用户登录后才能进行相关操作解决&#xff1a; 1&#xff09;前端&#xff1a; ① 写一个函数用于判断登录状态&#xff0c;如果返回的状态码是200就不进行任何操作&#xff0c;否则Ajax实现页面的跳转操作…

Apollo配置发布原理解析

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…

Windows下查看删除某一个端口号

背景&#xff1a;Java项目运行时&#xff0c;提示端口号被占用&#xff0c;然后就忘记之前是怎么处理的了&#xff0c;感觉还是像Linux中杀掉端口号就命令行的方式比较简单一些&#xff0c;然后就是各种搜索&#xff0c;记录一下 第一步&#xff1a;在cmd中查看该端口号是否被…

最强文生图跨模态大模型:Stable Diffusion

文章目录 一、概述二、Stable Diffusion v1 & v22.1 简介2.2 LAION-5B数据集2.3 CLIP条件控制模型2.4 模型训练 三、Stable Diffusion 发展3.1 图形界面3.1.1 Web UI3.1.2 Comfy UI 3.2 微调方法3.1 Lora 3.3 控制模型3.3.1 ControlNet 四、其他文生图模型4.1 DALL-E24.2 I…

Nginx的location匹配和rewrite重写

一、location匹配 常用的正则表达式 ^ &#xff1a;匹配输入字符串的起始位置 $ &#xff1a;匹配输入字符串的结束位置 * &#xff1a;匹配前面的字符零次或多次。如“ol*”能匹配“o”及“ol”、“oll”&#xff1a;匹配前面的字符一次或多次。如“ol”能匹配“ol”及“oll…

MySQL笔记-第14章_视图

视频链接&#xff1a;【MySQL数据库入门到大牛&#xff0c;mysql安装到优化&#xff0c;百科全书级&#xff0c;全网天花板】 文章目录 第14章_视图1. 常见的数据库对象2. 视图概述2.1 为什么使用视图&#xff1f;2.2 视图的理解 3. 创建视图3.1 创建单表视图3.2 创建多表联合视…

C++ exception类:C++标准异常的基类

C语言本身或者标准库抛出的异常都是 exception 的子类&#xff0c;称为标准异常&#xff08;Standard Exception&#xff09;。你可以通过下面的语句来捕获所有的标准异常&#xff1a; try{//可能抛出异常的语句}catch(exception &e){//处理异常的语句} 之所以使用引用&a…