如何在Linux中实现进程间通信

news2024/9/25 7:20:16

致前行路上的人:

        要努力,但不要着急,繁花锦簇,硕果累累都需要过程!

目录

1.进程间通信介绍

1.1进程间通信的目的

1.2进程间通信发展

1.3进程间通信分类

1.4进程间通信的本质

2.管道

2.1什么是管道

2.2管道与进程的关系

2.3什么是匿名管道

2.4进程与管道通信的特点

 2.5实例代码

2.6进程间通信读写策略

2.7管道的特点

2.8命名管道

2.9实例代码:

3.system V共享内存

3.1共享内存的概念

3.2共享内存的原理

3.3申请共享内存的函数

3.4查看共享内存

 3.5控制共享内存的函数

3.6共享内存和进程关联的函数

3.7共享内存和进程去关联的函数

3.8实例代码

3.9共享内存的特点

4.system V消息队列

4.system V信号量 

5.IPC资源组织方式

1.进程间通信介绍

1.1进程间通信的目的

~数据传输:一个进程需要将它的数据发送给另一个进程
~资源共享:多个进程之间共享同样的资源。
~通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
~进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

1.2进程间通信发展

管道——>基于文件系统
System V进程间通信——>聚焦在本地通信
POSIX进程间通信——>跨主机通信

1.3进程间通信分类

管道
<匿名管道pipe>                                <命名管道>
System V IPC
<System V 消息队列 >                  <System V 共享内存>                        < System V 信号量>
POSIX IPC
<消息队列>                 <共享内存 >                <信号量>                 <互斥量 >                 <条件变量 >                   <读写锁>

1.4进程间通信的本质

要通信的进程必须看到同一份公共资源,所以要求操作系统必须直接或间接的方式给进程提供“内存空间”,而不同进程间通信的分类是操作系统中不同模块提供的公共资源进行分类的,因此不论是哪一种通信,前提必须是要提供一份公共资源!

2.管道

2.1什么是管道

管道是一种进程间通信的形式,是基于文件系统给进程提供的一份公共资源。

2.2管道与进程的关系

磁盘上的文件被打开,创建struct file结构体对象,父进程struct files_struct中保存打开文件的文件描述符,然后让父进程指向该文件,fork创建子进程,因为进程间具有独立性,所以子进程继承父进程的进程管理结构也指向该文件,每个被打开的文件对象中都有一块自己的内核缓冲区,而这个内核缓冲区就是一份公共资源,一个进程在这块区域写入数据,一个进程在这块区域读数据,这样就实现了进程间的通信了!

 注:把实现进程间通信的这种文件就被称为管道文件

2.3什么是匿名管道

不同进程间通信的管道文件并不是磁盘上的文件,而是内存级文件,因为如果说是磁盘上的文件,每次进程间通信都需要进行访问外设,效率就会很慢,所以操作系统为了提高效率,就将该文件创建在了内存,而在内存上创建的该文件是通过文件描述符来进行寻找的,而不是通过文件名进行标识的,所以把这种创建在内存级的文件就被称为是匿名管道!

2.4进程与管道通信的特点

 2.5实例代码

创建管道文件:

 参数
pipefd:文件描述符数组,其中pipefd[0]表示读端, pipefd[1]表示写端
返回值:成功返回0,失败返回错误代码

1.子进程写入,父进程读取

#include<iostream>
#include<cassert>
#include<cstring>
#include<cstdio>
#include<sys/wait.h>
#include<sys/types.h>
#include<unistd.h>
using namespace std;
//父进程读取。子进程写入
int main()
{
    //第一步:创建文件,打开读写端
    int fd[2];
    int n = pipe(fd);
    assert(n == 0);
    //第二步:创建子进程
    pid_t id = fork();
    assert(id >= 0);
    //子进程:
    if(id == 0)
    {
        close(fd[0]);
        //子进程通信代码:
        const char*s = "我是子进程";
        int cnt = 0;
        while(true)
        {
            cnt++;
            char buffer[1024];
            snprintf(buffer,sizeof buffer,"child->parent say:%s[%d][%d]",s,cnt,getpid());
            write(fd[1],buffer,strlen(buffer));
            sleep(1);
        }
        _exit(0);
    }
    //父进程:
    close(fd[1]);
    //父进程通信代码:
    while(true)
    {
        char buffer[1024];
        //读取成功,返回读取字符的个数
        ssize_t s = read(fd[0],buffer,sizeof(buffer)-1);
        if(s > 0) buffer[s] = 0;
        cout << "# " << buffer << "|my pid:"<< getpid() <<endl;
    }
    int status = 0;
    n = waitpid(id,&status,0);
    assert(n == id);

    return 0;
}

2.6进程间通信读写策略

1.读慢,写快,缓冲区空间写满之后,写端不再写入

2.读块,写慢,读端读取数据之后,下一次读取为阻塞式等待,等待写端写入数据

3.向缓冲区写入一次数据之后,写端关闭,读端读取一次数据之后,下一次读取到0

4.读关闭,写端向缓冲区写入数据,操作系统会给写端发信号,终止写端

2.7管道的特点

1.管道的声明周期随进程,进程退出,管道释放!

2.只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创
建,然后该进程调用fork,此后父、子进程之间就可应用该管道!

3.管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道!

4.内核会对管道操作进行同步与互斥,是对共享资源进行保护的方案!

2.8命名管道

概念:

命名管道和匿名管道一样也是一种内存级的管道文件,向该管道文件中写入数据的时候不需要刷新到磁盘,不同的是命名管道是具有文件名的,在Linux操作系统中文件是具有唯一性的,可以通过路径加文件名的方式唯一标识,因此两个没有关系的进程可以通过文件名打开同一份文件,也是能够看到同一份资源,因此一个进程向该文件中写入数据,一个进程向该文件中读取数据,此时就完成了进程间通信了!

创建命名管道

命名管道可以从命令行上创建,命令行方法是使用下面这个命令:

mkfifo filename

命名管道也可以从程序里创建,相关函数有:

 创建成功返回0,失败返回-1

删除管道文件:

 删除成功返回0,失败返回-1

2.9实例代码:

server.cc从管道文件中读取,client.cc向管道文件中写入:

comm.hpp:

#pragma once

#include<iostream>
#include<sys/types.h>
#include<unistd.h>
#include<string>
#include<sys/stat.h>
#include<cstring>
#include<cassert>
#include<errno.h>
#include<fcntl.h>
#include<stdlib.h>
using namespace std;
#define NAME_PIPE "mypipe"

//创建管道文件
bool createFifo(const string& path)
{
    umask(0);
    int n = mkfifo(path.c_str(),0666);
    if(n == 0)
    {
        return true;
    }
    else
    {
        cout << "errno" << errno << "error:" << strerror(errno) << endl;
        return false;
    }
}
void removeFifo(const string& path)
{
    int n = unlink(path.c_str());
    assert(n == 0);
}

sever.cc

#include"comm.hpp"

int main()
{
    bool ret = createFifo(NAME_PIPE);
    assert(ret);

    int rfd = open(NAME_PIPE,O_RDONLY);
    if(rfd == -1)   exit(-1);
    //read:
    char buffer[1024];
    while(true)
    {
        ssize_t s = read(rfd,buffer,sizeof(buffer)-1);
        if(s > 0)
        {
            cout << "client->sever#:" << buffer << endl;
        }
        else if(s == 0)
        {
            cout << "client quit!" << endl;
            break;
        }
        else
        {
            perror("read fail:");
        }
    }
    removeFifo(NAME_PIPE);
    return 0;
}

client.cc:

#include"comm.hpp"


int main()
{
    int wfd = open(NAME_PIPE,O_WRONLY);
    if(wfd == -1)   exit(-1);
    //write:
    char buffer[1024];
    while(true)
    {
        cout << "Please say:"<< endl;
        fgets(buffer,sizeof(buffer),stdin);
        buffer[strlen(buffer)-1] = 0;
        ssize_t n = write(wfd,buffer,strlen(buffer));
        assert(n == strlen(buffer));
    }
    return 0;
}

运行结果:

3.system V共享内存

3.1共享内存的概念

通过让不同的进程,看到同一个内存块,把这个内存块称为共享内存

3.2共享内存的原理

如图所示:

 不同的进程通过进程地址空间,再通过页表映射到内存的同一块区域

 共享内存是一种通信方式,所有想通信的进程,都可以调用对应的函数接口,申请一块相同的空间实现进程间通信

3.3申请共享内存的函数

shmget函数

功能:用来创建共享内存

原型
int shmget(key_t key, size_t size, int shmflg);

参数
key:这个共享内存段名字

获取key:调用ftok函数


pathname:路径名  proj_id:一个整型

能够让不同的进程申请同一块内存空间,在底层内核数据结构中是通过key来实现的,key被shmget设置进入共享内存相关属性,用来标识该共享内存在内核中的唯一性

size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
 

IPC_CREAT:如果不存在,则进行创建,如果存在,则进行获取

IPC_EXCL:无法单独使用,使用的时候和IPC_CREAT一起使用,IPC_CREAT | IPC_EXCL,如果不存在就创建,如果存在则会报错

返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

注:共享内存也是操作系统申请的空间,所以就需要被管理,而管理的本质是先描述,再组织,所以一定存在结构体描述共享内存的相关属性,申请共享内存,除了申请空间外还包含保存共享内存的相关属性的字段

3.4查看共享内存

共享内存的属性:

 共享内存的属性是随OS的,不是随进程的:

指令删除共享内存:ipcrm -m + shmid

 3.5控制共享内存的函数

shmctl函数

控制共享内存的选项: 

 功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的相关属性的数据结构
 


返回值:成功返回0;失败返回-1

3.6共享内存和进程关联的函数

shmat函数

 功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个字节;失败返回-1

说明:

shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -(shmaddr % SHMLBA) shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

3.7共享内存和进程去关联的函数

shmdt函数

 功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

3.8实例代码

//comm.hpp
#ifndef __COMM_HPP_
#define __COMM_HPP_

#include<iostream>
#include<cstring>
#include<cerrno>
#include<cstdlib>
#include<cstdio>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
using namespace std;
#define PATHNAME "."
#define PROJ_ID 0x66
#define SIZE 4096
key_t getKey()
{
    key_t key = ftok(PATHNAME,PROJ_ID);
    if(key == -1)
    {
         cerr << errno << ":" << strerror(errno) << endl;
         exit(1);
    }
    return key;
}
int getShmHelper(key_t key,int flags)
{
    int shmid = shmget(key,SIZE,flags);
    if(shmid == -1)
    {
        cerr << errno << ":" << strerror(errno) << endl;
        exit(2);
    }
    return shmid;
}
int createShm(key_t key)
{
    return getShmHelper(key,IPC_CREAT | IPC_EXCL | 0600); //0600 创建共享内存权限
}
int getShm(key_t key)
{
    return getShmHelper(key,IPC_CREAT);
}
void delShm(int shmid)
{
    int key = shmctl(shmid,IPC_RMID,nullptr);
    if(key == -1)
    {
        cerr << errno << ":" << strerror(errno) << endl;
        exit(3);
    }
}
void* attachShm(int shmid)
{
    void* mem = shmat(shmid,nullptr,0);
    if((long long)mem == -1L)
    {
        cerr << errno << ":" << strerror(errno) << endl;
        exit(4);
    }
    return mem;
}
void detachShm(void* start)
{
    if(shmdt(start) == -1)
    {
        cerr << errno << ":" << strerror(errno) << endl;
    }
}
#endif
//client.cc
#include"comm.hpp"

int main()
{
    key_t key = getKey();
    printf("0x%x\n",key);
    int shmid = getShm(key);
    printf("%d\n",shmid);
    char* start = (char*)attachShm(shmid);
    printf("attach sucess,address start:%p\n",start);
    const char*message = "hello server:我是另一个进程,正在给你发信息";
    pid_t id = getpid();
    int cnt = 1;
    char buffer[1024];
    while(true)
    {
        // snprintf(buffer,sizeof(buffer),"%s[pid:%d],%d",message,id,cnt++);
        // memcpy(start,buffer,strlen(buffer)+1);
        snprintf(start,SIZE,"%s[pid:%d],消息编号:%d",message,id,cnt++);
        sleep(1);
    }
    //去关联
    detachShm(start);
    
    return 0;
}
//server.cc
#include"comm.hpp"

int main()
{
    key_t key = getKey();
    printf("0x%x\n",key);
    int shmid = createShm(key);
    printf("%d\n",shmid);
    //关联成功:挂接成功
    char* start = (char*)attachShm(shmid);
    printf("attach sucess,address start:%p\n",start);
    //使用:
    while(true)
    {
        printf("client say:%s\n",start);
        sleep(1);
    }
    //去关联
    detachShm(start);
    //删除共享内存
    delShm(shmid);
    return 0;
}

效果演示:

 3.9共享内存的特点

优点:所有进程通信,速度是最快的,能大大减少拷贝的次数

关于共享内存数据拷贝的一道面试题:

题目:考虑共享内存,键盘输入,键盘输出,共享内存有几次数据拷贝?

共享内存的缺点:不会进行同步和互斥的操作,不对数据进行保护

 共享内存的大小:

共享内存的大小,一般建议是4kb的整数倍,系统分配共享内存是以4kb为单位的,4kb是内存划分内存块的基本单位,内核分配内存是采取向上取整的原则,申请小于4kb大小的内存,操作系统也会给你4kb,但是你只能使用自己申请的大小空间

4.system V消息队列

消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法

每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值特性方面
IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

如图所示:

4.system V信号量 

信号量概念:

在了解什么是信号量之前我们来看看,进程要通信需要什么?根据上面管道和共享内存的学习,我们知道不同的进程想实现通信,两个进程必须看到同一份公共资源,一个进程在这个公共资源中写入数据,一个进程从公共资源中读取数据就能实现进程间通信,但是这种方式在实现进程间通信存在隐含的问题,就是不同的进程都能看到这份公共资源,这也就造成公共资源中的数据可以被任何进程修改,使得公共资源不安全的问题,所以为了解决这个问题就需要把公共资源保护起来,如何将这部分公共资源保护起来呢?操作系统实现了两种机制,就是同步互斥

互斥:是指一个进程访问公共资源的数据的时候,另一个进程不能够访问,这也就解决了一个进程正在向公共资源写数据的时候被另一个进程读取,造成数据不一致的问题。

注:将保护起来的公共资源被称为临界资源,使用公共资源的代码称之为临界区,不是使用公共资源的代码称为非临界区

了解上面这些之后,下面举一个电影院买票的例子来理解信号量,假设电影院有100个座位,所以卖票的时候最多只能卖100张票,为了避免电影院多卖票的问题,可以定义一个count变量保存电影院总共的票数,每次卖出一张票之后就将count--,当count==0的时候,就不会再卖票了,这种售票的机制联想到进程申请资源,可以将信号量类比为电影院卖票的个数count,当我们向内存申请公共资源的时候就将信号量--,释放资源的时候就将信号量++,所以所有进程在访问公共资源的时候,必须先申请公共资源,通过这样的方式就可以将公共资源保护起来,但是此时又引入了新的问题,就是信号量本身就是公共资源,那信号量该如何保护自己的安全呢?关于信号量如何保护自己的安全,介绍一个新的概念就是原子性。

什么是原子性?

所谓的原子性就是两中状态,要么做,要么不做。

而信号量就具有这种特性,所以不会有多个进程同时访问同一个信号量的问题,进而也就保护了信号量。

了解了上面这些之后就能得出一个结论,信号量本质是一个计数器,通常用来表示公共资源中,资源数量有多少的问题

注:上面说的预定资源信号量--,释放资源信号量++的两种操作被称为PV操作

当信号量为1的时候,将这个信号称为二元信号量,此时就具备互斥功能。

5.IPC资源组织方式

共享内存:

 消息队列: 

信号量:

这三种进程间通信的接口方式都非常类似,目的是操作系统在对这三种通信的方式公共资源管理的时候就可以通过一个结构体指针数组进行管理,数组的每一个下标存放每种通信方式公共资源属性的第一个字段的地址,然后进而维护和管理这三种通信方式申请的公共资源

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

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

相关文章

华为OD机试题,用 Java 解【玩牌高手】问题

最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…

智能算法实现PID智能车控制系统

智能算法实现PID智能车控制系统摘要关键词第一章绪论1.1智能车概述1.2智能PID研究现状1.3本文工作第二章 PID控制简介第三章 内模PID简介3.1 内模PID控制第四章内模智能PID智能车控制系统设计4.1 系统设计4.2内模控制原理第五章 系统仿真及结果分析5.1 系统仿真分析5.2 控制效果…

前端资源浏览器渲染原理

浏览器的渲染页面过程 HTML解析过程 一般情况下服务器会给浏览器返回 xx.html 文件 解析html 其实就是 Dom 树的构建过程 我们可以根据以下html 结构 来简单的分析出 html 的解析过程 解析CSS 规则树 在解析的过程中&#xff0c;如果遇到CSS的link元素&#xff0c;那么会由浏览…

分享 7 个有用的 JavaScript 库,也许你会用的上

使用这7个库&#xff0c;加速你的项目开发当我们可以通过使用库轻松实现相同的结果时&#xff0c;为什么还要编写自定义功能&#xff1f;开发人员最好的朋友和救星就是这些第三方库。我相信一个好的项目会利用一些可用的最佳库。本系列就是帮助前端开发者整理一些相关的优秀的J…

一文了解互联网中的UI设计师

&#xff08;点击即可收听&#xff09;一文了解UI设计师UI设计师是什么岗位,以及它的职责所在是什么,初入职场,今天带你一起来了解一下宏观上:设计师微观上: 上游对接产品经理,根据产品经理产出的PRD(也就是产品文档手册),以及产品原型,设计产品的UI界面,产出设计稿(如:蓝湖,Sk…

vite:常见的配置

最近在捣鼓一下vite&#xff0c;因为自己一直在使用react&#xff0c;就选择vite、react来体验一下vite。 使用最简单的方法创建一个应用&#xff1a;yarn create vite&#xff0c;然后选择react框架。 vite默认配置是使用了defineConfig工具函数&#xff1a; import { defi…

TCP协议原理二

文章目录四、滑动窗口二、流量窗口三、拥塞控制四、滑动窗口 前面我们学习了 确认应答&#xff0c;超时重传&#xff0c;连接管理&#xff0c;这些机制都为我们TCP的可靠性提供了保证&#xff0c;当然在保证TCP的可靠性的同时&#xff0c;传输效率也受到了一定的影响&#xff…

[创业之路-56] :职场真相:越便宜的员工其实越贵

引言&#xff1a;创业公司由于资金的限制&#xff0c;喜欢招聘经验不多、价格便宜的员工&#xff0c;殊不知&#xff0c;放在宏观的视角看&#xff0c;越发发现&#xff0c;越便宜的员工其实越贵&#xff0c;虽然他们能加班、好学习、听话&#xff0c;但创业公司不是大学&#…

【华为OD机试模拟题】用 C++ 实现 - 剩余可用字符集 or @分割可用字符集(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 获得完美走位(2023.Q1) 文章目录 最近更新的博客使用说明剩余可用字符集 or @分割可用字符集题目输入输出示例一输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才…

LLVM高级架构介绍

LLVM 为什么要开一个LLVM的新坑呢&#xff1f; 我从智能穿戴转行到芯片软件行业&#xff0c;从事编译器开发&#xff0c;不过是AI编译器。不过基本的传统编译器还是绕不过去啊&#xff0c;所以开始学习LLVM&#xff0c;后面开始学习TVM&#xff0c;MLIR。 LLVM GitHub地址 L…

vue-知识点总结

历史 2015年 10月27 1.0.0 Evangelion 新福音战士2016年 10月1日 2.0.0 Ghost in the Shell 攻壳机动队2019年 2月 2.62020年 9月18日 3.0.0 One Piece(海贼王)2021年 8月10日 3.2.0 .sync where 父向子传递props, 需要双向绑定的时候(子组件想更新这个值) how <one-comp…

vscode配置drawio绘制流程图

目录 vscode配置drwaio drawio使用 添加元素 调整元素 连接元素 添加公式 图像与表格 组合元素 保存以及导出 最近课程设计需要画流程框图&#xff0c;所以在网上找找绘制流程图的软件。然后我选择了这个drawio&#xff0c;无他&#xff0c;只是因为用vscode插件就…

同步和异步promise

进程和线程进程&#xff08;厂房&#xff09;&#xff1a;程序的运行环境线程&#xff08;工人&#xff09;&#xff1a;进行运算的东西同步和异步同步&#xff1a;一件事干完才去干下一件事&#xff0c;前面的代码不执行&#xff0c;后面的代码也不执行。同步的代码可能会出现…

lesson01_ts知识回顾

基本数据类型 undefined 和 null 可赋值给其他基本数据类型 let num: number 3; let flag: boolean false; let str: string "abc";let a: null null; let b: undefined undefined;联合数据类型 let variable: number | boolean false; variable 123;数组 …

指针的进阶【中篇】

文章目录&#x1f4c0;4.数组参数&#x1f4bf;4.1.一维数组传参&#x1f4bf;4.2.二维数组传参&#x1f4c0;5.指针参数&#x1f4bf;5.1.一级指针传参&#x1f4bf;5.2.二级指针传参&#x1f4c0;6.函数指针&#x1f4bf;6.1. 代码1&#x1f4bf;6.2. 代码2&#x1f4c0;7.函…

Linux 进程:理解进程和pcb

目录一、进程的概念二、CPU分时机制三、并发与并行1.并发2.并行四、pcb的概念一、进程的概念 什么是进程&#xff1f; 进程就是进行中的程序&#xff0c;即运行中的应用程序。比如&#xff1a;电脑上打开的LOL、QQ…… 这些都是一个个的进程。 什么是应用程序&#xff1f; 应用…

【java基础】static和final关键字的作用及其用法详解

文章目录static关键字静态字段静态方法静态代码块静态内部类final关键字final字段final方法final类static关键字 这个关键字表示静态的&#xff0c;用于不同地方意思不一样 静态字段 如果我们将其作用到字段上&#xff0c;那么该字段为类所拥有&#xff0c;我们使用new关键字…

【Node.js】 Express编写接口和跨域解决的方法!

Node.jsExpress编写接口接口跨域问题使用cors中间件解决跨域问题CORS响应头分类&#xff1a;CORS请求分类&#xff1a;编写JSONP接口Express编写接口 注意想获取URl-encoded格式的请求体数据&#xff0c;必须配置中间件app.use(express.urlencoded({extended:false}))创建api路…

动态规划:leetcode 1049.最后一块石头的重量II、494.目标和、474.一和零

leetcode 1049.最后一块石头的重量IIleetcode 494.目标和leetcode 474.一和零leetcode 1049.最后一块石头的重量II有一堆石头&#xff0c;每块石头的重量都是正整数。每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&am…

【算法经典题集】递归(持续更新~~~)

&#x1f63d;PREFACE&#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐ 评论&#x1f4dd;&#x1f4e2;系列专栏&#xff1a;算法经典题集&#x1f50a;本专栏涉及到的知识点或者题目是算法专栏的补充与应用&#x1f4aa;种一棵树最好是十年前其次是现在1.递归1.1 递归实现…