diff --git a/app/docs/computer-science/cpp_backend/threadpool_cpp.mdx b/app/docs/computer-science/cpp_backend/Handwritten_pool_components/1_Handwritten_threadpool.md similarity index 94% rename from app/docs/computer-science/cpp_backend/threadpool_cpp.mdx rename to app/docs/computer-science/cpp_backend/Handwritten_pool_components/1_Handwritten_threadpool.md index 26c2ec7..740a8b2 100644 --- a/app/docs/computer-science/cpp_backend/threadpool_cpp.mdx +++ b/app/docs/computer-science/cpp_backend/Handwritten_pool_components/1_Handwritten_threadpool.md @@ -1,12 +1,24 @@ --- title: '手写线程池' description: "" -date: "2025-09-27" +date: "2025-09-29" tags: - tag-one --- + # 线程池 + +> 以下是调用单队列BlockingQueue的代码,使用双队列时将所有BlockingQueue改为BlockingQueuePro +> + +## 接口 +构造:用智能指针 unique_ptr 初始化阻塞队列,创建threads_num个线程并给每个绑定Worker函数开始执行task。(此时全部Worker阻塞在Pop()里) + +Post: 发布任务到线程池。 + +析构:通知唤醒所有消费者,没有任务就自己下班。与生产者没关系。 + ```cpp #pragma once @@ -38,8 +50,6 @@ private: }; ``` - - ```cpp #include "blockingqueue.h" #include @@ -55,6 +65,7 @@ ThreadPool::ThreadPool(int threads_num) { } // 停止线程池 +// 取消队列并唤醒所有阻塞线程 ThreadPool::~ThreadPool() { task_queue_->Cancel(); for(auto &worker : workers_) { @@ -72,6 +83,7 @@ void ThreadPool::Post(std::function task) { void ThreadPool::Worker() { while (true) { std::function task; + // 阻塞在Pop里实现 if (!task_queue_->Pop(task)) { break; } @@ -86,7 +98,7 @@ void ThreadPool::Worker() { **单队列维护:** 1. nonblock_(bool): 未阻塞标记。为true时,队列不会阻塞。初始(构造时)默认为阻塞状态。 -2. `queue_(std::queue)`: 底层存储容器。 +2. queue_(std::queue): 底层存储容器。 3. mutex_(std::mutex): 保证多线程操作安全的互斥锁。 4. not_empty_(std::condition_variable):用于线程前同步。 @@ -147,6 +159,8 @@ private: ``` # 阻塞队列(双队列版) +![画板](https://cdn.nlark.com/yuque/0/2025/jpeg/43055607/1759131100901-946e59ae-cd19-4546-aa9d-a9ee658f0b5a.jpeg) + 单队列中, 生产者和消费者都要竞争同一把锁。 双队列: @@ -211,6 +225,8 @@ private: }; ``` + + # Test 理论上双队列比单队列快,因为锁的竞争/碰撞少了。下面的实验结果也是如此: @@ -398,4 +414,3 @@ private: }; ``` - diff --git a/app/docs/computer-science/cpp_backend/Handwritten_pool_components/2_Handwritten_mempool1.md b/app/docs/computer-science/cpp_backend/Handwritten_pool_components/2_Handwritten_mempool1.md new file mode 100644 index 0000000..e2934f8 --- /dev/null +++ b/app/docs/computer-science/cpp_backend/Handwritten_pool_components/2_Handwritten_mempool1.md @@ -0,0 +1,127 @@ +--- +title: '手写定长内存池' +description: "" +date: "2025-09-29" +tags: + - tag-one +--- + + +# 手写定长内存池 + +## 设计图 +![画板](https://cdn.nlark.com/yuque/0/2025/jpeg/43055607/1758718719250-e6f52459-0f73-493b-8294-7b8f931da054.jpeg) + + + +## 代码部分 +### 结构体定义 +#### 管理内存池结构的结构体mempool_s +```c +typedef struct mempool_s { + int blocksize; // 每个内存块的size + int freecount; // 剩余空的内存块数量 + char *free_ptr; // 指向下一空内存块 + char *mem; // 整块内存的头指针 +} mempool_t; +``` + +### 对外接口 +#### memp_create: 内存池创建 +```c +int memp_create(mempool_t *m, int block_size) { + + if (!m) return -1; + + // 1. 初始化这两个简单的int + m->blocksize = block_size; + m->freecount = MEM_PAGE_SIZE / block_size; + + // 2. 开整个内存池的空间,顺便初始化m->mem + m->mem = (char *)malloc(MEM_PAGE_SIZE); + if (!m->mem) { // 开失败了(剩余空闲内存不够) + return -2; + } + // 将这个空间初始化一下 + memset(m->mem, 0, MEM_PAGE_SIZE); + + // 3. 初始化free_ptr + m->free_ptr = m->mem; + + // 依次初始化每个block里的"next->ptr" + int i = 0; + char *ptr = m->mem; + for (i = 0;i < m->freecount;i ++) { + + *(char **)ptr = ptr + block_size; + ptr = ptr + block_size; + } + // 最后一个block的"next_ptr"指向NULL + *(char **)ptr = NULL; + return 0; +} +``` + +#### memp_alloc: 分配block +```c +void *memp_alloc(mempool_t *m) { + // 满了 + if (!m || m->freecount == 0) return NULL; + // 1. 获取当前下一个空闲块,作为返回值 + void *ptr = m->free_ptr; + // 2. 更新free_ptr + m->free_ptr = *(char **)ptr; + // 3. 更新freecount + m->freecount --; + + return ptr; +} +``` + +#### memp_free: 删除指定block +```c +void memp_free(mempool_t *m, void *ptr) { + // 相当于 ptr->next = m->free_ptr; + // 头插法将要释放的block插到空闲block链表的头部,即空闲block链表又增加了一个 + *(char **)ptr = m->free_ptr; + // 更新free_ptr这一空闲block链表头指针 + m->free_ptr = (char *)ptr; + // 更新freecount + m->freecount ++; +} +``` + +#### memp_destory: 删除整个内存池 +```c +void memp_destory(mempool_t *m) { + if (!m) return ; + // 直接free内存池,因为内存池是整体malloc的,而不是一个一个block malloc的 + free(m->mem); +} +``` + + + +## 使用example +```c +int main() { + mempool_t m; + memp_create(&m, 32); + + void *p1 = memp_alloc(&m); + printf("memp_alloc : %p\n", p1); + + void *p2 = memp_alloc(&m); + printf("memp_alloc : %p\n", p2); + + void *p3 = memp_alloc(&m); + printf("memp_alloc : %p\n", p3); + + memp_free(&m, p2); +} +``` + +输出:可以看到每个block确实是32个字节 + +![](https://cdn.nlark.com/yuque/0/2025/png/43055607/1759069995143-4548da88-8c23-463e-b9e7-0f7d8978f03b.png) + diff --git a/app/docs/computer-science/cpp_backend/easy_compile/1_cpp_libs.md b/app/docs/computer-science/cpp_backend/easy_compile/1_cpp_libs.md new file mode 100644 index 0000000..87f3283 --- /dev/null +++ b/app/docs/computer-science/cpp_backend/easy_compile/1_cpp_libs.md @@ -0,0 +1,179 @@ +--- +title: 'linux/win上的c++库' +description: "" +date: "2025-09-29" +tags: + - tag-one +--- + +# linux/win上的c++库 + + +## 一、一个库长什么样? +每个库源码的格式都不一样。但都会有: + +1. .hpp/.h(位于include或根目录下) :用来链接的头文件,不能没有 +2. .cpp :实现头文件的逻辑,有的源码被称为头文件库(直接在头文件里实现了逻辑,没有.cpp实现,也不需要链接动态库或静态库)。 ++ 在 Linux 上:`XXX.so`(shared object动态库) 或 `XXX.a`(static静态库) ++ 在 Windows 上:`XXX.lib`(静态库) 或 `XXX.dll`(动态库) + +## 二、如何得到一个库? +> 非特殊标明,都是linux环境(具体为Ubuntu) +> + +### 下载别人写的库 +#### win环境 + 1. **手动从源码编译安装 :**下载或git clone一个zip/7z等压缩包,解压后得到源代码,编译得到所需库。 + 2. **使用对应语言的包管理工具**(c++: 如vcpkg,python: 如pip,java: 如maven)下载。如vcpkg会将包下载到vcpkg/installed/下(默认)。 + +> 简单介绍一下vcpkg +> +> + **vcpkg** 是微软开源的跨平台 C/C++ 包管理器。 +> + 支持 **Windows / Linux / macOS**,但最早是为 Windows 生态开发的。 +> + 主要用来解决:C/C++ 第三方库获取困难、编译配置复杂的问题。 +> + +#### linux环境 + 1. **从源码编译安装 ** + +> 有些库只能源码安装,比如最新版本的 gRPC、Protobuf。 +> +> 一般步骤是:clone -> make流程或者build.sh这种一键脚本。 +> + + 2. **使用系统自带的包管理器** + +> apt, yum, dnf等 +> + + 3. **使用对应语言的包管理工具** + +> vcpkg,conan等 +> + +### 自己写一个库 +写完.cpp和.h后,可选择将其编译成静态库或动态库。 + +#### 打包为静态库 +```bash +g++ -c mylib.cpp -o mylib.o +ar rcs my_static_lib.a mylib.o // 不建议 +// 事实上,打包时应在目标文件名前+lib。 +// 这不只是约定俗成。 +// 使用-lmylib时,链接器会自动加前缀去找,即找libmylib.a或.so。 +// 如果不加lib前缀,只能写清完整路径了(不能依靠链接器自己去找了) +ar rcs libmy_static_lib.a mylib.o // 建议 +``` + +#### 打包为动态库 +```bash +// 简单的项目,一步到位 +g++ -shared -o mylib.dll mylib.cpp +// 复杂的项目,先转为中间文件.o,再将.o文件转为mylib.dll +g++ -c -fPIC mylib.cpp -o mylib.o +g++ -shared -o my_dynamic_lib.dll mylib.o +``` + +-fPIC表示生成位置独立代码(Position Independent Code),这是动态库所需要的。 + +如上,我们就得到my_dynamic_lib.dll动态库和my_static_lib.a静态库了。 + +## 三、下载的库被放在了哪里 +#### python的库可能放在 ++ 项目根目录下的venv文件夹中(用venv创建的虚拟环境,也可以叫其他名,自己创建时命名) ++ conda目录下的某环境文件夹中(下载conda时指定一个存放环境的文件路径,所有由conda创建的虚拟环境都放在这里,环境名作为二级目录名) + +#### 前端的库可能放在 ++ 项目根目录下的node_modules中(有npm包管理创建和管理) + +#### Java的Spring项目(Maven包管理)可能放在 ++ 缓存到 ~/.m2/repository/ (本地仓库,win和linux都是) + +#### c++的库(linux环境) ++ 系统自带库:`/lib`, `/lib64`, `/usr/lib`, `/usr/lib64` ++ apt/yum/dnf/pacman 包管理器库:`/usr/lib/x86_64-linux-gnu`(库文件), `/usr/include`(头文件) ++ 自己编译:`/usr/local/lib`, `/usr/local/include` ++ 包管理器(vcpkg/conan):用户目录下的专用路径 + - vcpkg:`~/vcpkg/installed//lib` + - conan:`~/.conan/data///...` + - 自己放的:`~/lib`, `~/include` + +## 四、如何使用一个库呢? +> 使用库两个步骤,一是**链接头文件**,二是**链接库实现** +> + +### 链接 头文件 +> 不用-I(大写i)的时候默认寻找当前文件目录 +> + +1. 不改变库头文件位置,硬编码指定所有库的头文件路径在哪(每个头文件各指定一次) + +```bash +g++ -I path/to/s_lib1.h -I path/to/s_lib2.h ... -o output main.cpp +``` + +2. 整合头文件移到include目录下(注意如果是三方库,不建议移动,因为一般还有其他依赖项。自己的库,建议整合头文件),移到项目下的include目录下,然后指定头文件目录为include目录(只指定一次) + +```bash +g++ -I include/ -o output main.cpp +``` + +(注意指定头文件目录 + #include “path/to/lib.h”共同拼接成头文件完整路径) + +> 是否觉得纯命令行编译太麻烦太长,后面用CMake时写进CMakeList.txt里更方便 +> + +### 链接 库实现 +> **对于非纯头文件库,还需要连接库实现,其分为动态库和静态库** +> + +#### 链接动态库 +```bash +g++ myapp.cpp -L /path/to/library -l mylib +``` + +在运行时建议将 .dll动态库 放在与 可执行文件 同级目录下。因为其查找顺序为: + +1. win环境(优先级高到低) ++ 程序当前目录(通常是 `exe` 文件所在的目录)。 ++ Windows 系统目录(例如 `C:\Windows\System32`)。 ++ 当前用户的 `AppData` 文件夹。 +2. linux环境(优先级高到低) ++ 运行时指定的 `LD_LIBRARY_PATH` 环境变量 ++ 可执行文件的 `rpath` / `runpath` 设置 ++ 系统配置文件 `/etc/ld.so.cache` ++ ** 系统默认目录(最常用)** + - `/lib` + - `/usr/lib` + - `/usr/local/lib` + - `/lib64`(64 位系统) +这些是硬编码在动态链接器里的。 + +> 也可以1. 设置PATH环境变量。2. 在代码里使用LoadLibrary("D:\libs\mylib.dll"); 显示加载DLL +> + +#### 链接静态库 +```bash +g++ main.cpp -L /path/to/lib -l +``` + +注意:静态库在编译过程中,只会检查头文件的可用性,不会在编译阶段检查三方库是否存在或是否链接正确。这是因为静态库的编译阶段只生成目标文件(`.o` 文件)并打包成 `.a` 文件,而不会涉及链接阶段。因此,在一个c++程序实现时,可划分为以下三个阶段: + ++ **编译静态库时**: + - 只需要包含头文件(`include_directories`),不需要指定三方库的 `.a` 或 `.so` 文件。 ++ **编译主程序时**: + - 只需要包含静态库的头文件。 + - 不需要指定三方库的 `.a` 或 `.so` 文件。 ++ **主程序链接阶段**: + - 必须显式引入静态库和它依赖的三方库文件(如 `lthirdparty`)。 + + + +**四. 常见编译参数解释**(命令行编译方式): + +1. -I(这是个大写i) + 库的头文件目录,告诉编译器去哪找头文件(必须) +2. -L + 指定库文件所在目录 +3. -l(这是个小写L) + ,指定库名,搜索目标是.so或.a(Win的是.dll和.lib)(不需要写后缀,优先链接动态库) + +-L和-l(小写L)的区别是前者指定库所在路径,后者指定该路径下具体库名 + diff --git a/app/docs/computer-science/cpp_backend/easy_compile/2_base_gcc.md b/app/docs/computer-science/cpp_backend/easy_compile/2_base_gcc.md new file mode 100644 index 0000000..b4705f3 --- /dev/null +++ b/app/docs/computer-science/cpp_backend/easy_compile/2_base_gcc.md @@ -0,0 +1,152 @@ +--- +title: '基础gcc/g++' +description: "" +date: "2025-09-29" +tags: + - tag-one +--- + +# 基础gcc/g++ + + +## g++启蒙 +### 下载gcc/g++(Linux平台) +利用主流发行版的包管理器安装。 + +```cpp +sudo apt update +sudo apt install build-essential -y +// 检查版本 +gcc --version +g++ --version +``` + +```cpp +// CentOS / RHEL 7: +sudo yum groupinstall "Development Tools" -y +// CentOS Stream 8 / RHEL 8+ / Fedora: +sudo dnf groupinstall "Development Tools" -y +``` + +### 下载gcc/g++(windows平台) +下载(以下是几个网址)并写入环境变量(以便在任何工作目录下都可以使用gcc/g++命令(.exe可执行文件)) + +1. minGW + +[https://osdn.net/projects/mingw/downloads/68260/mingw-get-setup.exe/](https://www.mingw-w64.org/downloads/) + +[https://www.mingw-w64.org/downloads/](https://www.mingw-w64.org/downloads/) (推荐) + +**MinGW关于thread内置库踩了个坑:** + +纯MinGW 9.2.0版本使用thread时需要这几步: + +1. 需要去这个仓库下载(补充)几个头文件[https://github.com/meganz/mingw-std-threads](https://github.com/meganz/mingw-std-threads) + +![](https://cdn.nlark.com/yuque/0/2025/png/43055607/1759049314232-221dc93b-c560-4036-b049-db786935066f.png) + +将这几个头文件放入MinGW的include下。 + +1. 代码中引入头文件将include 改为include (这个点在上述仓库中的ReadMe里有写) +2. 如果是命令行编译,加上-D_WIN32_WINNT=0x0501这个参数,让编译器知道你正在针对 **Windows XP**(或更高版本)进行编译。(不知道是不是我的版本是win32的原因,也许mingw-win64版本不需要) + +这是我踩坑的版本 + +![](https://cdn.nlark.com/yuque/0/2025/png/43055607/1759049360158-4e44b580-0b41-4c64-8266-3a2bf893aa12.png) + +这是换成w64devkit + +![](https://cdn.nlark.com/yuque/0/2025/png/43055607/1759049365783-54107cad-7f77-497a-a679-1cc80f2c5095.png) + +而且纯MinGW在使用MinGW Installer(mingw-get.exe)时还要下载mingw32-make + +### g++ 基本使用example +创建一个文本文件(test.txt),改后缀为cpp(表明是一个cpp源文件),用记事本/VS code(等文本编辑器)打开,写入: + +```cpp +int main(){ + return 0; +} +``` + +保存关闭后。 + +在当前工作目录下(包含test.cpp的目录下)打开命令行(cmd),输入: + +```bash +g++ test.cpp +``` + +在当前工作目录下出现a.exe可执行文件(execute是执行的意思)。这是因为没有指定输出文件的名称,编译器默认命名输出的.exe文件为a + +下面在输出时命名输出文件(-o) + +```bash +g++ -o b test.cpp +``` + +执行后在当前工作目录下出现b.exe可执行文件。 + +-o参数表达output,后面跟着输出文件名。(上述命令中我写了输出文件名为b) + +可以更换参数与cpp文件的位置,如下(也可以随意更换参数与参数之间的位置) + +```bash +g++ test.cpp -o b +``` + +(但注意参数(-o)和后面的参数值(b)是一体的,不要拆开。) + +众所周知,c++/c的编译分为了四个阶段:(上述展示了“一步到位”的命令) + +预处理(Preprocessing)→ 编译(Compilation)→ 汇编(Assembly)→ 链接(Linking) + +| 阶段 | 输入文件 | 输出文件及扩展名 | 命令参数(缩写含义) | +| --- | --- | --- | --- | +| 预处理 | .cpp/.h | 预处理文件.i (Intermediate或Include) | -E (Expansion) | +| 编译 | .i | 汇编代码.s | -S (Source) | +| 汇编 | .s | 目标文件.o | -c (Compile) | +| 链接 | .o | 可执行文件.exe或无扩展 | 单g++ | + + +_**预处理阶段: .cpp → .i**_ +处理头文件包含(`#include`)、宏展开(`#define`)、条件编译(`#ifdef` 等)等指令 + +```bash +g++ -E test.cpp // 预处理后的代码(包含展开的宏和包含的头文件内容)直接显示在终端。 +g++ -E test.cpp -o preprocess.i // 生成b.i输出文件 +``` + +_**编译阶段: .i → .s**_ + +预处理后的代码 → 汇编代码 + +```bash +g++ -S preprocess.i -o assemble.s +``` + +_**汇编阶段: .s → .o**_ + +将汇编代码 → 机器代码,生成目标文件(通常不可直接执行)。 + +```bash +g++ -c assemble.s -o machine.o +g++ -c test.cpp // 也可以直接放入.cpp生成同名.o机器代码 +``` + +_**链接阶段: .o → .exe**_ + +将一个或多个目标文件与库文件链接 → 可执行文件。 + +```bash +g++ machine.o -o test +``` + +_**生成调试信息:**_ + +```bash +g++ -g test.cpp -o test // 比直接g++ test.cpp -o test 多生成调试信息 +``` + + + diff --git a/app/docs/computer-science/cpp_backend/easy_compile/3_Make.md b/app/docs/computer-science/cpp_backend/easy_compile/3_Make.md new file mode 100644 index 0000000..ca84a83 --- /dev/null +++ b/app/docs/computer-science/cpp_backend/easy_compile/3_Make.md @@ -0,0 +1,62 @@ +--- +title: 'Make编译' +description: "" +date: "2025-09-29" +tags: + - tag-one +--- + +# Make编译 + + +### **1. **`make`** 工作原理** +`make` 的工作方式基于文件的 **依赖关系** 和 **时间戳**,它通过以下步骤来管理构建过程: + +1. 读取 `Makefile`:`make` 通过读取 `Makefile` 文件来获取构建规则。 +2. **检查目标文件的修改时间**:`make` 会根据文件的修改时间来判断是否需要重新编译。例如,如果源文件 `source.c` 的修改时间晚于目标文件 `source.o`,`make` 会认为目标文件过时,重新执行相关的编译命令。 +3. **执行构建规则**:如果目标文件需要重新构建,`make` 会根据依赖关系和规则执行编译、链接等操作,直到最终目标文件(如可执行文件或库文件)完成。 + +### **2. **`Makefile`** 的基本结构** +`Makefile` 是 `make` 使用的配置文件,定义了构建规则、目标文件、依赖关系和命令。一个基本的 `Makefile` 通常包含以下几个部分: + +### **基本语法** ++ **目标(Target)**:要构建的文件(通常是目标文件或最终可执行文件)。 ++ **依赖(Dependency)**:目标文件所依赖的文件。如果依赖文件有更新,目标文件就需要重新构建。 ++ **命令(Command)**:用于生成目标的具体命令(如编译命令)。命令必须以 `TAB` 缩进。 + +```makefile +target: dependencies + command +``` + +### **3. **`Makefile`** 示例** +假设我们有一个简单的 C++ 项目,包含两个源文件 `main.cpp` 和 `utils.cpp`,它们生成目标文件 `main.o` 和 `utils.o`,并最终生成可执行文件 `myapp`。 + +```makefile +CC = g++ # 编译器设置为 g++ +CFLAGS = -Wall -g # 编译选项,-Wall 启用所有警告,-g 用于调试 + +# 目标文件 +OBJS = main.o utils.o # 目标文件 + +# 可执行文件 +TARGET = myapp + +# 默认目标 +all: $(TARGET) + +$(TARGET): $(OBJS) # 目标文件依赖关系 + $(CC) $(OBJS) -o $(TARGET) # 链接命令,生成可执行文件 + +main.o: main.cpp utils.h + $(CC) $(CFLAGS) -c main.cpp # 编译命令,生成 main.o + +utils.o: utils.cpp utils.h + $(CC) $(CFLAGS) -c utils.cpp # 编译命令,生成 utils.o + +clean: + rm -f $(OBJS) $(TARGET) # 清理中间文件和目标文件 +``` + +目标作为执行的 + diff --git a/app/docs/computer-science/cpp_backend/easy_compile/4_CMake.md b/app/docs/computer-science/cpp_backend/easy_compile/4_CMake.md new file mode 100644 index 0000000..4fbbc94 --- /dev/null +++ b/app/docs/computer-science/cpp_backend/easy_compile/4_CMake.md @@ -0,0 +1,163 @@ +--- +title: 'CMake' +description: "" +date: "2025-09-29" +tags: + - tag-one +--- + +# CMake + + +[https://juejin.cn/post/6844903557183832078](https://juejin.cn/post/6844903557183832078) 掘金的一个cmake教程 + +[https://zhuanlan.zhihu.com/p/97369704](https://zhuanlan.zhihu.com/p/97369704) 知乎的一个cmake教程 + +CMake用来编译一个c++项目,将其编译为一个可执行文件或动态\静态lib库 + +## cmake相关指令 +cmake —help查看更多信息 + +1. CMake需要一个CMakeList.txt或在执行CMake时将参数手动加入 +2. 在构建时建议在项目目录下新建一个build目录,进入build目录中执行(后续用—build参数可指定构建目录) + +```bash +cmake .. // + 需要额外添加的参数(未在CMakeList.txt中声明的,一般都在CMakeList.txt中声明了) +``` + +从命令可以看出,执行cmake的工作目录在build,是为了构建生成文件(包括构建过程中的中间文件和目标文件)不与项目目录文件混为一谈。而上一层指令(“..”)代表CMakeList.txt和源代码位于上一层即项目根目录中。 + +cmake分两步,第一步生成构建系统文件,第二部使用构建系统。对应关键指令: + +1. 执行预设 + +```bash +cmake --preset=default +``` + +build.ninja + +CMakeCache.txt + +CMakeFiles + +cmake_install.cmake + +vcpkg_installed + +vcpkg-manifest-install.log + +除了执行预设文件,还有原始的手动版本: + +声明-S配置源文件目录,-B配置build目录即可。比如 + +cmake -S . 代表设定当前目录为源目录(在cmake中有关键字**`CMAKE_SOURCE_DIR`**得到源目录) + +1. 执行构建 + +```bash +cmake --build vcpkg-build // 表示构建目标去vcpkg-build,这就不用进入build目录再cmake ..了 +``` + +build.ninja + +CMakeCache.txt + +CMakeFiles + +cmake_install.cmake + +libfeature-extraction-lib.a (比上面第一步多的一个生成的静态库) + +vcpkg_installed + +vcpkg-manifest-install.log + +## CMakeList.txt +### find_package方法深入理解 +find_package(某个库名 可选参数:CONFIG REQUIRED) + +find_package**不会提前查找所有 Boost 组件**,而是延迟到 `target_link_libraries` 或其他需要时才加载相应的组件。只是指定一个全局目标。 + +CONFIG + +REQUIRED + +find_package源代码 + +find_package寻找路径 + +### .cmake文件 +CMakeList.txt和.cmake文件的语法是一模一样的。准确来说,.cmake文件就像CMakeList.txt一个封装好的结构。使用以下方法引入: + +```makefile +include(cmakes/gtests_main.cmake) +include(cmakes/gtests_fe.cmake) +``` + +一般结合条件语句,比如是否开启单元测试: + +```makefile +option(BUILD_TESTS "Build tests" OFF) +if (BUILD_TESTS) + include(cmakes/gtests_main.cmake) + include(cmakes/gtests_fe.cmake) +endif () +``` + +1. 每个cmake语法中的执行参数都与g++参数对应,除非是内部使用的(类似于内部形参,方便编写用的) + +CMakeList.txt中几个必需的执行参数/配置项,及其对应的g++参数 + +1. 定义生成的目标是exe还是lib add_executable(my_program main.cpp) 或 add_library(my_library STATIC my_library.cpp) + +前者对应 + +```bash +g++ -o my_program main.cpp +``` + +后者对应 + +```bash +g++ -c my_library.cpp -o my_library.o +ar rcs libmy_library.a my_library.o +``` + +1. 链接依赖库 target_link_libraries(my_program gtest gtest_main) +2. 依赖的外部头文件和库的头文件include_directories(my_program PRIVATE /path/to/include) 或 target_include_directories(my_program PRIVATE /path/to/include) +3. 设置构建类型 CMAKE_BUILD_TYPE + +内部使用,cmake专属: + +1. 指定CMake最低版本 cmake_minimum_required(VERSION 3.10) +2. 设置项目名称和版本 project(项目名称 VERSION 1.0 LANGUAGES CXX) +3. 找包 find_package(包名:与文件名同名 CONFIG REQUIRED) + +找包路径顺序(来自chatGPT,期待后续改正): + +**默认查找路径**`CMake` 会按照以下顺序查找 `spdlog` 的配置文件: + ++ 在环境变量 `CMAKE_PREFIX_PATH` 指定的路径下查找。 ++ 在系统默认的安装路径(如 `/usr/lib/cmake`、`/usr/local/lib/cmake`)中查找。 ++ 在 `CMAKE_INSTALL_PREFIX` 指定的路径下查找。 ++ 如果项目使用包管理工具(如 `vcpkg`),在工具链文件指定的路径下查找。 + +(默认是[vcpkg-root]/installed/[triplet]/**share/**[package]/[package]Config.cmake) + +引申一下: + +包的头文件会安装到:[vcpkg-root]/installed/[triplet]/include/ + +库文件会安装到:[vcpkg-root]/installed/[triplet]/lib/ + +1. 如果不使用CMakeList.txt, 命令行中所必需的参数\配置 + 1. 指定源代码路径(必须) + 2. 指定构建类型-D CMAKE_BUILD_TYPE=Release或Debug + 3. 选择构建生成器 + 1. -G "Unix Makefiles” // unix的一个make生成器 + 2. -G “MinGW Makefiles” // win的一个make生成器 + 3. -G “Ninja” // Ninja生成器,需要额外安装 + 4. -G "Visual Studio 16 2019” // Visual Studio 生成器2019版本 + 5. -G "Xcode” // 苹果的Xcode + diff --git a/app/docs/computer-science/cpp_backend/easy_compile/5_vcpkg.md b/app/docs/computer-science/cpp_backend/easy_compile/5_vcpkg.md new file mode 100644 index 0000000..ad2f1fe --- /dev/null +++ b/app/docs/computer-science/cpp_backend/easy_compile/5_vcpkg.md @@ -0,0 +1,147 @@ +--- +title: 'vcpkg包管理器' +description: "" +date: "2025-09-29" +tags: + - tag-one +--- + +# vcpkg包管理器 + + +vcpkg分经典Classic模式和清单Manifest模式 + +经典模式是直接vcpkg install下载包,然后在CMakeList.txt中指定include目录和库文件目录引用 + +清单模式是编写vcpkg.json清单文件 + +## 配置 +下载并配置环境变量: + +```bash +git clone +cd vcpkg/ +./bootstrap-vcpkg.sh +``` + +之后建议加入环境变量 + +```bash +echo 'export VCPKG_ROOT="$HOME/vcpkg"' >> ~/.bashrc +// 我是直接clone到了~根目录下,具体换成自己clone到的目录 +``` + +1. 在项目中添加vcpkg + +```bash +// 创建清单文件vcpkg.json和vcpkg-configuration.json并初始化 +vcpkg new --application +// 添加XXX库(比如fmt)依赖项,将在vcpkg.json中增加一个dependencies:fmt +vcpkg add port fmt // 不会检查正确与否,只是单纯修改vcpkg.json, 与手动修改vcpkg.json一样 +``` + +vcpkg 读取清单文件(vcpkg.json),以了解要安装和与 CMake 集成的依赖项,从而提供项目所需的依赖项。 + +1. 在CMakeList.txt中添加库相关信息 + +```bash +find_package(fmt CONFIG REQUIRED) +target_link_libraries(HelloWorld PRIVATE fmt::fmt) +``` + +1. 运行CMake配置 + +创建CMakePresets.json预设文件,设置工具链(CMAKE_TOOLCHAIN_FILE)使用vcpkg自定义工具链时,CMake 可以自动链接 vcpkg 安装的库 + +```json +{ + "version": 2, + "configurePresets": [ + { + "name": "vcpkg", + "generator": "Ninja", //(或"MinGW Makefiles")这里就是上述用的-G "" + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" + // 其中{VCPKG_ROOT}可在CMakeUserPresets.json中设置 + // 也可配置成环境变量 + } + } + ] +} +``` + +```json +{ + "version": 2, + "configurePresets": [ + { + "name": "default", + "inherits": "vcpkg", + "environment": { + "VCPKG_ROOT": "" + } + } + ] + } +``` + +## 使用 +1. 打包自己的库并用vcpkg管理(注册进vcpkg) +2. 下载使用vcpkg管理的库(三方库) + +**创建自己的vcpkg注册表** + +有别于官方git注册表https://github.com/microsoft/vcpkg + +也可以建立自己的git注册表(除了git注册表这种形式,还有文件系统注册表,这里先说git注册表) + +**什么是vcpkg注册表?** + +注册表里放库的一些信息(但不是库的源码,如果是源码的话,那太臃肿了)。 + +**做什么用?** + +在vcpkg-configuration.json中使用,格式如下 + +```bash +{ + "default-registry": { + "kind": "git", + "repository": "https://github.com/microsoft/vcpkg.git", + "baseline": "234534dfvbsdvw43434f" + }, // 默认仓库必须有,加载官方注册表(上千个库) + "registries": [ // 这里是自定义注册表 + { + "kind": "git", + "repository": "https://github.com/xxx/xxx.git", // 仓库地址 + "baseline": "d3e4723c1224t34fsdsvd0e4c2615f6d75", // 版本基线 + "reference": "main", // 分支名 + "packages": [ + "datastax-cpp-driver", // 注册表中包含的库名 + "cpp-common", + "ppconsul", + "leveldb", + "grpc", + "polaris-cpp" + ] + }, + { + "kind": "git", + "repository": "git@gitlab.xxxxx/xxxx.git", + "baseline": "15efa5017d9a3esdvsdvsdvwecs1d316", + "reference": "main", + "packages": [ + "feature-generation-lib", + "nps-client-brpc", + "brpc", + "cybercore-sdk-cpp", + "opentelemetry-cpp", + "mv-protocols-cpp", + "feature-extraction-lib" + ] + } + ] +} +``` +