进程间通信2

3. system  V-IPC 

3.1 知识点

ipcs -a查看所有的ipc对象

在系统中他们都使用一种叫做 key 的键值来唯一标识,而且他们都是“持续性”资源——即他 们被创建之后,不会因为进程的退出而消失,而会持续地存在,除非调用特殊的函数或者命 令删除他们。

跟文件类型,进程每次“打开”一个 IPC 对象,就会获得一个表征这个对象的 ID,进而再使用这个 ID 来操作这个对象。IPC 对象的 key 是唯一的,但是 ID 是可变的。key 类似于文件的路径名,ID 类似于文件的描述符

系统中的多个进程,如果他们需要使用IPC 对象来通信,那么他们必须持有这个对象的 键值 key:

3.1.1 创建key

path:这是一个字符串,通常是指向一个存在的文件的路径。ftok() 将使用这个路径名来生成键。这个路径名应该是一个可访问的文件,因为它的存在与访问权限会影响键的生成。通常情况下,不同的路径名会生成不同的键。

proj:这是一个整数,通常取值在0到255之间。它用于在指定路径名的情况下生成唯一的键。如果不同的进程使用相同的路径名,可以通过不同的 proj 值生成不同的键。通常,每个IPC对象(消息队列、信号量、共享内存)都会有一个唯一的 proj 值。

这个函数需要注意的几点:

1,如果两个参数相同,那么产生的 key 值也相同。

2 ,第一个参数一般取进程所在的目录,因为在一个项目中需要通信的几个进程通常会 出现在同一个目录当中。

3 ,如果同一个目录中的进程需要超过 1 个 IPC 对象,可以通过第二个参数来标识。

4 ,系统中只有一套 key 标识,也就是说,不同类型的 IPC 对象也不能重复。 

3.1.2 查看和删除ipc

可以使用以下命令来查看或删除当前系统中的IPC 对象:

查看消息队列:ipcs -q

查看共享内存:ipcs -m

查看信号量:ipcs -s

查看所有的 IPC 对象:ipcs -a

删除指定的消息队列:ipcrm -q MSG_ID 或者 ipcrm -Q msg_key

删除指定的共享内存:ipcrm -m SHM_ID 或者 ipcrm -M shm_key

删除指定的信号量:ipcrm -s SEM_ID 或者 ipcrm -S sem_key

3.2  消息队列

3.2.1 创建消息队列,获取操作id

权限只有读和写,执行权限是无效的,例如 0777 跟 0666 是等价的。

删除消息队列

可以用

ipcrm -q 消息队列id

或者

ipcrm -Q 消息队列的key

3.2.2 发送和接收消息

使用这两个收、发消息函数需要注意以下几点:

1,发送消息时,消息必须被组织成以下形式:

struct msgbuf

{

long mtype;  // 消息的标识

char mtext[1];    // 消息的正文

};

也就是说:发送出去的消息必须以一个 long 型数据打头,作为该消息的标识,后 面的数据则没有要求。

2,消息的标识可以是任意长整型数值,但不能是 0L。

3,参数 msgsz 是消息中正文的大小,不包含消息的标识。

3.2.3 代码

ipc_msg_que_send.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>

//消息队列的使用

struct msgbuf
{
    long mtype;//消息的标识(整数)
    char mtext[128];//消息的正文
};


int main()
{
    //创建key
    key_t key = ftok("/",0);

    //创建消息队列,获取操作消息队列的id
    int msg_id = msgget(key,IPC_CREAT | 0777);
    if(msg_id == -1)
	{
		perror("msg_id error");
	}
    struct msgbuf buf;
    buf.mtype = 1;
    while(1){
        memset(buf.mtext,0,sizeof(buf.mtext));//清零
        fgets(buf.mtext,sizeof(buf.mtext),stdin);//获取输入
        //发送
        msgsnd(msg_id,&buf,128,0);
    }
    return 0;
}
ipc_msg_que_receive.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>

//消息队列的使用

struct msgbuf
{
    long mtype;//消息的标识(整数)
    char mtext[128];//消息的正文
};


int main()
{
    //创建key
    key_t key = ftok("/",0);

    //创建消息队列,获取操作消息队列的id
    int msg_id = msgget(key,IPC_CREAT | 0777);
    if(msg_id == -1)
	{
		perror("msg_id error");
	}
    struct msgbuf msg;
    while(1){
        memset(msg.mtext,0,sizeof(msg.mtext));//清零
        //接收
        msgrcv(msg_id,&msg,128,1,0);

        printf("receive msg: %s\n",msg.mtext);
    }
    return 0;
}

3.2.4 练习

1.设计一个消息队列程序,发送时,通过键盘来指定消息标识接收时,通过键盘来指定要接收的消息

ipc_que_test_w.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>

//设计一个消息队列程序,发送时,通过键盘来指定消息标识接收时,通过键盘来指定要接收的消息
struct msgbuf{
    long mtype;//消息的标识(整数)
    char mtext[128];//消息的正文
};

int main()
{
    //创建key
    key_t key = ftok("/",4);
    //创建消息队列
    int msg_id = msgget(key,IPC_CREAT | 0777);
    if(msg_id==-1){
        perror("msg_id error");
    }

    struct msgbuf buf;

    
    int type;

    while(1){
        memset(buf.mtext,0,sizeof(buf.mtext));

        //键盘输入消息标识符
        printf("请输入发送消息标识符/(99退出):");
        scanf("%d",&type);
        if(type==99){
            break;
        }

        buf.mtype = type;

        getchar();//清空缓冲区

        //键盘接收消息正文
        printf("请输入消息正文:");

        fgets(buf.mtext,sizeof(buf.mtext),stdin);

        //发送
        msgsnd(msg_id,&buf,128,0);
    }

    return 0;
}
ipc_que_test_r.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>

//设计一个消息队列程序,发送时,通过键盘来指定消息标识接收时,通过键盘来指定要接收的消息
struct msgbuf{
    long mtype;//消息的标识(整数)
    char mtext[128];//消息的正文
};

int main()
{
    //创建key
    key_t key = ftok("/",4);
    //创建消息队列
    int msg_id = msgget(key,IPC_CREAT | 0777);
    if(msg_id==-1){
        perror("msg_id error");
    }

    struct msgbuf buf;
    
    int type;

    while(1){

        memset(buf.mtext,0,sizeof(buf.mtext));

        //键盘输入消息标识符
        printf("请输入接收消息标识符/(99退出):");
        scanf("%d",&type);

        if(type==99){
            break;
        }

        buf.mtype = type;

        printf("\n");

        //发送
        int ret = msgrcv(msg_id,&buf,128,type,0);
    
        printf("接收到标识符尾%d的消息为: %s\n",type,buf.mtext);
    }

    return 0;
}

3.3 共享内存

3.3.1 使用步骤

使用共享内存的一般步骤是:

1,获取共享内存对象的 ID

2,将共享内存映射至本进程虚拟内存空间的某个区域

3,当不再使用时,解除映射关系

4,当没有进程再需要这块共享内存时,删除它。

3.3.2 获取共享内存的 ID

共享内存的大小必须是偶数

3.3.3 共享内存映射

3.3.4 代码

ipc_share_w.c
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/shm.h>


//共享内存
int main()
{
    //创建key  和  获取共享内存id
    int share_id = shmget(ftok("/",1),1024,IPC_CREAT | 0777);

    //对共享内存进行映射  将这个共享内存映射至本进程的虚拟空间
    char* p = shmat(share_id,NULL,0);//p就是共享内存的首地址

    fgets(p,1024,stdin);//获取键盘输入
    
    shmdt(p);//解除映射


    return 0;
}
ipc_share_r.c
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/shm.h>


//共享内存
int main()
{
    //创建key  和  获取共享内存id
    int share_id = shmget(ftok("/",1),1024,IPC_CREAT | 0777);

    //对共享内存进行映射  将这个共享内存映射至本进程的虚拟空间
    char* p = shmat(share_id,NULL,0);//p就是共享内存的首地址

    printf("%s\n",p);//打印

    shmdt(p);//解除映射


    return 0;
}

3.4 信号量

一些基本概念如下:

1,多个进程或线程有可能同时访问的资源 (变量、链表、文件等等) 称为共享资源, 也叫临界资源 (critical resources) 。

2,访问这些资源的代码称为临界代码,这些代码区域称为临界区 (critical zone) 。

3,程序进入临界区之前必须要对资源进行申请,这个动作被称为 P 操作,这就像你要 把车开进停车场之前,先要向保安申请一张停车卡一样,P 操作就是申请资源,如果申请成 功,资源数将会减少。如果申请失败,要不在门口等,要不走人。

4,程序离开临界区之后必须要释放相应的资源,这个动作被称为 V 操作,这就像你把 车开出停车场之后,要将停车卡归还给保安一样,V 操作就是释放资源,释放资源就是让资 源数增加。

system-V 的信号量并不是单个的值,而是一组 (事实上是一个数 组) 信号量元素构成的

信号量跟前面的 MSG 和 SHM 有极大的不同,SEM 不是用来传输数据的,而 是作为“旗语”,用来协调各进程或者线程工作的。

信号量的 P、V 操作最核心的特征是:他们是原子性的,也就是说对信号量元素的值的 增加和减少,系统保证在 CPU 的电气特性级别上不可分割,这跟整型数据的加减法有本质 的区别。

3.4.1 创建信号量获取操作id

创建信号量时,还受到以下系统信息的影响:

1,SEMMNI:系统中信号量的总数最大值。

2,SEMMSL:每个信号量中信号量元素的个数最大值。

3,SEMMNS:系统中所有信号量中的信号量元素的总数最大值。

Linux 中,以上信息在/proc/sys/kernel/sem 中可查看。

3.4.2 获取或者设置信号量的相关属性

使用以上函数接口,需要注意以下几点:

1,这是一个变参函数,根据cmd 的不同,可能需要第四个参数,第四个参数是一个如 下所示的联合体,用户必须自己定义:

union semun

{

int   val;        /* 当 cmd 为 SETVAL 时使用 */

struct semid_ds *buf;       /* 当 cmd 为 IPC_STAT 或 IPC_SET 时使用 */

unsigned short   *array;   /* 当 cmd 为 GETALL 或 SETALL 时使用 */

struct seminfo   *__buf;   /* 当 cmd 为 IPC_INFO 时使用 */

};

3.4.3 对信号量进行 P/V 操作,或者等零操作

使用以上函数接口需要注意以下几点:

1,信号量操作结构体的定义如下:

struct sembuf

{

unsigned short short  sem_num;    /* 信号量元素序号 (数组下标)  */

short   sem_op;       /* 操作参数 */

short sem_flg;                  /* 操作选项 */

};

请注意:信号量元素的序号从 0 开始,实际上就是数组下标。

1. 当 sem_op 大于 0 时:进行 V 操作,即信号量元素的值 (semval) 将会被加上 sem_op 的值。

2. 当 sem_op 等于 0 时:进行等零操作

3. 当 sem_op 小于 0 时:进行 P 操作,即信号量元素的值 (semval) 将会被减去 sem_op 的绝对值。

重点

一个信号量(Semaphore)通常对应一个 struct sembuf 数组,但这个 struct sembuf 数组可以包含多个操作,而不仅限于一个操作。让我更详细地解释:

一个信号量对应一个 struct sembuf 数组:每个信号量在信号量集合中都有一个唯一的标识符,你可以使用这个标识符来指定要操作的信号量。通常情况下,你会创建一个 struct sembuf 数组,其中的每个元素描述了对某个特定信号量的操作。

一个 struct sembuf 数组可以包含多个操作:在 struct sembuf 数组中,你可以定义多个操作,这些操作都是针对同一个信号量的。每个 struct sembuf 结构体包括三主要字段:sem_num(信号量集合中的索引,用于指定操作的目标信号量)、sem_op(要执行的操作,通常是 -1 表示 P 操作或 1 表示 V 操作),以及 sem_flg(标志位,通常设置为 0)。

所以,你可以创建一个 struct sembuf 数组,其中的每个元素描述了对同一个信号量的不同操作。这种方式允许你在一个数组中组织多个操作,以便在一次函数调用中执行多个操作,这些操作都是基于同一个信号量的。

总结,一个信号量对应一个 struct sembuf 数组,而这个数组可以包含多个操作,用于对该信号量执行不同的操作。这是一种有效的方式来管理并发访问共享资源或进行同步操作。

3.4.5 代码

sys_sem_w.c
#include <stdio.h>
#include <fcntl.h>              
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>           
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/sem.h>

#define DATA 0
#define SPACE 1

//共享内存的结合信号量,循环写入0-9
union semun
{
	int                          val;       /* 当 cmd 为 SETVAL 时使用 */
	struct semid_ds *buf;     /* 当 cmd 为 IPC_STAT 或 IPC_SET 时使用 */
	unsigned short         *array;  /* 当 cmd 为 GETALL 或 SETALL 时使用 */
	struct seminfo          *__buf; /* 当 cmd 为 IPC_INFO 时使用 */
};


void sem_init(int id, int semnum, int val)
{
	union semun a;
	a.val = val;
	
	semctl(id, semnum,SETVAL, a);
}

void sem_p(int id,int semnum)//资源-1
{
	struct sembuf sops[1];
	sops[0].sem_num = semnum;
	sops[0].sem_op = -1;
	sops[0].sem_flg = 0;
	
	semop(id, sops, 1);
}

void sem_v(int id, int semnum)//资源+1
{
	struct sembuf sops[1];
	sops[0].sem_num = semnum;
	sops[0].sem_op = 1;
	sops[0].sem_flg = 0;
	
	semop(id, sops, 1);
}
		   
int main()
{
	//创建共享内存的IPC对象
	int shmid = shmget(ftok("/",2), 2,IPC_CREAT | 0666);//共享内存的大小必须是偶数,实际上在本代码中只需要1个
	
	char *p = shmat(shmid, NULL, 0);//映射
	
	//创建信号量的IPC对象
	int sem_id = semget(ftok("/",3),2,IPC_CREAT | 0666);
	
	//数据当0
	//数据初始化为0个
	sem_init(sem_id, DATA, 0);
	
	//空间当1
	//空间初始化为1个
	sem_init(sem_id, SPACE, 1);
	
	char *msg = "0123456789";
	int i = 0;
	
	while(1)
	{
		//1个空间
		//0个数据
		//写入数据
		sem_p(sem_id,SPACE);//p -1 s 0
		memcpy(p, msg+i, 1);//0,1,2,3,4,5,6,7,8,9
		sem_v(sem_id,DATA);//v +1   d 1
		
		i = (i+1)%10;//10%10=0
		
	}
}
 sys_sem_r.c
#include <stdio.h>
#include <fcntl.h>              
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>           
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/sem.h>
//共享内存的结合信号量,循环写入0-9
//首先搞清楚一个概念 信号量集合中可以有多个信号量,一个信号量对应一个struct sembuf sops[]结构体数组
//, 一个信号量可以有多个资源也就是说一个struct sembuf sops[]结构体数组可以有多个元素

//这里两个宏定义 的0  1 可以理解为信号量的下标(因为有的情况会有很多个信号量
//我们的例子中只有两个信号量,如果有多个的话 就是 0  1 2 3 4.....这样排下去)
#define DATA 0
#define SPACE 1

//获取或设置信号量的相关属性涉及到的结构体
union semun
{
	int        val;       /* 当 cmd 为 SETVAL 时使用 */
	struct semid_ds *buf;     /* 当 cmd 为 IPC_STAT 或 IPC_SET 时使用 */
	unsigned short         *array;  /* 当 cmd 为 GETALL 或 SETALL 时使用 */
	struct seminfo          *__buf; /* 当 cmd 为 IPC_INFO 时使用 */
};


//信号量初始化
void sem_init(int id, int semnum, int val)
{
	union semun a;//联合体
	a.val = val;
	//获取或设置信号量的相关属性
	//第二个参数是指定哪个信号量
	//这是一个变参函数 第三个参数SETVAL(设置该信号量元素的值) 会影响第四个参数应该传递啥(参数是个联合体)
	//这里带入解释一下我们假如传来的的是我们定义的SPACE信号量,val参数为1
	//参数semnum是SPACE   所以semctl函数的意思是,给SPACE信号量 设置该信号量元素的值为 a联合体
	//因为我们第三个参数使用的是SETVAL 所以a联合体中主要用到属性val被赋值为该信号量元素的值为1
	semctl(id, semnum,SETVAL, a);
}

//p操作
void sem_p(int id,int semnum)//资源-1
{
	//这个结构体数组可以有多个元素,代表同一个信号量可以携带多个资源并在一个函数内对多个资源操作
	//这里我们的一个信号量只设置了一个资源,所以结构体数组的大小为1,千万别被迷惑了
	struct sembuf sops[1];
	sops[0].sem_num = semnum; //semnum指定信号量
	sops[0].sem_op = -1;
	sops[0].sem_flg = 0;
	
	//对信号量进行pv操作的函数,这个函数的第二参数需要一个struct sembuf类型的结构体数组
	//所以我们上面才会定义一个结构体数组
	//第三个参数 就是你的结构体数组中有几个元素
	semop(id, sops, 1);
}

//v操作
void sem_v(int id, int semnum)//资源+1
{
	struct sembuf sops[1];
	sops[0].sem_num = semnum;
	sops[0].sem_op = 1;
	sops[0].sem_flg = 0;
	
	semop(id, sops, 1);
}
		   
int main()
{
	//创建共享内存的IPC对象,获取操作id
	int shmid = shmget(ftok("/",2), 2,IPC_CREAT | 0666);//共享内存的大小必须是偶数,实际上在本代码中只需要1个
	
	//将共享内存 映射 到虚拟内存
	char *p = shmat(shmid, NULL, 0);
	
	//创建信号量的IPC对象 ,,2,代表这里我们创建2个信号量
	int sem_id = semget(ftok("/",3),2,IPC_CREAT | 0666);
	

	//初始化信号量
	//数据信号量 的下标为0
	//数据初始化为0个
	sem_init(sem_id, DATA, 0);//这里的DATA代表它在信号量集合中的下标(就是我们上面的宏定义),0代表这个信号量的大小
	//空间信号量我们规定下标为1
	//空间初始化为1个
	sem_init(sem_id, SPACE, 1);
	
	char *msg = "0123456789";
	int i = 0;
	
	while(1)
	{
		//1个空间
		//0个数据
		//写入数据
		//p操作
		sem_p(sem_id,DATA);//p -1 s 0
		fprintf(stderr,p);
		//v操作
		sem_v(sem_id,SPACE);//v +1   d 1
		
		i = (i+1)%10;//10%10=0
		
	}
}

编译运行

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

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

相关文章

春秋云镜ED01-CMS v20180505 存在任意文件上传漏洞

靶场介绍 春秋云镜ED01-CMS v20180505 存在任意文件上传漏洞 漏洞分析&#xff1a; 文件类型未校验可以任意上传执行文件&#xff0c;获取服务器权限 登录注册界面 Hi-Lo-Yohttp://eci-2ze2qm1cbaon2lylin0q.cloudeci1.ichunqiu.com/registration.php 注册了几个发现注册不…

Echarts大屏可视化_03 定制柱状图

柱状图模块引入 1.找到合适的图表 在echarts中寻找与目标样式相近的图表 Examples - Apache ECharts 2. 引入柱状图 使用立即执行函数构建&#xff0c;防止变量全局污染 实例化对象 将官网中提供的option复制到代码中&#xff0c;并且构建图表 // 柱状图模块1 (function () {/…

【算法】算法题-20231128

这里写目录标题 一、55. 跳跃游戏二、274. H 指数三、125. 验证回文串 一、55. 跳跃游戏 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&am…

sqli-labs(9)

45. 不会显示报错信息通过or 1验证 在密码处输入)or(1 登录成功 )union select 1,2,3 # )union select 1,database(),3 # )union select 1,(select group_concat(table_name) from information_schema.tables where table_schemasecurity),3 # )union select 1,(select gro…

使用libssh2建立安全的SSH连接:C++开发者的综合指南

使用libssh2建立安全的SSH连接&#xff1a;C开发者的综合指南 一、介绍二、准备工作三、建立SSH连接3.1、初始化libssh2库3.2、连接到远程主机3.4、完整示例 四、文件传输4.1、上传文件到远程主机4.2、下载文件到本地主机 五、总结 一、介绍 SSH和安全连接的重要性是不可忽视的…

【CAD二次开发】标注箭头,获取修改标注箭头图块

常见的的标注箭头有以下种类 public static List<string> ArrowBlock = new List<string>(){" ","_CLOSEDBLANK&

dubbo框架技术文档-《spring-boot整合dubbo框架搭建+配置文件》框架的本地基础搭建

阿丹&#xff1a; 目前流行的微服务更多的就是dubbo和springcould微服务。之前阿丹没有出过dubbo相关的文章&#xff0c;因为之前接触springcould的微服务概念比较多一点&#xff0c;但是相对于springcould来说&#xff0c;springcould服务之间的调用是大多是使用了nacos&#…

中学老师求职简历(精选9篇)

以下简历内容以中学老师招聘需求为背景&#xff0c;我们整理并修改了9篇全面、专业且具有参考价值的简历案例&#xff0c;大家可以灵活借鉴&#xff0c;希望能帮助大家在众多候选人中脱颖而出。 中学老师简历下载&#xff08;可在下制作下载&#xff09;&#xff1a;百度幻主简…

保障美味不失传,上海迅软DSE为餐饮业提供一键式数据高效备份服务!

如今&#xff0c;随着经济技术的飞速发展&#xff0c;餐饮行业对各项业务与财务数据的容灾能力要求越来越高。信息数据不仅要做好安全备份&#xff0c;而且出现故障后&#xff0c;还要能及时、准确、安全、完整的进行恢复。 餐饮行业数据安全存在的隐患 1.餐饮行业各项业务与财…

剪辑必备AI去水印神器,手把手教你轻松消除图片水印

当我们的剪辑制作过程中&#xff0c;前期需要准备图片或视频素材&#xff0c;水印往往成为了我们首要解决的难题。 幸运的是&#xff0c;今天我为大家介绍一款在线AI去水印神器--水印云。 水印云是一个的在线去除图片水印工具。仅需三步&#xff0c;即可使用强大的 AI 技术从图…

Django回顾【四】之模型层

目录 一、基本使用 1、ORM框架 2、创建表 二、常用和非常用字段 三、常用和非常用字段参数 四、settings配置 五、基本操作 5.1 增加表记录 5.2 删除表纪录 5.3 更新表纪录 5.4 查询表纪录 六、 多表操作-创建关系 七、基于对象的跨表查询 八、基于链表的跨表…

LeetCode Hot100 287.寻找重复数

题目&#xff1a; 给定一个包含 n 1 个整数的数组 nums &#xff0c;其数字都在 [1, n] 范围内&#xff08;包括 1 和 n&#xff09;&#xff0c;可知至少存在一个重复的整数。 假设 nums 只有 一个重复的整数 &#xff0c;返回 这个重复的数 。 你设计的解决方案必须 不修…

干货:如何拯救程序员的苦恼?

本站的同志大多都是IT行业的从业者。今天博主给大家推荐几个帮助程序员解决烦恼的网站&#xff0c;大家一定要收藏哦&#xff01; 目录 1. 图标平台——ByteDance IconPark 2. 进制转换——so json在线工具 3. 代码高亮——CodeInWord 4. 取名利器——codelf 5. 颜色图签—…

Mac右键添加通过VSCode打开

Mac右键添加通过VSCode打开 1 首先打开自动操作 进入方式 访达 – 应用程序 – 自动操作 2. 选择快速操作 3. 添加 最后 commands保存&#xff0c;可以输入自定义的名称 for f in "$" doopen -a "Visual Studio Code" "$f" done4. 找到保存的快…

引领数据趋势:2023年最值得关注的十大ETL数据集成工具

在这个数据至上的时代&#xff0c;对于以数据为驱动的组织来说&#xff0c;建立一个信息集中的强大源头是成功的关键。众多企业依靠ETL工具来管理和理解它们的数据。 ETL&#xff0c;即提取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;、加载&…

Grammarly premium语法检测工具使用方法,及删除检测记录

科研写作神器&#xff1a;Grammarly—语法&#xff0c;标点&#xff0c;单词拼写错误修改。 一、背景 在写英文论文时&#xff0c;作为母语不是英语的我们&#xff0c;不可避免的存在语法错误或笔误&#xff0c;这时就需要Grammarly语法修改软件帮助我们进行修正&#xff0c…

2021年12月14日 Go生态洞察:Go 1.18 Beta 1 发布与泛型的引入

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

优化肠道菌群——对抗肌肉减少和骨质流失

谷禾健康 现代的生活工作方式大多是久坐&#xff0c;户外运动少&#xff0c;导致与骨骼肌肉相关的亚健康症状越来越普遍&#xff0c;覆盖人群越来越广。 例如长时间的低头垂肩的姿势会让竖脊肌处在伸展的位置&#xff0c;进而导致竖脊肌的无力&#xff0c;产生受伤的状况。长时…

Android 滑动按钮(开关) SwitchCompat 自定义风格

原生的SwitchCompat控件如下图&#xff0c;不说不堪入目&#xff0c;也算是不敢恭维了。开个玩笑... 所以我们就需要对SwitchCompat进行自定义风格&#xff0c;效果如下图 代码如下 <androidx.appcompat.widget.SwitchCompatandroid:id"id/switch_compat"android:…

【UGUI】Unity为下拉菜单添加选项(DropDown)

要想控制谁就把谁拿到代码里-获取组件-修改组件参数&#xff08;变量或者方法&#xff09; 代码示例&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using TMPro;public class UIcontrol : MonoBehavi…