『Linux从入门到精通』第 ㉒ 期 - 动静态库

在这里插入图片描述

文章目录

  • 💐专栏导读
  • 💐文章导读
  • 🐧什么是库?
  • 🐧为什么要有库?
  • 🐧写一个自己的库
    • 🐦方法一
    • 🐦方法二 静态库
    • 🐦标准化
    • 🐦方法三 动态库
    • 🐦配置动态库
      • 🐱环境变量
      • 🐱软链接
      • 🐱配置文件
  • 🐧静态库与动态库的区别
  • 🐧动态库的运作原理
      • 🐔为什么进程可以在运行时加载动态库?
      • 🐔为什么多个进程可以共享一个动态库

💐专栏导读

🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。

🌸专栏简介:本文收录于 Linux从入门到精通,本专栏主要内容为本专栏主要内容为Linux的系统性学习,专为小白打造的文章专栏。

🌸相关专栏推荐:C语言初阶系列C语言进阶系列C++系列数据结构与算法

💐文章导读

本章我们将深入学习Linux中动静态库的使用及其原理。

在这里插入图片描述

🐧什么是库?

在学习生涯中,我们总是能谈到库,例如C语言标准库、C++标准库。那么到底什么是库呢?

在计算机科学中,术语“库”通常指的是库文件(Library),它是一组预编译的、可重用的代码和资源的集合,用于支持软件开发。库的目的是为开发人员提供一组常用的功能,以便在应用程序中进行调用,从而避免重复编写相同的代码。

库可以分为两大类:

  • 静态库(Static Library): 静态库在编译时被链接到应用程序中,形成一个独立的可执行文件。在程序运行时,静态库的代码被完全复制到应用程序中,因此应用程序不再依赖于原始的库文件。静态库的文件扩展名通常是.a(在Unix/Linux系统中)或.lib(在Windows系统中)。

  • 动态库(Dynamic Library): 动态库在运行时加载到内存中,多个应用程序可以共享同一个动态库的实例。这可以减小应用程序的大小,因为动态库的代码只需要存在一份,而且可以在运行时更新。动态库的文件扩展名通常是.so(在Unix/Linux系统中)或.dll(在Windows系统中)。

在编程中,开发人员通过包含库的头文件、链接库文件,以及在代码中调用库提供的函数或方法,可以轻松地利用库的功能。

在我们的Linxu机器上,系统已经为我们预装了C/C++的头文件和库文件。头文件提供方法说明,库文件提供方法的实现。头文件与库文件是有对应关系的,需要组合在一起使用。

一个可执行程序生成需要经历四个阶段:预处理、编译、汇编、链接。头文件在预处理阶段被引入,库文件在链接阶段被引入。

C/C++库文件

$ ls /usr/lib64/libc*
/usr/lib64/libc-2.17.so         /usr/lib64/libc_nonshared.a        /usr/lib64/libcroco-0.6.so.3.0.1    /usr/lib64/libc.so
/usr/lib64/libcap-ng.so.0       /usr/lib64/libcollection.so.2      /usr/lib64/libcrypt-2.17.so         /usr/lib64/libc.so.6
/usr/lib64/libcap-ng.so.0.0.0   /usr/lib64/libcollection.so.2.1.1  /usr/lib64/libcrypto.so.10          /usr/lib64/libcupscgi.so.1
/usr/lib64/libcap.so.2          /usr/lib64/libcom_err.so.2         /usr/lib64/libcrypto.so.1.0.2k      /usr/lib64/libcupsimage.so.2
/usr/lib64/libcap.so.2.22       /usr/lib64/libcom_err.so.2.1       /usr/lib64/libcryptsetup.so.12      /usr/lib64/libcupsmime.so.1
/usr/lib64/libcgroup.so.1       /usr/lib64/libcpupower.so.0        /usr/lib64/libcryptsetup.so.12.3.0  /usr/lib64/libcupsppdc.so.1
/usr/lib64/libcgroup.so.1.0.41  /usr/lib64/libcpupower.so.0.0.0    /usr/lib64/libcryptsetup.so.4       /usr/lib64/libcups.so.2
/usr/lib64/libcidn-2.17.so      /usr/lib64/libcrack.so.2           /usr/lib64/libcryptsetup.so.4.7.0   /usr/lib64/libcurl.so.4
/usr/lib64/libcidn.so           /usr/lib64/libcrack.so.2.9.0       /usr/lib64/libcrypt.so              /usr/lib64/libcurl.so.4.3.0
/usr/lib64/libcidn.so.1         /usr/lib64/libcroco-0.6.so.3       /usr/lib64/libcrypt.so.1

C/C++头文件

$ ls /usr/include/stdio.h
/usr/include/stdio.h
$ ls /usr/include/c++/4.8.5/iostream 
/usr/include/c++/4.8.5/iostream

🐧为什么要有库?

引入库的概念有很多重要的原因,它们有助于提高软件开发的效率、可维护性和可扩展性。以下是一些主要的原因:

  1. 代码重用: 库提供了一组通用的功能或工具,可以在多个项目中被重复使用。这样可以避免开发人员重复编写相同的代码,提高了开发效率。

  2. 模块化开发: 库使软件能够以模块化的方式构建。通过将不同的功能分解成独立的库,开发人员可以更容易地理解和维护代码。模块化开发还使得团队能够并行工作,每个成员专注于特定的任务或功能。

  3. 抽象和封装: 库提供了对底层实现的抽象,使开发人员可以专注于高层次的问题而不必关心底层的细节。这种抽象和封装的概念有助于隐藏复杂性,提高代码的可读性和可维护性。

  4. 提高可靠性: 库经过充分测试和验证,可以提供高质量的代码。开发人员使用库时,可以信任这些已经验证过的功能,减少了潜在的错误和漏洞。

  5. 快速开发: 使用库可以加速开发过程。通过利用现成的库,开发人员可以更快地构建应用程序,而不必从头开始编写每一个功能。

  6. 标准化: 某些库成为行业或社区标准,提供了一致的接口和实现。这种标准化有助于确保代码的一致性,同时使得不同项目之间更容易进行集成和交互。

  7. 可扩展性: 库使得软件的架构更具扩展性。通过将不同的模块组织为库,可以更容易地添加新功能、升级现有功能或替换特定的实现,而无需修改整个应用程序。

总体而言,引入库的概念有助于构建更可靠、可维护和可扩展的软件系统,提高了软件开发的效率和质量。

🐧写一个自己的库

为了更深入的理解库运作的原理,我们尝试自己写一个库,并交给其他小伙伴使用。

接下来,我们将实现一个加减运算的程序,并将给程序的源代码与头文件进行打包,并交给小伙伴——小黑使用。

$ touch add.c
$ touch add.h
$ touch sub.c
$ touch sub.h
/* add.h */
#pragma once 
int add(int a, int b);
/* add.c */
#include "add.h"
int add(int a, int b){
  return a + b;
}
/* sub.h */
#pragma once 
int sub(int a, int b);
/* sub.c */
#include "sub.h"
int sub(int a, int b){
  return a - b;
}

现在我们已经将计算器的源代码写好,现在我们想让小黑使用我们的成果,倘若我们直接把源文件以及头文件发给小黑,这种做法肯定是没问题的。

但是我们又想让小黑使用我们的成果,又不想让小黑看到我们的源代码,现在该怎么办呢?

🐦方法一

第一种方法是我们可以将源代码经过预处理、编译、汇编后形成二进制文件。小黑拿到该二进制文件后,再将它自己写的程序同样经过预处理、编译、汇编形成二进制文件,然后将两个二进制文件进行链接即可。

$ gcc -c *.c
$ ll
total 28
-rw-rw-r-- 1 hxy hxy   60 Feb 28 16:25 add.c
-rw-rw-r-- 1 hxy hxy   38 Feb 28 16:25 add.h
-rw-rw-r-- 1 hxy hxy 1240 Feb 28 16:40 add.o
-rw-rw-r-- 1 hxy hxy   60 Feb 28 16:20 sub.c
-rw-rw-r-- 1 hxy hxy   39 Feb 28 16:19 sub.h
-rw-rw-r-- 1 hxy hxy 1240 Feb 28 16:40 sub.o
drwxrwxr-x 2 hxy hxy 4096 Feb 28 16:40 xiaohei
$ cp *h xiaohei/
$ cp *o xiaohei/

/*小黑视角*/
ll
total 20
-rw-rw-r-- 1 hxy hxy   38 Feb 28 16:43 add.h
-rw-rw-r-- 1 hxy hxy 1240 Feb 28 16:45 add.o
-rw-rw-r-- 1 hxy hxy  200 Feb 28 16:26 main.c
-rw-rw-r-- 1 hxy hxy   39 Feb 28 16:43 sub.h
-rw-rw-r-- 1 hxy hxy 1240 Feb 28 16:45 sub.o
/*小黑视角*/
$ gcc -c main.c 
$ gcc -o test add.o sub.o main.o
$ ls
add.h  add.o  main.c  main.o  sub.h  sub.o  test
$ ./test 
10 + 3 = 13
10 - 3 = 7

🐦方法二 静态库

方法一中我们需要将许多的 .o文件以及.h文件打包给对方,这种做法明显感觉不是特别优雅。接下来我们就是用静态库的方式。

  1. 先将我们的.o文件打包生成一个静态库,并发送给小黑;
$ ar -rc libcalculate.a *.o
$ ll
total 32
-rw-rw-r-- 1 hxy hxy   60 Feb 28 16:25 add.c
-rw-rw-r-- 1 hxy hxy   38 Feb 28 16:25 add.h
-rw-rw-r-- 1 hxy hxy 1240 Feb 28 16:40 add.o
-rw-rw-r-- 1 hxy hxy 2688 Feb 28 18:31 libcalculate.a
-rw-rw-r-- 1 hxy hxy   60 Feb 28 16:20 sub.c
-rw-rw-r-- 1 hxy hxy   39 Feb 28 16:19 sub.h
-rw-rw-r-- 1 hxy hxy 1240 Feb 28 16:40 sub.o
drwxrwxr-x 2 hxy hxy 4096 Feb 28 18:23 xiaohei
$ cp libcalculate.a xiaohei

/* 小黑视角 */
$ ll
total 16
-rw-rw-r-- 1 hxy hxy   38 Feb 28 18:35 add.h
-rw-rw-r-- 1 hxy hxy 2688 Feb 28 18:33 libcalculate.a
-rw-rw-r-- 1 hxy hxy  200 Feb 28 16:26 main.c
-rw-rw-r-- 1 hxy hxy   39 Feb 28 18:35 sub.h

注意

这里我们需要注意库的命名规则。库的命名是以lib为开头,以.a或.so为结尾。例如 libcalculate.a 的真实名称为 calculate

在小黑拿到我们的库文件后,他就可以编译生成自己的程序了。但是这里有几个细节需要注意:

  • 因为我们的库是第三方的,编译器并不知道这个库的存在,所以我们需要指明库所在的路径;
  • 同样,我们需要告诉编译器该链接哪一个库;
  • 同理,我们还需指明头文件所在的路径。但是目前头文件就在当前路径下,所以可省略;

2.小黑进行编译链接;

/* 小黑视角 */
$ gcc -o test main.c -L . -l calculate -I .
$ ./test 
10 + 3 = 13
10 - 3 = 7
  • -L 选项:指明库所在的路径;
  • -l 选项: 告诉编译器链接哪一个库;
  • -I 选项:告诉编译器头文件的位置;

🐦标准化

上面方法二中我们演示了一个库文件的使用原理。在实际的项目开发中,我们并不会这么随意潦草。
再以小黑为例:

  1. 将头文件全部移至一个目录下;
  2. 将库文件全部移至一个目录下;
  3. 将头文件与库文件进行打包;
  4. 将打包好的文件上传至云端;
$ mkdir lib
$ mkdir include
$ cp *.h include/
$ cp *.a lib
$ tar -czf calcuate.tgz include lib

远在海外的小黑想用我们写好的库,于是在云端将压缩包下载到了本地;

/* 小黑视角 */
$ ll
total 8
-rw-rw-r-- 1 hxy hxy 788 Feb 28 19:01 calcuate.tgz
-rw-rw-r-- 1 hxy hxy 200 Feb 28 16:26 main.c

小黑将它进行解压看到了头文件与库文件;

$ tar xzf calcuate.tgz 
$ ll
total 16
-rw-rw-r-- 1 hxy hxy  788 Feb 28 19:01 calcuate.tgz
drwxrwxr-x 2 hxy hxy 4096 Feb 28 18:56 include
drwxrwxr-x 2 hxy hxy 4096 Feb 28 18:56 lib
-rw-rw-r-- 1 hxy hxy  200 Feb 28 16:26 main.c

最后小黑进行了编译链接等一系列操作,成功运行了自己的程序;

$ gcc -o test main.c -I ./include -L ./lib -l calculate
$ ./test 
10 + 3 = 13
10 - 3 = 7

以后的小黑会经常用到这个库,但是他觉得每次都要写这么长的指令有些麻烦。于是他将这个库的头文件全部移至系统的/usr/include目录下;将库文件移至/usr/lib目录下;

$ sudo cp include/*.h /usr/include/
$ sudo cp lib/* /lib64/

以后他每次使用这个库时,编译器会自动在这两个目录下寻找所程序所依赖的头文件与库文件;

$ gcc -o test main.c -lcalculate
$ ./test 
10 + 3 = 13
10 - 3 = 7

🐦方法三 动态库

以上我们尝试将自己的源文件制作为一个静态库供小黑使用,接下来我们在尝试制作一个动态库。

$ gcc -fPIC -c add.c sub.c
  • fPIC:产生位置无关码(position independent code);
gcc -shared -o libcalculate.so *.o
  • shared: 表示生成共享库格式;

接着我们把生成的.so文件放在lib目录下,将.h文件放到include目录下,并打包发给小黑(重复之前的操作)。

$ rm lib/libcalculate.a
$ rm calcuate.tgz 
$ cp *.so lib
$ tar -czf calculate.tgz lib include
$ cp calculate.tgz xiaohei/

小黑拿到压缩文件,解压后得到libinclude于是用我们的库来链接自己的程序。

/*小黑视角*/
$ tar xzf calculate.tgz 
$ ll
total 16
-rw-rw-r-- 1 hxy hxy 2343 Feb 29 16:39 calculate.tgz
drwxrwxr-x 2 hxy hxy 4096 Feb 28 18:56 include
drwxrwxr-x 2 hxy hxy 4096 Feb 29 16:38 lib
-rw-rw-r-- 1 hxy hxy  200 Feb 28 16:26 main.c
$
$ gcc -o test main.c -I include -L lib -lcalculate
$ ll
total 28
-rw-rw-r-- 1 hxy hxy 2343 Feb 29 16:39 calculate.tgz
drwxrwxr-x 2 hxy hxy 4096 Feb 28 18:56 include
drwxrwxr-x 2 hxy hxy 4096 Feb 29 16:38 lib
-rw-rw-r-- 1 hxy hxy  200 Feb 28 16:26 main.c
-rwxrwxr-x 1 hxy hxy 8432 Feb 29 16:49 test
$
$ ./test 
./test: error while loading shared libraries: libcalculate.so: cannot open shared object file: No such file or directory

小黑发现了事情的不妙,心想刚才不是还好好的吗?怎么运行时提示找不到库文件呢?

原来是因为在程序运行时,calculate.so并没有在系统的默认路径下,所以OS找不到!那么如何才能让OS找到我们的库呢?答案是需要我们自己来配置。

🐦配置动态库

配置动态库有三种方法:

  1. 环境变量:LD_LIBRARY_PATH (临时方案);
  2. 软链接方案;
  3. 配置文件方案

🐱环境变量

导入环境变量LD_LIBRARY_PATH:

$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/..../lib/ # 你的.so文件存放路径

查看环境变量LD_LIBRARY_PATH:

$ echo $LD_LIBRARY_PATH
:/usr/local/protobuf/lib/:/home/hxy/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/..../lib/ # 你的.so

现在再来运行程序就可以成功运行了:

$ ./test 
10 + 3 = 13
10 - 3 = 7

🐱软链接

上面我们说在程序运行时,calculate.so并没有在系统的默认路径下,所以OS找不到我们的库,那么这个默认路径在哪里呢?

# 一般在这两个路径下
$ /lib
$ /lib64/

所以我们直接将库文件移动到这两个路径下也可以,但是还有比较优雅一点的方案,那就是为我们的库文件建立软链接。

$ sudo ln -s lib/libcalculate.so /lib64/libcalculate.so
$ ./test 
10 + 3 = 13
10 - 3 = 7

🐱配置文件

  1. 在系统的 /etc/ld.so.conf.d/ 目录下创建一个配置文件;
$ sudo touch /etc/ld.so.conf.d/calculate.conf
  1. 将动态库所在路径写入配置文件;
$ sudo vim /etc/ld.so.conf.d/calculate.conf
$ sudo cat /etc/ld.so.conf.d/calculate.conf
/home/.../lib # 你的.so文件路径
  1. 让配置文件生效;
$ sudo ldconfig

现在小黑的程序也能成功运行了;

$ ./test 
10 + 3 = 13
10 - 3 = 7

🐧静态库与动态库的区别

以上我们通过制作一个自己的静态库,对库文件有了基础的了解。那么库为何又要分为静态库与动态库呢?二者有何区别?

  1. 链接时机和方式:

    • 静态库: 在编译时被链接到目标程序中,链接器将库的代码和数据拷贝到最终的可执行文件中。因此,可执行文件在运行时独立于库文件。
    • 动态库: 在编译时并不直接链接到目标程序,而是在运行时由操作系统动态加载到内存中。动态库的链接发生在程序启动时(静态加载)或在运行时(动态加载)。
  2. 文件大小和内存占用:

    • 静态库: 链接时会将库的代码和数据完全复制到目标程序中,可能导致可执行文件较大。每个使用该库的可执行文件都包含一份库的拷贝。
    • 动态库: 多个程序可以共享同一个动态库的实例,因此相同的库只需要在内存中存在一份,可以减小程序的大小。
  3. 更新和维护:

    • 静态库: 如果库的代码或数据发生变化,需要重新编译并重新链接所有使用该库的程序。每个程序都需要更新以包含最新版本的库。
    • 动态库: 如果库的代码或数据发生变化,只需要替换库文件而无需重新编译和链接使用该库的程序。这使得动态库更容易更新和维护。
  4. 跨平台兼容性:

    • 静态库: 可执行文件与库的链接是在编译时完成的,因此在不同平台上可能需要不同版本的库。
    • 动态库: 由于动态库的加载是在运行时由操作系统完成的,因此相同的动态库文件可以在多个平台上使用。
  5. 运行时灵活性:

    • 静态库: 执行文件在编译时固定了对静态库的依赖,无法在运行时更改。
    • 动态库: 可以在运行时加载或替换动态库,这使得系统更加灵活。

🐧动态库的运作原理

🐔为什么进程可以在运行时加载动态库?

我们知道每个程序在运行时就变成一个进程,一个进程拥有自己的虚拟地址空间。

在程序运行时,我们只需要将库加载到内存当中,经过页表映射到进程的地址空间中,我们的代码执行库中的方法就依旧还是在自己的地址空间中进行函数跳转。

🐔为什么多个进程可以共享一个动态库

当多个进程同时运行时,按照同样的方式,将库中的地址映射到每个进程的地址空间中,那么如果每个程序使用的地址都是相同的,不会产生冲突吗?

还记得我们在用 gcc 生成动态库时用到的参数 - fPIC 吗?

-fPIC 是 GCC 编译器选项,用于生成位置无关码(Position Independent Code,PIC)。位置无关码是一种在内存中加载时不依赖于特定内存地址的机器码,通常用于共享库(动态链接库)的编译。

具体来说,使用 -fPIC 选项的目的是允许将生成的目标文件用于共享库,而这些库可以被多个进程加载到内存的不同地址上,而不会发生地址冲突。

它主要特点包括:

  1. 位置独立性: 生成的代码不依赖于特定的内存地址,可以在不同的内存地址空间中运行。这对于动态链接库是必要的,因为它们可能在不同的进程中加载并映射到不同的地址。

  2. 全局偏移表(Global Offset Table,GOT): 在运行时,PIC 代码使用全局偏移表,其中包含指向全局和共享库中的符号的指针。这些指针在加载库时进行重定位,以便正确地找到符号的位置。

  3. 避免绝对地址: 使用相对寻址或基于 GOT 的寻址,而不是绝对地址。这使得代码更容易在不同的地址空间中重定位。

本章的内容到这里就结束了!如果觉得对你有所帮助的话,欢迎三连~

在这里插入图片描述

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

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

相关文章

Python根据3个点确定两个向量之间的夹角-180度到180方向进行矫正

import cv2 import numpy as np # 读取图片 image cv2.imread(rD:\dmp\cat.jpg) height, width image.shape[:2] # 定义三个定位点(这里假设是图片上的坐标),分别表示原点,向量1终点,向量2终点,下…

动画原理:表面形变算法的思考与总结

前言: 之前我的文章 Mesh形变算法_mesh算法-CSDN博客就有大致的讨论过,介绍的也比较粗略!现在主要是想在Triangulated Surface Mesh Deformation方向上更深入的讨论一下!结合今年我对这一块的学习谈谈我的理解~ 下面要介绍大致几…

学校机房Dev c++解决中文乱码问题

工具->编译选项->勾选 编译时加入以下命令 -fexec-charsetGBK -finput-charsetUTF-8 显示中文:工具->编辑器选项->去掉第一个的勾勾。

WebFlux的探索与实战 - r2dbc的分页查询

自从上次立下这系列的FLAG之后就再也不想碰了。今天难得早起出门面试,回家之后突发奇想打算再写点儿什么敷衍一下,于是便有了这篇文章。 前言 虽然响应式API更加适合流式列表的查询,但是分页这东西可是很常见的。 也没什么前言可说&#xf…

opencv中的rgb转gray的计算方法

转换原理 在opencv中,可以使用cv2.cvtColor函数将rgb图像转换为gray图像。示例代码如下, import cv2img_path "image.jpg" image cv2.imread(img_path) gray_image cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) mean gray_image.mean() pri…

在实训云平台上配置云主机

文章目录 零、学习目标一、实训云升级二、实训云登录(一)登录实训云(二)切换界面语言(三)规划云主机实例 三、创建网络三、创建路由器2024-2-29更新到此四、添加接口五、创建端口六、添加安全组规则七、创建…

公网IP怎么获取?

公网IP是网络中设备的唯一标识符,用于在Internet上进行通信和定位。对于普通用户来说,了解如何获取自己的公网IP是很有必要的,本文将介绍几种获取公网IP的方法。 方法一:通过路由器查询 大多数家庭和办公室使用的路由器都会有一个…

生成式AI设计模式:综合指南

原文地址:Generative AI Design Patterns: A Comprehensive Guide 使用大型语言模型 (LLM) 的参考架构模式和心理模型 2024 年 2 月 14 日 对人工智能模式的需求 我们在构建新事物时,都会依赖一些经过验证的方法、途径和模式。对于软件工程师来说&am…

Maven【3】( 依赖的范围,传递性和依赖的排除)(命令行操作)

文章目录 【1】依赖的范围结论验证①验证 compile 范围对 main 目录有效②验证test范围对main目录无效③验证test和provided范围不参与服务器部署 【2】依赖的传递性①compile 范围:可以传递②test 或 provided 范围:不能传递 【3】依赖的排除 【1】依赖…

apollo cyber RT初学

一 初识 ROS无法调度协调,且通信开销大,耗资源。百度自动驾驶团队开发了Cyber RT。 CyberRT从下到上依次为: 基础库:高性能,无锁队列; 通信层:Publish/Subscribe机制,Service/Cli…

h5移动端开发,android常见面试题及答案

1、Android系统的架构 Android系统架构之应用程序 Android会同一系列核心应用程序包一起发布,该应用程序包包括email客户端,SMS短消息程序,日历,地图,浏览器,联系人管理程序等。所有的应用程序都是使用JAV…

【进阶C语言】内存函数(详解)

前言 上一期讲的函数都是和字符串相关的,但是我们在操作数据的时候,不仅仅是操作字符串的数据,还得需要内存函数的应用 内存函数的应用 1. memcpy1.1 memcpy的介绍1.2 memcpy的使用1.3 模拟实现memcpy库函数1.4 我想在1,2后面打印…

day05_用户管理minIO角色分配(页面制作,查询用户,添加用户,修改用户,删除用户,用户头像,查询所有角色,保存角色数据)

文章目录 1 用户管理1.1 页面制作1.2 查询用户1.2.1 需求说明1.2.2 后端接口需求分析SysUserSysUserDtoSysUserControllerSysUserServiceSysUserMapperSysUserMapper.xml 1.2.3 前端对接实现思路sysUser.jssysRole.vue 1.3 添加用户1.3.1 需求说明1.3.2 页面制作1.3.3 后端接口…

C语言题目:指针

1. 下面代码的结果是&#xff1a; #include <stdio.h> int i; int main() {i--;if (i > sizeof(i)){printf(">\n");}else{printf("<\n");}return 0; }答案&#xff1a;> 解析&#xff1a; i作为全局变量且在未赋值的情况下初始值为1&…

每日一练:LeeCode-701、二叉搜索树中的插入操作【二叉搜索树+DFS+全搜】

本文是力扣 每日一练&#xff1a;LeeCode-701、二叉搜索树中的插入操作【二叉搜索树DFS全搜】学习与理解过程&#xff0c;本文仅做学习之用&#xff0c;对本题感兴趣的小伙伴可以出门左拐LeeCode。 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和要插入树中的值 …

看视频,学习使用MindOpt APL 建模语言编码数学规划问题,练习语法,实战拿奖品

活动介绍 活动名称&#xff1a;看视频&#xff0c;补充代码&#xff0c;拿精美礼品 活动规则&#xff1a; 浏览视频学习MAPL&#xff0c;完善“例题”。需要完善的内容&#xff1a;补充约束条件、读取csv表格数据&#xff0c;将决策变量的取值输出为csv表格&#xff0c;验证一…

pyuic生成py文件到指定文件夹

pyuic生成py文件到指定文件夹 关于如何在pycharm配置外部工具的方法这里不做赘述&#xff0c;本文主要说明&#xff0c;如何利用pyuic将ui文件生成到指定的项目目录中。 前提条件&#xff1a;已配置的pyuic工具可以正常使用生成文件到目录中。 一、打开外部工具配置页面 打开…

Java注解之@PathVariable,一文掌握@PathVariable注解知识(2)

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

Office/WPS 好用的PPT插件-智能选择布局

软件介绍 PPT大珩助手是一款全新设计的Office PPT插件&#xff0c;它是一款功能强大且实用的PPT辅助工具&#xff0c;能够轻松帮助您修改、优化和管理幻灯片。凭借丰富的功能和用户友好的界面&#xff0c;PPT大珩助手能够助力您打造出精美而专业的演示文稿。我们致力于为用户提…

“平民化”非结构数据处理

在全球信息产业高速发展的背景下&#xff0c;IDC预测&#xff0c;2018 到 2025 年之间&#xff0c;全球产生的数据量将会从 33 ZB 增长到 175 ZB&#xff0c; 复合增长率27%&#xff0c;其中超过 80%的数据都会是处理难度较大的非结构化数据&#xff0c;如文档、文本、图形、图…