Linux:进程等待 进程替换

news2024/11/26 10:49:18

Linux:进程等待 & 进程替换

    • 进程等待
      • wait接口
      • status
      • waitpid接口
    • 进程替换
      • exec系列接口


当一个进程死亡后,会变成僵尸进程,此时进程的PCB被保留,等待父进程将该PCB回收。那么父进程要如何回收这个僵尸进程的PCB呢?父进程通过进程等待的方式,来回收子进程的PCB,并得知子进程的退出信息


进程等待

进程等待用于回收子进程的资源,避免子进程的PCB一直占用资源,并且可以获取子进程的退出信息,得知子进程任务的执行情况,进程等待主要通过两个系统调用接口waitwaitpid来完成。

wait接口

使用wait接口,需要包含头文件<sys/types.h><sys/wait.h>,其函数原型为:

pid_t wait(int* stat_loc);

其接收一个int*指针,该参数是一个输出型参数,用于返回子进程的相关推出信息。

wait的返回值是一个int类型:

  • 返回值大于0:返回等待到的子进程的pid
  • 返回值小于0:等待失败

用一段代码来演示一下:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    pid_t id = fork();

    if(id == 0)
    {
        int cnt = 5;
        printf("I'm child, pid = %d\n", getpid());

        while(cnt--)
        {
            sleep(1);
            printf("%d\n", cnt);
        }

        return 5;
    }

    int status = 0;
    int ret = wait(&status);

    printf("wait over! status = %d, ret = %d\n", status, ret);

    return 0;
}

以上代码中,先通过fork创建了一个子进程,子进程进入if语句,进行五秒倒计时,然后退出,并且退出码为-5。父进程则通过wait函数进行等待,传入指针&status,接收返回值ret,最后输出statusret的值。

输出结果:

在这里插入图片描述

首先,子进程的pid34890,而wait的返回值就是子进程的pid。其次,status一开始被初始化为0wait之后,status = 1280,可知wait确实会修改传入的参数。

而这中间还有一个细节,那就是子进程总共sleep了五秒,而父进程在等待的这五秒中,啥事也没干,就等着子进程结束,然后对它进行回收,这个过程父进程处于阻塞状态,称为阻塞等待

简单了解wait后,那么现在的问题就是,status为什么是1280

status

status要当作一个位图来看:
在这里插入图片描述

  • 灰色部分:status是一个int类型,占32比特,但是后16比特是无效的,不填入任何内容
  • 黄色部分:第8 - 15位,共8比特,用于表示wait到的子进程的退出码
  • 绿色部分:第7位,core dump标志位本博客不关心该位置
  • 蓝色部分:第0 - 6位,共7比特,用户表示wait到的子进程的退出信号

那么我们要从status中提取出退出码退出信号,就要对其进行位操作:

status直接与01111111进行按位与&,就能得到退出信号01111111的十六进制表示为0X7F

int sig = status & 0x7F;

status右移8位后,与11111111进行按位与&,就能得到退出码11111111的十六进制表示为0XFF

int code = (status >> 8) & 0xFF;

现在在代码的最后加上这样一段:

int sig = status & 0x7F;
int code = (status >> 8) & 0xFF;

printf("exit code = %d, signal = %d\n", code, sig);

现在运行一下进程:

在这里插入图片描述

现在我们可以看到,子进程的退出码为5,退出信号为0了。你也可以尝试在另外一个窗口对进程发送信号,看看信号接收是否准确,本博客不演示了。

Linux还给用户提供了两个宏函数,用于检测status

WIFEXITED:检测进程是否正常退出,返回一个布尔值,如果进从正常退出,返回真
WEXITSTATUS:提取子进程的退出码,也就是第8 - 15

if(WIFSIGNALED(status))
    printf("exit code = %d\n", WEXITSTATUS(status));
else
    printf("子进程退出异常...\n");

这样就可以更简单的提取错误码了。


waitpid接口

进程等待的另外一个接口是waitpid接口,需要包含头文件<sys/types.h><sys/wait.h>,其函数原型为:

pid_t waitpid(pid_t pid, int* stat_loc, int options);

相比于wait接口,该接口功能更丰富和强大,但是使用也更加麻烦。

一个进程是可以有多个子进程的,一个wait只能等待一个子进程,如果有多个子进程,那么wait函数等待第一个结束的子进程。waitpid则是针对pid来对进程进行等待

其第一个参数传入子进程的pid,第二个参数用于接收推出信息,也就是刚刚的status,第三个参数用于控制等待的模式。

现在我们先用以下代码来验证一下waitwaitpid的区别:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    pid_t id1 = fork();

    if(id1 == 0)
    {
        printf("I'm child1, pid = %d\n", getpid());
        sleep(5);
        return 0;
    }

    pid_t id2 = fork();

    if(id2 == 0)
    {
        printf("I'm child2, pid = %d\n", getpid());
        sleep(1);
        return 0;
    }

    int status = 0;
    int ret = wait(&status);

    printf("wait over! pid = %d\n", ret);
    sleep(10);

    return 0;
}

以上代码中,我们通过fork创建了两个子进程,第一个子进程输出自己的pid后会sleep五秒,而第二个子进程输出pidsleep一秒。父进程只wait一次,最后父进程输出wait的返回值,而返回值就是等待到的子进程的pid,这样就可以判断wait到了哪一个子进程。

输出结果:

在这里插入图片描述

child1pid = 35042child2pid = 35043,而wait的返回值为35043,说明wait到了第二个进程。因为第二个进程先结束,所以被wait先接收了

现在我们把wait改为waitpid

int status = 0;
int ret = waitpid(id1, &status, 0);

现在我们通过waitpid的第一个参数,指定等待id1,也就是第一个子进程,其第三个参数先设为0,后续讲解该参数的作用。

输出结果:

在这里插入图片描述

这一次返回值和child1匹配上了,可以说明虽然child1更晚结束,但是waitpid只会等待指定的进程,如果有子进程先结束了,waitpid也不会回收它。

简单了解waitpid后,我们再来看看第三个参数。第三个参数用于控制进程等待的模式:

  • 0:进行阻塞等待
  • WNOHANG:进行非阻塞等待

我在讲解wait时,简单提到了阻塞等待,也就是父进程在wait的时候,什么也不做,进入阻塞状态,直到wait成功。

而非阻塞等待不一样,进行非阻塞等待时,如果本次waitpid没有等待到,那么父进程不会阻塞,waitpid直接返回0,表示本次等待没有等待到子进程。此时父进程就可以空出时间去完成别的任务,而不是傻乎乎地死等了。

示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    pid_t id = fork();

    if(id == 0)
    {
        printf("I'm child, pid = %d\n", getpid());
        sleep(5);

        return 0;
    }

    int status = 0;

    while(1)
    {
        sleep(1);
        int ret = waitpid(id, &status, WNOHANG);

        if(ret == 0)
        {
            printf("子进程未结束,执行其他任务...\n");
            //执行其他任务
        }
        else if (ret > 0)
        {
            printf("wait over! pid = %d\n", ret);
            break;
        }
        else
        {
            printf("waitpid错误!\n");
            break;
        }
    }

    return 0;
}

以上代码中先通过fork创建了一个子进程,子进程sleep五秒。父进程陷入一个while死循环,每次循环开始,都waitpid一次,以WNOHANG模式。由于该模式不会阻塞,只要当前子进程没有结束,那么waitpid直接返回,去执行后面的if语句。

如果当前返回值为0,说明当前子进程没有结束,那么父进程可以去做些别的事情,一秒后再回来检测子进程有没有结束。

如果当前返回值> 0,说明子进程结束了,waitpid也成功了,此时返回值就是子进程的pid ,跳出循环。

输出结果:

在这里插入图片描述

子进程一共执行五秒后才退出,以非阻塞等待的模式,父进程就可以把这五秒拿去做其他事情。


进程替换

通过fork创建的子进程,会继承父进程的代码和数据,因此本质上还是在执行父进程的代码。但是我们大部分时候创建子进程的目的是用于执行其它代码的,而不是父进程自己的代码,那么此时就要有操作,让进程去执行其他进程的代码,这个操作就叫做进程替换

进程替换可以将别的进程的代码替换到自己的代码区,让自己去执行别人的代码。进程替换是通过exec系列系统调用接口实现的。

exec系列接口

先看看man手册中的exec

在这里插入图片描述

exec系列接口整体还是比较复杂的,它们包含在<unistd.h>中,总共有六个接口,我们一个一个来讲解。

execl接口

函数原型如下:

int execl(const char* pathname, const char* arg, ... /* (char  *) NULL */);

其接收两个固定的参数pathnamearg,以及一个可变参数...,也许你先前没了解过,这个...就是指可以接收任意个数的参数。

  • pathname:用于指定替换的进程的路径
  • arg:以何种方式运行进程
  • ...:以何种方式运行该进程

另外的,函数声明中还有一小段备注/* (char *) NULL */,其意图告诉使用者:==使用可变参数...时,必须以NULL空指针来结尾。

也许你现在还不能很好理解这个接口的用法,我们先看一个示例:

当前目录结构如下:

在这里插入图片描述

当前目录下有一个test.c,在dir目录下有一个process.exe进程,该进程中的代码如下:

#include <stdio.h>

int main()
{
    for(int i = 0; i < 5; i++)
    {
        printf("I am process.c!\n");
    }

    return 0;
}

也就是说,process.exe进程会输出五条I am process.c!,现在我们的目的是把进程process.exe替换到test.c中。

代码如下:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("execl start!\n");

    execl("./dir/process.exe", "dir/process.exe", NULL);

    printf("execl over!\n");

    return 0;
}

其中execl("dir/process.exe", "dir/process.exe", NULL);就是进程替换的语句

  • 第一个参数"dir/process.exe":用于指明该进程的路径
  • 第二个参数 "dir/process.exe":它和第一个参数虽然一样,但只是一个巧合,如果你在当前目录下,要运行process.exe,你会执行什么样的指令?应该就是dir/process.exe,也就是说这个参数相当于你在命令行中输入的内容,这里只是碰巧路径和命令行输入的内容是一致的
  • 第三个参数NULL:格式要求以NULL结尾

那么我们的代码就完成了先输出execl start!,然后替换process.exe到当前进程后,输出五条I am process.c!,最后输出execl over!,是这样吗?

看看结果:

在这里插入图片描述

可以看到,在execl start!之后,发送进程替换,把process.exe替换到当前进程后,输出了五条I am process.c!,但是最后一句execl over!消失了

这是因为,进程替换不是简单的执行别的进程的代码,而是用别的进程的代码区覆盖掉自己原先的代码区,所以execl 一旦执行,整个进程的代码都被替换了,那么printf("execl over!\n");就会被覆盖掉,最后不输出。

刚刚的例子意图展示,在自己写的两个进程中,发送进程替换。那么我们在shell中执行的指令是不是也是进程呢?是的!所以我们也可以尝试去替换一些指令当我们自己的进程中,比如ls,pwd等指令。

现在我们尝试替换系统自带的一些进程到自己的进程中:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("----------- execl start! -----------\n");

    execl("/usr/bin/ls", "ls", "-l", "-a", NULL);

    return 0;
}

我们现在要替换ls指令到自己的进程中,ls指令在/usr/bin/ls中,我们希望以ls -l -a的形式来调用这个进程,因此我们的三个参数 "ls", "-l", "-a"就是这个指令拆分出来的三个字符串。现在你应该更好地理解了,中间这部分参数的作用,最后以NULL结尾。

输出结果:

在这里插入图片描述

我们成功在当前进程中,替换了ls指令,并且是以ls -l -a的形式调用的。


execlp接口

函数原型如下:

int execlp(const char* file, const char* arg, ... /* (char  *) NULL */);
  • file:用于指定替换的进程名称
  • arg:以何种方式运行进程
  • ...:运行该进程的选项
  • 最后以NULL结尾

与刚刚的execl不同的是,第一个参数从pathname路径,变成了file文件名。

该接口的意思是:不用指明路径,只需指明替换的进程的名称,然后会自动去环境变量PATH指定的路径中查找

也就是说:可以在系统中直接执行的指令,无需指明路径,只需要指明文件名就可以替换

示例:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("----------- execl start! -----------\n");

    execlp("ls", "ls", "-l", "-a", NULL);

    return 0;
}

现在我们依然要执行ls -l -a,但是我们用了execlp接口,ls是系统自带的指令,所以不用指明路径,系统会自己去查找。

  • "ls":要替换的进程名称为ls
  • "ls", "-l", "-a":以ls -l -a形式执行
  • NULL结尾

执行结果:

在这里插入图片描述

和刚才一样,我们成功替换了ls指令到当前进程。


execle接口

函数原型如下:

int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */);

从函数原型,我们可以看到一些熟悉的参数:

  • pathname:用于指定替换的进程的路径
  • arg:以何种方式运行进程
  • ...:以何种形式执行进程
  • NULL

唯一不同的是,要求我们在NULL后面额外加一个char* const envp[]

这个envp是一个指针数组,存储的是环境变量。一般来说,进程替换后,进程的环境变量是会用原先的环境变量的。

示例:

现在我们在process.exe中执行以下代码:

#include <stdio.h>

int main(int argc, char* argv[], char* env[])
{

    for(int i = 0; env[i] != NULL; i++)
    {
        printf("%s\n", env[i]);
    }

    return 0;
}

process.exe会输出所有的环境变量,然后我们再在test.c中替换这个进程:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("----------- execl start! -----------\n");

    execl("dir/process.exe", "dir/process.exe", NULL);

    return 0;
}

输出结果:

在这里插入图片描述

test.c输出了一句----------- execl start! -----------后就去替换了process.exe,随后输出了默认的环境变量表。

execle可以给替换后的进程指定环境变量表

示例:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("----------- execl start! -----------\n");

    char* const envp[] = {"A=aaa", "B=bbb", NULL};

    execle("dir/process.exe", "dir/process.exe", NULL, envp);

    return 0;
}

我自己伪造了一个环境变量表envp,并把它作为最后一个参数传递给替换后的进程。

输出结果:

在这里插入图片描述

可以看到,此时替换后的进程,环境变量表就变成了我们指定的变量表。


接下来我带大家回顾一下以上三个接口:

  • execl:指定路径,进行进程替换
  • execlp:指定文件名,进行进程替换
  • execle:指定路径,进行进程替换,并给替换后的进程指定环境变量表
字符含义
p用文件名代替路径,到环境变量PATH指定的路径查找
e指定环境变量

看到后面的三个接口,可以看到一些熟悉的身影:

int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

除去v字符,pe的功能我们都了解,那么我就只以execv为案例:

execv接口

函数原型如下:

int execv(const char *pathname, char *const argv[]);

相比于execl,其少了一个...的可变参数,改为了一个argv数组,而...就是用来指定以何种方式调用进程,或者说指定选项的,带有v系列的接口,将这些选项存储在一个数组中,然后把数组传入

示例:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("----------- execl start! -----------\n");

    char* const argv[] = {"ls", "-l", "-a", NULL};

    execv("/usr/bin/ls", argv);

    return 0;
}

我希望以ls -l -a形式调用ls,于是把ls-l-a三个字符串存储到数组argv中,并以NULL结尾。

字符含义
llist,以列表的形式,把选项一个一个以参数形式传入
vvector,以数组的形式,把选项都存在数组中,将整个数组传入

汇总一下六个接口:

//list系列
int execl(const char* pathname, const char* arg, ... /* (char  *) NULL */);
int execlp(const char* file, const char* arg, ... /* (char  *) NULL */);
int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */);
//vector系列
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
字符含义
p用文件名代替路径,到环境变量PATH指定的路径查找
e指定环境变量
llist,以列表的形式,把选项一个一个以参数形式传入
vvector,以数组的形式,把选项都存在数组中,将整个数组传入

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

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

相关文章

bfs之八数码

文章目录 八数码解题思路图解举例算法思路 代码CPP代码Java代码 八数码 在一个 33的网格中&#xff0c;1∼8这 8个数字和一个 x 恰好不重不漏地分布在这 33 的网格中。 例如&#xff1a; 1 2 3 x 4 6 7 5 8在游戏过程中&#xff0c;可以把 x 与其上、下、左、右四个方向之一…

学成在线 - 第3章任务补偿机制实现 + 分块文件清理

7.9 额外实现 7.9.1 任务补偿机制 问题&#xff1a;如果有线程抢占了某个视频的处理任务&#xff0c;如果线程处理过程中挂掉了&#xff0c;该视频的状态将会一直是处理中&#xff0c;其它线程将无法处理&#xff0c;这个问题需要用补偿机制。 单独启动一个任务找到待处理任…

scikit-learn实现单因子线性回归模型

1.是什么&#xff1a; 针对机器学习提供了数据预处理&#xff0c;分类&#xff0c;回归等常见算法的框架 2.基于scikit-learn求解线性回归的问题&#xff1a; 2.1.求解a&#xff0c;b对新数据进行预测&#xff1a; 2.2评估模型表现&#xff08;y和y’的方差MSE&#xff09;…

论文查重率高,有什么办法降重吗?

现在大部分学校已经进入到论文查重降重的阶段了。如果查重率居高不下&#xff0c;延毕的威胁可能就在眼前。对于即将告别校园的学子们&#xff0c;这无疑是个噩梦。四年磨一剑&#xff0c;谁也不想在最后关头功亏一篑。 查重率过高&#xff0c;无非以下两种原因。要么是作为“…

小程序搜索排名优化 三步操作提升

搜索排名优化最直接的一个目的就是为了提升小程序的排名和流量&#xff0c;获取用户的信任度。当用户在搜索关键词的时候&#xff0c;能让用户看到小程序&#xff0c;增加被发现和点击的机会。 一、关键词优化&#xff1a; 1.选择合适的关键词&#xff1a;选择与小程序内容高…

解决Gitlab集成Jira时报SSL证书问题

1. 问题描述 在gitlab中集成jira的时候&#xff0c;由于jira是企业内部网址&#xff0c;并使用自己签名的SSL证书&#xff0c;一直会报证书验证不过的问题&#xff0c;报错信息如下&#xff1a; Connection failed. Check your integration settings. SSL_connect returned1 …

odoo实施之各种导航设计

odoo各种基础能力&#xff1a;活动、讨论 玩转odoo&#xff0c;真有玩的体验 odoo消息提醒能力 odoo 讨论模块 odoo 通过new message触发任务 安装odoo studio进行拖拉拽设计 查阅官方文档&#xff0c;向官方提issue 欧洲和美国&#xff0c;虽然都是英语&#xff0c;但日期格式…

win10下,svn上传.so文件失败

问题&#xff1a;win10下使用TortoiseSVN&#xff0c;svn上传.so文件失败 解决&#xff1a;右键&#xff0c;选择Settings&#xff0c;Global ignore pattern中删除*.so&#xff0c;保存即可。

Verilog中4bit超前进位加法器

4bit超前进位加法器的逻辑表达式如下&#xff1a; 中间变量GiAiBi&#xff0c;PiAi⊕BiGi​Ai​Bi​&#xff0c;Pi​Ai​⊕Bi​ 和&#xff1a;SiPi⊕Ci−1Si​Pi​⊕Ci−1​&#xff0c;进位&#xff1a;CiGiPiCi−1Ci​Gi​Pi​Ci−1​ 用Verilog语言采用门级描述方式&am…

页面嵌套,界面套娃,除了用iframe,还有其他方式吗?

UIOTOS可以了解下&#xff0c;uiotos.net&#xff0c;通过连线来代替脚本逻辑开发&#xff0c;复杂的交互界面&#xff0c;通过页面嵌套轻松解决&#xff0c;是个很新颖的思路&#xff0c;前端零代码&#xff01; 蓝图连线尤其是独创的页面嵌套和属性继承技术&#xff0c;好家…

如何使用dockerfile文件将项目打包成镜像

要根据Dockerfile文件来打包一个Docker镜像&#xff0c;你需要遵循以下步骤。这里假设你已经安装了Docker环境。 1. 准备Dockerfile 确保你的Dockerfile文件已经准备就绪&#xff0c;并且位于你希望构建上下文的目录中。Dockerfile是一个文本文件&#xff0c;包含了用户可以调…

vue3专栏项目 -- 项目介绍以及准备工作

这是vue3TS的项目&#xff0c;是一个类似知乎的网站&#xff0c;可以展示专栏和文章的详情&#xff0c;可以登录、注册用户&#xff0c;可以创建、删除、修改文章&#xff0c;可以上传图片等等。 这个项目全部采用Composition API 编写&#xff0c;并且使用了TypeScript&#…

【k8s多集群管理平台开发实践】八、client-go实现service读取列表、创建service、读取yaml配置并更新

文章目录 简介 一.k8s的service列表1.1.controllers控制器代码1.2.models模型代码 二.创建service2.1.controllers控制器代码2.2.models模分代码 三.读取和更新service的yaml配置3.1.controllers控制器代码3.2.models模型代码 四.路由设置4.1.路由设置 五.前端代码5.1.列表部分…

湖仓一体 - Apache Arrow的那些事

湖仓一体 - Apache Arrow的那些事 Arrow是高性能列式内存格式标准。它的优势&#xff1a;高效计算&#xff1a;所有列存的通用优势&#xff0c;CPU缓存友好、SIMD向量化计算友好等&#xff1b;零序列化/反序列化&#xff1a;arrow的任何数据结构都是一段连续的内存&#xff0c;…

基于单片机的无线数据传输系统设计

摘要:基于单片机的无线数据传输系统的设计,实现了温度和湿度的自动采集、无线通讯和报警功能。该系统包括了LCD1602显示电路、DHT11温湿度采集电路等,完成了基于无线数据传输的方法来实现温湿度的采集。 关键词:温湿度检测;N RF 24 L 01;单片机 0 引言 随着科技水平的提高,…

将矩阵按对角线排序(Lc1329)——排序

矩阵对角线 是一条从矩阵最上面行或者最左侧列中的某个元素开始的对角线&#xff0c;沿右下方向一直到矩阵末尾的元素。例如&#xff0c;矩阵 mat 有 6 行 3 列&#xff0c;从 mat[2][0] 开始的 矩阵对角线 将会经过 mat[2][0]、mat[3][1] 和 mat[4][2] 。 给你一个 m * n 的整…

Mac 链接 HP 136w 打印机步骤

打开 WI-FI 【1】打开打印机左下角Wi-Fi网络设计【或者点击…按钮进入WI-FI菜单】&#xff0c;找到NetWork选项OK进入&#xff1b; 【2】设置WI-FI选项&#xff1a;在菜单内找到Wi-Fi选项OK进入&#xff1b; 【3】在菜单内找到Wi-Fi Direct选项OK进入&#xff1b; 【4】在菜单…

flutter开发实战-webview_flutter 4.x版本使用

flutter开发实战-webview_flutter 4.x版本使用 在之前使用的webview_flutter版本是3.x的&#xff0c;升级到4.x后&#xff0c;使用方式有所变化。 一、webview_flutter 在工程的pubspec.yaml中引入插件 webview_flutter: ^4.4.2二、使用webview_flutter 在4.x版本中&#…

使用mxnet中的img2rec.py制作rec数据集

源码链接&#xff1a;mxnet/tools/im2rec.py at master apache/mxnet GitHub 重点关注入参函数即可&#xff0c; def parse_args():"""Defines all arguments.Returns-------args object that contains all the params"""parser argparse.A…

每日OJ题_贪心算法三②_力扣553. 最优除法

目录 力扣553. 最优除法 解析代码 力扣553. 最优除法 553. 最优除法 难度 中等 给定一正整数数组 nums&#xff0c;nums 中的相邻整数将进行浮点除法。例如&#xff0c; [2,3,4] -> 2 / 3 / 4 。 例如&#xff0c;nums [2,3,4]&#xff0c;我们将求表达式的值 "…