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

全面深入解析:C语言动态库

引言

动态库(Dynamic Library)是现代软件开发中不可或缺的一部分,它们不仅提高了代码的重用性和维护性,还显著提升了系统的性能和资源利用率。本文将全面探讨C语言中的动态库,从基础概念到高级应用,通过丰富的实例和详细的技术细节,帮助读者深入理解动态库的原理和使用方法。
在这里插入图片描述

1. 动态库的基础概念
1.1 动态库的历史和发展

动态库的概念最早可以追溯到20世纪70年代的Unix系统。当时的操作系统设计师们面临的主要问题是:如何在有限的内存资源下,高效地运行多个程序。传统的静态链接方式会导致大量的内存浪费,因为每个程序都需要包含其依赖的所有库函数。为了解决这一问题,动态链接的概念应运而生。

动态链接的思想是将常用的库函数分离出来,形成独立的动态库文件。这些动态库在程序启动时或运行过程中按需加载,从而显著减少了内存占用。这一技术的引入极大地提高了系统的性能和资源利用率,成为现代操作系统的重要组成部分。

1.2 动态库在不同操作系统中的实现
  • Unix/Linux:在Unix和Linux系统中,动态库文件通常以 .so(Shared Object)为扩展名。这些系统使用 ld 链接器和 dlopendlsym 等API来管理和使用动态库。
  • Windows:在Windows系统中,动态库文件通常以 .dll(Dynamic Link Library)为扩展名。Windows系统使用 LoadLibraryGetProcAddress 等API来加载和调用动态库中的函数。
2. 创建和使用动态库
2.1 创建动态库

假设我们有一个简单的数学库,提供加法和初始化函数。我们将逐步展示如何创建、编译和使用这个动态库。

步骤1:编写库函数

// mathlib.h
#ifndef MATHLIB_H
#define MATHLIB_H#ifdef __cplusplus
extern "C" {
#endifvoid init_mathlib();
int add(int a, int b);#ifdef __cplusplus
}
#endif#endif // MATHLIB_H
// mathlib.c
#include "mathlib.h"
#include <stdio.h>void init_mathlib() {printf("Math library initialized.\n");
}int add(int a, int b) {return a + b;
}

步骤2:编译为对象文件

gcc -c -Wall -fPIC mathlib.c -o mathlib.o

步骤3:创建动态库

gcc -shared -o libmathlib.so mathlib.o
2.2 使用动态库

步骤4:编写用户程序

// main.c
#include <stdio.h>
#include "mathlib.h"int main() {init_mathlib();int result = add(3, 4);printf("Result: %d\n", result);return 0;
}

步骤5:编译并运行用户程序

gcc main.c -o main -L. -lmathlib -Wl,-rpath=.
./main
2.3 动态加载库函数

在某些情况下,我们可能需要在运行时动态加载库函数。这可以通过操作系统提供的API来实现。

Windows示例

#include <windows.h>
#include <stdio.h>typedef void (*InitMathLibFunc)();
typedef int (*AddFunc)(int, int);int main() {HINSTANCE hLib = LoadLibrary("libmathlib.dll");if (hLib == NULL) {printf("Failed to load library.\n");return 1;}InitMathLibFunc init_mathlib = (InitMathLibFunc)GetProcAddress(hLib, "init_mathlib");AddFunc add = (AddFunc)GetProcAddress(hLib, "add");if (init_mathlib == NULL || add == NULL) {printf("Failed to get function address.\n");FreeLibrary(hLib);return 1;}init_mathlib();int result = add(3, 4);printf("Result: %d\n", result);FreeLibrary(hLib);return 0;
}

Linux示例

#include <dlfcn.h>
#include <stdio.h>typedef void (*InitMathLibFunc)();
typedef int (*AddFunc)(int, int);int main() {void *handle = dlopen("./libmathlib.so", RTLD_LAZY);if (!handle) {fprintf(stderr, "%s\n", dlerror());return 1;}InitMathLibFunc init_mathlib = (InitMathLibFunc)dlsym(handle, "init_mathlib");const char *dlsym_error = dlerror();if (dlsym_error) {fprintf(stderr, "%s\n", dlsym_error);dlclose(handle);return 1;}AddFunc add = (AddFunc)dlsym(handle, "add");dlsym_error = dlerror();if (dlsym_error) {fprintf(stderr, "%s\n", dlsym_error);dlclose(handle);return 1;}init_mathlib();int result = add(3, 4);printf("Result: %d\n", result);dlclose(handle);return 0;
}
3. 动态库的加载过程
3.1 动态库的加载步骤

动态库的加载过程可以分为以下几个步骤:

  1. 查找库文件:根据指定的路径或环境变量(如 LD_LIBRARY_PATH)查找动态库文件。
  2. 加载库文件:将库文件映射到内存中,并解析库中的符号表。
  3. 解析符号:根据程序的需要,解析库中的函数和变量符号。
  4. 重定位:将库中的地址引用从相对地址转换为绝对地址。
  5. 初始化:调用库的初始化函数(如果有)。
3.2 符号解析与重定位

符号解析是指在加载动态库时,将程序中引用的符号(如函数名、变量名)与库中的实际地址对应起来。重定位是指将库中的地址引用从相对地址转换为绝对地址,以便程序可以直接访问库中的数据和函数。

在ELF格式的动态库中,符号表通常包含在 .symtab 段中,重定位信息则存储在 .rel.dyn.rel.plt 段中。加载器会根据这些信息进行符号解析和重定位。

4. 动态库的应用场景
4.1 图形库

动态库在图形库中广泛应用,如 OpenGL 和 DirectX。通过动态加载图形库,可以实现跨平台的图形渲染。

4.2 插件系统

许多应用程序使用插件系统来扩展功能。插件通常以动态库的形式存在,应用程序在运行时动态加载和卸载插件。

4.3 驱动程序

设备驱动程序通常以动态库的形式存在,操作系统在需要时加载驱动程序,管理硬件设备。

5. 动态库的高级话题
5.1 动态库的版本管理

动态库的版本管理是一个重要的问题。不同的应用程序可能依赖于不同版本的同一个库。为了确保兼容性,动态库通常会使用版本号来区分不同的版本。在Linux系统中,可以使用 soname 来指定库的版本。

示例

gcc -shared -o libmathlib.so.1.0 mathlib.o -Wl,-soname,libmathlib.so.1
5.2 动态库的安全性

动态库的安全性也是一个值得关注的话题。恶意代码可以通过动态库注入的方式攻击应用程序。为了防止这种情况,可以使用符号绑定(Symbol Binding)和符号隐藏(Symbol Hiding)等技术。

符号绑定

符号绑定是指在编译时将符号绑定到特定的库。这样可以防止其他库覆盖这些符号。

符号隐藏

符号隐藏是指在编译时将符号隐藏,使其对外部不可见。这样可以防止其他库访问这些符号。

5.3 动态库的性能优化

动态库的性能优化是一个复杂但重要的话题。以下是一些常见的优化方法:

  1. 减少符号解析次数:通过预加载常用库或使用符号缓存,减少每次加载时的符号解析次数。
  2. 使用位置独立代码(Position Independent Code, PIC):PIC使得库可以在内存中的任意位置加载,从而提高加载效率。
  3. 优化重定位信息:通过减少不必要的重定位信息,加快加载速度。
  4. 懒加载(Lazy Loading):延迟加载库中的函数,直到第一次调用时才加载,从而减少初始加载时间。
5.4 动态库的调试技巧

调试动态库中的问题可以是一项挑战。以下是一些常用的调试技巧:

  1. 使用 gdb 调试器:附加到运行中的进程,设置断点并逐步调试。
  2. 查看符号表:使用 nm 命令查看动态库的符号表,帮助定位符号问题。
  3. 检查加载路径:使用 ldd 命令检查程序依赖的动态库及其加载路径。
  4. 日志记录:在库中添加日志记录功能,帮助追踪问题。
6. 动态库的实际案例分析
6.1 图形库案例:OpenGL

OpenGL 是一个广泛使用的图形库,用于跨平台的二维和三维图形渲染。OpenGL 库通常以动态库的形式提供,应用程序在运行时动态加载 OpenGL 库,调用其提供的函数进行图形渲染。

示例代码

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>void display() {glClear(GL_COLOR_BUFFER_BIT);glBegin(GL_TRIANGLES);glVertex2f(-0.5, -0.5);glVertex2f(0.5, -0.5);glVertex2f(0.0, 0.5);glEnd();glFlush();
}int main(int argc, char **argv) {glutInit(&argc, argv);glutCreateWindow("OpenGL Example");glutDisplayFunc(display);glutMainLoop();return 0;
}
6.2 插件系统案例:图像处理插件

许多图像处理软件支持插件系统,允许用户扩展软件的功能。这些插件通常以动态库的形式存在,软件在运行时动态加载插件,调用其提供的函数进行图像处理。

插件接口定义

// plugin.h
#ifndef PLUGIN_H
#define PLUGIN_Htypedef struct {const char *name;void (*process_image)(unsigned char *image, int width, int height);
} Plugin;#endif // PLUGIN_H

插件实现

// grayscale_plugin.c
#include "plugin.h"
#include <stdio.h>void process_image_grayscale(unsigned char *image, int width, int height) {for (int i = 0; i < width * height * 3; i += 3) {unsigned char gray = (image[i] + image[i + 1] + image[i + 2]) / 3;image[i] = gray;image[i + 1] = gray;image[i + 2] = gray;}
}Plugin plugin = {.name = "Grayscale",.process_image = process_image_grayscale
};__attribute__((constructor))
void register_plugin() {printf("Registering Grayscale plugin.\n");
}

主程序

// main.c
#include <stdio.h>
#include <dlfcn.h>
#include "plugin.h"int main() {void *handle = dlopen("./grayscale_plugin.so", RTLD_LAZY);if (!handle) {fprintf(stderr, "%s\n", dlerror());return 1;}Plugin *plugin = (Plugin *)dlsym(handle, "plugin");if (!plugin) {fprintf(stderr, "%s\n", dlerror());dlclose(handle);return 1;}printf("Loaded plugin: %s\n", plugin->name);unsigned char image[] = {255, 0, 0, 0, 255, 0, 0, 0, 255};int width = 3;int height = 1;printf("Before processing:\n");for (int i = 0; i < width * height * 3; i++) {printf("%d ", image[i]);}printf("\n");plugin->process_image(image, width, height);printf("After processing:\n");for (int i = 0; i < width * height * 3; i++) {printf("%d ", image[i]);}printf("\n");dlclose(handle);return 0;
}
7. 动态库的内部机制
7.1 ELF 文件格式

ELF(Executable and Linkable Format)是Unix系统中最常用的可执行文件和目标文件格式。ELF文件包含多个段(Section),每个段包含不同类型的数据。

常见段类型

  • .text:存放程序的机器码。
  • .data:存放已初始化的全局变量。
  • .bss:存放未初始化的全局变量。
  • .rodata:存放只读数据。
  • .symtab:存放符号表。
  • .rel.dyn:存放重定位信息。
  • .rel.plt:存放过程链接表(PLT)的重定位信息。
7.2 动态链接器

动态链接器(Dynamic Linker)负责在程序启动时加载和解析动态库。在Linux系统中,动态链接器通常是 /lib/ld-linux.so

动态链接器的工作流程

  1. 解析依赖库:读取可执行文件的 .interp 段,找到动态链接器的路径。
  2. 加载动态链接器:将动态链接器映射到内存中。
  3. 解析符号表:读取可执行文件和动态库的符号表,解析符号引用。
  4. 重定位:根据重定位信息,将符号引用从相对地址转换为绝对地址。
  5. 初始化:调用动态库的初始化函数(如果有)。
  6. 跳转到入口点:将控制权交给程序的入口点,开始执行程序。
7.3 动态库的加载策略

动态库的加载策略决定了库文件何时被加载到内存中。常见的加载策略包括:

  • 立即加载(Immediate Loading):在程序启动时立即加载所有依赖的动态库。
  • 延迟加载(Lazy Loading):在首次调用库函数时才加载相应的动态库。
  • 预加载(Preloading):在程序启动前预先加载指定的动态库。
8. 动态库的管理和维护
8.1 动态库的安装和卸载

在Linux系统中,动态库通常安装在 /usr/lib/usr/local/lib 目录下。可以通过 ldconfig 命令更新动态库的缓存。

安装动态库

sudo cp libmathlib.so /usr/local/lib/
sudo ldconfig

卸载动态库

sudo rm /usr/local/lib/libmathlib.so
sudo ldconfig
8.2 动态库的版本控制

动态库的版本控制可以通过文件命名和 soname 来实现。文件命名通常包含版本号,例如 libmathlib.so.1.0soname 是库的共享对象名称,用于标识库的主版本。

示例

gcc -shared -o libmathlib.so.1.0 mathlib.o -Wl,-soname,libmathlib.so.1
ln -sf libmathlib.so.1.0 libmathlib.so.1
ln -sf libmathlib.so.1.0 libmathlib.so
8.3 动态库的依赖管理

动态库的依赖关系可以通过 ldd 命令查看。ldd 命令显示可执行文件或动态库依赖的动态库及其路径。

示例

ldd ./main
9. 动态库的常见问题及解决方案
9.1 符号冲突问题

符号冲突是指两个不同的库中定义了相同的符号。这会导致链接错误或运行时错误。解决方法包括:

  • 符号绑定:在编译时使用 -fvisibility=hidden 选项,将默认的符号可见性设置为隐藏。
  • 符号重命名:手动重命名冲突的符号。
  • 使用命名空间:在C++中使用命名空间来避免符号冲突。
9.2 加载失败问题

动态库加载失败可能是由于路径错误、权限问题或依赖库缺失等原因引起的。解决方法包括:

  • 检查路径:确保库文件路径正确,可以使用 ldd 命令检查依赖库路径。
  • 检查权限:确保库文件具有适当的读取权限。
  • 安装依赖库:确保所有依赖的动态库已正确安装。
9.3 性能问题

动态库的性能问题可能包括加载时间过长、符号解析次数过多等。解决方法包括:

  • 预加载:在程序启动前预先加载常用的动态库。
  • 符号缓存:使用符号缓存减少符号解析次数。
  • 优化重定位信息:减少不必要的重定位信息,加快加载速度。
10. 动态库的最佳实践
10.1 设计良好的接口

设计良好的接口是动态库成功的关键。接口应该简洁、清晰、易于使用。遵循以下原则:

  • 保持接口稳定:避免频繁更改接口,以保证兼容性。
  • 提供详细的文档:编写详细的文档,说明接口的使用方法和注意事项。
  • 使用版本控制:通过版本号管理不同版本的接口。
10.2 使用符号隐藏

符号隐藏可以防止其他库访问动态库中的私有符号,提高代码的安全性和模块化。在编译时使用 -fvisibility=hidden 选项,并在需要导出的符号前加上 __attribute__((visibility("default")))

示例

// mathlib.h
#ifndef MATHLIB_H
#define MATHLIB_H#ifdef __cplusplus
extern "C" {
#endif__attribute__((visibility("default")))
void init_mathlib();__attribute__((visibility("default")))
int add(int a, int b);#ifdef __cplusplus
}
#endif#endif // MATHLIB_H
10.3 使用动态加载

动态加载可以在运行时按需加载库函数,提高程序的灵活性和性能。使用 dlopendlsym 等API进行动态加载。

示例

#include <dlfcn.h>
#include <stdio.h>typedef void (*InitMathLibFunc)();
typedef int (*AddFunc)(int, int);int main() {void *handle = dlopen("./libmathlib.so", RTLD_LAZY);if (!handle) {fprintf(stderr, "%s\n", dlerror());return 1;}InitMathLibFunc init_mathlib = (InitMathLibFunc)dlsym(handle, "init_mathlib");const char *dlsym_error = dlerror();if (dlsym_error) {fprintf(stderr, "%s\n", dlsym_error);dlclose(handle);return 1;}AddFunc add = (AddFunc)dlsym(handle, "add");dlsym_error = dlerror();if (dlsym_error) {fprintf(stderr, "%s\n", dlsym_error);dlclose(handle);return 1;}init_mathlib();int result = add(3, 4);printf("Result: %d\n", result);dlclose(handle);return 0;
}
11. 动态库的未来发展方向

随着技术的发展,动态库也在不断进化。未来的动态库可能会有以下发展方向:

  • 更好的安全性:通过更先进的加密技术和访问控制机制,提高动态库的安全性。
  • 更高的性能:通过更高效的加载算法和优化技术,提高动态库的加载和运行性能。
  • 更强大的调试工具:开发更强大的调试工具,帮助开发者更快地定位和解决动态库中的问题。
  • 更广泛的跨平台支持:通过标准化和开源技术,提高动态库在不同平台上的兼容性和互操作性。
12. 结论

通过本文的全面讲解,希望读者能够深入理解C语言动态库的相关知识,并在实际开发中灵活应用。动态库不仅能够提高代码的重用性和维护性,还能显著提高系统的性能和资源利用率。无论是图形库、插件系统还是驱动程序,动态库都扮演着不可或缺的角色。

附录
附录A:动态库加载流程图
+-------------------+
| 查找库文件         |
+-------------------+|v
+-------------------+
| 加载库文件         |
+-------------------+|v
+-------------------+
| 解析符号           |
+-------------------+|v
+-------------------+
| 重定位             |
+-------------------+|v
+-------------------+
| 初始化             |
+-------------------+
附录B:常见问题解答

Q1: 动态库和静态库有什么区别?

A1: 静态库在编译时被链接到可执行文件中,而动态库在运行时按需加载。静态库增加了可执行文件的大小,但提高了运行速度;动态库减少了内存占用,但增加了加载时间。

Q2: 如何查看动态库的符号表?

A2: 可以使用 nm 命令查看动态库的符号表。例如:

nm -g libmathlib.so

Q3: 如何调试动态库中的问题?

A3: 可以使用 gdb 调试器附加到运行中的进程,然后设置断点并逐步调试。例如:

gdb ./main
(gdb) break add
(gdb) run

通过本文的详细讲解,希望读者能够全面掌握 C 语言动态库的相关知识,并在实际开发中灵活应用。动态库不仅能够提高代码的重用性和维护性,还能显著提高系统的性能和资源利用率。无论是图形库、插件系统还是驱动程序,动态库都扮演着不可或缺的角色。

相关文章:

全面深入解析:C语言动态库

引言 动态库&#xff08;Dynamic Library&#xff09;是现代软件开发中不可或缺的一部分&#xff0c;它们不仅提高了代码的重用性和维护性&#xff0c;还显著提升了系统的性能和资源利用率。本文将全面探讨C语言中的动态库&#xff0c;从基础概念到高级应用&#xff0c;通过丰…...

运用 SSM 实现垃圾分类系统智能化升级

目 录 摘 要 1 前 言 3 第1章 概述 4 1.1 研究背景 4 1.2 研究目的 4 1.3 研究内容 4 第二章 开发技术介绍 5 2.1Java技术 6 2.2 Mysql数据库 6 2.3 B/S结构 7 2.4 SSM框架 8 第三章 系统分析 9 3.1 可行性分析 9 3.1.1 技术可行性 9 3.1.2 经济可行性 10 3.1.3 操作可行性 10 …...

LeNet-5:深度学习与卷积神经网络的里程碑

目录 ​编辑 引言 LeNet-5的结构与原理 输入层 C1层&#xff1a;卷积层 S2层&#xff1a;池化层 C3层&#xff1a;卷积层 S4层&#xff1a;池化层 C5层&#xff1a;卷积层 F6层&#xff1a;全连接层 输出层 LeNet-5的算法基础 LeNet-5的优点 LeNet-5的现代应用 …...

从资产流动分析WIF市场潜力X.game深究其他未知因素

近日&#xff0c;两则关于WIF最新消息引起了投资者们的注意。据报道&#xff0c;11月28日Vintermute在过去13小时内累计从Binance交易所提取了价值533万美元的WIF&#xff0c;此举不仅彰显了其强大的资金实力&#xff0c;更在某种程度上推动了WIF币价的反弹&#xff1b;另一方面…...

深入解析Vue3响应式系统:从Proxy实现到依赖收集的核心原理

深入解析Vue3响应式系统&#xff1a;从Proxy实现到依赖收集的核心原理 响应式系统的基本原理 作为一个热门的JavaScript框架&#xff0c;Vue在3.x版本中引入了基于Proxy的响应式系统。这个系统的核心思想是利用Proxy对象拦截对数据的访问和修改&#xff0c;从而实现数据的自动更…...

FPGA实现GTP光口数据回环传输,基于Aurora 8b/10b编解码架构,提供2套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我这里已有的 GT 高速接口解决方案 3、工程详细设计方案工程设计原理框图用户数据发送模块基于GTP高速接口的数据回环传输架构GTP IP 简介GTP 基本结构GTP 发送和接收…...

Linux网络 UDP socket

背景知识 我们知道&#xff0c; IP 地址用来标识互联网中唯一的一台主机&#xff0c; port 用来标识该主机上唯一的一个网络进程&#xff0c;IPPort 就能表示互联网中唯一的一个进程。所以通信的时候&#xff0c;本质是两个互联网进程代表人来进行通信&#xff0c;{srcIp&…...

如何持续优化呼叫中心大模型呼入机器人的性能?

如何持续优化呼叫中心大模型呼入机器人的性能&#xff1f; 原作者&#xff1a;开源呼叫中心FreeIPCC&#xff0c;其Github&#xff1a;https://github.com/lihaiya/freeipcc 持续优化呼叫中心大模型呼入机器人的性能是一个复杂而细致的过程&#xff0c;它涉及到数据、模型结构…...

鸿蒙项目云捐助第四讲鸿蒙App应用的登陆注册页实现

根据app的操作流程可以知道&#xff0c;当启动页启动后&#xff0c;点击启动页中的页面就进入到了登录页。本讲就是针对于登录注册页的实现&#xff0c;实现的界面参考下图。 这里根据这个素材的参考实现鸿蒙Next云捐助的登录页。 一、鸿蒙Next云捐助登录页的实现 在项目中继…...

Windows本地搭建Redis集群(集群模式)

手打不易&#xff0c;如果转摘&#xff0c;请注明出处&#xff01; 注明原文&#xff1a;https://blog.csdn.net/q258523454/article/details/144477957 前言 Redis版本&#xff1a;redis 5.0.14.1 Windows版本&#xff1a;Windows10 本文只讲集群模式 1. 安装Redis 1.1 …...

使用FastGPT制做一个AI网站日志分析器

越来越的多网站面临每天上千次的扫描和各类攻击&#xff0c;及时发现攻击IP&#xff0c;并有效的屏蔽不良访问成为网站安全的重要保障&#xff0c;这里我们使用AI来完成对网站日志的日常分析。 我们来使用FastGPT来制做一个AI网站日志析器&#xff0c;下面就开始&#xff1a; …...

探索 Echarts 绘图:数据可视化的奇妙之旅

目录 一、Echarts 初印象 二、搭建 Echarts 绘图环境 三、绘制第一个图表&#xff1a;柱状图的诞生 四、图表的美化与定制&#xff1a;让数据更具吸引力 1. 主题切换&#xff1a;一键变换风格 2. 颜色调整&#xff1a;色彩搭配的艺术 3. 标签与提示框&#xff1a;丰富信…...

网络基础(IP和端口)

网络连接的核心-TCP/IP体系结构&#xff08;IP和端口&#xff09; 什么是IP地址 1.IP地址是电子设备&#xff08;计算机&#xff09;在互联网上的唯一标识 2.用来在互联网中寻找电脑 IP 地址就像是你家的地址一样&#xff0c;不过它是在网络世界里用来找到一台电脑或者其他网…...

UE4与WEB-UI通信

前端HTML代码 <!DOCTYPE html><html><head><meta charset"utf-8"><meta name"viewport" content"widthdevice-width, initial-scale1"><title>test web ui</title><script src"https://cdn.b…...

前缀和与差分算法详解

定义 前缀和是一种数据预处理技术&#xff0c;它指的是从数组的第一个元素开始&#xff0c;到当前元素为止的所有元素的和。这种技术可以快速计算任意区间内元素的和&#xff0c;而不需要每次都从头开始累加。 差分则是前缀和的逆运算&#xff0c;它主要用于处理对数组某个区…...

《深入探究:C++ 在多方面对 C 语言实现的优化》

目录 一、C 在 C 上进行的优化二、C 关键字&#xff08;C 98&#xff09;三、C 的输入输出1. cin 和 cout 的使用2. cin、cout 和 scanf()、printf() 的区别 三、命名空间1. 命名空间的使用2. 嵌套命名空间3. 在多个头文件中使用相同的命名空间 四、函数缺省值1. 缺省值的使用2…...

React 第十六节 useCallback 使用详解注意事项

useCallback 概述 1、useCallback 是在React 中多次渲染缓存函数的 Hook&#xff0c;返回一个函数的 memoized的值&#xff1b; 2、如果多次传入的依赖项不变&#xff0c;那么多次定义的时候&#xff0c;返回的值是相同的,防止频繁触发更新&#xff1b; 3、多应用在 父组件为函…...

使用C#和OPenCV实现圆形检测

文章目录 霍夫变换使用 OpenCV 和 C# 实现圆形检测 霍夫变换 在计算机视觉中&#xff0c;圆形检测是一个常见且有用的任务&#xff0c;特别是在物体识别、图像分析和图形处理等领域。OpenCV 是一个强大的开源计算机视觉库&#xff0c;它提供了许多工具来实现不同的图像处理功能…...

评估一套呼叫中心大模型呼入机器人的投入回报比?

评估一套呼叫中心大模型呼入机器人的投入回报比&#xff1f; 原作者&#xff1a;开源呼叫中心FreeIPCC&#xff0c;其Github&#xff1a;https://github.com/lihaiya/freeipcc 评估一套呼叫中心大模型呼入机器人的投入回报比&#xff08;ROI&#xff09;&#xff0c;是一个多…...

十八、Label 和 Selector

Label 是键值对,用来标识 Kubernetes 资源(如 Pod、Node、Service 等)的属性。它们并不直接影响资源的行为,但可以帮助用户快速组织、查询和操作这些资源。标签可以用于选择、过滤和分组。 Label: 标签对 k8s 中各种资源进行分类、分组,如Pod和节点进行分组。通过添加kev…...

实现按键按下(低电平)检测到下降沿

按照流程进行编程 步骤1&#xff1a; 初始化函数 包括时基工作参数配置 输入通道配置 更新中断使能 使能捕获、捕获中断及计数器 HAL_TIM_IC_Init(&ic_handle) //时基参数配置 HAL_TIM_IC_ConfigChannel(&ic_handle,&ic_config,TIM_CHANNEL_2) //输…...

解析 SSM 垃圾分类系统,助力生态平衡

前 言 垃圾分类系统&#xff0c;传统的垃圾分类系统模式还处于线下管理阶段&#xff0c;管理效率极低。随着垃圾分类系统信息的不断增多&#xff0c;传统基于线下管理模式已经无法满足当前用户需求&#xff0c;随着信息化时代的到来。通过该系统的设计&#xff0c;管理员可以管…...

软件工程 设计的复杂性

复杂性代表事件或事物的状态&#xff0c;它们具有多个相互关联的链接和高度复杂的结构。在软件编程中&#xff0c;随着软件设计的实现&#xff0c;元素的数量以及它们之间的相互联系逐渐变得庞大&#xff0c;一下子变得难以理解。 如果不使用复杂性指标和度量&#xff0c;软件…...

Nginx 限制只能白名单 uri 请求的配置

实际生产项目中&#xff0c;大多数时候我们会将后端的 http 接口通过前置 nginx 进行反向代理&#xff0c;对互联网用户提供服务。往往我们后端服务所能提供的接口服务是大于互联网用户侧的实际请求的接口地址数量的&#xff08;例如后端服务一共有100个api接口&#xff0c;经过…...

QT c++ 同时使用sqlite 和mysql数据库的问题

在项目开发中&#xff0c;同时使用了sqlite 和mysql数据库&#xff0c;分开这两部分运行功能都正常&#xff0c;但是一起运行&#xff0c;就异常&#xff0c;sqlite部分不能使用。 现象&#xff1a;出现如下提示 QSqlDatabasePrivate::addDatabase: duplicate connection nam…...

redis集群 服务器更换ip,怎么办,怎么更换redis集群的ip

redis集群 服务器更换ip&#xff0c;怎么办&#xff0c;怎么更换redis集群的ip 1、安装redis三主三从集群2、正常状态的redis集群3、更改redis集群服务器的ip 重启服务器 集群会down4、更改redis集群服务器的ip 重启服务器 集群down的原因5、更改redis集群服务器的ip后&#xf…...

【C++习题】19.数组中第K个大的元素

题目&#xff1a;数组中第K个大的元素 链接&#x1f517;&#xff1a;数组中第K个大的元素 题目&#xff1a; 代码&#xff1a; class Solution { public:int findKthLargest(vector<int>& nums, int k) {// 将数组中的元素先放入优先级队列中priority_queue<i…...

JIS-CTF: VulnUpload靶场渗透

JIS-CTF: VulnUpload来自 <https://www.vulnhub.com/entry/jis-ctf-vulnupload,228/> 1,将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 靶机IP地址192.168.23.162&#xff0c;攻击机IP地址192.168.23.140…...

BGP-面试

简单介绍一下BGP BGP&#xff0c;边界网关协议&#xff0c;属于路径矢量路由协议。属于触发式更新或者增量更新。具有丰富的路由策略&#xff0c;能够灵活的进行路由选择。重心不是在路由学习&#xff0c;而是路由优选、更高效的传递路由和维护大量的路由信息。基于TCP&#xf…...

Git-安装与常用命令

目录 1.Git环境配置 1.1下载 1.2配置 1.2.1基本配置 1.2.2常用指令配置别名 1.2.3获取本地仓库 git命令在git bash中演示&#xff0c;会用到一些Linux命令。 1.Git环境配置 1.1下载 Git下载地址&#xff1a;https://git-scm.com/download 傻瓜式安装就可以了。 安装…...