【动态库.so | 头文件.hpp】基于CMake与CMakeList编写C++自定义库

前言

  • 最近比较忙,其他系列教程得等到年后一起更!请大家多多包涵!!
  • 相信各位在配置C++环境和各类库的时候一定经常看到如下小连招
git clone https://github.com/opencv/opencv.git 
cd opencv
mkdir build && cd build
cmake .. && make -j4
sudo make install 
sudo ldconfig
  • 今天我们就来谈一谈上述每一行指令的具体含义,并尝试构造我们自己的示例库。
  • 之后我们就可以使用编译生成的动态库文件.so和我们提供的头文件hpp给用户使用。
  • 通过配置我们可以在cmake直接包含链接我们的自定义库find_package(MyTime REQUIRED)

1 分布讲解

1-1 拉去仓库
git clone https://github.com/opencv/opencv.git
  • 用于从远程仓库复制一个完整的版本库到本地。
  • https://github.com/opencv/opencv.git: 指定了要克隆的远程仓库的URL。此处是OpenCV的官方GitHub仓库地址。
  • 执行此命令后,会在当前目录下生成一个名为opencv的子文件夹,其中包含OpenCV的完整源代码。
cd opencv
1-2 编译
mkdir build && cd build
  • 创建一个名为build的子目录,并切换到该目录。通常在此目录中进行编译过程。
cmake .. && make -j4
  • 使用CMake生成构建配置并启动构建过程。
    • cmake ..: 运行CMake命令,..表示CMake应该在父目录(即源代码目录)中查找CMakeLists.txt文件。CMake会根据这个文件生成构建配置文件(如Makefile或Visual Studio工程文件等)。
    • make -j4: 使用make工具来开始构建过程。-j4指定了并行构建的线程数,这里是4个线程,可以加速编译过程。
1-3 安装
sudo make install
  • make install: 这个命令会将构建完成的库、头文件和可执行文件拷贝到系统的标准位置(例如/usr/local/lib/usr/local/include等)。具体的安装路径和内容是由CMakeLists.txt文件中指定的。
  • sudo: 由于make install通常需要系统级的权限,所以需要sudo来提权执行命令。
sudo ldconfig
  • ldconfig: 是一个用于更新动态链接库缓存的命令。操作系统会根据/etc/ld.so.conf/lib/usr/lib等目录中的库文件来更新缓存。ldconfig命令的目的是让操作系统能够找到新安装的库文件。
  • sudo: 因为ldconfig需要修改系统级配置,所以需要sudo权限。
1-4 使用
  • 通过上述过程,如果你在其他项目中需要使用到 OpenCV,你只需要在项目中正确配置 OpenCV 的头文件和库文件路径,并在编译时链接 OpenCV 库即可。
cmake_minimum_required(VERSION 3.10)project(MyProject)# 查找 OpenCV 包
find_package(OpenCV REQUIRED)# 将 OpenCV 的 include 目录添加到编译器的头文件路径
include_directories(${OpenCV_INCLUDE_DIRS})# 定义源文件
add_executable(my_program main.cpp)# 将 OpenCV 库链接到项目中
target_link_libraries(my_program ${OpenCV_LIBS})

2 构建自己的库

  • 通过上述说明,你也许还是懵懵懂懂,下面我们创建自己的一个库,并逐步细致的说明。
2-1 创建文件夹
  • 我们打开终端,创建一个文件夹用来存放我们的自定义库,我们给库取名为MyTime,并在这个文件夹下创建srcinclude目录。
mkdir -p MyTime/src MyTime/include
  • -p:表示递归创建目录,若父目录不存在,也会一并创建。
  • MyTime/src:存放源代码文件。
  • MyTime/include:存放头文件。
2-2 编写头文件
  • 我们实现一个简单的打印当前时间库,这个库将提供几个函数
    • printCurrentDate:打印当前日期(年月日格式)。
    • getFormattedTime:返回当前时间的格式化字符串(比如 “YYYY-MM-DD HH:MM:SS”)。
    • calculateTimeDifference:计算两个时间点之间的差异,返回秒数。
  • 然后根据上述旨意,我们编写include/MyTime.hpp
// MyTime/include/MyTime.h
#ifndef __MY_TIME_HPP__
#define __MY_TIME_HPP__#include <string>namespace mytime{class MyTime{public:// 打印当前的时间void printCurrentTime();// 打印当前的日期(年月日)void printCurrentDate();// 获取当前时间的格式化字符串(YYYY-MM-DD HH:MM:SS)std::string getFormattedTime();// 计算两个时间点之间的差异,返回秒数double calculateTimeDifference(const std::string& time1, const std::string& time2);};};#endif // __MY_TIME_HPP__
  • 头文件保护机制#ifndef, #define#endif 这一组预处理指令保证了头文件只会被包含一次,从而防止重复定义。
    • __MY_TIME_HPP__ 是一个宏定义,它会在头文件被第一次包含时被定义,从而防止第二次包含。
    • 除此之外我们还可以使用pragma once防止头文件被重复编译
  • 命名空间 mytime:为了避免与其他库中的类名冲突,我们将 MyTime 类放在 mytime 命名空间中。这样你就可以通过 mytime::MyTime 来访问 MyTime 类。
  • MyTime 类声明
    • printCurrentTime:用于打印当前系统时间。
    • printCurrentDate:用于打印当前系统日期,格式为 YYYY-MM-DD
    • getFormattedTime:返回当前时间的格式化字符串,格式为 YYYY-MM-DD HH:MM:SS
    • calculateTimeDifference:计算两个给定时间字符串之间的差异,单位为秒。该函数接受两个字符串形式的时间(格式为 YYYY-MM-DD HH:MM:SS),并返回它们之间的差异(秒)。
2-3 编写源代码
  • src\Mytime.cpp
// MyTime/src/MyTime.cpp
#include "../include/MyTime.hpp"
#include <iostream>
#include <chrono>
#include <ctime>
#include <sstream>
#include <iomanip>namespace mytime {// 打印当前的时间void MyTime::printCurrentTime() {// 获取当前时间auto now = std::chrono::system_clock::now();std::time_t now_time = std::chrono::system_clock::to_time_t(now);// 打印时间std::cout << "Current time: " << std::ctime(&now_time);}// 打印当前的日期(年月日)void MyTime::printCurrentDate() {// 获取当前时间auto now = std::chrono::system_clock::now();std::time_t now_time = std::chrono::system_clock::to_time_t(now);std::tm *tm = std::localtime(&now_time);// 打印日期(格式为 YYYY-MM-DD)std::cout << "Current date: "<< std::put_time(tm, "%Y-%m-%d")  // 格式化为 YYYY-MM-DD<< std::endl;}// 获取当前时间的格式化字符串(YYYY-MM-DD HH:MM:SS)std::string MyTime::getFormattedTime() {// 获取当前时间auto now = std::chrono::system_clock::now();std::time_t now_time = std::chrono::system_clock::to_time_t(now);std::tm *tm = std::localtime(&now_time);// 使用 stringstream 格式化时间为字符串std::ostringstream oss;oss << std::put_time(tm, "%Y-%m-%d %H:%M:%S");  // 格式化为 YYYY-MM-DD HH:MM:SSreturn oss.str();}// 计算两个时间点之间的差异,返回秒数double MyTime::calculateTimeDifference(const std::string& time1, const std::string& time2) {std::tm tm1 = {}, tm2 = {};std::istringstream ss1(time1), ss2(time2);// 解析时间字符串(格式: YYYY-MM-DD HH:MM:SS)ss1 >> std::get_time(&tm1, "%Y-%m-%d %H:%M:%S");ss2 >> std::get_time(&tm2, "%Y-%m-%d %H:%M:%S");// 将 std::tm 转换为 time_tstd::time_t t1 = std::mktime(&tm1);std::time_t t2 = std::mktime(&tm2);// 计算两个时间点的时间差,单位为秒double diff = std::difftime(t2, t1);return diff;}} // namespace mytime
2-4 Cmake
2-4-1 Cmake介绍
  • CMake 是一个跨平台的自动化构建工具,它能够生成不同平台(如 Linux、Windows、macOS)上的构建文件,并支持多种构建系统(如 Make, Ninja, Visual Studio, Xcode 等)。CMake 主要用于管理大型软件项目的构建过程,通过配置项目的构建步骤来生成适合目标平台的构建文件。
2-4-2 CMake 的核心概念
  1. CMakeLists.txt: 这是 CMake 项目的配置文件,通常在每个子目录中都有一个,CMake 会根据这些文件来决定如何构建整个项目。CMakeLists.txt 文件包含了 CMake 的指令和设置,它们告诉 CMake 如何配置、编译和链接项目。
  2. CMake 构建过程: CMake 的构建过程通常包括两个步骤:
    • 配置步骤:通过执行 cmake 命令,CMake 会根据 CMakeLists.txt 文件的内容生成构建系统的配置文件(如 MakefileVisual Studio 项目文件等)。
    • 构建步骤:通过执行构建命令(如 makeninja 等),CMake 会使用生成的构建文件来编译项目。
  3. CMake 变量: CMake 允许在 CMakeLists.txt 文件中定义变量,这些变量可以在整个项目中使用。变量可以用来指定路径、设置编译选项或管理构建参数。
  4. 目标(Target): 在 CMake 中,“目标” 是你要构建的最终产物(比如库文件、可执行文件等)。通过 CMake 指令如 add_executableadd_library 来定义目标。
2-4-3 CMake 的主要指令
  1. cmake_minimum_required(VERSION <version>): 这个指令设置项目所需的最低 CMake 版本。如果当前的 CMake 版本低于这个版本,CMake 会发出警告。
cmake_minimum_required(VERSION 3.10)
  1. project(<project-name>): 设置当前项目的名称。
project(MyProject)
  1. set(<variable> <value>): 设置变量的值。CMake 中的变量可以用来存储路径、文件名、编译选项等。
set(CMAKE_CXX_STANDARD 11)
  1. add_executable(<target> <source-files>): 用于创建一个可执行文件目标,指定源文件列表。
add_executable(MyApp main.cpp)
  1. add_library(<target> <source-files>): 用于创建一个库目标,可以是静态库或动态库。
add_library(MyLib STATIC mylib.cpp)
  1. target_link_libraries(<target> <libraries>): 指定可执行文件或库所依赖的其他库。
target_link_libraries(MyApp MyLib)
  1. include_directories(<dir>): 用于添加头文件的搜索路径。
include_directories(${CMAKE_SOURCE_DIR}/include)
  1. find_package(<package-name>): 查找已安装的外部库(如 Boost、OpenGL、Qt 等)。
find_package(Boost REQUIRED)
  1. install(<target>): 指定安装规则,用于将构建好的目标安装到指定目录。
install(TARGETS MyApp DESTINATION /usr/local/bin)
2-5 配置Cmake
  • 我们为我们的MyTime库进行cmake的配置
  • 我们在MyTime文件夹下新建文件CMakeLists.txt
# 设置最低 CMake 版本
cmake_minimum_required(VERSION 3.10)# 设置工程名称
project(MyTime)# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 11)# 设置包含头文件的目录
include_directories(${CMAKE_SOURCE_DIR}/include)# 添加源文件目录
add_library(MyTime SHAREDsrc/MyTime.cpp
)# 如果你要构建可执行文件(例如测试程序),可以添加以下内容
# add_executable(MyTimeTest src/test.cpp)
# target_link_libraries(MyTimeTest MyTime)
2-5-1 静态库和动态库
  • 静态库(Static Library)和动态库(Dynamic Library)是两种常见的库类型,它们主要在 编译时运行时 的行为上有很大的不同。
特性静态库(Static Library)动态库(Dynamic Library)
链接时机编译时链接,生成的程序包含库的代码运行时链接,程序在运行时加载库
文件格式.a(Linux/macOS)或 .lib(Windows).so(Linux)、.dll(Windows)、.dylib(macOS)
文件大小可执行文件较大,因为库代码包含在内可执行文件较小,库共享但需单独管理
程序启动时间启动较快,不需要加载外部库文件启动时需要加载外部库文件,可能稍慢
依赖性不依赖外部库,完全独立依赖外部库,必须保证库文件存在
更新和维护更新库时必须重新编译所有依赖该库的程序更新库时无需重新编译程序,只要库接口不变
内存使用每个程序有一份库的副本多个程序共享一份库的副本
兼容性编译后不易出现兼容性问题可能出现库版本不匹配或丢失的问题
  • CMakeLists.txt中,我们可以制定参数SHARED用于配置动态库
add_library(MyTime SHAREDsrc/MyTime.cpp
)
  • 如果你希望配置静态库,可以把SHARED替换为STATIC
add_library(MyTime STATICsrc/MyTime.cpp
)
2-6 cmake …
  • 在配置好CMakeLists以后,我们就可以进行编译了,我们需要创建一个build文件夹,build文件夹通常用于存储编译中间和结果文件
mkdir build && cd build
  • 自此我们的库一共有如下结构

  • 请添加图片描述

  • 紧接着我们进行编译,再cd build进入build目录下以后,我们执行

cmake ..

请添加图片描述

  • 执行完上述指令后,我们可以看到build目录下多出了如下文件
  • 请添加图片描述
  1. CMakeCache.txt
    • 作用CMakeCache.txt 是 CMake 配置过程的缓存文件,保存了配置过程中产生的一些变量值(例如编译器路径、编译选项等)。当你重新运行 CMake 时,它会使用这个文件中的信息来加速配置过程。
    • 内容:文件中包含了各种设置项,如编译器路径、标志、包含的头文件路径、库路径等。
    • 解释:CMake 使用这个文件存储了用户和 CMake 检测到的配置结果,因此如果没有做任何修改,下一次执行 cmake 时会直接读取此文件,加速配置过程。
  2. CMakeFiles 目录
    • 作用CMakeFiles 目录包含 CMake 使用的内部文件,这些文件是 CMake 在生成构建系统时创建的。
    • 内容:该目录中包含了一些用于构建的中间文件和子目录,CMake 会根据它们来管理依赖关系和生成最终的构建文件。
    • 解释:通常你不需要直接操作这个目录,它是 CMake 在后台处理过程中需要用到的。
  3. cmake_install.cmake
    • 作用cmake_install.cmake 是 CMake 在配置时生成的安装脚本文件。当你执行 make install 或其他安装命令时,CMake 会使用这个文件来执行安装过程。
    • 内容:该文件定义了安装目标,如如何将生成的可执行文件、库文件、头文件等复制到指定的系统目录。
    • 解释:通常你不需要手动编辑这个文件,除非你有特定的安装需求。它是构建完成后安装过程的关键部分。
  4. Makefile
    • 作用Makefile 是一个标准的构建脚本文件,CMake 在执行配置时生成它。它包含了如何编译项目的详细信息和规则。
    • 内容:这个文件列出了所有的源文件、编译选项、依赖关系以及如何将它们编译和链接成最终的可执行文件。
    • 解释:当你运行 make 命令时,它会读取 Makefile,并根据其中的规则来执行编译任务。你可以使用 make 来编译项目,或者使用 make install 来安装项目。
  • 实际上我们使用cmake ..就是借助CMakeLists.txt的配置项去生成编译最关键的Makefile,用于辅助一会要进行的make,值得一提的是makefile是可以直接手写的,因此cmake ..这一步骤不是必要的,但是当构建大型项目的时候,手写makefile不方便,因此我们通常借助CMakeLists.txtcmake ..进行makefile的生成。
2-7 make -j4
  • CMakeLists.txt的辅助下,我们已经生成了makefile,因此我们正式开始编译
make -j4

请添加图片描述

  • **[ 50%] Building CXX object CMakeFiles/MyTime.dir/src/MyTime.cpp.o**这行表示正在编译 src/MyTime.cpp 这个源文件。
    • Building CXX object:说明正在编译 C++ 源文件,目标是生成对应的目标文件(.o 文件)。此时,CMake 会将 src/MyTime.cpp 文件编译成一个名为 MyTime.cpp.o 的目标文件(即中间文件)。
  • [100%] Linking CXX shared library libMyTime.so
    • 这行表示链接阶段,正在将之前生成的目标文件(如 .o 文件)链接成一个共享库(.so 文件)。
  • 编译完成后,我们看到build文件夹下生成了libMyTime.so的动态库文件
  • 有了编译生成的libMyTime.so和头文件MyTime.hpp,用户就可以在没有源文件的情况下使用你的代码了。

2-8 sudo make install 安装
  • 在用户完成编译后,手动链接动态库其实就可以直接使用你的库了,但是一般我们会使用sudo make install将编译生成的动态库和静态库移动到usr/local/lib中,这样方便我们直接调用。
sudo make install 
  • 然后不出意外你会抱错请添加图片描述

  • 那是因为如果你在项目的 MakefileCMakeLists.txt 中没有显式定义 install 规则,make install 命令就无法找到要执行的目标。install 是一种标准的构建目标,但它不是自动存在的,必须在 CMakeLists.txtMakefile 中显式声明。

  • 我们在CMakeLists.txt中添加

install(TARGETS MyTime DESTINATION /usr/local/lib)
install(FILES include/MyTime.hpp DESTINATION /usr/local/include)
  • 然我们需要重新生成构建文件并编译
cmake .. && make -j4
  • 然后执行
sudo make install

请添加图片描述

  • 我们可以在上述路径中找到这两个文件。这样用户在使用代码的时候,就不需要手动进行库链接了。

2-9 sudo ldconfig
sudo ldconfig
  • sudo ldconfig 是一个在 Linux 系统中用于更新和管理动态链接库缓存的命令。它会扫描系统上指定的库目录,并更新缓存,以便程序能够找到新的共享库(.so 文件)。通常在安装新库(尤其是共享库)时,执行 sudo ldconfig 以确保系统能够正确地识别和链接新库。
  1. 更新共享库缓存
    • Linux 系统使用动态链接库(shared libraries)来支持程序的运行。ldconfig 会扫描 /etc/ld.so.conf 文件中列出的目录以及 LD_LIBRARY_PATH 环境变量指定的路径,找到所有共享库,并将它们的信息保存到缓存中。这个缓存文件通常位于 /etc/ld.so.cache
    • 执行 ldconfig 后,程序在运行时就可以更快地查找需要的库文件,而不必每次都重新搜索整个文件系统。
  2. 让新安装的库生效
    • 当你使用 make install 或通过包管理工具(如 aptyum 等)安装共享库时,ldconfig 会帮助系统更新库的路径,使得程序能够找到新安装的库。特别是在安装自定义库时,执行 ldconfig 是很有必要的。
  3. ldld.so 正确工作
    • 在使用动态链接器加载共享库时(例如,运行一个依赖于共享库的程序),ldconfig 会确保这些库的路径信息被正确记录,并且链接器能够正确地找到并加载这些库。

3 使用自定义库

  • 通过上述过程,我们已经成功把MyTime写入下属路径了
-- Up-to-date: /usr/local/lib/MyTime.so
-- Installing: /usr/local/include/MyTime.hpp
  • 我们分别讲讲手动链接和Cmake链接
3-1 调用头文件<MyTime.hpp>
  • 我们创建一个新的cpp文件并调用我们的自定义库,testMyTime.cpp
#include <iostream>
#include <MyTime.hpp>int main() {mytime::MyTime timeObj;// 打印当前时间timeObj.printCurrentTime();// 打印当前日期(年月日)timeObj.printCurrentDate();// 获取当前时间的格式化字符串std::string formattedTime = timeObj.getFormattedTime();std::cout << "Formatted time: " << formattedTime << std::endl;// 计算两个时间点之间的差异double timeDiff = timeObj.calculateTimeDifference("2024-12-09 10:00:00", "2024-12-09 12:00:00");std::cout << "Time difference: " << timeDiff << " seconds" << std::endl;return 0;
}
3-2 手动链接
  • 由于MyTime是一个动态链接库(.so文件),你需要确保在编译时链接该库。可以通过以下两种方式之一来链接:
    • 编译时指定库路径:在编译时,使用-L选项指定库文件的路径(例如:/usr/local/lib/),并使用-l选项指定库名(不包括lib前缀和文件扩展名)。例如:
    g++ -o testMyTime testMyTime.cpp -L/usr/local/lib -lMyTime
    
    • 设置环境变量:你还可以通过设置LD_LIBRARY_PATH环境变量来告诉系统动态库的路径:
    export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
    
3-3 编译并测试
  • 编译
g++ -o testMyTime testMyTime.cpp -L/usr/local/bin -lMyTime

请添加图片描述

  • 可以看到我们中途在手动链接的时候出现了错误
./testMyTime: error while loading shared libraries: libMyTime.so: cannot open shared object file: No such file or directory
  • 顾名思义是由于找不到动态库.so导致的,我们只需要手动进行制定即可
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
  • 请添加图片描述

3-4 CMake配置
  • 上述手动配置对于小的项目还好,大的项目还是要借助CMake比较方便
  • 同理我们创建src目录,并把testMyTime.cpp移动到底下,并创建CMakeLists.txt
# 设置最低 CMake 版本
cmake_minimum_required(VERSION 3.10)# 设置工程名称
project(MyTime)# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 11)set(CMAKE_INSTALL_PREFIX "/usr/local")
# 设置包含头文件的目录
include_directories(${CMAKE_SOURCE_DIR}/include)# 添加源文件目录
add_library(MyTime SHAREDsrc/MyTime.cpp
)
install(TARGETS MyTime DESTINATION /usr/local/lib)
install(FILES include/MyTime.hpp DESTINATION /usr/local/include)
# 安装 CMake 配置文件 
install(FILES cmake/MyTimeConfig.cmake DESTINATION /usr/local/lib/cmake/myTime)# 如果你要构建可执行文件(例如测试程序),可以添加以下内容
# add_executable(MyTimeTest src/test.cpp)
# target_link_libraries(MyTimeTest MyTime)
  • 然后我们
 mkdir build && cd build cmake .. && make -j4
  • 可以看到我们的so被找到,程序正确执行了。

4 如何让 find_package(myTime REQUIRED) 可用

  • 果你希望通过 find_package(myTime REQUIRED) 来查找你的自定义库 MyTime,你需要为它提供一个合适的 CMake 配置文件或者模块。
4-1 配置myTimeConfig.cmake
  • 我们让MyTime 库提供一个 CMake 配置文件 myTimeConfig.cmake,这样你可以直接使用 find_package(myTime REQUIRED)
  • 我们回到刚刚的库MyTime文件夹中,创建文件夹cmake,并在底下创建myTimeConfig.cmake
# myTimeConfig.cmake# 设置 MyTime 库的位置
set(MYTIME_LIBRARY /usr/local/lib/libMyTime.so)# 设置 MyTime 头文件的路径
set(MYTIME_INCLUDE_DIR /usr/local/include)# 提供 MyTime 库和头文件路径
set(MyTime_INCLUDE_DIRS ${MYTIME_INCLUDE_DIR})
set(MyTime_LIBRARIES ${MYTIME_LIBRARY})# 设置 MyTime_FOUND 标志
set(MyTime_FOUND TRUE)
  • 然后我们需要在CMakeLists.txt 中设置安装规则
# 安装 CMake 配置文件
install(FILES cmake/myTimeConfig.cmake DESTINATION /usr/local/lib/cmake/myTime)

请添加图片描述

  • 然后在build目录中执行丝滑小连招
cd build 
cmake .. && make -j4
sudo make install
sudo ldconfig
  • 注意取名必须是 MyTimeConfig.cmake 或者 mytime-config.cmake
    请添加图片描述

  • 否则你在进行find_package(myTime REQUIRED)会出现报错请添加图片描述

4-2 find_package(myTime REQUIRED)
  • 我们打开TestMyTime文件夹下的CMakeLists.txt
# 设置最低的 CMake 版本要求
cmake_minimum_required(VERSION 3.10)# 设置项目名称
project(MyTimeProject)# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 11)# 查找 MyTime 库和其配置
find_package(MyTime REQUIRED)# 添加源文件
add_executable(testMyTime src/testMyTime.cpp)# 使用 MyTime 库链接目标
target_include_directories(testMyTime PRIVATE ${MyTime_INCLUDE_DIRS})
target_link_libraries(testMyTime PRIVATE ${MyTime_LIBRARIES})# 如果 MyTime 库是动态库,还需要设置运行时库路径
# 这样在运行时可以找到动态库
# 如果动态库不在默认的路径中,可以通过以下方式指定:
# set(CMAKE_INSTALL_RPATH /usr/local/lib)
  • 重新进行连招,我们就有
    请添加图片描述

5总结

  • 本文讲述了如何借助CMakeCMakeLists去创建自定义C++库,如何链接到.so并包含hpp去运行自定义库
  • 如有错误欢迎指出!!!
  • 感谢支持!!!

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

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

相关文章

前端编辑器JSON HTML等,vue2-ace-editor,vue3-ace-editor

与框架无关 vue2-ace-editor有问题&#xff0c;ace拿不到&#xff08;brace&#xff09; 一些组件都是基于ace-builds或者brace包装的 不如直接用下面的&#xff0c;不如直接使用下面的 <template><div ref"editor" class"json-editor"><…

SpringMVC框架——入门

目录 一、三层框架和MVC 1. 三层架构 2. MVC模型 二、SpringMVC入门案例 1. SpringMVC的概述 2. SpringMVC的入门程序 3. 入门案例的执行过程分析 4. RequestMapping注解 一、三层框架和MVC 1. 三层架构 开发服务器端程序&#xff0c;一般都基于两种形式&#xff0c;一…

港口智慧应急管理平台:应急先锋,港航卫士

在风云变幻的港航世界&#xff0c;安全是永恒的基石。港口智慧应急管理平台宛如一位无畏的先锋&#xff0c;以智能化、数字化、信息化为利刃&#xff0c;借助 AI 与大数据的神奇力量&#xff0c;为港口保驾护航。 传统应急管理往往在事故发生后被动响应&#xff0c;而此平台却…

Prime2_解法二:openssl解密凭据

Prime2_解法二&#xff1a;openssl解密凭据 本博客提供的所有信息仅供学习和研究目的&#xff0c;旨在提高读者的网络安全意识和技术能力。请在合法合规的前提下使用本文中提供的任何技术、方法或工具。如果您选择使用本博客中的任何信息进行非法活动&#xff0c;您将独自承担全…

恢复删除的文件:6个免费Windows电脑数据恢复软件

数据恢复软件可帮助您从众多存储设备中恢复损坏或删除的数据。您可以使用这些文件恢复软件来检索文件、文档、视频、图片等。这些应用程序支持多种标准文件格式&#xff0c;如 PNG、RTF、PDF、HTML、JPG、MP3 等。 经过超过 75 小时的研究&#xff0c;我分析了 25 最佳免费数据…

爬虫自动化之drissionpage+SwitchyOmega实现随时切换代理ip

本文介绍了如何使用DrizzlePage进行爬虫自动化,并重点讲解了首次启动时设置代理IP以及通过SwitchyOmega插件实现随时切换代理IP的方法。 安装一次,后面调用就不会再去安装了 下载地址:https://github.com/FelisCatus/SwitchyOmega/releases 这两个文件随便那个都可以,下载…

今天你学C++了吗?——C++中的类与对象(日期类的实现)——实践与知识的碰撞❤

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨ 个…

kafka进阶_4.kafka扩展

文章目录 一、Controller选举二、Kafka集成2.1、大数据应用场景2.1.1、Flume集成2.1.2、Spark集成2.1.3、Flink集成 2.2、Java应用场景(SpringBoot集成) 三、Kafka常见问题3.1、Kafka都有哪些组件&#xff1f;3.2、分区副本AR, ISR, OSR的含义&#xff1f;3.3、Producer 消息重…

Flink Python作业快速入门

Flink Python快速入门_实时计算 Flink版(Flink)-阿里云帮助中心 import argparse # 用于处理命令行参数和选项&#xff0c;使程序能够接收用户通过命令行传递的参数 import logging import sysfrom pyflink.common import WatermarkStrategy, Encoder, Types from pyflink.data…

前端(五)css属性

css属性 文章目录 css属性一、字体属性二、文本属性三、背景属性四、盒子模型 一、字体属性 font-weight&#xff1a;文字粗细&#xff0c;在100到900之间&#xff0c;normal(400),bord(700),inherit(继承父类) font-style&#xff1a;文字风格&#xff0c;normal表示正常(默认…

nginx反向代理(负载均衡)和tomcat介绍

nginx的代理 负载均衡 负载均衡的算法 负载均衡的架构 基于ip的七层代理 upstream模块要写在http模块中 七层代理的调用要写在location模块中 轮询 加权轮询 最小连接数 ip_Hash URL_HASH 基于域名的七层代理 配置主机 给其余客户机配置域名 给所有机器做域名映射 四层代理…

ansible自动化运维(二)playbook模式详解

相关文章ansible自动化运维&#xff08;一&#xff09;简介及清单,模块-CSDN博客ansible自动化运维&#xff08;三&#xff09;jinja2模板&&roles角色管理-CSDN博客ansible自动化运维&#xff08;四&#xff09;运维实战-CSDN博客 一.Ansible中的playbook模式 Playbo…

验证码功能实现

预览: 前端代码 让图片src 产生验证码图片的servlet <img src""></img> servlet代码 public void checkCode(HttpServletRequest request, HttpServletResponse response) throws IOException {ServletOutputStream os response.getOutputStream()…

Redis的哨兵机制

目录 1. 文章前言2. 基本概念2.1 主从复制的问题2.2 人工恢复主节点故障2.3 哨兵机制自动恢复主节点故障 3. 安装部署哨兵&#xff08;基于docker&#xff09;3.1 安装docker3.2 编排redis主从节点3.3 编排redis-sentinel节点 4. 重新选举5. 选举原理6. 总结 1. 文章前言 &…

Java:集合(List、Map、Set)

文章目录 1. Collection集合1-1. 迭代器遍历方式1-2. 通过for循环进行遍历1-3. forEach遍历 2. List集合2-1. ArrayList底层实现原理2-2. LinkedList底层实现原理 3. Set集合3-1. HashSet 底层实现3-2. LinkedHashSet 底层实现3-3. TreeSet 4. Collection集合->总结5. Map集…

什么是Apache日志?为什么Apache日志分析很重要?

Apache是全球最受欢迎的Web服务器软件&#xff0c;支持约30.2%的所有活跃网站。凭借其可靠性、灵活性和强大的功能&#xff0c;Apache数十年来一直是互联网的中坚力量。 一、Apache Web服务器的工作原理 Apache Web服务器的工作原理如下&#xff1a; 接收HTTP请求&#xff1…

【再谈设计模式】组合模式~层次构建的多面手

一、引言 在软件开发的世界里&#xff0c;我们经常面临着处理对象之间复杂关系的挑战。如何有效地表示对象的部分 - 整体层次结构&#xff0c;并且能够以一种统一的方式操作这些对象&#xff0c;是一个值得探讨的问题。组合模式&#xff08;Composite Pattern&#xff09;为我们…

论文翻译 | ChunkRAG: Novel LLM-Chunk Filtering Method for RAG Systems

摘要 使用大型语言模型&#xff08;LLM&#xff09;的检索-增强生成&#xff08;RAG&#xff09;系统经常由于检索不相关或松散相关的信息而生成不准确的响应。现有的在文档级别操作的方法无法有效地过滤掉此类内容。我们提出了LLM驱动的块过滤&#xff0c;ChunkRAG&#xff0…

Redis配置文件中 supervised指令

什么是Supervised&#xff1f; supervised模式允许Redis被外部进程管理器监控。通过这个选项&#xff0c;Redis能够在崩溃后自动重启&#xff0c;确保服务的高可用性。常见的进程管理器包括systemd和upstart。 开启方法 vim修改&#xff1a; sudo vi /etc/redis/redis.conf…

Android四大组件——Activity(二)

一、Activity之间传递消息 在&#xff08;一&#xff09;中&#xff0c;我们把数据作为独立的键值对进行传递&#xff0c;那么现在把多条数据打包成一个对象进行传递&#xff1a; 1.假设有一个User类的对象&#xff0c;我们先使用putExtra进行传递 activity_demo06.xml <…