Linux:动静态库介绍

动静态库

  • 库的介绍
  • 开发环境 & 编译器
  • 库存在的意义
  • 库的实现
    • 库的命名
    • 静态库制作和使用
      • 总结
    • 动态库的制作和使用
      • 动态库的使用方法
        • 方法一
        • 方法二
        • 方法三
  • 库加载问题
    • 静态库加载问题
    • 动态库的加载问题
      • 与位置无关码
  • C/C++静态库下载方式

库的介绍

  • 静态库:程序在编译链接的时候把库的代码链接到可执行文件中,程序运行的时候将不再需要静态库
  • 动态库程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码

在 Linux 下,静态库文件 通常以 .a 为后缀名静态库文件 通常以 .so 为后缀名

  • 可以在 Linux下的 /usr/lib64路径中查看对应的系统库:
    在这里插入图片描述

在 windows 下, 静态库文件 通常以 .lib 为后缀名;动态库文件 通常以 .dll 为后缀名

  • 可以在windows中的C盘目录下:C:\Windows\System32,存放着大量的以 .dll 结尾的动态库:
    在这里插入图片描述

Linux 系统中已经预装载了 C/C++ 的头文件和库文件。头文件提供函数的说明方法,库用于提供方法的实现。

头文件通常保存在 Linux 的 /usr/include路径下:

在这里插入图片描述
头文件和库文件虽然没有保存在同一个目录下,但是不妨碍它们不能一起工作!

代码被编写好后,需要经过4个步骤才能翻译变成可执行文件,分别是:预处理、编译、汇编、链接

头文件就是在 预处理 阶段就被引入到代码中,当处理完 预处理、编译、汇编工作后才到 链接阶段。链接的本质其实就是链接库的操作!

开发环境 & 编译器

一般情况下,我们要使用一种语言进行开发的时候都需要安装对应的开发环境。例如:安装 VS2022 集成开发环境

安装这些环境实质是在安装 编译软件 与 安装对应开发语言对应的配套的 库文件头文件

  1. 在使用编译器编写代码时发生的 语法高亮 和 语法提示 也是因为包含了对应语言的头文件。编译器或者编辑器会将用户输入的内容,自动的不断的在被包含的头文件内进行搜索,自动提醒功能就是依赖头文件而来
  2. 编译器包含有命令行模式和其他自动化的模式。就拿 VS2022来说,为什么在编写代码就能检测到语法问题?
    这是在当用户在编写代码的时,编译器在后端的会创建多线程在对应的自动化模式下对代码进行检测(也就是在进行编译阶段的工作),不断的对代码语法进行检测,从而体现出检测语法的功能现象。

库存在的意义

库的存在就是提高开发者的效率!

诸如 IO类的功能、字符串相关功能、与数学相关的功能等等, 这些公共的用到比较多、比较频繁的功能,如果都让每个开发者都去实现的话,那么将会花费大量的时间和精力。

但是,如果将用得比较频繁的功能都全部封装成库,后面只需要将这些库下载下来,开发者直接去使用这些库,在这些库上直接调用接口,进行二次开发。将可以大大缩减开发时间,不用再去实现,也即是直接使用轮子!

库的实现

开头提到,库分为: 静态库动态库

库的命名

库通常由 前缀 lib + 库名称 + 版本号 + 后缀 组成。

后缀要做分类:动态库 和 静态库

静态库后缀为: .a
动态库后缀为: .so

示例:

/usr/lib64/目录下的C++动态库:
在这里插入图片描述

libstdc++.so.6 这个库名称为:去掉前缀 lib、去掉后缀 .so、去掉版本号 6.0.19 ,最后所剩的 stdc++ 就是库名!

静态库名称也是如此,只不过是去掉的后缀名称不一样,在这里就不再做演示。

提示:一般的云服务器只有动态库,静态库是需要下载的!

下面来模拟实现一个加减乘除的库:

注意:一般的库都是比较庞大的,这里为了方便介绍,所实现的内容都是简单易懂,是为了接下来实现库的来进行讲解!

首先创建 4 个文件分别是:myadd.c、myadd.h、mysub.c、mysub.h
在这里插入图片描述
myadd.c源文件实现加法功能;mysub.c源文件实现减法功能;两个头文件内部实现分别包含对应的函数声明:
在这里插入图片描述
在这里插入图片描述
同一目录下,可以直接将这些源代码同时翻译变成可执行文件,直接运行:

在这里插入图片描述
在这里插入图片描述

上面可以直接通过编译是因为源文件和头文件都在同一个目录文件下,如果不是在同一个目录下呢?如下情况:

在这里插入图片描述
直接编译会报错:在这里插入图片描述

下面就来讲解一下库的封装过程:

思考一下,要怎么样将 myadd.c、myadd.h、mysub.c、mysub.h 变成一个库?

是直接将源代码和头文件一起打包给到其他人?

出于安全问题考虑,一般的库都不会将源文件直接打包给出,而是需要经过一些处理,我们接着往下看:

下面来想一个问题:库是什么时候被程序调用的?

代码被翻译分为4个过程:预处理、编译、汇编、链接

  1. 预处理阶段是将源文件所包含的头文件进行展开、宏定义的替换和去注释
  2. 编译是接收预处理后的代码,将其转换为汇编语言代码,进行语法分析、语义分析,并生成中间代码
  3. 汇编将编译阶段生成的汇编语言代码转换为机器语言指令,生成可重定位的目标二进制文件(以.o为后缀)
  4. 链接器接收由汇编器生成的目标文件,以及需要用到的库文件

因此,可以将 myadd.c mysub.c 通过编译器编译成目标二进制文件就停下来,然后,将二进制目标文件和头文件传给调用这些函数的人即可。

将源文件编译成目标二进制文件的方法:

gcc -o test.o -c test.c
  1. 将 myadd.c 和 mysub.c 编译成目标二进制文件:
    在这里插入图片描述
  2. 将 myadd.o 和 mysub.o 以及头文件拷贝到 orderfile 目录下:
    在这里插入图片描述
  3. 再将 main.c 源文件编译成 目标二进制文件 main.o:
    在这里插入图片描述
  4. 再一起将 目标二进制文件 通过 gcc 编译形成可执行文件:
    在这里插入图片描述

传目标二进制文件的方法可以避免源代码泄漏和安全问题。

但是,一个库中的源文件不仅仅只有一两个,甚至有多个,那么传目标二进制文件的方法就很挫,也很不推荐。

解决方案:将目标二进制文件打包

静态库制作和使用

静态库打包目标二进制文件的方式:利用 ar 指令

这里举例打包的库文件名字为 :mymath

ar -rc libmymath.a *.o         # *.o 表示所有的目标二进制文件
  • -rc选项:r 表示替换、c 表示创建

下面将 mylib 目录下的所有 .o 文件打包再和.h一起拷贝到 ortherfile 目录下:

在这里插入图片描述

此时再将 main.c 进行编译:

在这里插入图片描述

直接报错了,报错内容发生在链接阶段。这是为什么呢?

原因:不管是自己实现的库还是网上下载的库,在编译器看来都是第三方库。诸如 C标准库系统调用库 编译器是会自动识别的,在面对第三方库如果不主动告诉编译器,就算这个库就在当前文件下,编译器还是两眼一抹黑装作看不见!!!

因此,不管是自己实现的库还是网上下载的库,都需要事先告诉编译器。

方法如下:告诉 gcc 这个 libmymath.a 库的路径还有这个库的名称

gcc -o test main.c -L. -lmymath
  • -L 选项:告诉编译器库所在的路径
  • -l 选项:告诉编译器库的名称(去掉前缀、去掉版本号、去掉后缀的库名字)

使用上面两个选项可以不用带空格!!!
在这里插入图片描述
此时就可以将 main.c 源文件编译成功了。

就拿C标准库来说,IO库、字符库等等,所实现的头文件会有很多个,零零散散不好管理。

因此,会专门创建两个目录:includelib

  • include 目录用于存储各种的头文件
  • lib 目录用于存储各种库文件

将实现的所有的库文件都放到 lib 目录下,所有的头文件的都放到 include 目录下:
在这里插入图片描述

当一个用户想要用到某个库时,从网上下载这两个文件目录即可,也比较直观。

但是接收的这两个目录文件怎么去直接编译呢?如下情况:

在这里插入图片描述
直接进行编译的话会报错:
在这里插入图片描述

找不到库文件所对应的头文件,此时就要用手动告诉 gcc 编译器头文间的路径,方法如下:

gcc -o test main.c -lmymath -L./lib -I./include
  • -I (i的大写) 选项:表示告诉gcc库文件包含的头文件的路径
    在这里插入图片描述

凡是有代价的,为了更好的管理将头文件全部放到同一个目录下。但是,带来的麻烦就是需要将头文件的路径告诉编译器!当然还包括:库的路径还有名称统统都要告诉编译器。

解决方式也有,将自己制作的库或者是从网上下载的第三方库 和 头文件一起存放到系统默认路径下,编译器也可以找到。但是,这些库不属于标准库的范畴内,最后的最后还是需要告诉 gcc 编译器库的名称的

例如:将 libmymath.a 这个库拷贝到 /lib64 目录下,需要获取管理员权限!
在这里插入图片描述
此时,再编译就可以不用告诉 gcc 编译器库的所在路径了:
在这里插入图片描述
对于头文件也是可以拷贝到默认的系统路径下,在这里就不再演示。

对于任何软件来说,安装和卸载本质就是将这个软件的库和头文件拷贝到系统的特定的路径下,这样使用软件的时候就可以直接使用对于的库内函数的内容了!但是,安装到系统默认路径下都是要获取管理员权限的,这也是为什么每次下载安装软件的时候都需要 sudo 提权的原因!

总结

  • 静态库的制作
  1. 将封装成库的所有源文件都编译成 .o 目标二进制文件
  2. 通过 ar 指令的 -rc 选项,将所有的 .o 目标二进制文件打包形成 lib().a 库文件,括号内为自己命名的库名称!
  3. 创建 include 和 lib 目录,将库文件都放入到 lib 目录下;将头文件都放到 include 目录下

至此,就将 includelib 目录打包形成压缩包就形成了一个静态的第三方库了。

  • 静态库的使用
  1. 告诉 gcc 编译器,静态库的名称(去掉前缀、去掉后缀、去掉版本号的库名称),用到选项 -l
  2. 告诉 gcc 编译器,静态库的所在路径,用到选项 -L
  3. 告诉 gcc 编译器,静态库用到的头文件所在的路径,用到选项 -I

动态库的制作和使用

动态库的制作和静态库有相似的地方,接下来开始进行动态库的制作:

  1. 将源文件编译生成目标二进制文件

与静态库不同的是,形成目标二进制文件使用 gcc 编译选项是不一样的,这里还是拿上面的 myadd.c、mysub.c 为样例:

gcc -fPIC -c myadd.c
gcc -fPIC -c mysub.c
  • -fPIC 选项:表示形成的 .o 目标二进制文件是一个与位置无关码

后续会谈到 与位置无关码的概念,现在先来看看结果:

在这里插入图片描述

这里 gcc 不用带上 -o 选项去命名一个 以 .o文件,gcc 会自动生成对应源文件的目标二进制文件。

库中的源文件很多的话,也会形成很多的目标二进制文件,这一步也跟静态库一样需要进行打包!

  1. 将所有目标二进制文件进行打包

打包时,用的还是 gcc 编译器。没想到吧,居然不是 ar 指令,这也是动态库和静态库制作方式不同之一。这是因为,现在使用库的主流都是以动态库为主,所以 gcc 编译器会自带打包的选项:

gcc -shared -o libmymath.so *.o
  • -shared 选项:表示打的包为一个共享库或者说是一个动态库

在这里插入图片描述

  1. 将库和头文件进行分类处理

这一步和静态库一样,创建 include 目录 和 lib 目录。include 目录下存放着这个库需要用到的头文件;lib 目录下存放着库文件:

在这里插入图片描述

动态库的使用方法

这里,在使用动态库的方式可以和上面提到的静态库使用方法一样吗?

下面来测试一下:

在这里插入图片描述
现在通过在静态库方法来编译一下 main.c 源文件,告诉编译器库的路径、名称 还有头文件的路径:
在这里插入图片描述

编译是通过了,但是运行却报错。问题出在哪里呢?

问题出现在 OS 身上!

把库的路径、名称,头文件所在路径告诉的是 gcc 编译器,并不是操作系统。gcc 编译的时候当然能够通过,但是当可执行文件在运行的时候,OS 不知道去哪里找这个库 和 头文件(这个库没有在系统的默认路径下),因此程序就运行不起来!!

但是问题又来了:为什么在刚刚的静态库就能找到呢?

因为,静态库的链接原则是将用户使用的二进制代码直接拷贝一份到目标可执行文件中。这样做法就是牺牲了空间,会造成可执行文件内容变得很大。

处理 OS 找不到库的方式有 4 种,其中一种方法在静态库那里提过了,就是 将第三方库和自己实现的库拷贝一份放到 /usr/lib64 路径下,将包含头文件的 include 目录下的所有头文件放到 /lib64 路径下即可。在这里不再演示,下面主要来介绍其他三种方法:

方法一
  • 通过环境变量的方式让 OS 找到库

将下载的第三方库 或是 自己编写库的路径添加到 LD_LIBRARY_PATH 环境变量下:

下面我以刚刚编写库的路径为例子:
在这里插入图片描述
注意:这里填写的路径是 绝对路径

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/kunkun/c++_exercise/Test_4_17/orderfile/lib

环境变量设置好后,执行 test 可执行文件:
在这里插入图片描述

但是这个环境变量的方法是一种临时方案。当用户退出后,环境变量会初始化为最初样子,需要每次登录都配置以下很麻烦。

方法二

下面来介绍第二中方法:

  • 在系统默认的库路径下,通过创建的软连接的方式来找到第三方库

注意:这个方式需要管理员权限!

sudo ln -s /home/kunkun/c++_exercise/Test_4_17/orderfile/lib/libmymath.so /lib64/libmymath.so

在这里插入图片描述
在这里插入图片描述

通过创建软连接的方式告诉OS 库的所在位置,是一种文件数据持久性的方法,一次设置永久使用。

方法三
  • 使用配置文件方案

/etc 目录下存在一个 ld.so.conf.d 基本的配置文件的目录。在这个目录下,只需要创建一个文件 再将我们自己创建的库 或者 网上下载的第三方库的路径保存到这个文件即可。

示例:

在这里插入图片描述

  1. 创建文件,这个文件可以自己随便命名:
    在这里插入图片描述
  2. 将第三方库的路径保存到刚刚创建的文件中,记得使用管理员权限
    在这里插入图片描述
    在这里插入图片描述
  3. 保存退出,最后将配置文件生效,需要管理员权限:
    ldconfig:让配置文件即刻生效
    在这里插入图片描述
    运行:
    在这里插入图片描述

库加载问题

程序在翻译的链接阶段会使用到库,库分为两种:静态库 和 动态库。

当动态库和静态库同时存在时,操作系统会优先使用动态库链接方式!

为什么呢?因为操作系统为了节省空间的开销。

下面就来介绍一下,库是如何加载的。

静态库加载问题

  • 静态库链接形成的可执行文件,本身就包含有静态库中各种函数实现方法

代码在链接阶段,将需要用到的函数实现方式从静态库中拷贝一份,跟原来源文件一起编译形成可执行文件。

这样的好处就是,当静态库被删除后,编译后的可执行文件还是可以运行。

但是,如果这个源代码依赖静中的态函数比较多,会造成这个可执行文件变得很大!毕竟是直接进行拷贝静态库中的代码。

诸多源文件在经过程序翻译形成可执行文件,如果都采用静态库这个方式。假设这些源文件都使用到静态库中的其中一个或多个相同的函数实现,那么在形成可执行文件的时候,这些可执行文件的都会独自保存一份这样的函数。这样不仅仅会造成磁盘的空间浪费,在加载这些可执行文件的时候也会造成内存负荷!可执行文件的大小摆在那里,程序被加载,是被加载到内存中代码区。

静态库不是没有优势,但是为了空间的使用率,还是不建议采用静态库。

这便提出了另一种节省空间的方式:动态库

动态库的加载问题

  • 程序在运行时,操作系统会将可执行文件中外部符号,替换成库中的具体地址,完成动态库的加载

在编译器将代码链接库时,编译形成的可执行文件内部需要到的动态库的函数会替换成一些特殊的符号和一些偏移量(偏移量后面说),这些符号操作系统认识。

一个被编译好的可执行文件是被存放到磁盘中的。

可执行文件被运行起来后,操作系统会将这个可执行文件被加载到内存(物理内存)然后,操作系统会创建对应的进程 PCB ,创建进程地址空间,创建对应的页表 来维护 进程地址空间 和 物理内存空间 。

程序运行到一定的时候,在需要用到某个动态库中的函数时,会在对应进程地址空间中找这个函数的实现方法。此时,在进程地址空间种是找不到的,因为这个可执行文件在编译的时候,只有这个动态库对应函数的符号。此时,操作系统就要出手了,因为操作系统认识这些符号,OS就会在磁盘中默认路径去找这个动态库(这也是为什么前面提到 4 种方法中,要告诉操作系统动态库的存储位置)假设找到了这个动态库,OS就会将这个库加载到物理内存中。为了能够让这个进程找到这个函数的实现方式,OS就会把动态库所在的物理内存的地址,通过页表的方式映射到进程地址空间

那么问题来了,映射到进程地址空的哪个区块呢?
进程地址空间存在那么一块区域,位于栈区和堆区之间的共享区。这里提一下,可执行文件的代码被加载到进程地址空间,是在代码区的!以上准备工作都做好后,此时程序再用到动态库中函数时,就会在进程的地址空间的 共享区去找对应函数实现方式。因为在共享区这个地方,进程可以通过页表的映射方式,直接找到动态库在物理内存的地址,进而,推进代码继续运行下去!

一个进程在使用动态库的方式就犹如上面所提到那般的过程。那么,如果两个可执行文件用到同一个库呢?甚至多个呢?这个动态库是都要被加载多次吗?

一个动态库被加载一次后,不用再次被加载多次。OS在检测到动态库被加载后,会直接将动态库所在的物理内存地址,直接通过第二个进程的页表映射到该进程地址空间中!

这也是为什么动态库能够节省内存空间资源的情况了!

下面来回答一下,上面提到的 与位置无关码

与位置无关码

动态库多大,取决于这个库所实现内容有多少。一个进程可能使用到多个动态库,进程运行多久了这个也不能确定。那么,此时就会面临这样的一个问题:进程地址空间的共享区,使用一个动态库就加载到共享区中,那么剩余的共享区空闲位置难以确定。后面加载的库在共享区内的位置,操作系统能知道。但是动态库具体的函数实现方法的地址,进程又是如何去正确的寻找呢

还记得 与位置无关码 吗?

使用 gcc 编译器的 -fPIC 选项,就可以将源文件形成一个 与位置无关码 的目标二进制文件。

与位置无关码作用:gcc 编译器在汇编阶段,在源文件需要用到的库函数的代码中,记录一下这个函数在库中的相对于库的起始位置的偏移量。

有了这个偏移量能干嘛呢?

当一个进程运行到某个阶段,加载不知道第几个动态库时。为什么加载库,因为要使用这个库中的某个函数,一个库很大。但是,在汇编阶段我们已经形成了一个与位置无关码。此时,我们只需要找到这个库在进程地址空间的共享区的起始位置即可。动态库加载到进程地址空间时,操作系统是知道这个库的起始位置的。库的起始位置加上这个偏移量就可以在库中找到这个函数实现方式了

因此,动态库在进程地址空间的共享区可以随便加载。我们并不需要去找这个库的具体某个函数的实现的地址,因为在编译阶段,就已经记录了这个函数实现在库中的具体偏移量了!

C/C++静态库下载方式

C语言静态库下载方式:

sudo yum -y install glibc-static

C++静态库下载方式:

sudo yum -y install libstdc++-static

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

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

相关文章

计算机网络---第十一天

生成树协议 stp作用: 作用:stp用于解决二层环路问题。 BPDU: 含义:桥协议数据单元,用于传递stp协议相关报文 分类:配置bpdu---用于传递stp的配置信息 tcn bpdu---用于通告拓扑变更信息 包含信息&…

数据库主键ID自增,两种方法获取插入数据库那条数据自动生成的主键ID值

目录 1. 前言 2. 适用于 MyBatis 框架 2.1 获取单条插入语句生成的ID 2.2 获取集合插入生成的多条数据的ID 3. 适用于 MyBatisPlus 框架 3.1 获取单条数据插入生成的ID 3.2 获取集合插入数据生成的多条数据的ID 4. 小结 1. 前言 在开发过程中,我们可能会遇…

OpenCompass 大模型评测实战——作业

OpenCompass 大模型评测实战——作业 一、基础作业1.1、使用 OpenCompass 评测 internlm2-chat-1_8b 模型在 C-Eval 数据集上的性能1.1.1、安装基本环境1.1.2、解压数据集1.1.3、查看支持的数据集和模型1.1.4、启动评测 二、进阶作业2.1、将自定义数据集提交至OpenCompass官网 …

2024春季春日主题活动策划方案

2024解冻派对“春日浪漫”主题活动策划方案-32P 方案页码:32页 文件格式:pptx 方案简介: 春来一季,新生欢喜 花香丨微风丨阳光 活动唤起【春日浪漫记忆】! 年轻人不一样的派对活动 可以与朋友/小朋友/家人互动…

深度学习-线性代数

目录 标量向量矩阵特殊矩阵特征向量和特征值 标量由只有一个元素的张量表示将向量视为标量值组成的列表通过张量的索引来访问任一元素访问张量的长度只有一个轴的张量,形状只有一个元素通过指定两个分量m和n来创建一个形状为mn的矩阵矩阵的转置对称矩阵的转置逻辑运…

03-JAVA设计模式-访问者模式

访问者模式 什么是访问者模式 访问者模式(Visitor Pattern)是软件设计模式中的一种行为模式,它用于将数据结构中的元素与操作这些元素的操作解耦。这种模式使得可以在不修改数据结构的情况下添加新的操作。 在访问者模式中,我们…

图文教程 | Git安装配置、常用命令大全以及常见问题

前言 因为多了一台电脑,平时写一些代码,改一些文件,用U盘存着转来转去特别麻烦。于是打算用Git管理我的文件,方便在两个终端之间传输数据啥的。也正好给新电脑装好Git。 📢博客主页:程序源⠀-CSDN博客 &…

HFSS端口介绍2---波端口

前面我们讨论了Lumped Port设定相关的内容,这节我们继续讨论Wave Port(波端口)使用相关的问题。 波端口使用范围 封闭结构:如波导、同轴电缆等 包含多个传播模式的模型 端口平面在求解区域外的模型 模型中包含均匀的波导或者传输线结构 波端口的大小 对于封闭的传输线结构:边…

视频教程下载:用ChatGPT的 API 开发AI应用指南

通过这门关于 OpenAI API 和 ChatGPT API 的全面课程,在您的应用中释放人工智能的力量。随着人工智能技术的快速发展,比以往任何时候都更重要的是保持领先地位,并为您的项目利用这些尖端工具。在本课程中,您将深入了解人工智能驱动…

物联网硬件设计开发全攻略:十大关键阶段深度解析

为物联网应用设计开发高效稳定的硬件系统本身是一项既复杂又精细的艰巨任务。看似小巧的物联网设备一般由软件、固件和硬件组件组成,其中,硬件组件更是占据了约80%的成本与开发挑战。那么,为何硬件部分如此棘手?在这篇文章中&…

x汽车登陆网站登陆rsa加密逆向

声明: 本文章内容仅供学习交流,不用于其他其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关, 各位看官好哇,今天给大家带来一篇web自动化逆向的文章,如下图当前我…

芯科科技大大简化面向无电池物联网的能量采集产品的开发

芯科科技推出其迄今最高能量效率且支持能量采集功能的无线SoC 中国,北京 – 2024年4月22日 – 致力于以安全、智能无线连接技术,建立更互联世界的全球领导厂商Silicon Labs(亦称“芯科科技”,NASDAQ:SLAB)…

超星图书转成PDF格式

转为pdf 为避免浪费您的时间,本篇转载文章不值得花费您的宝贵时间阅读 方法一 感谢医学插画动画杜鹏 Roison An两位提供的方法,经试验后简化了一下,得出以下方法:1、使用超星打开你想要转换的图书2、依次打开本书的所有页面,不要…

Property ‘auth‘ does not exist on type ‘AGCApi‘.

Property ‘auth’ does not exist on type ‘AGCApi’. 解决 清理项目重新运行模拟器就可以了

CentOS-7安装clickhouse并允许其他主机登录

一、通用设置 1、配置主机名 hostnamectl set-hostname --static 主机名2、修改hosts文件 vim /etc/hosts 输入: 192.168.15.129 master 192.168.15.133 node1 192.168.15.134 node2 192.168.15.136 node33、 保持服务器之间时间同步 yum install -y ntpdate &…

Java | Leetcode Java题解之第42题接雨水

题目&#xff1a; 题解&#xff1a; class Solution {public int trap(int[] height) {int n height.length;if (n 0) {return 0;}int[] leftMax new int[n];leftMax[0] height[0];for (int i 1; i < n; i) {leftMax[i] Math.max(leftMax[i - 1], height[i]);}int[] …

云南旅游攻略

丽江景点 Day1 ——丽江古城 丽江古城是一个充满文化和历史的地方&#xff0c;拥有丰富的景点和活动。 推荐游玩&#xff1a; 参观标志性建筑&#xff1a;大水车是丽江古城的标志性建筑&#xff0c;可以在这里拍照留念。 探索中心广场&#xff1a;四方街是古城的中心&#xf…

【第6节】Lagent AgentLego 智能体应用搭建

目录 1 基础课程2 安装环境2.1 教程要求2.2 安装 Lagent 和 AgentLego 3 实践操作3.1 Lagent&#xff1a;轻量级智能体框架3.1.1 Lagent Web Demo 使用3.1.2 用 Lagent 自定义工具 3.2 AgentLego&#xff1a;组装智能体“乐高”3.2.1 AgentLego 直接使用部分3.2.2 AgentLego We…

C++笔记:类和对象(一)->封装

类和对象 认识类和对象 先来回忆一下C语言中的类型和变量&#xff0c;类型就像是定义了数据的规则&#xff0c;而变量则是根据这些规则来实际存储数据的容器。类是我们自己定义的一种数据类型&#xff0c;而对象则是这种数据类型的一个具体实例。类就可以理解为类型&#xff0c…

二维码存储图片如何实现?相册二维码的制作技巧

如何将照片生成二维码后存储展示&#xff1f;现在很多人会将图片生成二维码以后&#xff0c;用于分享或者储存的用途&#xff0c;减少个人内存的占用量&#xff0c;而且分享照片也会更加的方便&#xff0c;只需要扫描二维码就可以让其他人查看图片。 想要制作图片二维码的步骤…