当前位置: 首页 > news >正文

【Linux修炼】16.共享内存

在这里插入图片描述每一个不曾起舞的日子,都是对生命的辜负。

共享内存

  • 一.共享内存的原理
  • 二.共享内存你的概念
    • 2.1 接口认识
    • 2.2演示生成key的唯一性
    • 2.3 再谈key
  • 三.共享资源的查看
    • 3.1 如何查看IPC资源
    • 3.2 IPC资源的特征
    • 3.3 进程之间通过共享内存进行关联
  • 四.共享内存的特点
  • 五.共享内存的内核结构
  • 六.共享内存函数的总结

共享内存是为通信而诞生的。除了上一节中讲到的公共文件的方案,还有什么其他方案呢?----以共享内存的方式

一.共享内存的原理

在之前学过的进程地址空间的基础上,我们知道,进程之间具有独立性,因为每个进程的内核数据结构的数据以及页表的映射都是独立的。而对于共享内存,我们同样了解,这是为了让进程之间能够进行通信的公共空间,接下来就通过进程地址空间的结构去了解共享空间的位置及原理:

image-20230311104955697

OS为了让两个毫不相关的进程之间进行通信,进行了三个工作:

  1. 在对应的内存当中让用户帮OS申请一块空间(通过指定的调用接口)
  2. 将创建好的内存映射进进程的地址空间(用户就可以通过访问起始地址的方式来进行对申请的这块内存空间的访问)
  3. 未来不想通信:
    • 取消进程和内存的映射关系
    • 释放内存

因此,我们把申请的这块空间称之为共享内存,将映射关系称之为进程和共享内存进行挂接。将取消进程和内存的映射关系称之为去关联,释放内存释放的就是共享内存。

理解:

  • 进程间通信,是专门设计的,用来IPC的,和malloc/new不是一个东西。
  • 共享内存是一种通信方式,所有想通信的进程,都可以用。
  • OS中一定会存在着很多共享内存。

二.共享内存你的概念

通过让不同的进程,看到同一个内存块的方式,叫做共享内存。

2.1 接口认识

#include<sys/ipc.h>
#include<sys/shm.h>int shmget(key_t key, size_t size, int shmflg);// size:共享内存的大小

对于shmflg,常见的有这么两种选择:

  • IPC_CREAT:如果不存在共享文件则创建,存在则获取
  • IPC_EXCL
    • 1.无法单独使用,单独使用没有意义,需要结合IPC_CREAT
    • 2.IPC_CREAT|IPC_EXCL:如果不存在,就创建,如果已存在,就出错返回。即在用户的角度,如果创建成功,一定是一个新的shm!

shmget返回值: 记住他是一个标识符就够用了,得到的是共享内存的标识符。(和文件fd没有任何关系)

key: 是什么不重要,最重要的是其具备的唯一性。

而获取key值,则通过一个新的接口:ftok ,ftok通过指定的字符串数据*pathname以及char类型的proj_id数据进行一系列的算法整合返回了具有唯一性的Key:

key_t ftok(char *pathname, char proj_id);

由于创建的key值有可能已经被别人使用了,因此有失败的可能性。创建Key值如果失败,则返回-1。

2.2演示生成key的唯一性

makefile

.PHONY:all
all:shm_client shm_servershm_client:shm_client.ccg++ -o $@ $^ -std=c++11
shm_server:shm_server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f shm_client shm_server

comm.hpp

#ifndef _COMM_HPP_
#define _COMM_HPP_#include<iostream>
#include<cerrno>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<sys/ipc.h>
#include<sys/shm.h>#define PATHNAME "."//当前路径
#define PROJ_ID 0x66key_t getKey()
{key_t k = ftok(PATHNAME, PROJ_ID);if(k < 0){// cin, cout, cerr ->stdin, stdout, stderr->0, 1, 2;标准错误stderr向2打印。std::cerr << errno << ":" << strerror(errno) << std::endl;exit(1);//终止进程}return k;
}#endif

shm_server.cc

#include"comm.hpp"int main()
{key_t k = getKey();printf("0x%x\n", k);return 0;
}

shm_client.cc

#include"comm.hpp"int main()
{key_t k = getKey();printf("0x%x\n", k);return 0;
}

image-20230313161821844

通过make后执行发现,两个程序的k值是一样的,这就证明了ftok指定参数的返回值是唯一的。(k实际上就是32位的一个整数)

2.3 再谈key

OS中一定存在多个共享内存,因为彼此之间可能都需要通信,因此也就都需要申请一块空间。而OS申请的共享空间,也一定和进程一样需要被管理,既然需要管理,那么一定也是先描述再组织的方式,即共享内存 = 物理内存块+共享内存的相关属性

之前谈到过,key是什么不重要,能进行唯一性的标识最重要,因此创建共享内存的时候,是如何保证共享内存在系统中是唯一的呢?当然是通过key来确定的,只要一个进程也看到了同一个key,就能够访问这个共享内存。那么key在哪里,实际上这就和PCB一样,key就在内核中的属性集合里,即:

struct shm{key_t key;//...
}

即:key是通过shmget这样的系统调用,设置进入共享内存属性中,用来表示该共享内存在内核中的唯一性!

shmid和key就好比fd和inode。为什么有了key还需要shmid呢?通过key和shmid的区分,能够面向系统层面和用户层面,这样能够更好的进行解耦,以免内核中的变化影响到用户级。

三.共享资源的查看

共享(IPC)

3.1 如何查看IPC资源

ipcs -m/q/s查看:image-20230313194544812

3.2 IPC资源的特征

image-20230313195548826

我们发现,当第一次执行成功之后,再次调用不会成功,这是因为共享内存并不像管道一样进程结束之后自动释放内存,共享内存的声明周期是随着OS的,不是随着进程的!因此这就需要我们主动的去释放这块空间,即通过指令:ipcrm -m 加上这块共享内存shmid的值,虽然key也是唯一的,但key是系统层面的,shmid才是对于我们用户层面的。

对于释放共享内存,除了上述的手动命令,其也有自己的接口能够进行共享内存物理空间的释放,即:

#include<sys/ipc.h>
#include<sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//cmd代表控制的种类,即内置的有多种选择。
//返回值:失败则返回-1
void delShm(int shmid)
{if(shmctl(shmid, IPC_RMID, nullptr) == -1){std::cerr << errno << " : " << strerror(errno) << std::endl;}
}

以下代码复制SSH渠道:执行while :; do ipcs -m; sleep 1; echo"--------------------";done,并在左侧执行shm_server,观察现象:

#ifndef _COMM_HPP_
#define _COMM_HPP_#include<iostream>
#include<cerrno>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>#define PATHNAME "."//当前路径
#define PROJ_ID 0x66
#define MAX_SIZE 4096 //单位是字节
key_t getKey()
{key_t k = ftok(PATHNAME, PROJ_ID);//可以获取同样的keyif(k < 0){// cin, cout, cerr ->stdin, stdout, stderr->0, 1, 2;标准错误stderr向2打印。std::cerr << errno << ":" << strerror(errno) << std::endl;exit(1);//终止进程}return k;
}int getShmHelper(key_t k, int flags)
{//k是要shmget,设置进入共享内存属性中的!用来表示//该共享内存,在内核中的唯一性!!//shmid VS key//fd    VS inodeint shmid = shmget(k, MAX_SIZE, flags);if(shmid < 0){std::cerr << errno << ":" << strerror(errno) << std::endl;//失败就终止,没有共享内存了exit(2);}return shmid;
}
int getShm(key_t k)
{return getShmHelper(k, IPC_CREAT);
}int createShm(key_t k)
{return getShmHelper(k, IPC_CREAT | IPC_EXCL);
}void delShm(int shmid)
{if(shmctl(shmid, IPC_RMID, nullptr) == -1){std::cerr << errno << " : " << strerror(errno) << std::endl;}}#endif
#include"comm.hpp"int main()
{key_t k = getKey();printf("key: 0x%x\n", k); // keyint shmid = createShm(k);printf("shmid: %d\n", shmid); // shmidsleep(5);//5s之后就会被删除delShm(shmid);return 0;
}

共享内存1

发现,进程执行之前没有对应的信息,执行之后信息出现5s,最终被释放。再次执行也不会出错。

进程之间进行关联

上述都没有提到进程进行关联的问题,有几个进程能够进行关联在上述动图右侧nattch可以看到,明显看到上面的nattch值为0,那么下面就来进行挂接一下:

此时就又有一个新的接口:

#include<sys/types.h>
#include<sys/shm.h>//参数1:指定的共享内存,参数二:地址空间,一般设置为nullptr,参数三:读写权限,一般设置为0就可以了,默认就可以读写。//返回值:共享内存空间的起始地址,就等价于malloc的返回值
void *shmat(int shmid, const void* shmaddr, int shmflg);

因此就可以在comm.hpp中加上这个功能:

void* attchShm(int shmid)
{void* mem = shmat(shmid, nullptr, 0);//64系统:指针占8字节if((long long)mem == -1L){std::cerr << errno << ":" << strerror(errno) << std::endl;exit(3);}return mem;
}

image-20230313211728082

但此时挂接就需要进行读写,因此还需要在createshm中添加选项:image-20230313212309740

接下来看看运行结果:

共享内存2

可以发现的是,由于我们新增了0600即拥有者的读写权限,perm也就显示了600,此外nattch的链接数量也变成了1,这说明有一个进程和这个共享内存关联起来了,而我们所演示的就是我自己的进程与共享内存进行了关联。运行之后同样可以释放。但上面直接释放过于粗暴,因为我们之前将进程和共享内存进行了关联,所以我们需要在释放之前将这个关联去掉,否则就有可能出问题。去关联并不是删掉共享内存,而是回收对应的页表。为了去关联,就又引出了一个接口:

// 参数就是在shmat时设定的返回值,对于返回值:成功就是0,失败就是-1.
int shmdt(const void* shmaddr);

image-20230313213653870

image-20230313213721589

因此,添加了这段代码后,就比上述现象在结束之前多了一个nattch变为0的过程。


3.3 进程之间通过共享内存进行关联

上述我们已经实现了shm_server与共享内存的关联,如果想让两个进程之间进行通信,那就需要另一个shm_client也与同一个共享内存进行关联:image-20230314101239560

但对于这段代码,不需要释放共享内存,因为在shm_server.cc中已经实现。这段接入之后,nattch的关联数就会变成2。


在之前的学习中,我们通过管道采用char buffer[1024]缓冲区的方式进行通信,现在有了共享内存就可以通过共享内存将两个进程连接起来。

shm_server.cc

#include"comm.hpp"int main()
{key_t k = getKey();printf("key: 0x%x\n", k); // keyint shmid = createShm(k);printf("shmid: %d\n", shmid); // shmidsleep(5);//5s之后就会被删除char* start = (char*)attchShm(shmid);//挂接成功printf("attach success, addresss start: %p\n", start);//使用while(true){//char buffer[]; read(pipefd, buffer)printf("client say: %s\n", start);sleep(1);}//去关联:让进程和共享内存丧失关联性detachShm(start);sleep(5);delShm(shmid);return 0;
}

shm_client.cc

#include"comm.hpp"int main()
{key_t k = getKey();printf("key: 0x%x\n", k);int shmid = getShm(k);printf("shmid: %d\n", shmid);sleep(5);//与共享内存产生关联char* start = (char*)attchShm(shmid);printf("attach success, address start: %p\n", start);const char* message = "hello server,我是另一个进程,正在和你通信";pid_t id = getpid();int cnt = 1;char buffer[1024];while(true){sleep(1);snprintf(start, MAX_SIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);// snprintf(buffer, sizeof(buffer), "%s[pid:%d][消息编号:%d]", message, id, cnt);// memcpy(start, buffer, strlen(buffer)+1);// //pid, count, message}//去关联:让进程和共享内存丧失关联性detachShm(start);return 0;
}

执行:(记得手动ipcrm -m)

共享内存3

四.共享内存的特点

共享内存的优点:

所有进程间通信,速度是最快的!因为其共享内存在进程地址空间能够大大减少数据的拷贝次数。(即本来用buffer,现在没有必要)

综合考虑管道和共享内存,考虑键盘输入和显示器输入,共享内存共有几次数据拷贝,即同一段代码,通过管道和共享内存,分别进行了几次拷贝?

对于管道来说,通过的是如下步骤:image-20230314143948484

将键盘输入的数据放到自己指定的缓冲区buffer中为第一次,将buffer中的数据拷贝到管道中是第二次,将管道中的数据拷贝到另一个进程的缓冲区中为第三次,将缓冲区的数据打印在显示器中为第四次,此外还有输入输出流stdin和stdout,所以为4+2次。

对于共享内存来说,没有中间的buffer,因此也就是2+2次。

共享内存的缺点:

共享内存不会进行同步和互斥的操作,没有对数据做任何的保护。也就是说,共享内存并不像管道一样,管道是当读写都打开时,如果不读,写满就会不写了,如果写端不写,读端就不会继续读了并且阻塞在那里,而共享内存没有做这样的保护。那么如何保护?今后将会学到信号量和互斥锁的方式对管道进行保护。

五.共享内存的内核结构

共享内存数据结构:

struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};struct ipc_perm{key_t          __key;    /* Key supplied to shmget(2) */uid_t          uid;      /* Effective UID of owner */gid_t          gid;      /* Effective GID of owner */uid_t          cuid;     /* Effective UID of creator */gid_t          cgid;     /* Effective GID of creator */unsigned short mode;     /* Permissions + SHM_DEST and SHM_LOCKED flags */unsigned short __seq;    /* Sequence number */
}

共享内存的大小

共享内存的大小,一般建议是4KB的整数倍,因为系统分配共享内存是以4KB为单位的! — 内存划分内存块的基本单位。

否则内核会给你向上取整。但我们能够使用的仍是我们指定的大小。

六.共享内存函数的总结

上面在演示的时候,已经逐步的介绍了有关共享内存函数的功能,我们在这里总结一下:

shmget函数

功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数key:这个共享内存段名字size:共享内存大小shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

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,表示连接操作用来只读共享内存
    

shmdt函数

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

shmctl函数

功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数shmid:由shmget返回的共享内存标识码cmd:将要采取的动作(有三个可取值),如下buf:指向一个保存着共享内存的模式状态和访问权限的数据结构,一般设定为nullptr即可
返回值:成功返回0;失败返回-1

image-20230314152338242

此外,关于最终的代码展示在如下链接:共享内存代码

相关文章:

【Linux修炼】16.共享内存

每一个不曾起舞的日子&#xff0c;都是对生命的辜负。 共享内存一.共享内存的原理二.共享内存你的概念2.1 接口认识2.2演示生成key的唯一性2.3 再谈key三.共享资源的查看3.1 如何查看IPC资源3.2 IPC资源的特征3.3 进程之间通过共享内存进行关联四.共享内存的特点五.共享内存的内…...

JAVA进阶 —— Stream流

目录 一、 引言 二、 Stream流概述 三、Stream流的使用步骤 1. 获取Stream流 1.1 单列集合 1.2 双列集合 1.3 数组 1.4 零散数据 2. Stream流的中间方法 3. Stream流的终结方法 四、 练习 1. 数据过滤 2. 数据操作 - 按年龄筛选 3. 数据操作 - 演员信息要求…...

Linux基础命令大全(上)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的绽放&#xff0…...

嵌入式 串口通信

目录 1、通信的基本概念 1.1 串行通信 1.2 并行通信 2、串行通信的特点 2.1 单工 2.2 半双工 2.3 全双工 3、串口在STM32的引脚 4、STM32的串口的接线 4.1 STM32的串口1和电脑通信的接线方式 4.2 单片机和具备串口的设备连接图 5、串口通信协议 6、串口通信…...

C语言函数调用栈

栈溢出&#xff08;stack overflow&#xff09;是最常见的二进制漏洞&#xff0c;在介绍栈溢出之前&#xff0c;我们首先需要了解函数调用栈。 函数调用栈是一块连续的用来保存函数运行状态的内存区域&#xff0c;调用函数&#xff08;caller&#xff09;和被调用函数&#xf…...

【高阶数据结构】红黑树

文章目录1. 使用场景2. 性质3. 结点定义4. 结点旋转5. 结点插入1. 使用场景 Linux进程调度CFSNginx Timer事件管理Epoll事件块的管理 2. 性质 每一个节点是红色或者黑色根节点一定是黑色每个叶子节点是黑色如果一个节点是红色&#xff0c;那么它的两个儿子节点都是黑色从任意…...

网络协议分析期末复习(二)

目录 12. 端口的定义及常见应用对应的端口号 13. UDP协议概述 14.UDP数据报格式及各字段意义 15. UDP-Lite协议概述 16. TCP数据报格式及各字段意义 17. TCP连接建立及协商参数的过程 18. TCP连接释放过程 19. 路由协议分类及各类的具体协议 20. 路由算法常用的度量 2…...

【C++】STL简介 及 string的使用

文章目录1. STL简介1.1 什么是STL1.2 STL的版本1.3 STL的六大组件2. string类的使用2.1 C语言中的字符串2.2 标准库中的string类2.3 string类的常用接口说明1. string类对象的常见构造2. string类对象的容量操作3. string类对象的修改操作4. resize和reserve5. 认识迭代器&…...

MySQL事务详解

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;Spring事务和MySQL事务详解 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: …...

ChatGPT背后的技术和多模态异构数据处理的未来展望——我与一位资深工程师的走心探讨

上周&#xff0c;我和一位从业三十余年的工程师聊到ChatGPT。 作为一名人工智能领域研究者&#xff0c;我也一直对对话式大型语言模型非常感兴趣&#xff0c;在讨论中&#xff0c;我向他解释这个技术时&#xff0c;他瞬间被其中惊人之处所吸引&#x1f64c;&#xff0c;我们深…...

iOS-砸壳篇(两种砸壳方式)

CrackerXI砸壳呢&#xff0c;当时你要是使用 frida-ios-dump 也是可以的&#xff1b; https://github.com/AloneMonkey/frida-ios-dump frida-ios-dump: 代码中需要更改的&#xff1a;手机中的内网ip 密码 等 最后放到我的砸壳路径里&#xff1a; python dump.py -l查看应用…...

linux 基础

1.Shell 命令的格式如下&#xff1a;command -options [argument]command: Shell 命令名称。options&#xff1a; 选项&#xff0c;同一种命令可能有不同的选项&#xff0c;不同的选项其实现的功能不同。argument&#xff1a; Shell 命令是可以带参数的&#xff0c;也可以不带参…...

Java:SpringBoot给Controller添加统一路由前缀

网上的文章五花八门&#xff0c;不写SpringBoot的版本号&#xff0c;导致代码拿来主义不好使了。 本文采用的版本 SpringBoot 2.7.7 Java 1.8目录1、默认访问路径2、整个项目增加路由前缀3、通过注解方式增加路由前缀4、按照目录结构添加前缀参考文章1、默认访问路径 packag…...

Java 基于 JAVE 库 实现 视频转音频的批量转换

文章目录 Java 基于 JAVE 库 实现 视频转音频的批量转换Maven:方案一:代码优化:方案二:示例代码:代码优化:结语Java 基于 JAVE 库 实现 视频转音频的批量转换 实现视频转音频的功能需要使用到一个第三方的 Java 库,叫做 JAVE。JAVE 是一个开源的 Java 库,提供了视频和音频转换…...

Spring容器——基于XML注入

1. 容器&#xff1a;IOC IoC 是 Inversion of Control 的简写&#xff0c;译为“控制反转”&#xff0c;它不是一门技术&#xff0c;而是一种设计思想&#xff0c;是一个重要的面向对象编程法则&#xff0c;能够指导我们如何设计出松耦合、更优良的程序 Spring 通过 IoC 容器来…...

设计模式(二十一)----行为型模式之状态模式

1 概述 【例】通过按钮来控制一个电梯的状态&#xff0c;一个电梯有开门状态&#xff0c;关门状态&#xff0c;停止状态&#xff0c;运行状态。每一种状态改变&#xff0c;都有可能要根据其他状态来更新处理。例如&#xff0c;如果电梯门现在处于运行时状态&#xff0c;就不能…...

一分钟理解 AP(Affinity Propagation) 亲和⼒传播算法

从来没有一个算法让我研究好几天都搞不明白&#xff0c;AP算法算是第一个。弄了好几天&#xff0c;打草纸用了几十页&#xff0c;反复琢磨&#xff0c;最后都怀疑人生了。我觉得网上那么多介绍 AP 的文章&#xff0c;基本上没有一篇能讲明白的。最后我都觉得 AP 的作者可能都没…...

使用mybatis的映射文件操作存储过程

先随便创建一个存储过程 DELIMITER $$ CREATE PROCEDURE getUserNameById (IN i_id BIGINT, OUT o_name VARCHAR(10)) BEGINSELECT u.name INTO o_name FROM tb_user u WHERE id i_id; END $$delimiter $$ : 是将sql语句的结束符号先替换成$$的意思&#xff0c;因为sql是遇到…...

世界上最完美的两个软件,太厉害了!

今天给大家介绍两个软件&#xff0c;一个体现了人类在软件开发流程上的极致&#xff0c;另外一个则体现了程序员个体能力的巅峰。01航天飞机飞控软件先来说第一个&#xff0c;航天飞机飞行控制软件&#xff0c;就是下图这个大家伙。航天飞机重达120吨&#xff0c;还携带着2000吨…...

教你成为比卡卡西还牛逼的全能忍者,全拷贝与分割函数

如何成为一个集雷切&#xff0c;写轮眼侦查和拷贝与一身的卡卡西&#xff0c;下面教你&#xff01; 目录 第一式——雷切&#xff01; strtok 第二式——写轮眼侦查&#xff01; strerror函数 第三式——写轮眼拷贝&#xff01; memcpy 模拟实现memcpy函数 &#x1f60e;…...

【LeetCode】剑指 Offer(24)

目录 题目&#xff1a;剑指 Offer 47. 礼物的最大价值 - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a; 题目&#xff1a;剑指 Offer 47. 礼物的…...

javaEE 初阶 — CSS 元素的显示模式与盒模型

文章目录1. 元素的显示模式1.1 块级元素1.2 行内元素1.3 行内元素和块级元素的区别1.4 改变显示模式2. 盒模型2.1 边框2.1.1 边框的粗细2.1.2 边框的颜色2.1.3 边框的风格2.2 内边距2.3 外边距2.3.1 margin 的特殊情况1. 元素的显示模式 1.1 块级元素 常见的元素: h1 - h6 、…...

新星计划-我为什么要写博客?写博客的意义是什么

CSDN的各位友友们你们好,今天千泽要和大家交流一下写博客的意义,并且鼓励大家参加CSDN官方举办的新星计划,这个可以让我们更快的成长,十分有价值.接下来让我们一起开始吧!如果对您有帮助的话希望能够得到您的支持和帮助,我会持续更新的!&#x1f6a9;part1:自我介绍我是一名来自…...

嵌入式学习笔记——STM32的USART收发字符串及串口中断

USART收发字符串及串口中断前言字符串的收发发送一个字符串接收字符串需求利用串口实现printf中断中断是什么前言 上一篇中&#xff0c;介绍了串口收发相关的寄存器&#xff0c;通过代码实现了一个字节的收发&#xff0c;本文接着上面的内容&#xff0c;通过功能函数实现字符串…...

数据分析之Pandas(1)

3.Pandas 文章目录3.Pandas3.1 Pandas基本介绍3.1.1 Pandas的基本数据结构3.1.1.1 Pandas库的Series类型3.1.1.2 Pandas库的DataFrame类型DataFrame初始化DataFrame查看数据3.1.2 Pandas读取数据及数据操作行操作添加一行删除一行列操作增加一列删除一列通过标签选择数据条件选…...

17、江科大stm32视频学习笔记——USART串口协议和USART串口外设

目录 1、通信接口 2、 硬件电路 3、电平标准 4、串口参数及时序 5、USART简介 6、USART工作 &#xff08;1&#xff09;写操作 &#xff08;2&#xff09;读操作 &#xff08;3&#xff09;帧头和帧尾的添加和除由电路自动执行 &#xff08;4&#xff09;硬件数据控制…...

leetcode:有效地括号

给定一个只包括 ‘(’&#xff0c;‘)’&#xff0c;‘{’&#xff0c;‘}’&#xff0c;‘[’&#xff0c;‘]’ 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都…...

四等分list

Testpublic void s (){ int targ 4; List mList new ArrayList(); List<List> mEndList new ArrayList<>(); for (int i 0; i <34; i) { mList.add(“item” i); } // System.out.println(mList.toString()); if (mList.size() % targ ! 0) { for (int j …...

php连接sqlserver数据库

docker 安装sqlserver数据库sudo docker pull mcr.microsoft.com/mssql/server:2017-latestsudo docker run -e "ACCEPT_EULAY" -e "MSSQL_SA_PASSWORD<YourStrongPassw0rd>" -p 1433:1433 --name sqlserver --hostname sqlserver -d mcr.microsoft.…...

The 2019 China Collegiate Programming Contest Harbin Site F. Fixing Banners

Problem - F - Codeforces 翻译&#xff1a; 哈尔滨&#xff0c;这个名字最初是一个满语单词&#xff0c;意思是“晒渔网的地方”&#xff0c;从松花江边的一个小农村居民点发展成为中国东北最大的城市之一。1898年&#xff0c;随着中国东部铁路的到来&#xff0c;这座城市首先…...

我是做网站的 怎么才能提高业绩/全网整合营销平台

文章目录前言1、乐观锁2、悲观锁3、自旋锁4、可重入锁&#xff08;递归锁&#xff09;5、读写锁6、公平锁7、非公平锁8、共享锁9、独占锁10、重量级锁11、轻量级锁12、偏向锁13、分段锁14、互斥锁15、同步锁16、死锁17、锁粗化18、锁消除19、synchronized20、Lock和synchronize…...

直播网站建设重庆/seo网站关键词优化机构

3分钟学会&#xff0c;2种Wincc v14多语言组态&#xff0c;实现工控屏语言切换项目组态效果预览如下方动态图所示&#xff0c;挺好的吧&#xff01;西门子WIncc V14项目多语言组态效果图一&#xff1a;必背技巧1.1&#xff1a;按钮事件组态系统函数修改显示语言(相比于利用VB脚…...

wordpress博客登录/seo如何优化

文章目录 项目介绍主要功能截图:登录首页学生管理教师管理课程类型管理权重设置管理思想道德管理拓展素质管理平时成绩管理成绩信息管理考勤信息管理系统功能架构图部分代码展示设计总结项目获取方式🍅 作者主页:Java韩立 🍅 简介:Java领域优质创作者🏆、 简历模板、学…...

橱柜衣柜做网站/网络营销的基本方法

未知高度容器的多种垂直居中方法在已知父子高度的情况下&#xff0c;实现垂直居中是很容易的事。margin &#xff0c; padding&#xff0c;absolute 负margin , 甚至于 line-height都是可行的方案。这里不再展开&#xff0c;文章主要来介绍在父容器高度固定&#xff0c;自容器…...

什么是网站app/培训总结心得体会

一、今天学习到了vim编辑器的快捷键操作 全选复制&#xff1a;gg->y->G 全选删除&#xff1a;gg->d->G 二、shell编程方面 1、echo -e 表示开始转义&#xff0c;所以 echo -e "Hello \n World"执行结果是&#xff1a; Hello World 2、echo -n 表示…...

深圳企业建站设计公司/营销活动推广方案

使用python逐行读取文件内容的几个方法&#xff1f;方法一:使用readline读取&#xff1a; f open("fix.txt") line f.readline() while line: print line line f.readline() f.close() 方法二&#xff1a; for line in open("fix.txt&q…...