Linux - 动静态库(下篇)

前言

在上篇博客当中,对静态库是什么,怎么使用,简单实现自己的静态库,这些做了描述,具体请看上篇博客:
 

本篇博客将会对 动态库是什么,怎么使用,简单实现自己的动态库,这些做描述。

动态库

动态库的创建

 动态库的创建其实说和 静态库是非常类似的,都是先想要把 源程序文件生成 ".o" 为后缀的文件,也就是编译成功过的文件,然后再 进行链接。

只是在链接过程当中链接方式不同。

在静态库当中,我们使用的编译源文件的方式是直接使用 "-c" 选项的方式生产源程序的 ".o" 编译文件。

gcc -c $^   // -c 选项不在后续 $@ 确认生成的目标文件名,默认生成的 与源程序名相同的 .o 文件

但是在动态库当中,我们在上述生成 ".o" 文件使用 "-c" 选项 之外,还需要带上 "-fPIC" (其中 PIC 要大小)选项,这个  fPIC 代表的意思是 产生位置无关码

gcc -fPIC -c xxx.c

而gcc 是默认 优先链接 动态库的,也就是,当 同一个动态库和静态库同时存在的时候,gcc 会优先链接动态库吗,除非是 当前只有 静态库,没有动态库,那么 gcc 没有办法只能使用静态库;又或者是 用户强制要求要用某一个静态库。

所以,静态库在安装的过程当中,需要用户手动去安装,但是对于 动态库,gcc 就可以自己帮助我们实现一个 ".so" 文件。

利用gcc命令,使用 "-shared" 选项,生成一个动态库:
 

gcc -shared -o libxxx.so *.o

上述就是利用在当前目录当中的所有后缀为 ".o" 的文件,打包为一个 xxx 动态库(动态库必须是 "lib" 开头 ,".o" 结尾的文件名)

上述的 "-shared" 选项就是生成一个 分享动态库。


静态库只是该源程序提供代码,本质上也就是提供一些二进制数据,所以,静态库的作用其实就是把源程序需要的代码拷贝到源程序当中,拷贝完之后,源程序当中是如何进行执行的,就和 这个静态库没关系了;静态库是不用被加载到内存当中执行的

 而动态库不一样,当源程序当中执行到 动态库当中的代码的时候,是直接跳转到 动态库当中去执行,所以,注定了 动态库当中的代码是需要被加载到内存当中供 cpu 执行的

从 两 ".a" 和 ".so" 文件的 默认权限你也可以看出,动态库的文件权限当中是带 "x" 可执行的,而 静态库不是。

动态库:

 静态库:所以,上述动态库我们直接执行就会报错:
 

 并不是 这个程序不能执行,而是这个动态库不能单独执行,他需要源程序来调用其中的代码来执行。

而,动态库和静态库不是不能执行,只是不能单独执行,他们都是未来 链接出的 可执行程序当中代码的一部分。


现在我们给出几个源程序文件:

// mylog.c
#include "mylog.h"

void Log(const char*info)
{
    printf("Warning: %s\n", info);
}

//mylog.h
#pragma once

#include <stdio.h>


void Log(const char*);

//mymath.c
#include "mymath.h"

int myerrno = 0;

int add(int x, int y)
{
    return x + y;
}
int sub(int x, int y)
{
    return x - y;
}
int mul(int x, int y)
{
    return x * y;
}
int div(int x, int y)
{
    if(y == 0){
        myerrno = 1;
        return -1;
    }
    return x / y;
}

//mymath.h
#pragma once

#include <stdio.h>

extern int myerrno;

int add(int x, int y);
int sub(int x, int y);
int mul(int x, int y);
int div(int x, int y);

//myprint.c
#include "myprint.h"


void Print()
{
    printf("hello new world!\n");
    printf("hello new world!\n");
    printf("hello new world!\n");
    printf("hello new world!\n");
}

//myprint.h

#pragma once

#include <stdio.h>


void Print();

所以,有了上述动态库创建,和上篇博客当中 对于 静态库的 创建,现在我们在makefile 当中写上对应 生成的目标文件,就可以一键生成的 对应的 动态库 和 静态库了:
 

dy-lib=libmymethod.so   #目标动态库文件的生成
static-lib=libmymath.a  # 目标静态库文件的生成

.PHONY:all              #同时生成多个目标文件
all: $(dy-lib) $(static-lib)   all 这个文件依赖于 dy-lib 和 static-lib

$(static-lib):mymath.o   # 生成静态库(把 .o 文件打包生成 libmymath.a文件)
	ar -rc $@ $^
mymath.o:mymath.c        # 由源程序 生成 对应 .o 文件
	gcc -c $^           

$(dy-lib):mylog.o myprint.o  # 生成动态库(把 .o 文件打包生成 libmymethod.so文件)
	gcc -shared -o $@ $^
mylog.o:mylog.c              # 由源程序 生成 对应 .o 文件
	gcc -fPIC -c $^    
myprint.o:myprint.c          # 由源程序 生成 对应 .o 文件
	gcc -fPIC -c $^

.PHONY:clean
clean:
	rm -rf *.o *.a *.so mylib

.PHONY:output            # 用于发布的出来 的 生成的目标文件
output:
	mkdir -p mylib/include
	mkdir -p mylib/lib
	cp *.h mylib/include
	cp *.a mylib/lib
	cp *.so mylib/lib

先 make 把对应的 源程序文件 编译生成 ".o" 文件:

下述就是上述生成之后的结果:

   把上述这些  ".o" 文件 打包到 各自的 动静态库 当中:

此时就发布完成了:
 

 上述的这个 lib 文件夹当中的内容,就是我们上述由 各个源程序文件生成的 各自的 .o 文件的动态库,和 静态库的 。

此时,直接使用这种 "#include<mylog.h>" ,然后直接编译的话,他就会提示找不到这个头文件,所以,我们还需要把 这个存储 动静态库 和 各自头文件的 mylib 文件夹,拷贝到 系统默认路径 当中,这种才能使用 这种 "#include<mylog.h>" 方式来引用头文件,和使用其中的代码。

 上述拷贝到 默认 路径当中,其实就是库的安装,这种方式使用最多的,当然,你也可以使用 gcc 命令当中的 "-i"  "-L" "-l" 三个选项来实现。

上述我们就创建了一个 动态库 和 一个静态库。


如何让可执行程序找到动态库(LD_LIBRARY_PATH 环境变量

现在我们来用上述的 动态库,来实现一个 可执行程序,然后编译运行:

在上述,生成的 动态库 和 静态库的基础之上,我们来使用上述的库,来写一个程序,看看能不能编译成功:
 

//main.c

#include "mylog.h"
#include "myprint.h"

int main()
{

    Print();
    Log("hello log function");
    return 0;
}

上述引用的两个头文件都是动态库当中头文件,也就是说上述源程序当中只引入了 我们上述实现的 动态库。

但是我们发现,虽然我们上述的 动静态库,没有在 系统默认路径下安装,但是我们还用了gcc 当中的三个指令来引入 头文件 和 库;虽然成功生成了 可执行文件,但是,当我们运行这个可执行程序的时候,却报错:找不到我们上述实现的  动态库:

 但是,当我们 file 命令 这个 a.out 可执行文件之时,却告知我们 这个可执行文件是有 动态库链接的

但是在 ldd 命令当中,显示这个 动态链接是 not found 的 :

 但是我们上述在 gcc 命令当中不是已经对 对应的 头文件 和 库名称,和库所在路径做了声明吗?已经告诉编译器了,头文件是什么,什么库,在哪里了。

是的,你已经告诉了编译器了,编译器已经知道了,但是别忘了,动态库是在加载到 内存当中,进行调度执行的,他不像静态库一样单独拷贝一份给对应的源程序当中,拷贝完就不管了;

动态库要加载到内存当中进行调度执行。

所以,上述只是 编译器知道了这个动态库要被执行,也只是编译器知道这个 库是什么,在哪里;但是,系统并不知道。

所以,系统要知道,也就是 加载器 要知道。

 所以,首先你要明白的是,动态库是一个单独的文件,他和 可执行文件是两个文件,我没在执行可执行文件之时,是带上了 路径的,就算没有带上路径,要想成功运行,必须在 PATH 环境变量当中存储的路径下,有这个可执行文件,才能够不带上路径执行。

所以,我们要想执行一个 动态库,就要让 系统找到这个动态库

 第一个方法就是把 这个 动态库拷贝到 C程序默认的系统路径当中,这样,系统就可以在 这个默认路径当中找到这个 动态库,从而执行。

如上所示,拷贝到 lib64 这个目录当中,系统 就可以找到这个 动态库,从而执行。


除了上述 拷贝 的方式,还可以 ln 命令 在上述 lib64 当中创建软链接,链接到 libmymethod.so 这个 动态库 所在的存储路径当中。

 软链接建立结果:

此时他直接就指向了 动态库的存储位置。

如上所示,我们都没有重新对 a.out 重新编译,但是上述使用 ldd 命令却已经可以看到链接 到 libmymethod.so 动态库的链接的结果了。 也可以运行了。


除了上述的 在 默认目录当中进行 拷贝,软链接 的操作,和 系统找命令的可执行程序的 PATH 环境变量一样,上述的 在C当中和 默认 查找 库的也有一个环境变量 --- LD_LIBRARY_PATH

说得更详细一点,LD_LIBRARY_PATH  环境变量当中存储的就是 系统用来搜索动态库的路径 

 

这个环境变量,可能你的系统当中会没有,但是如果你配置过 VIM 的话,可能会有。

如果没有也没关系,可以自己配置一下:

在冒号后面跟上 动态库所在的路径就可以了 ,不需要加上 动态库文件名。 

 因为,需要链接哪一个库,在 gcc 当中 选项当中已经给出了。

此时,我们再 ldd 命令查看 链接情况,发现也是链接成功了的。 


但是,上述我们是修改了 环境变量,但是我们修改的是 bash 的环境变量,因为 当前我们所有的运行的命令,都是bash 的为我们生成的子命令,export 命令是一个内建命令,因为它要修改 bash 的环境变量,所以,export 是不需要创建子进程,而是由 bash 自己的来执行。

但是,修改的终究是 bash 的环境变量,bash 当中的环境变量是在 程序启动之时,由对应的 环境变量生成脚本,一层一层 加载(添加)出来的。

所以,也就是说,如果我们不修改 每一次开机,系统脚本对 bahs 当中的环境变量的添加进行修改的话,那么,当我们关机之后,再次启动,脚本生成的还是之前的 那些环境变量。

所以,要想根本解决上述 由 LD_LIBRARY_PATH 环境变量当中存储的 路径的话,需要修改 生成的 bash 环境变量的脚本。


除了上述方式,还有一个访问可以实现:
在系统当中,有一个目录当中存储的是 后缀为 ".conf" 的文件,这些文件我们称之为 配置文件,在这些文件当中存储的都是路径信息

 这个目录是 :  /etc/ld.so.conf.d

 我们可以随便打开一个看看:

 所以,如果像系统找到 对应 动态库的话,就可以在   /etc/ld.so.conf.d 这个 路径下,创建一个 后缀为 ".conf"  的文件(文件名随便取),在这个文件当中,直接像上述一样,存放 对应 动态库的存储位置(注意此时 不需要带上 库名称)

 然后,很重要的一步,需要使用 ldconfig 命令 ,重新加载一下 上述路径下的配置文件

 上述这种 在 /etc/ld.so.conf.d 路径当中进行修改的操作,只要我们不修改我们对于其中的配置文件,那么这个修改就是永久有效的;

其实只有 上述的第三种使用 LD_LIBPARY_PATH 环境变量这个操作,如果不修改 bash 启动的脚本的话,关机之后就无效了


同样的,如果我们在上述程序当中加上 我们在上篇博客当中实现的静态库的话,只要我们gcc 编译链接成功了,把静态库当中的代码已经拷贝到 可执行程序当中用于链接了,而且此时我们运行程序是可以正常运行的,结果也是正确的。

那么,在之后,不管这个静态库被移动到什么其他位置了,不在之前链接的位置了,或者更加夸张一点,直接把静态库删除;

我们上述生成的可制成程序一样说可以跑的,因为,静态库链接,就是把静态库当中的代码直接拷贝到 对应源程序的当中,成为 源程序的 一部分。

而不像 动态库链接,链接成功之后,动态库的位置是不能移动的,因为编译链接成功之后,不仅仅是编译器在编译之时需要找到这个动态库位置,因为 动态库是要被加载到 内存当中进行执行的。

连接成功后,执行可执行程序,当 可执行程序执行到 需要动态库当中的代码之时, 就需要直接跳转到 动态库 当中执行,此时动态库需要被加载到内存当中,配合 原可执行程序 进行 执行。

所以,动态库在被加载之前,还需要被 系统知道在什么位置存储的,也就是 加载器需要知道 此动态库在什么位置存储的。

至此才能保证 动态库可以被加载到 内存当中进行运行。

如下所示:

在上述代码当中:"mymath.h" 就是上一篇博客当中实现的 静态库。 

上图就在之前 gcc 链接动态库的基础之上,加上 "-lmymath.h" 链接上静态库。 

删除静态库,并且在此执行程序:

 由上图你会发现,当我们删除掉静态库,没有重新编译链接 形成可执行文件,但是,在之前形成的 可执行文件 还是可以运行。没有出错。


当然,不管是静态库 还是 动态库,都是可以被多个 源程序所引用的。但是,由静态库 链接生成 的 多个 可执行文件,如果把此静态库删除掉,这些个 可执行程序还是可以跑的;

但是如果是 由 动态库 链接形成的多个可执行文件,如果是把此动态库删除掉了,那个由此动态库链接的所有 可执行程序就都不能跑了。

如上图所示:我们把上述 main 和 mytest 两个可执行文件 需要的 libmymethod.so 动态库删除了,发现就不能运行了。 

所以,动态库再被系统加载之后,会被所有的 进程所共享!!!

使用了动态链接的方式 形成 的 可执行文件,都是需要系统加载到内存,运行这个 动态库的。

而且,这个动态库被加载到内存,只需要加载一次,因为一次也已经够所有 进程所使用了。这样的话,在系统当中, 就减少了重复的代码。

所以,一个进程,在磁盘(外存)上有自己独立的一份代码,而在 内存当中又有 和其他 进程 有相同的 代码,共享这些相同的代码

所以,我们把 动态库 又叫做 共享库

那么,一个系统当中肯定会加载很多个 动态库到内存当中,操作系统如何管理这些海量的 库呢?

我们说,只要是加载到内存当中,被操作系统所管理的,都离不开 这 六字真言:先描述,再组织 

 先把 管理一个库,所需要的所有属性,都用一个 struct 结构体描述起来(这叫做 先描述对象),然后用某种数据结构,把上述生成的 很多个 struct 结构体都链接起来(这叫做 再组织)。


动态库是如何做到被所有进程共享的?(重谈进程地址空间)

 

如上所示,在每一个进程的 地址空间当中,每一个地址都是一个 虚拟地址,通过 页表映射到 物理内存地址。

 在磁盘当中,每一个进程 独立的 可执行程序,从磁盘当中被加载到 内存当中,通过 页表来映射到 进程地址空间的 代码区当中。

而,如果在 进程 独立的 可执行程序 当中发现,比如是 printf()函数并没有在 可执行程序当中实现,但是,在编译链接之时,又有一个动态库当中有 printf()函数的实现,那么在运行到 printf()函数之时,就会 跳转到 共享区 当中去寻找 printf()实现的虚拟地址,通过页表映射到物理内存当中。

在执行完之后,就会跳回 代码区当中继续执行 这个进程的 代码。

至此,动态库当中的代码就被进程所使用,同样的,上述只是一个进程,同样可以是多个进程。

那么,动态库加载到内存当中的 代码数据,就可以被 多个进程所使用了。

所以,在页表当中建立映射之后,从此往后,进程在执行任何的代码,不管是自己 可执行程序当中的代码,还是 共享区当中的 动态库的共享代码,都是可以在 进程地址空间当中进行 执行的。

 此时,对于动态库 和 自己可知程序当中的代码,就都可以看做是一份代码了,只不过在执行到 动态库当中的代码之时,需要跳转到 动态库当中代码进行执行,只是可能两份代码跳转的比较远罢了。


所以,在操作系统是如何 跳转到共享区当中,找到对应的 共享代码区执行的。其实就是上述所说的,先描述在组织。

操作系统只需要管理好 先描述在组织 的数据结构,就可以管理好其中的 对象,而管理好一个一个的 存储 库 属性的 对象,就可以管理好 一个一个库在内存当中的 使用情况:比如 , 当前是否正在被使用?大小占多少?进程要使用的库,有没有被加载到内存当中?起始地址是什么?·········

所以,在上述 操作之后,OS 对于 库的 加载情况就非常清楚了。


共享库当中的 errno 变量存储的问题

 共享库当中的代码是共享的,那么其中的数据是不是共享的呢?

如果我们在一个 C 程序当中,对 errno 这个 C库当中的全局变量进行了修改,其他C 程序会不会受到影响?

答案是不会的,因为 共享区是在 栈 和 堆区当中的,这也就意味着,当 某一个进程对 errno 变量做了修改,就会发生写时拷贝,谁写谁就自己拷贝一份 自己存储 自己修改的数据。

这个写时拷贝的 出来的空间 在共享区当中存储,同时,在 C 管理 要输出到文件当中的数据,也有一个 struct_file 结构体来管理,在这个结构体当中就有 C 帮我们提供的 语言级别的缓冲区(用户级缓冲区)。

 


总结 

静态库 只需要在 编译时期 ,可以找到这个 静态库在哪,把这个静态库当中的数据,拷贝到 对应源程序当中。

但是动态链接不一样,如果要是用到 动态库的话,动态库就需要再 内存当中被加载,从而执行。而 静态库是不需要 加载到内存当中执行的,它完成了 拷贝的工作就可以 下岗了,在当前可执行程序的链接就不需要他了。

在上述说明的 四种 可执行程序链接 动态库 的方式的当中,其实用得最多就是 拷贝 这种方式。因为这种方式最直接了当,你会发现,这种方式其实是最方便的。

第三方库链接操作例子:【Linux拓展】ncurses库的安装和使用 {ncurses库的安装方法,ncurses库的使用手册,基于终端的贪吃蛇游戏}_芥末虾的博客-CSDN博客

上述的 ncurses库 是一个 基于 终端的 图形界面的库,有兴趣的可是尝试链接这个第三方库。

上述的 ncurses 库是在Linux 下的,如果是在windows 当中其实还有 easyx 库,在windows 当中这些库会做的更酷炫一些。还有 QT。

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

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

相关文章

14 网关实战:网关聚合API文档

上节课介绍了网关层的认证鉴权,今天这节介绍一下网关层如何聚合API接口文文档。 为什么需要聚合API接口文档? 大型微服务系统模块众多,木谷博客系统就有9个,如果这些服务的接口地址没有一个统一,那么客户端将要保存每个服务的接口地址,这个肯定是不现实。 先来看一下A…

paddleocr笔记

PP-OCRv1 PP-OCR中&#xff0c;对于一张图像&#xff0c;需要完成以下3个步骤提取其中的文字信息&#xff1a; 使用文本检测方法&#xff0c;获取文本区域多边形信息&#xff08;PP-OCR中文本检测使用的是DBNet&#xff0c;因此获取的是四点信息&#xff09;。对上述文本多边形…

leetcode面试经典150题——33 最小覆盖子串(滑动窗口)

题目&#xff1a; 最小覆盖子串 描述&#xff1a; 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串 “” 。 注意&#xff1a; 对于 t 中重复字符&#xff0c;我们寻找的子字符串中…

67从零开始学Java之List集合有哪些特性?

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 千锋教育高级教研员、CSDN博客专家、万粉博主、掘金优质作者、阿里云专家博主 前言 在上一篇文章中&#xff0c;壹哥给大家介绍了Java里的集合&#xff0c;我们了解了集合的由来、特点&a…

高防服务器和高防CDN的区别是什么?

现今大环境下攻击问题愈发严峻&#xff0c;许多网站有遇到被攻击导致网站崩溃&#xff0c;资源消耗的问题&#xff0c;那么这时候高防就是给为站长&#xff0c;企业等的第一选择了&#xff0c;那边目前高防CDN和高防服务器这两种抵御DDoS攻击的两种主流防御&#xff0c;那种会更…

在线陪诊系统: 医学科技的革新之路

医疗服务的数字化时代已经到来&#xff0c;而在线陪诊系统正是医学科技革新的杰出代表。通过巧妙的技术代码&#xff0c;这一系统不仅实现了患者和医生之间的远程互动&#xff0c;还将医疗服务推向了一个更加智能化的未来。在这篇文章中&#xff0c;我们将深入探讨在线陪诊系统…

大学招聘平台存在逻辑漏洞

找到一个学校的就业信息网&#xff0c; 随便点击一个招聘会&#xff0c;并且抓包查看返回包 注意返回包中的dwmc参数&#xff0c;这个是公司名称&#xff0c;zplxr参数这个是招聘人员姓名&#xff0c;lxdh参数是电话号码&#xff0c;这几个参数后面有用 在第一张图点击单位登…

正则表达式回溯陷阱

一、匹配场景 判断一个句子是不是正规英文句子 text "I am a student" 一个正常的英文句子如上&#xff0c;英文单词 空格隔开 英文单词 多个英文字符 [a-zA-Z] 空格用 \s 表示 那么一个句子就是单词 空格&#xff08;一个或者多个&#xff0c;最后那个单词…

MIT_线性代数笔记:第 08 讲 求解 Ax=b:可解性与结构

目录 可解的条件 Solvability conditions on b特解 A particular solution通解 Complete solution与零空间进行线性组合 Combined with nullspace 秩 Rank 可解的条件 Solvability conditions on b 矩阵 A 的第三行为第一行和第二行的加和&#xff0c;因此 Axb 中 b 的第 3 个分…

【vue ui 一直卡在 Starting GUI..】

vue ui 解决问题 1.如果项目一直卡在 Starting GUI..2.解决方法 (切换数据源)3.成功解决 1.如果项目一直卡在 Starting GUI… 2.解决方法 (切换数据源) 直接在cmd中输入如下 npm config set registry http://registry.npm.taobao.org/3.成功解决

C语言:求二维数组鞍点 。鞍点就是指二维数组中在该位置上的元素在该行上最大,在该列上最小,也可能没有鞍点。

分析&#xff1a; 在主函数 main 中&#xff0c;程序首先定义一个二维数组 a[5][5] 和五个整型变量 i、j、max、maxj 和 k&#xff0c;并用于寻找鞍点。然后使用 printf 函数输出提示信息。 接下来&#xff0c;程序使用两个 for 循环结构&#xff0c;从键盘输入一个 5x5 的二…

Linux—进程状态、僵尸进程、孤独进程、优先级

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、进程状态二、僵尸进程、孤儿进程1、Z(zombie)-僵尸进程2、僵尸进程危害3、孤儿进程 三、进…

Python实现WOA智能鲸鱼优化算法优化XGBoost回归模型(XGBRegressor算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 鲸鱼优化算法 (whale optimization algorithm,WOA)是 2016 年由澳大利亚格里菲斯大学的Mirjalili 等提…

iPaaS or RPA,企业自动化选型指南

随着科技的不断发展&#xff0c;企业自动化已成为现代商业的必然选择。在众多自动化工具中&#xff0c;iPaaS&#xff08;Integration Platform as a Service&#xff09;和RPA&#xff08;Robotic Process Automation&#xff09;备受关注。那么&#xff0c;企业在选择自动化工…

骨传导耳机对人有伤害吗?佩戴骨传导耳机有什么副作用?

使用骨传导耳机并不会对人体造成伤害&#xff0c;也没有副作用&#xff0c;相反&#xff0c;使用骨传导耳机还可以在一定程度上起到保护听力的作用。 一、什么是骨传导耳机&#xff1f; 首先让我们先了解下骨传导耳机是什么&#xff1a; 骨传导耳机是指通过人体骨骼来传递声…

蓝桥杯day01——负二进制数相加

题目描述 给出基数为 -2 的两个数 arr1 和 arr2&#xff0c;返回两数相加的结果。 数字以 数组形式 给出&#xff1a;数组由若干 0 和 1 组成&#xff0c;按最高有效位到最低有效位的顺序排列。例如&#xff0c;arr [1,1,0,1] 表示数字 (-2)^3 (-2)^2 (-2)^0 -3。数组形式…

leetcode:有效的括号

题目描述 题目链接&#xff1a;20. 有效的括号 - 力扣&#xff08;LeetCode&#xff09; 题目分析 题目给了我们三种括号&#xff1a;&#xff08;&#xff09;、{ }、[ ] 这里的匹配包括&#xff1a;顺序匹配和数量匹配 最优的思路就是用栈来解决&#xff1a; 括号依次入栈…

微信支付和微信红包设计用例

微信支付 功能 扫二维码 1.第一次扫描付钱二维码时可以得到相机权限&#xff0c;进入付钱界面 2.第一次扫描付钱二维码时可以拒绝相机权限&#xff0c;退回聊天界面 3.扫一扫可以扫描收钱的二维码 4.扫描出来的信息与收钱人信息相符 5.输入框只能输入数字 6.一次能支付的…

Linux文件与路径

Linux文件与路径 1、文件结构 ​ Windows和Linux文件系统区别 ​ 在windows平台下&#xff0c;打开“此电脑”&#xff0c;我们可以看到盘符分区 ​ 每个驱动器都有自己的根目录结构&#xff0c;这样形成了多个树并列的情形 ​ 但是在 Linux 下&#xff0c;我们是看不到这些…

基于SpringBoot实现的教务查询系统

一、系统架构 前端&#xff1a;html | js | css | jquery | bootstrap 后端&#xff1a;springboot | springdata-jpa 环境&#xff1a;jdk1.7 | mysql | maven 二、代码及数据库 三、功能介绍 01. 登录页 02. 管理员端-课程管理 03. 管理员端-学生管理 04. 管理员端-教师管理…