【Linux】基础I/O——动静态库的制作

我想把我写的头文件和源文件给别人用

  • 1.把源代码直接给他
  • 2.把我们的源代码想办法打包为库

1.制作静态库

1.1.制作静态库的过程

我们先看看怎么制作静态库的!

 

makefile

所谓制作静态库

  1. 需要将所有的.c源文件都编译为(.o)目标文件。
  2. 使用ar指令将所有目标文件打包为静态库。
  3. 将打包成的静态库需要和头文件组织起来。
  • 1.需要将所有的.c源文件都编译为目标文件。
gcc -c mymath.c

     -c选项告诉gcc只编译源代码,但不进行链接。这会生成一个目标文件(通常以.o结尾),该文件包含了编译后的代码,但还不能直接运行。

        你可以使用-c选项来编译多个源文件,然后再使用链接器将它们链接成一个可执行文件或共享库。

那么如果我们只把.o.h文件给别人,别人能用吗? 

我们写一个程序

main.c编译:

gcc main.c -c

 然后,将main.o和其他.o文件链接以后生成的文件就是可执行程序:

是可以运行的

        通过上面的例子我们知道,需要将生成的所有目标文件和main.o文件链接才能生成可执行程序,但是除了main.o之外的.o文件都太分散了,用起来很麻烦(当然可以通过Makefile简化步骤),给别人使用也不太方便,还容易缺失,所以将它们打包。而将目标文件打包的结果就是一个静态库。

  • 2.使用ar指令将所有目标文件打包为静态库。

ar 命令是 GNU Binutils 的一员,可以用来创建、修改静态库,也可以从静态库中提取单个模块。它可以将一个或多个指定的文件并入单个写成 ar 压缩文档格式的压缩文档文件。

常用参数:

  • -r(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件。
  • -c(create):建立静态库文件。
  • -t:列出静态库中的文件。
  • -v(verbose):显示详细的信息。
语法:ar [选项] [库名] [依赖文件]

 例如,将mymath.o打包:

-t-v选项查看静态库中的文件及信息:

我在这里想先声明一下库的名字这个问题

  1. 静态库的名字一般为libxxx.a,其中xxx是该lib的名称。
  2. 动态库的名字一般为libxxx.so.major.minor,xxx是该lib的名称,major是主版本号, minor是副版本号。

 

我们给库取名字的时候可要注意这个前缀后缀的事情啊!!!!

  • 3.将打包成的静态库需要和头文件组织起来。

        这是因为头文件包含了静态库中函数和变量的声明,而这些声明对于使用静态库的程序来说是必要的。如果没有头文件,编译器将无法确定如何使用静态库中的函数和变量。

        头文件和函数的实现分离是为了提高代码的可维护性和可重用性。头文件中只包含函数和变量的声明,而不包含具体的实现。这样,当我们需要修改函数的实现时,只需要修改对应的源文件,而不需要修改头文件。同时,由于头文件只包含声明,因此可以被多个源文件共享。这样,当我们需要在多个源文件中使用同一个函数时,只需要在每个源文件中包含对应的头文件即可。

        组织静态库和头文件的方法有很多种。一种常见的方法是将静态库文件(.a文件)和头文件放在同一个目录下。在使用静态库时,需要在程序中包含对应的头文件,并在编译时指定静态库的位置。这样,编译器就能够找到静态库中的函数和变量,并将它们链接到程序中。

        例如,将所有的头文件(.h)放在一个名为include的目录下,将生成的静态库文件(.a)放在一个名为lib(自己创建的)的目录下。然后将这两个目录都放在名为lib的目录下,这个libtest就可以作为一个第三方库被使用。

至此我们的库就已经完成了

1.2.makefile完成库的创建

上面的步骤其实我们可以一步到位

lib文件就是我们创建的静态库 ,未来别人想用我们的库,就把lib文件给它就好了。

1.3.使用静态库

我们来演示一下

我们创建一个新的.c文件

现在这个写好的main.c,让它和含有头文件、静态库文件的lib共处同一目录B下才能调用库中写好的函数。 

我们现在编译 

 

报错了,它说没找到头文件!!!!

为什么?

因为gcc会在默认的路径寻找(/usr/include),我们这个mymath.c不在系统目录下面,那么就找不到了

我们可以使用gcc -I(大写i)来指定寻找的路径去找头文件

  • -I(大写i):指定查询头文件路径

还报错了!!!! 

这个是因为它们没有找到add函数,这个就是没有找到方法实现,即没找到库文件了,因为实现在库里面

这是因为gcc只会在默认路径下面寻找静态库(/lib64/),但是我们这个库可不在那个目录里面

这个就需要加选项-L选项来给gcc指定查询路线

  • -L:指定静态库位置

还报错,显示显示没找到库文件!!!

明明我这个目录下面只有1个库,还是不链接起来,这个就是库的链接方式不同之处:库需要库的位置+库的名字

这个时候还要加选项:-l:链接对应的库

 ??????什么鬼,还是找不到,其实这是我们的库的名字搞错了

库的真实名字是去掉库的前缀,后缀之后的东西

Linux下:一般要求是lib + 库的真实名称 +(版本号)+ .so /.a 

所以这个库的真实名字就是mymath

我们先看看我们设置的自定义环境变量

我们改一下代码

出错了???为什么这里没有将myerror设置为1?

这个是因为printf传参是从最右边开始传的,打印myerror的语句在调用div之前就已经准备好了

我们可以来验证一下

怎么样,是不是很神奇?那我还是想先打印10/0怎么办?

 

很好

这个就是c语言的errno全局变量的简单类比!!! 

至此,我们就学了这些东西

  1. 第三方库往后使用的时候必定要使用gcc -l(小写L)
  2. 深刻理解errno的本质

我们这么知道某个程序是使用动态链接还是静态链接的方式呢?

为什么我们没有看到mymath这个库?

gcc默认动态链接,但是我们没有提供动态库,只提供静态库,所以只能用静态链接

 如果系统中需要链接多个库,则gcc可以链接多个库

来总结一下吧

1.4.使用库的便捷方法 

现在终于完成了,我现在不想带这么多选项,怎么办?

  1. 把库拷贝到/lib64/,把头文件拷贝到/usr/include
  2. 我们可以创建软连接来解决这个问题

我们可以这么干

  • 我们把头文件和库文件拷贝到系统指定目录下面,就可以不用带这么多选项了

拷贝完成之后,我们执行一下这个

发现是链接错误,我们就得加上-l选项,这个名字注意啦!!!!

我们将库的拷贝,头文件的拷贝这个过程叫作安装!!!!

我们先把安装进去的删掉

第二种方法就是使用软链接

从此往后我们使用这个头文件的时候就像下面这样子

 我们也给我们的库创建一个软链接

 我们现在编译main.c

很成功

来总结一下

首先我们在/usr/inuclde/里面创建了一个软连接,我们在头文件写了这样子的东西,就完成了对头文件的链接

上面这个相当于下面这个-I选项对头文件的链接

 -I ./lib/include/ 

 然后我们在/usr/include/里面创建一个软链接,然后库的链接还是需要指定库的名字的!!!所以最后那个-l+库名不可缺少

-L ./lib/mymathlib/ -l mymath

但实际上我们这样子用软链接的情况很少

1.5.总结

  1. 需要指定的头文件,和库文件
  2. 如果没有默认安装到系统gcc、g++默认的搜索路径下,用户必须指明对应的选项,告知编译器: a.头文件在哪里 b.库文件在哪里 c.库文件具体是谁
  3. 将我们下载下来的库和头文件,拷贝到系统默认路径下,在Linux下就是安装库! 那么卸载呢?对任何软件而言,安装和卸载的本质就是拷贝到系统特定的路径下!
  4. 如果我们安装的库是第三方的库,我们要正常使用,即便是已经全部安装到了系统中,gcc g++必须用-l指明具体库的名称!

 2.制作动态库

2.1.制作动态库的过程

这次我们要使用新文件来创建

 myprint.h

myprint.c

mylog.h

 mylog.c

我们要使用这几个来打包成动态库

制作静态库有3个步骤

  • 1.生成所有源文件(.c文件)对应的目标文件(.o文件)。 
  •  2.使用 gcc 的 -shared 选项将所有目标文件打包为一个动态库。
  • 3.组织头文件和动态库文件。
  • 1.生成所有源文件(.c文件)对应的目标文件(.o文件)。 

gcc 需要增加-fPIC选项(position independent code):位置无关码。这个我们下面讲讲

gcc -fPIC -c mylog.c 
gcc -fPIC -c myprint.c 

    -c选项告诉gcc只编译源代码,但不进行链接。这会生成一个目标文件(通常以.o结尾),该文件包含了编译后的代码,但还不能直接运行。

        你可以使用-c选项来编译多个源文件,然后再使用链接器将它们链接成一个可执行文件或共享库。

位置无关码

        位置无关代码(Position Independent Code,PIC)是一种特殊的机器代码,它可以在内存中的任何位置运行,而不需要重新定位。这意味着,当程序被加载到内存中时,它的代码段可以被放置在任何可用的内存地址,而不需要修改代码中的任何地址引用。

        这对于创建共享库(即动态库)非常有用,因为共享库可以被多个程序同时使用,而每个程序都可能将其加载到不同的内存地址。如果共享库中的代码不是位置无关的,那么每次加载时都需要对其进行重新定位,这会增加程序启动的时间和内存占用。

        使用位置无关代码可以避免这些问题,因为它可以在内存中的任何位置运行,而不需要重新定位。这样,当多个程序使用同一个共享库时,它们都可以直接使用共享库中的代码,而不需要对其进行重新定位。这样可以节省大量的 RAM,因为共享库的代码节只需加载到内存一次,然后映射到许多进程的虚拟内存中。

        和静态库采用的绝对编址相比,动态库采用的就是相对编址,各个模块在库中的地址可能不相同,但是它们之间的相对位置是固定的。就好像房车旅行一样,房车的位置虽然一直在变,但是房车内家具的相对位置一直不变。

位置无关代码对于 gcc 来说:

  • -fPIC作用于编译阶段,告诉编译器产生与位置无关的代码,此时产生的代码中没有绝对地址,全部都使用相对地址,从而代码可以被加载器加载到内存的任意位置都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
  • 如果不加-fPIC选项,则加载. so 文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个. so 文件代码段的进程在内核里都会生成这个. so 文件代码段的拷贝,并且每个拷贝都不一样,这样就和动态库一样占用内存了,具体取决于这个. so 文件代码段和数据段内存映射的位置。
  • 不加-fPIC编译生成的. so 文件是要在加载时根据加载到的位置再次重定位的,因为它里面的代码 BBS 位置无关代码。如果该. so 文件被多个应用程序共同使用,那么它们必须每个程序维护一份. so 的代码副本 (因为. so 被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)。
  • 我们总是用-fPIC来生成. so,但从来不用-fPIC来生成. a。但是. so 一样可以不用-fPIC选项进行编译,只是这样的. so 必须要在加载到用户程序的地址空间时重定向所有表目。
  •   2.使用 gcc 的 -shared 选项将所有目标文件打包为一个动态库。
gcc -shared -o libmymethod.so mylog.o myprint.o

 其中,在选项-o后面的是要生成动态库的名称,在它之后是动态库依赖的目标文件。

这个库文件有了x权限,是因为这个动态库被使用的时候会被加载到内存里,所以默认有x权限

  • 3.组织头文件和动态库文件。

        同样地,将所有的头文件(.h)放在一个名为include的目录下,将生成的静态库文件(.a)放在一个名为lib的目录下。然后将这两个目录都放在名为mylib的目录下,这个mylib就可以作为一个第三方库被使用。

 2.2.使用makefile制作动态库加静态库

上面那个步骤太繁琐了,我们借助makefile来看看,我们这里还有之前的静态库生成代码

我们使用make完成第1步和第2步,make out就可以完成上面的第3步 

 

 2.3.使用动态库

我们将我们的库给了别人

我们编写一个main函数来使用

先看看里面的静态库能不能用

 

很好啊,能用

我们接下来修改我们的main函数

找不到头文件

这个是说找不到库的实现, 这个和静态库一样

如果我们有多个库就得多使用多个选项去选就好了 

还是报错,这个和静态库一样,还是要指明链接库

 我们生成了,但是运行起来的时候怎么报错了???

这个和静态库就不一样了啊

这个a.out就是动态链接的 ,那为什么ldd的第二行为什么说not found呢?

我已经告诉编译器(gcc)库的路径了,但是运行的时候是加载器来运行的,所以还是要告诉加载器

 我们以前运行c语言程序的时候,为什么能直接执行呢?

  • 这个是因为系统默认了加载路径,LD_LIBRARY_PATH是程序运行动态查找库时所要搜索的路径

接下来我们将介绍4种解决方法

2.3.1.将库拷贝到系统目录——最常用的

类似静态库的操作:

sudo cp mylib/lib/libmymethod.so /lib64

现在这个动态库就被找到了。

        但是,为什么只要将动态库的.so文件拷贝到系统目录下,这个可执行程序就可以被链接到动态库呢?不应该重新编译链接一次吗?

        在编译链接时,只需要记录需要链接文件的编号,运行程序时才会进行真正的“链接”,所以称为“动态链接”因此,只要将动态库的.so文件拷贝到系统目录下,这个可执行程序就可以被链接到动态库,而不需要重新编译链接。

        也就是说,编译器只负责生成一个main.c对应的二进制编码文件,而链接的工作要等到运行程序时才会进行链接,所以生成可执行程序以后就没有编译器的事了。

缺点:同样地,将动态库的.so文件拷贝到系统目录下也可能会污染系统库目录。

2.3.2.软链接

sudo ln -s /home/zs_108/B/mylib/lib/libmymethod.so /lib64/libmymethod.so

 我甚至不用编译就找到了静态库

运行看看

完美

一解除软链接。又找不到了 ,也运行不了了

2.3.3.更改LD_LIBRARY_PATH

LD_LIBRARY_PATH程序运行动态查找库时所要搜索的路径

这个环境变量在有些系统是没有的!!!!

        我们只需将动态库所在的目录路径添加到LD_LIBRARY_PATH环境变量中,告诉系统程序依赖的动态库所在的路径:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/xy/Linux/libtest/libtest/mylib/lib

我们设置好了,使用ldd就发现,直接又找到了

这个时候我们也可以运行a.out

注意要用:隔开,否则会覆盖原来的环境变量。但是这个方法是临时的,因为这个环境变量是内存级别的环境变量,机器会在下次登录时清理。

2.3.4.使用ldconfig指令

        /etc/ld.so.conf.d/目录下的文件用来指定动态库搜索路径。

        这些文件被包含在/etc/ld.so.conf文件中,ldconfig命令会在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索可共享的动态链接库,并创建出动态装入程序(ld.so)所需的连接和缓存文件。

        这些.conf文件中存储的都是各种文件的路径,只要将我们写的第三方库的路径保存在一个.conf文件中,程序运行时在就会通过它链接到它依赖的动态库。

  •  1.将路径存放在.conf文件中。
echo "/home/zs_108/B/mylib/lib" > mylib.conf

 这样,当前目录下就会出现刚才创建的文件:

  •  将.conf文件拷贝到/etc/ld.so.conf.d/下。
sudo cp mylib.conf /etc/ld.so.conf.d/

这个时候ldd一下: 

系统还是没有找到a.out依赖的动态库,原因是此时的系统的数据库还未更新,使用命令ldconfig更新配置文件:

sudo ldconfig

怎么样?是不是很方便!!!!

 其实我们可以直接去/etc/ld.so.conf.d/下创建一个.conf文件,往里面写入动态库路径也行

这个比较神奇 

 删了还存在

我知道现在大家想试试看自己的能力

我给大家推荐一个库ncurses——图形化界面,大家可以去玩玩看

2.4.总结

我们现在修改main函数

这样子就大功告成了 

我们现在把静态库删掉了,还是可以运行的

个就是静态链接的特点,会拷贝静态库的内容过来

这个时候我们把动态库删了,就真的不能跑了

这个就是动态链接的加载模式,运行的时候才加载动态库,如果这个时候动态库不见了,那么就运行不了了

常见的动态库会被所有的可执行程序(动态链接)使用,所以动态库也被叫做共享库 

 动态库只会被加载1次,动态库被加载后,会被所有进程共享!!!!

3.动态库是怎么被加载的?

我们先回答动态库是如何与虚拟以及物理内存,以及PCB建立关系的

        先理解一下上图要表达的一个过程,顺便进行一些知识整合:当用户要加载一个进程时,操作系统就会为进程创建一个task_struct,并且把程序加载到内存中,并且会创建对应的mm_struct(虚拟地址空间)用来维护各个区域,最后再经过页表将虚拟地址和物理内存进行对应,这是可以理解的,也是前面已经提及到的内容

动态库是文件吗?

  • 答案是

但是库并不是立刻就被加载,而是在它需要被调用的时候才会被加载到内存中。 

        那么现在进程中会调用一些函数,这些函数会与多个动态库有联系,而我们知道,在可执行程序采用动态链接进行链接库的时候,会想办法让可执行程序与库建立联系,这个时候让动态库加载进来,动态库会被加载到进程地址空间的堆和栈之间的这一块区域,也被叫做共享区,之后也会在页表中和物理地址建立对应的联系,库被加载后就可以被进程所用了

  • 把动态库加载到共享区之后,我们执行的任何代码都是在我们的进程地址空间中执行

 我们看看进程地址空间的结构

这样子正文代码执行的时候就在正文代码区,执行到动态库的函数的时候就跳转到动态库的地方执行,执行完了又跳回正文代码区

事实:我们一个进程可能加载多个动态库,动态库相当于文件,动态库可以和多个进程,系统在运行中一定会存在多个动态库,一定会将它们先描述再阻止管理起来。

系统中,所有库的加载情况,操作系统特别清楚。

  •  c语言会提供一个errno全局变量,是不是在共享库里,a进程打开运行,出错了,b进程打开运行也出错了,那么errno会不会混乱了?

库是在堆栈之间,0-3G是用户空间,里面运行的都是子进程,如果发生异常了,就会进行写时拷贝,不会出现问题

 

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

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

相关文章

谷粒商城实战笔记-38-前端基础-Vue-指令-单向绑定双向绑定

文章目录 一,插值表达式注意事项1:不适合复杂的逻辑处理注意事项2:插值表达式支持文本拼接注意事项3:插值表达式只能在标签体中 二,v-html和v-textv-textv-html区别总结:最佳实践 三,v-model复选…

FastAPI 学习之路(五十六)将token缓存到redis

在之前的文章中,FastAPI 学习之路(二十九)使用(哈希)密码和 JWT Bearer 令牌的 OAuth2,FastAPI 学习之路(二十八)使用密码和 Bearer 的简单 OAuth2,FastAPI 学习之路&…

阵列信号处理学习笔记(二)--空域滤波基本原理

阵列信号 阵列信号处理学习笔记(一)–阵列信号处理定义 阵列信号处理学习笔记(二)–空域滤波基本原理 文章目录 阵列信号前言一、阵列信号模型1.1 信号的基本模型1.2 阵列的几何构型1.3 均匀直线阵的阵列信号基本模型 总结 前言…

嵌入式面试总结

C语言中struct和union的区别 struct和union都是常见的复合结构。 结构体和联合体虽然都是由多个不同的数据类型成员组成的,但不同之处在于联合体中所有成员共用一块地址空间,即联合体只存放了一个被选中的成员,结构体中所有成员占用空间是累…

【网络】windows和linux互通收发

windows和linux互通收发 一、windows的udp客户端代码1、代码剖析2、总体代码 二、linux服务器代码三、成果展示 一、windows的udp客户端代码 1、代码剖析 首先我们需要包含头文件以及lib的一个库&#xff1a; #include <iostream> #include <WinSock2.h> #inclu…

【模板代码】用于编写Threejs Demo的模板代码

基础模板代码 使用须知常规模板代码常规Shader模板代码 使用须知 本模板代码&#xff0c;主要用于编写Threejs的Demo&#xff0c;因为本人在早期学习的过程中&#xff0c;大量抄写Threejs/examples下的代码以及各个demo站的代码&#xff0c;所以养成了编写Threejs的demo的习惯…

SAP 采购订单 Adobe 消息输出

目录 1 简介 2 业务数据例子 3 选择增强 & 代码 1&#xff09;BADI: MM_PUR_S4_PO_MODIFY_HEADER 2&#xff09;BADI: MM_PUR_S4_PO_MODIFY_ITEM 4 自定义 Adobe form 1&#xff09;PO Master form 2&#xff09;PO form 5 前台主数据配置 6 后台配置 1&#xf…

掌握Rust:函数、闭包与迭代器的综合运用

掌握Rust&#xff1a;函数、闭包与迭代器的综合运用 引言&#xff1a;解锁 Rust 高效编程的钥匙函数定义与模式匹配&#xff1a;构建逻辑的基石高阶函数与闭包&#xff1a;代码复用的艺术迭代器与 for 循环&#xff1a;高效数据处理的引擎综合应用案例&#xff1a;构建一个简易…

【LeetCode】day15:110 - 平衡二叉树, 257 - 二叉树的所有路径, 404 - 左叶子之和, 222 - 完全二叉树的节点个数

LeetCode 代码随想录跟练 Day15 110.平衡二叉树257.二叉树的所有路径404.左叶子之和222.完全二叉树的节点个数 110.平衡二叉树 题目描述&#xff1a; 给定一个二叉树&#xff0c;判断它是否是 平衡二叉树 平衡二叉树的定义是&#xff0c;对于树中的每个节点&#xff0c;其左右…

qt自定义控件(QLabel)

先创建自定义控件类painter_label 1.自定义类必须给基类传入父窗口指针 2.重写控件中的方法 3.在UI中创建一个QLabel,右键“提升为”&#xff0c;输入类名

动画革命:Lottie如何改变我们对移动应用交互的认知

在数字世界的浩瀚星空中&#xff0c;每一个像素都跃动着无限创意与想象的火花。当静态的界面遇上动态的魔法&#xff0c;一场视觉盛宴便悄然开启。今天&#xff0c;让我们一同揭开一位幕后英雄的神秘面纱——Lottie&#xff0c;这个在UI/UX设计界掀起波澜的动画利器&#xff0c…

SVN与Git功能差异对比分析

最近在调研学习Git管理和分支模型相关内容&#xff0c;外延到了SVN和Git差异、工作原理等相关细节&#xff0c;学习整理如下。 SVN&#xff08;Subversion&#xff09;与 Git 的最大不同&#xff0c;主要包括以下几个方面&#xff1a; 交流探讨&#xff0c;加入群聊【Java学习…

51.2T 800G 以太网交换机,赋能AI开放生态

IB与以太之争 以太网替代IB趋势明显。据相关报告&#xff1a;2024年TOP500的超算中&#xff0c;采用以太网方案占比48.5%&#xff0c;InfiniBand占比为39.2%&#xff0c;其中排名前6的超算中已有5个使用以太网互联。 开放系统战胜封闭系统仅是时间问题。我们已经看到&#xf…

钡铼EdgeIO系统BL206对接MQTT、Modbus TCP、OPC UA

钡铼EdgeIO系统BL206提供双网口支持交换机级联功能&#xff0c;支持标准MQTT协议、Modbus TCP协议、OPC UA协议&#xff0c;由耦合器与IO模块组成&#xff0c;采用Web配置&#xff0c;内置云驱动、可编程逻辑控制功能&#xff0c;用户点击即可连接云平台。耦合器自带诊断功能&a…

网络结构-组件-AI(九)

深度学习网络组件 RNN公式讲解计算示意图讲解 CNN计算示意 Normalization(归一化层)Normalization常见两种方式 Dropout层 RNN 循环神经网络&#xff08;recurrent neural network&#xff09; 主要思想&#xff1a; 即将整个序列划分成多个时间步&#xff0c;将每一个时间步的…

GPU OpenGL 版本检测 GLview

官网链接 https://www.geeks3d.com/dl/show/10097 也可在此下载https://download.csdn.net/download/qq_51355375/89559913 下载后直接默认安装即可&#xff0c; 打开安装目录&#xff0c;双击运行openglex.exe即可

【Python系列】Python 缓存机制

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【一刷《剑指Offer》】面试题 49(案例):把字符串转换成整数

力扣对应题目链接&#xff1a;8. 字符串转换整数 (atoi) - 力扣&#xff08;LeetCode&#xff09; 一、《剑指Offer》对应内容 二、分析题目 根据题意&#xff0c;有以下四种字符需要考虑&#xff1a; 首部空格&#xff1a; 删除之即可。符号位&#xff1a; 三种情况&#xf…

计算机网络知识点面试总结3

#来自ウルトラマンゼロ&#xff08;赛罗&#xff09; 1 数据链路层功能 数据链路层在物理层提供的服务的基础上向网络层提供服务&#xff0c;其最基本的服务是将源自网络层来的数据可靠地传输到相邻节点的目标机网络层&#xff0c;其主要作用是加强物理层传输原始比特流的功能。…

【Day12】登录认证、异常处理

1 登录 先创建一个新的 controller 层&#xff1a;LoginController RestController public class LoginController {Autowiredprivate EmpService empService;// 注入PostMapping("/login")public Result login(RequestBody Emp emp) { // 包装对象Emp e empServic…