IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 学C++从Cmake学起 -> 正文阅读

[C++知识库]学C++从Cmake学起


0:46:55

1.构建系统make

2.构建系统CMake

  • 为了解决make的以上问题,跨平台的Cmake应运而生
  • 只需要写一份CmakeLists.txt,它就能在调用时生成当前系统所支持的构建系统
  • CMake可以自动检测源文件和头文件之间的依赖关系,导出到Makefile里面
  • CMake具有相对高级的语法,内置的函数能够处理configure,install等常见需求
  • CMake可以自动检测当前的编译器,需要添加哪些flag,必须OpenMP,只需要在CMakeLists,txt中指明target_link_libraries(a.out OpenMP::OpenMP_CXX)即可

3.CMake的常见命令行调用

生成构建文件Makefile
表示输出makefile文件的目录
表示试用clang++作为编译器
cmake -Bbuild  -DCMAKE_CXX_COMPILER=clang++
cmake -Bbuild  -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_STANDARD=17

构建可执行文件,编译
cd build;make
或者make -C build,优点是:跨平台
或者cmake --build build

4.CMakeLists.txt常见语法

add_executable(输出的可执行文件 输入的多个源文件)
add_executable(a.out main.cpp hello.cpp)

add_library(test STATIC source1.cpp source2.cpp)  # 生成静态库 libtest.a
add_library(test SHARED source1.cpp source2.cpp)  # 生成动态库 libtest.so

创建库以后,要在某个可执行文件中使用该库,只需要:
target_link_libraries(myexec PUBLIC test)  # 为 myexec 链接刚刚制作的库 libtest.a

5.为什么需要库library?

有时候我们会有多个可执行文件,他们之间用到的某些功能是相同的,我们想把这些共用的功能做成一个库,方便大家一起共享.

  • 静态库和动态库都是多个.o文件的打包
  • 库中的函数可以被可执行文件调用,也可以被其他库文件调用。
  • 库文件又分为静态库文件和动态库文件。
  • 其中静态库相当于直接把代码插入到生成的可执行文件中,会导致体积变大,但是只需要一个文件即可运行.
  • 而动态库则只在生成的可执行文件中生成“插桩”函数,当可执行文件被加载时会读取指定目录中的.dll文件,加载到内存中空闲的位置,并且替换相应的“ 插桩”指向的地址为加载后的地址,这个过程称为重定向。
    这样以后函数被调用就会跳转到动态加载的地址去。
    在这里插入图片描述
  • eg:以course/01/05/hello.cpp为例:objdump -D ./build/a.out |less
    插装函数puts如下,可跳转到libc.so库中,plt就是插装函数在这里插入图片描述

Windows:

  • 可执行文件同目录,其次是环境变量%PATH%

Linux:

  • ELF格式可执行文件的RPATH,其次是/usr/lib等

6.CMake 中的静态库与动态库

CMake 除了 add_executable 可以生成可执行文件外,还可以通过 add_library 生成库文件。

add_library 的语法与 add_executable 大致相同,除了他需要指定是动态库还是静态库:
add_library(test STATIC source1.cpp source2.cpp)  # 生成静态库 libtest.a
add_library(test SHARED source1.cpp source2.cpp)  # 生成动态库 libtest.so
  • 动态库有很多坑,特别是 Windows 环境下,初学者自己创建库时,建议使用静态库。
  • 创建库以后,要在某个可执行文件中使用该库,只需要:
target_link_libraries(myexec PUBLIC test)  # 为 myexec 链接刚刚制作的库 libtest.a
其中 PUBLIC 的含义稍后会说明(CMake 中有很多这样的大写修饰符)
  • eg:course/01/06
    动态库必须在可执行文件的同目录或者系统目录下,可执行程序才能运行;
    在这里插入图片描述
    在这里插入图片描述
    静态库则无所谓,静态库是将实现hello()直接放在a.out中,它不需要动态查找,但是可执行文件会变大
    objdump -D ./build/a.out |less
    在这里插入图片描述

7.为什么 C++ 需要声明

在多文件编译章中,说到了需要在 main.cpp 声明 hello() 才能引用。为什么?

  • 因为需要知道函数的参数和返回值类型:这样才能支持重载,隐式类型转换等特性。
    例如 show(3),如果声明了 void show(float x),那么编译器知道把 3 转换成 3.0f 才能调用。
  • 让编译器知道 hello 这个名字是一个函数,不是一个变量或者类的名字:这样当我写下 hello() 的时候,他知道我是想调用 hello 这个函数,而不是创建一个叫 hello 的类的对象。

其实,C++ 是一种强烈依赖上下文信息的编程语言,

  • 举个例子:
    vector < MyClass > a; // 声明一个由 MyClass 组成的数组
    如果编译器不知道 vector 是个模板类,那他完全可以把 vector 看做一个变量名,把 < 解释为小于号,从而理解成判断‘vector’这个变量的值是否小于‘MyClass’这个变量的值。
    正因如此,我们常常可以在 C++ 代码中看见这样的写法:typename decay::type
    因为 T 是不确定的,导致编译器无法确定 decay 的 type 是一个类型,还是一个值。
    因此用 typename 修饰来让编译器确信这是一个类型名……
  • eg:course/01/06
    在这里插入图片描述

8.为什么需要头文件?

问题:如果能只写一遍,然后自动插入到需要使用hello()的地方

  • 问题如下所示:
    在这里插入图片描述

头文件 - 批量插入几行代码的硬核方式

  • 把 hello() 的声明放到单独一个文件 hello.h 里,然后在需要用到 hello() 这个声明的地方,打上一个记号,#include “hello.h”。
  • 这个编译前替换的步骤逐渐变成编译器的了一部分,称为预处理阶段,#define 定义的宏也是这个阶段处理的。
  • 此外,在实现的文件 hello.cpp 中导入声明的文件 hello.h 是个好习惯,可以保证当 hello.cpp 被修改时,比如改成 hello(int),编译器能够发现 hello.h 声明的 hello() 和定义的 hello(int) 不一样,避免“沉默的错误”。(对支持重载的 C++ 不奏效)
  • 示例图如下:
  • eg:course/01/07
    在这里插入图片描述

递归地使用头文件

  • 在 C++ 中常常用到很多的类,和函数一样,类的声明也会被放到头文件中。
  • 有时候我们的函数声明需要使用到某些类,就需要用到声明了该类的头文件,像这样递归地 #include 即可:
  • course/01/08
    在这里插入图片描述
  • 解决如果多个头文件都引用了 MyClass.h,那么 MyClass 会被重复定义两遍的方法:
    在头文件前面加上一行:#pragma once,这样当预处理器第二次读到同一个文件时,就会自动跳过
  • eg:course/01/09
    在这里插入图片描述

9.CMake 中的子模块

复杂的工程中,我们需要划分子模块,通常一个库一个目录,比如:

  • 这里我们把 hellolib 库的东西移到 hellolib 文件夹下了,里面的 CMakeLists.txt 定义了 hellolib 的生成规则。
  • 要在根目录使用他,可以用 CMake 的 add_subdirectory 添加子目录,子目录也包含一个 CMakeLists.txt,其中定义的库在 add_subdirectory 之后就可以在外面使用。
  • 子目录的 CMakeLists.txt 里路径名(比如 hello.cpp)都是相对路径
  • eg:course/01/10
    在这里插入图片描述
    在这里插入图片描述

10.子模块的头文件如何处理

因为 hello.h 被移到了 hellolib 子文件夹里,因此 main.cpp 里也要改成

  • eg:course/01/10
    在这里插入图片描述
  • 如果要避免修改代码,我们可以通过 target_include_directories 指定a.out 的头文件搜索目录:(其中第一个 hellolib 是库名,第二个是目录)
add_executable(a.out main.cpp)
target_link_libraries(a.out PUBLIC hellolib)
target_include_directories(a.out PUBLIC hellolib)
  • 这样甚至可以用 <hello.h> 来引用这个头文件了,因为通过 target_include_directories 指定的路径会被视为与系统路径等价:
    在这里插入图片描述

  • 总结:course/01/10/main.cpp

#include <cstdio>

//方式1
#include "hellolib/hello.h"

//方式2
//#include "hello.h"  or #include <hello.h>
//CMake中增加# target_include_directories(a.out PUBLIC hellolib)

int main() {
    hello();
    return 0;
}
  • 如果另一个 b.out 也需要用 hellolib 这个库,难道也得再指定一遍搜索路径吗?
    不需要,其实我们只需要定义 hellolib 的头文件搜索路径,引用他的可执行文件 CMake 会自动添加这个路径:
  • eg:course/01/11
add_library(hellolib STATIC hello.cpp)
target_include_directories(hellolib PUBLIC .)

  • 此外,如果不希望让引用 hellolib 的可执行文件自动添加这个路径,把 PUBLIC 改成 PRIVATE 即可。
    这就是他们的用途:决定一个属性要不要在被 link 的时候传播(传染病,去传染别人)。

11.目标的一些其他选项

除了头文件搜索目录以外,还有这些选项,PUBLIC 和 PRIVATE 对他们同理:

target_include_directories(myapp PUBLIC /usr/include/eigen3)  # 添加头文件搜索目录
target_link_libraries(myapp PUBLIC hellolib)                               # 添加要链接的库
target_add_definitions(myapp PUBLIC MY_MACRO=1)             # 添加一个宏定义,等价于#define MY_MACRO 1
target_add_definitions(myapp PUBLIC -DMY_MACRO=1)         # 与 MY_MACRO=1 等价
target_compile_options(myapp PUBLIC -fopenmp)                     # 添加编译器命令行选项
target_sources(myapp PUBLIC hello.cpp other.cpp)                    # 添加要编译的源文件

以及可以通过下列指令(不推荐使用),把选项加到所有接下来的目标去:

include_directories(/opt/cuda/include)     # 给所有的目标添加头文件搜索目录
link_directories(/opt/cuda)                       # 给所有的目标添加库文件的搜索路径
add_definitions(MY_MACRO=1)             #给所有的目标添加一个宏定义
add_compile_options(-fopenmp)             #给所有的目标添加编译器命令行选项

12.第三方库 - 作为纯头文件引入

有时候我们不满足于 C++ 标准库的功能,难免会用到一些第三方库。

  • 最友好的一类库莫过于纯头文件库了,这里是一些好用的 header-only 库:
nothings/stb - 大名鼎鼎的 stb_image 系列,涵盖图像,声音,字体等,只需单头文件!
Neargye/magic_enum - 枚举类型的反射,如枚举转字符串等(实现方式很巧妙)
g-truc/glm - 模仿 GLSL 语法的数学矢量/矩阵库(附带一些常用函数,随机数生成等)
Tencent/rapidjson - 单纯的 JSON 库,甚至没依赖 STL(可定制性高,工程美学经典)
ericniebler/range-v3 - C++20 ranges 库就是受到他启发(完全是头文件组成)
fmtlib/fmt - 格式化库,提供 std::format 的替代品(需要 -DFMT_HEADER_ONLY)
gabime/spdlog - 能适配控制台,安卓等多后端的日志库(和 fmt 冲突!)
  • 用法:只需要把他们的 include 目录或头文件下载下来,然后 include_directories(spdlog/include) 即可。
  • 缺点:函数直接实现在头文件里,没有提前编译,从而需要重复编译同样内容,编译时间长。
  • eg:course/01/12
    在这里插入图片描述

13.第三方库 - 作为子模块引入

第二友好的方式则是作为 CMake 子模块引入,也就是通过 add_subdirectory。

  • 方法就是把那个项目(以fmt为例)的源码放到你工程的根目录:
  • 这些库能够很好地支持作为子模块引入:
fmtlib/fmt - 格式化库,提供 std::format 的替代品
gabime/spdlog - 能适配控制台,安卓等多后端的日志库
ericniebler/range-v3 - C++20 ranges 库就是受到他启发
g-truc/glm - 模仿 GLSL 语法的数学矢量/矩阵库
abseil/abseil-cpp - 旨在补充标准库没有的常用功能
bombela/backward-cpp - 实现了 C++ 的堆栈回溯便于调试
google/googletest - 谷歌单元测试框架
google/benchmark - 谷歌性能评估框架
glfw/glfw - OpenGL 窗口和上下文管理
libigl/libigl - 各种图形学算法大合集

  • eg:course/01/13
    在这里插入图片描述
    fmt - 使用这个神奇的格式化库
    在这里插入图片描述
    在这里插入图片描述

14.CMake - 引用系统中预安装的第三方库

可以通过 find_package 命令寻找系统中的包/库:

find_package(fmt REQUIRED)
target_link_libraries(myexec PUBLIC fmt::fmt)
  • 为什么是 fmt::fmt 而不是简单的 fmt?
现代 CMake 认为一个包 (package) 可以提供多个库,又称组件 (components),比如 TBB 这个包,就包含了 tbb, tbbmalloc, tbbmalloc_proxy 这三个组件。

因此为避免冲突,每个包都享有一个独立的名字空间,以 :: 的分割(和 C++ 还挺像的)。
你可以指定要用哪几个组件:
find_package(TBB REQUIRED COMPONENTS tbb tbbmalloc REQUIRED)
target_link_libraries(myexec PUBLIC TBB::tbb TBB::tbbmalloc)

  • 常用 package 列表
fmt::fmt
spdlog::spdlog
range-v3::range-v3
TBB::tbb
OpenVDB::openvdb
Boost::iostreams
Eigen3::Eigen
OpenMP::OpenMP_CXX
  • 不同的包之间常常有着依赖关系,而包管理器的作者为 find_package 编写的脚本(例如/usr/lib/cmake/TBB/TBBConfig.cmake)能够自动查找所有依赖,并利用刚刚提到的 PUBLIC PRIVATE 正确处理依赖项,比如如果你引用了 OpenVDB::openvdb 那么 TBB::tbb 也会被自动引用。
  • 其他包的引用格式和文档参考

15.安装第三方库 - 包管理器

Linux 可以用系统自带的包管理器(如 apt)安装 C++ 包。

pacman -S fmt
  • Windows 则没有自带的包管理器。因此可以用跨平台的 vcpkg:https://github.com/microsoft/vcpkg
  • 使用方法:下载 vcpkg 的源码,放到你的项目根目录,像这样:
    在这里插入图片描述
set -e

git clone https://github.com/microsoft/vcpkg.git --depth=1

rm -rf build
cmake -B build -DCMAKE_TOOLCHAIN_FILE="$PWD/vcpkg/scripts/buildsystems/vcpkg.cmake"
cmake --build build --target a.out

cd vcpkg
sh bootstrap-vcpkg.sh
./vcpkg install fmt:x64-linux
cd ..

build/a.out
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-03 10:32:27  更:2022-07-03 10:35:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/11 22:54:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码