【Linux从入门到精通】进程的控制(进程退出+进程等待)

  

  本篇文章主要讲述的是进程的退出和进程等待。希望本篇文章的内容会对你有所帮助。

文章目录

一、fork创建子进程

1、1 在创建子进程中操作系统的作用

1、2 写时拷贝

二、进程终止

2、1 常见的进程退出

2、2 进程的退出码

2、2、1 运行结果正确实例 

2、2、2 运行结果不正确实例

2、2、3 代码运行异常

2、3 常见的进程退出方式

三、进程等待

3、1 进程等待的引入

3、2 等待的方法

3、2、1 wait 方法

3、2、2 waitpid 方法

四、总结


🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️

👀 专栏:Linux从入门到精通  👀

💥 标题:进程控制💥

 ❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️  

一、fork创建子进程

  我们知道fork是创建子进程的。创建的子进程,在我们不做任何操作的情况下,子进程有数据和代码吗?操作系统都做了哪些事情呢?

1、1 在创建子进程中操作系统的作用

  我们先想一下,创建一个子进程本质上在是干啥?是不是系统中多了一个进程呢!进程是由所对应的代码和数据,再加上内核数据结构构成的。

  那一切都就很容易理解了。创建子进程,就是给子进程分配自己的内核数据结构。同时,把部分所需代码和数据加载到进程当中。我们父进程的代码和数据一般都是从磁盘加载(我们自己所写的代码)过来的。子进程的代码和数据哪里来的呢?

  因为在加载的过程中,子进程并没有自己的代码和数据,所以子进程只能使用父进程的代码和数据了!也就是子进程和父进程共享一份数据!

  我们知道,每个进程都是具有独立性的,进程之间不会相互影响。那子进程和父进程共享一份代码和数据,不管是子进程还是父进程去修改数据,那不就影响到了另一个进程吗?因为代码和数据是共享的。但是,事实并不是不会相互影响。呢么做到的不相互影响呢?

  我们为了保持进程的独立性,就采用了写时拷贝技术。那么我们接下来先了解一下写时拷贝技术。

1、2 写时拷贝

  我们知道子进程和父进程共享一份代码和数据,那么只要有任何一进程对其数据进行修改,就会对该数据进行深拷贝,再进行修改。这就很好的解决了相互影响的问题。这就是写时拷贝,只有在对其数据进行修改时,才对数据开空间,进行深拷贝

  写时拷贝就是一种拖延技术,是在浅拷贝的基础之上增加了引用计数的方式来实现的。

  引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

  那么这里有一个问题:为什么在创建子进程时,不直接对数据进行深拷贝,而是采用与父进程共享一份代码和数据呢?

  首先,我们所创建的子进程可能就不会用到数据空间,即使用到了,也可能是只读,并不会对其进行修改而影响到进程的独立性。其次,操作系统也无法预知那些空间会被写入。所以操作系统选择写时拷贝技术,会有以下好处:

  • 可以很好的分离父子进程的数据,保证进程的独立性;
  • 用时才分配空间,合理充分利用了内存,是高效使用内存的一种表现。

二、进程终止

2、1 常见的进程退出

   我们常见的进程退出有如下三种:

  1. 代码运行完毕,结果正确;
  2. 代码运行完毕,结果不正确;
  3. 代码异常终止。
  关键是我们怎么知道进程运行结果正确还是不正确呢?这时候就需要了解一下 进程的退出码了

2、2 进程的退出码

  进程的退出码(Exit Code)是一个整数值,用于表示进程在终止时返回给操作系统的状态信息。退出码提供了有关进程是否成功执行和执行结果的信息。如果出错,同时也方便我们地位错误的原因。

2、2、1 运行结果正确实例 

  我们平常写代码中也一直在用到退出码——return 0。在main函数中,return 的返回值就算是进程的退出码。我们可在linux下查看一下。linux下查看最近进程的退出码的命令是:echo $?。实例代码如下:

#include<iostream>    
    
using namespace std;    
    
int main()    
{    
    cout<<"正常运行"<<endl;    
    return 0;    
} 

  我们再来查看一下改进程的退出码,结果如下:

  那要是我们设置 return 的返回值为100呢?我们再来看看结果:

2、2、2 运行结果不正确实例

  我们可通过一个判断来完成结果是否正确。代码如下:

int main()    
{    
    const int N=100;    
    int ret=0;    
    for(int i=0;i<N;i++)                                                                                                                                     
    {    
        ret+=i;    
    }    
    
    if(ret!=5050)    
        return 1;    
    return 0;    
}    

  我们知道,1到100的和为5050。我们在这里故意把答案写错来判断一下,结果如下:

  

  运行结果不正确的原因有很多,我们可通过sterror函数将它们打印出来,结果如下:

   我们发现,一共是由133种运行错误的结果。我们不妨来验证一下:

  退出码是2,是不是与我们刚刚打印出的对上了! 

2、2、3 代码运行异常

  我们先看如下代码:

int main()
{
    cout<<"hello linux"<<endl;
    cout<<"hello linux"<<endl;    
    cout<<"hello linux"<<endl;    

    int* p=NULL;
    *p=1;

    cout<<"hello linux"<<endl;
    cout<<"hello linux"<<endl;
    cout<<"hello linux"<<endl;
    return 0;
}

  上述代码是对空指针进行了访问并且修改,必然会导致程序崩溃。结果如下:

 

  我们看到:Segmentation fault。我们再看退出码是139。当程序崩溃时,退出码毫无意义,并且一般情况下 return 语句都不会别执行!

2、3 常见的进程退出方式

  我们平常再写代码的时候,如何用代码终止一个进程呢?其实在main() 函数中的return语句,就是终止进程的。return 的值就是退出码,返回给操作系统。那还有其他的方式吗?答案是有的。我们接着往下看。

  可能我们大家在平常中也会用到exit()函数。exit在代码的任何地方调用,都是表示终止进程。那我们来简单测试一下exit()函数。代码如下:

#include<stdio.h>    
#include<stdlib.h>    
#include<unistd.h>    
    
int main()    
{    
    printf("hello linux");    
    sleep(3);    
    
    exit(111);                                                                                                                                               
    return 0;    
}

  我们上面打印的字符串,并没有选择通过 \n 或者fflush 将其在缓冲区刷新出来,而是休眠了3秒。那当exit 终止程序时,会将其刷新出来吗?我们看如下运行结果:

  通过上述发现,在exit 终止程序是,会刷新缓冲区。退出码也是我们所设置的 111 。 

  其实还有一个函数,_exit() 也是用来终止程序的。我们不妨也来简单测试一下 _exit() 函数,看是否与 exit() 函数相同。代码如下:

#include<stdio.h>    
#include<stdlib.h>    
#include<unistd.h>    
    
int main()    
{    
    printf("hello linux");    
    sleep(3);    
    
    _exit(111);                                                                                                                                               
    return 0;    
}

  运行结果如下:

  我们发现,_exit() 函数并没有刷新缓冲区!

  exit() 函数时C语言的库函数,_exit() 是系统的接口。其实exit() 底层也是调用的_exit(),但是在调用_exit() 之前,还做了一些其他的工作:

  1.  执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入。
  3. 调用_exit。

  那么提问:printf 所打印的数据并不是直接向输出设备输出,而是保存在了缓冲区。那么这个缓冲区是在哪里呢?是由谁来维护的呢?

  我们先看如下图片:

  _exit() 函数直接调的是操作系统內部的函数。如果上述的缓冲区是在操作系统內部的话,_exit() 函数也会把数据刷新出来。但是是并没有。从这点也可以说明,上述的缓冲区并不是在操作系统內部。显而易见,缓冲区是由C语言标准库函数来维护的。

  这里对exit() 和 _exit()函数进行总结。exit()和_exit()都是用于退出程序的函数,但在使用方式和功能上存在一些区别。

  exit()是C语言标准库中的函数,在C++中也可以使用。它执行以下操作:

  1. 调用终止处理程序(atexit函数注册的函数)。
  2. 刷新并关闭stdin、stdout和stderr流。
  3. 调用各个注册函数进行清理工作。
  4. 通过调用C库实现的_exit()系统调用来终止进程。

  _exit()是一个系统调用,用于直接终止进程,不会进行任何清理工作。它执行以下操作:

  1. 立即终止进程,并不执行后续的清理工作。
  2. 不会刷新缓冲区或关闭文件描述符,也不会执行终止处理程序。
  3. 可以带有一个整数参数,表示退出状态,将该值传递给操作系统。

  因此,exit()更安全,它可以确保执行所有的清理工作,并且允许某些回调函数得到执行的机会。_exit()则是一种粗暴的退出方式,立即终止进程,没有机会执行任何清理工作。

三、进程等待

3、1 进程等待的引入

  在进程的状态中讲到中讲到,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。

  最后,父进程派给子进程的任务完成的如何,我们需要知道。如子进程运行完成,结果对还是不对, 或者是否正常退出。那么父进程怎么获取这些信息呢?
  父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。这也是父进程等待的原因!那父进程是如何等待的呢?

3、2 等待的方法

3、2、1 wait 方法

  进程等待函数wait()是在操作系统中用于等待子进程结束并获取子进程的状态信息的函数wait()函数用于等待任意一个子进程终止,并可以获取子进程的终止状态。我们再来看一下wait的使用方法:

  上图我们可到使用wait需要引入的头文件。同时wait的返回值: 成功返回被等待进程pid,失败返回-1;参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL。

  下面讲述waitpid()函数时,会讲到status。我们再来看wait()的使用方法。代码如下:

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


int main()
{

    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        exit(1); //标识进程运行完毕,结果不正确
    }
    else if(id == 0)                                                                                     
    {                                                                                                    
        //子进程                                                                                         
        int cnt = 5;                                                                                     
        while(cnt)                                                                                       
        {                                                                                                
            printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt--, getpid(), getppid());             
            sleep(1);
        }
        exit(0);                                                                                                                                             
    }                                   
    else               
    {                
        //父进程
        printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
        pid_t ret=wait(NULL);
        if(ret>0)
        {
            printf("等待成功!,%d\n",ret);
        }
    
    }

    return 0;
}

  运行结果如下:

  观察上图发现,父进程打印出第一句话后,就不再往后执行。没错,wait()就是阻塞式等待。就是当子进程退出时,wait()回收子进程的资源和退出信息。当子进程一直在运行不退出,wait()就一直处于阻塞状态进行等待。

  我们不妨来验证一下,wait()到底回收子进程的资源了吗。验证的代码如上述验证wait()的代码几乎一样,只不过是当wait()等待成功后,不让父进程退出,接着打印。我们看如下运行结果:

  通过上图,我们看到当子进程推出后,父进程仍然在打印,并没有退出。如果父进程不对子进程进行回收,子进程就会进入僵尸状态。我们在查看的时候,子进程并没有进入僵尸状态,而是被回收了。 我们接下来再看一下waitpid的使用方法。

3、2、2 waitpid 方法

   waitpid()函数用于等待指定进程ID的子进程终止,也可等待任意子进程的终止。我们先来看看怎么使用waitpid。如下图:

  返回值: 当正常返回的时候waitpid 返回收集到的子进程的进程 ID ; 如果设置了选项WNOHANG, 而调用中 waitpid 发现没有已退出的子进程可收集 , 则返回 0 ;如果调用中出错, 则返回 -1, 这时 errno 会被设置成相应的值以指示错误所在;
参数:
  • pid: pid=-1,等待任一个子进程,与wait等效Pid>0.等待其进程IDpid相等的子进程
  • status: WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出);WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  • options: WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID0表示阻塞式等待

  获取子进程status的方式:

  • wait waitpid ,都有一个 status 参数,该参数是一个输出型参数,由操作系统填充。如果传递NULL ,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status 不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究 status 16 比特位)

  我们结合代码一起理解一下waitpid()和  获取子进程status。代码如下:

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

int code = 0;

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        exit(1); //标识进程运行完毕,结果不正确
    }    
    else if(id == 0)    
    {    
        //子进程    
        int cnt = 5;    
        while(cnt--)                                                                                                                                           
        {    
            printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());    
            sleep(1);    
        }    
        exit(0);  
    }
    else
    {
        //父进程
        printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
        int status = 0; 
        pid_t ret = waitpid(id, &status, 0); //阻塞式的等待!
        if(ret > 0)
        {
            // 0x7F -> 0000.000 111 1111
            printf("等待子进程成功, ret: %d, 子进程收到的信号编号: %d,子进程退出码: %d\n",\
                    ret, status & 0x7F ,(status >> 8)&0xFF); //0xff --> 0000...000 1111 1111
        }
    }
}

  运行结果如下:

  父进程也可通过如下方式看子进程是否正常终止,和获取子进程的退出码:

    if(WIFEXITED(status)) //是否正常终止子进程
    {
        //子进程是正常退出的
        printf("子进程执行完毕,子进程的退出码: %d\n", WEXITSTATUS(status)); //获取退出码
    }
    else{
        printf("子进程异常退出: %d\n", WIFEXITED(status));
    }

  我们再来看一下waitpid()进行非阻塞等待。 第三个参数就是:WNOHANG。我们知道Linux使用C语言写的,waitpid()是系统调用接口,也就是操作系统调用自己内部的函数。这个函数就是用C语言写的函数,全部是大写的WNOHANG是什么呢?宏定义!!!WNOHANG的值就是1,为什么不直接写1呢?因为可能过一段时间,我们就不知道1在这里是什么意思了,这类数字也被称为魔鬼数字/魔术数字WNOHANGWait No HANG。也就是没有夯住。"夯住"通常指的是一个进程或者应用程序无法继续正常执行,似乎被卡住了或者不再响应用户的输入或命令。这种情况也被称为"进程僵死"或"进程挂起"。

  我们通过如下代码测试一下非阻塞等待:

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

typedef void (*handler_t)(); //函数指针类型

std::vector<handler_t> handlers; //函数指针数组

void fun_one()
{
    printf("这是一个临时任务1\n");
}
void fun_two()
{
    printf("这是一个临时任务2\n");
}

// 设置对应的方法回调
// 以后想让父进程闲了执行任何方法的时候,只要向Load里面注册,就可以让父进程执行对应的方法喽!                                                                 
void Load()
{
    handlers.push_back(fun_one);
    handlers.push_back(fun_two);
}

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        // 子进程
        int cnt =  5;
        while(cnt)
        {
            printf("我是子进程: %d\n", cnt--);
            sleep(1);
        }

        exit(11); // 11 仅仅用来测试
    }
    else
    {
        int quit = 0;                                                                                                                                        
        while(!quit)
        {
            int status = 0;
            pid_t res = waitpid(-1, &status, WNOHANG); //以非阻塞方式等待
            if(res > 0)
            {        
                //等待成功 && 子进程退出    
                printf("等待子进程退出成功, 退出码: %d\n", WEXITSTATUS(status));    
                quit = 1;    
            }    
            else if( res == 0 )    
            {    
                //等待成功 && 但子进程并未退出    
                printf("子进程还在运行中,暂时还没有退出,父进程可以在等一等, 处理一下其他事情??\n");    
                if(handlers.empty())    
                    Load();    
    
                for(auto iter : handlers)    
                {    
                    //执行处理其他任务    
                    iter();    
                }    
            }    
            else    
            {    
                //等待失败    
                printf("wait失败!\n");    
                quit = 1;    
            }    
            sleep(1);    
        }    
    }    
    return 0;                                                                                                                                                
} 

  waitpid函数不管子进程是否运行结束,都会有一个返回值。返回值大于0:等待成功 且 子进程退出。返回值等于0:等待成功 且 子进程没有退出。返回值小于0:等待失败。当我们知道子进程并没投退出时,父进程还可做一些其他事情,直到子进程退出。我们看运行结果:

  从上述的运行结果中可看出,在子进程没有退出的情况下,父进程也可做一些其他事情。退出码为11是我们自己设置的。 

四、总结

  子进程的退出状态信息返回给了操作系统中的进程控制块内,为什么wait和waitpid能够拿到子进程的退出状态呢?wait和waitpid是系统调用接口, 就是操作系统內部的函数!!!当然可以拿到了。设置全局变量行吗?答案是不行的。一旦对全局变量修改,就会发生写时拷贝。同时信号也无法处理。

  由于内容较多,分为两篇文章来整理。本篇文章主要讲述的是进程的退出和进程等待,下篇文章会讲到进程的替换。感谢阅读ovo~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

怎样原生制作lis的CentOS容器镜像

本文介绍从一个空白的裸机CentOS自己构造检验允许的docker环境。来达到运行环境的高度定制&#xff0c;而不是只能依赖VS或者微软或者数据库厂商提供的镜像当做基础制作。更容易理解基础原理。最终输出产物为lisnew.tar&#xff0c;一个开箱即用的lis运行环境。 制作的整个过程…

1 快速构建mybatis项目

1.1 使用Maven的quickstart框架 注意是不出现w的quickstart&#xff1a; 1.2 加入依赖 <dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</s…

AIGC分享交流平台、GPT-4、GPT实时联网、Claude

拥有无限畅谈的AI个人助理&#xff0c;提高效率和创造力&#xff0c;引领未来的智能生活&#xff1b; 不仅承载着最前沿的科技理念&#xff0c;更集成了对人工智能可能性的深度理解。 已支持基于GPT、Claude等主流大模型的对话内容生成、支持GPT联网查询实时信息&#xff1b;基…

软件外包开发代码管理工具

在多人开发的软件中必然需要软件代码管理工具来协助&#xff0c;软件代码管理工具主要包括版本控制系统和代码仓库。以下是一些常见的软件代码管理工具&#xff0c;以及它们的一些主要特点&#xff0c;这些代码管理工具根据实际需求和开发团队的大小、目标和需求&#xff0c;都…

Jenkins环境配置篇-邮件发送

作为持续集成的利器Jenkins已经得到了广泛地应用&#xff0c;仅仅作为一个工具&#xff0c;Jenkins已然有了自己的生态圈&#xff0c;支持其的plugin更是超过1300。在实际中如何使用以及如何更好地使用jenkins&#xff0c;一直是大家在实践并讨论的。本系列文章将会从如何使用j…

HTTP中GET请求和POST请求的区别

前言 HTTP&#xff08;超文本传输协议&#xff09;是用于在 Web 浏览器和 Web 服务器之间传输数据的协议。在 HTTP 中&#xff0c;GET 和 POST 是两种常见的请求方法。一般我们在浏览器输入一个网址访问网站都是 GET 请求&#xff1b;在 FORM 表单中&#xff0c;可以通过设置 …

文件被识别为病毒,被删除,如何解决

我们的文件有时候有用&#xff0c;但是电脑却识别为病毒&#xff0c;直接给我删除掉了&#xff0c;这让人是真的很XX&#xff0c;那该怎么办呐。 我最近用了这个方法很多次&#xff0c;蛮好用&#xff0c;分享给大家&#xff01; 1、先找到安全中心 2、找不到排除项 3、点击添…

详细总结Webpack5的配置和使用

打包工具 使用框架&#xff08;React、Vue&#xff09;&#xff0c;ES6 模块化语法&#xff0c;Less/Sass 等 CSS预处理器等语法进行开发的代码要想在浏览器运行必须经过编译成浏览器能识别的 JS、CSS 等语法&#xff0c;才能运行。 所以需要打包工具帮我们做完这些事。除此之…

面试 | 双法妙解压缩字符串【遍历统计 + 双指针】

一、题目描述 原题传送门 二、思路分析 首先我们来分析一下解决本题所需要的思路 题目的意思很简单&#xff0c;就是统计原本的字符串中的每个字符出现的次数&#xff0c;然后以【字符&#xff0c;出现的次数】这样的结构来字符串&#xff0c;以起到一个压缩的效果&#xff0c…

概率论和随机过程的学习和整理--番外16,N合1的合成问题的求平均个数,次数,阶数

目录 1 问题 2 用条件期望&#xff0c;求合成的次数 2.1 思路1 2.2 思路2 3 用条件期望&#xff0c;求合成的个数 3.1 令X表示用材料1往上合成时&#xff0c;合成材料2的个数 3.2 令Y表示用材料1往上合成时&#xff0c;合成材料3的个数 4 用条件期望&#xff0c;求合成…

【算法基础:数学知识】4.4 快速幂

文章目录 快速幂例题列表875. 快速幂⭐⭐⭐⭐⭐&#xff08;重要&#xff01;&#xff09;代码写法1——递归代码写法2——迭代递归写法 与 迭代写法的 对比 876. 快速幂求逆元&#x1f6b9;&#xff08;需要理解逆元的概念&#xff09;TODO乘法逆元介绍解法代码 快速幂 https…

[MySQL]MySQL用户管理

[MySQL]MySQL用户管理 文章目录 [MySQL]MySQL用户管理1. 用户的概念2. 用户信息3. 创建用户4. 修改用户密码5. 删除用户6. MySQL中的权限7. 给用户授权8. 回收权限 1. 用户的概念 MySQL中的用户分为超级用户&#xff08;root&#xff09;和普通用户。超级用户的操作是不受权限…

奇舞周刊第500期:TQL,巧用 CSS 实现动态线条 Loading 动画

记得点击文章末尾的“ 阅读原文 ”查看哟~ 下面先一起看下本期周刊 摘要 吧~ 奇舞推荐 ■ ■ ■ TQL&#xff0c;巧用 CSS 实现动态线条 Loading 动画 最近&#xff0c;群里有个很有意思的问题&#xff0c;使用 CSS 如何实现如下 Loading 效果&#xff1a; leaferjs&#xff0c…

4.3 Bootstrap CSS编码规范

文章目录 Bootstrap CSS编码规范语法声明顺序不要使用 import媒体查询&#xff08;Media query&#xff09;的位置带前缀的属性单行规则声明简写形式的属性声明Less 和 Sass 中的嵌套注释class 命名选择器代码组织编辑器配置 Bootstrap CSS编码规范 语法 用两个空格来代替制表…

Java方法重载和Java方法重写

Java方法重载 Java允许同一个类中定义多个同名方法&#xff0c;只要它们的形参列表不同即可。如果同一个类中包含了两个或两个以上方法名相同的方法&#xff0c;但形参列表不同&#xff0c;这种情况被称为方法重载&#xff08;overload&#xff09;。 例如&#xff0c;在 JDK …

【多模态】16、DetCLIP | 构建超大词汇字典来进行开放世界目标检测

论文&#xff1a;DetCLIP: Dictionary-Enriched Visual-Concept Paralleled Pre-training for Open-world Detection 代码&#xff1a;无。。。 出处&#xff1a;NIPS2022 | 华为诺亚方舟 | 中山大学 | 香港科技大学 效果&#xff1a; 在 LVIS 的 1203 个类别上超越了 GLIP…

深入学习 Redis - 深挖经典数据类型之 list

目录 前言 一、list 类型 1.1、操作命令 lpush / rpush&#xff08;插入元素&#xff09; lrange&#xff08;查看范围元素&#xff09; lpushx / rpushx &#xff08;有约束的插入&#xff09; lpop / rpop&#xff08;头删尾删&#xff09; lindex&#xff08;获取下…

实现锂电池形状的数据可视化css+js

1.效果图 2.需求根据后端返回数据改变里面的高度 HTML&#xff1a; <div class"dianchichi"><div class"limian" id"divElementId"></div></div> css: .dianchichi {width: 84px;height: 146px;display: flex;justify-…

【Visual Studio】Qt 在其他 cpp 文件中调用操作 ui 界面控件

知识不是单独的&#xff0c;一定是成体系的。更多我的个人总结和相关经验可查阅这个专栏&#xff1a;Visual Studio。 还整了一个如何相互之间调用函数的文章&#xff0c;感兴趣可以看&#xff1a;【Visual Studio】Qt 在其他 cpp 文件中调用主工程下文件中的函数。 文章目录 …

react 实现小球加入购物车动画

代码 import React, { useRef } from react;const ProductLayout () > {const box useRef(null);const createBall (left, top) > {const ball document.createElement(div);ball.style.position absolute;ball.style.left left - 10 px;ball.style.top top - 1…