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

news2024/11/22 23:42:20

  

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

文章目录

一、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~

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

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

相关文章

防御第二天-防火墙演示实验

1.上课思维导图 2.防火墙演示实验 防火墙FW1&#xff1a;原用户名&#xff1a;admin 原密码&#xff1a;Admin123 配地址&#xff1a;<USG6000V1>sy [USG6000V1]int g0/0/0 [USG6000V1-GigabitEthernet0/0/0]ip add 192.168.18.2 24 打开所有权限[USG6000V1-Gig…

C++(一):基本数据类型

基本数据类型 基本内置类型定义变量type field value;type field(value);type field{value};type field {value}; 数学常量及函数静态类型转换 static_cast格式化字符串std::stringstreamstd::string引入三方库 fmt/core.h 字符运算auto 关键字枚举类型数据类型定义别名判断是…

牛客网刷题之链表(一)

链表 NB1 删除链表峰值NB2 牛群排列去重NB3 调整牛群顺序NB4 牛群的重新分组NB5 牛群的重新排列NB6 合并两群能量值&#xff08;合并有序单链表&#xff09;NB7 牛群的能量值&#xff08;单链表相加&#xff09; 以下题全部出自牛客网。 题目题目考察的知识点链表&#xff1a; …

Element Plus 日期选择器

计算开始日期到结束日期的总天数 结构 <el-form-item label"计划开始时间" required prop"StartTime"><el-date-pickertype"date"v-model"ruleForm.StartTime":disabled-date"StartTime"placeholder"计划开始…

pytorch工具——使用pytorch构建一个分类器

目录 分类器任务和数据介绍训练分类器的步骤在GPU上训练模型 分类器任务和数据介绍 训练分类器的步骤 #1 import torch import torchvision import torchvision.transforms as transformstransformtransforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,0.5,0.…

SpringCloud学习路线(8)—— Docker

一、Docker的开始 &#xff08;一&#xff09;项目部署问题&#xff1a; 依赖关系复杂&#xff0c;容易出现兼容性问题开发、测试、生产环境有差异 &#xff08;二&#xff09;Docker如何解决问题&#xff1f; 1、依赖兼容问题 &#xff08;1&#xff09;将应用的Libs&…

垃圾回收之三色标记法(Tri-color Marking)

关于垃圾回收算法&#xff0c;基本就是那么几种&#xff1a;标记-清除、标记-复制、标记-整理。在此基础上可以增加分代&#xff08;新生代/老年代&#xff09;&#xff0c;每代采取不同的回收算法&#xff0c;以提高整体的分配和回收效率。 无论使用哪种算法&#xff0c;标记…

(已解决)RuntimeError: Java gateway process exited before sending its port number

今天用Pycharm远程使用pysaprk解释器时&#xff0c;跑代码出现了这个错误&#xff1a; RuntimeError: Java gateway process exited before sending its port number 找了好多博客都没解决问题&#xff0c;有说重装spark的&#xff0c;有说本地配Java_home的&#xff0c;后面我…

[C语言刷题]杨氏矩阵、返回型参数

本文包含知识点 杨氏矩阵极其解法函数return多个值的四种方法 题目&#xff1a; 杨氏矩阵 有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c;请编写程序在这样的矩阵中查找某个数字是否存在。 要求&#xff1a;时间复杂度小于…

js 在浏览器窗口关闭后还可以不中断网络请求

有个需求&#xff0c;我们需要在用户发送数据过程中&#xff0c;如果用户关闭了网页(包括整个浏览器关闭)&#xff0c;不要中断数据传递 目前XMLHttpRequest对象是不支持的 http服务器 为了测试效果我们用nodejs写了个http服务器代码 文件名为httpServer.js如下&#xff0c;…

获取大疆无人机的飞控记录数据并绘制曲线

机型M350RTK&#xff0c;其飞行记录文件为加密的&#xff0c;我的完善代码如下 gitgithub.com:huashu996/DJFlightRecordParsing2TXT.git 一、下载安装官方的DJIFlightRecord git clone gitgithub.com:dji-sdk/FlightRecordParsingLib.git飞行记录文件在打开【我的电脑】&am…

Windows nvm 安装后webstrom vue项目编译报错,无法识别node

1 nvm安装流程 卸载原先nodejs用管理员权限打开exe安装nvmnvm文件夹和nodejs文件夹 都授权Authenticated Users 完全控制nvm list availablenvm install 16.20.1nvm use 16.20.1输入node和npm检查版本命令&#xff0c;正常显示确认系统变量和用户变量都有nvm 和nodejs 2 bug情…

数学建模-聚类算法 系统(层次)聚类

绝对值距离:网状道路 一般用组间和组内距离 聚类的距离计算如何选取&#xff1a;看结果是否解释的通&#xff0c;选择一种结果解释的通的方法。

【数据挖掘】将NLP技术引入到股市分析

一、说明 在交易中实施的机器学习模型通常根据历史股票价格和其他定量数据进行训练&#xff0c;以预测未来的股票价格。但是&#xff0c;自然语言处理&#xff08;NLP&#xff09;使我们能够分析财务文档&#xff0c;例如10-k表格&#xff0c;以预测股票走势。 二、对自然语言处…

【转载+修改】pytorch中backward求梯度方法的具体解析

原则上&#xff0c;pytorch不支持张量对张量的求导&#xff0c;它只支持标量对张量的求导 我们先看标量对张量求导的情况 import torch xtorch.ones(2,2,requires_gradTrue) print(x) print(x.grad_fn)输出&#xff0c;由于x是被直接创建的&#xff0c;也就是说它是一个叶子节…

Vue.js uni-app 混合模式原生App webview与H5的交互

在现代移动应用开发中&#xff0c;原生App与H5页面之间的交互已经成为一个常见的需求。本文将介绍如何在Vue.js框架中实现原生App与H5页面之间的数据传递和方法调用。我们将通过一个简单的示例来展示如何实现这一功能。附完整源码下载地址:https://ext.dcloud.net.cn/plugin?i…

Java集成openAi的ChatGPT实战

效果图&#xff1a; 免费体验地址&#xff1a;AI智能助手 具体实现 public class OpenAiUtils {private static final Log LOG LogFactory.getLog(OpenAiUtils.class);private static OpenAiProxyService openAiProxyService;public OpenAiUtils(OpenAiProxyService openAiP…

【C++】入门 --- 命名空间

文章目录 &#x1f36a;一、前言&#x1f369;1、C简介&#x1f369;2、C关键字 &#x1f36a;二、命名冲突&#x1f36a;三、命名空间&#x1f369;1、命名空间定义&#x1f369;2、命名空间的使用 &#x1f36a;四、C输入&输出 &#x1f36a;一、前言 本篇文章是《C 初阶…

Data Transfer Object-DTO,数据传输对象,前端参数设计多个数据表对象

涉及两张表的两个实体对象 用于在业务逻辑层和持久层&#xff08;数据库访问层&#xff09;之间传输数据。 DTO的主要目的是将多个实体&#xff08;Entity&#xff09;的部分属性或多个实体关联属性封装成一个对象&#xff0c;以便在业务层进行数据传输和处理&#xff0c;从而…

八、HAL_UART(串口)的接收和发送

1、开发环境 (1)Keil MDK: V5.38.0.0 (2)STM32CubeMX: V6.8.1 (3)MCU: STM32F407ZGT6 2、UART和USART的区别 2.1、UART (1)通用异步收发收发器&#xff1a;Universal Asynchronous Receiver/Transmitter)。 2.2、USART (1)通用同步异步收发器&#xff1a;Universal Syn…