
Intro
同样是复习,上一篇中我们讲过几个API
CMAKE_CXX_STANDARDCMAKE_CXX_STANDARD_REQUIREDsetproject(NAME VERSION x.x.x)configure_file()target_include_directories()message
你能否回忆起来呢?回忆不起来的同学们请回顾上一篇内容哦~
这一章我们直接拿官方的代码过来做初始代码Code。
Q? & A!
- 当我拥有一份通用库代码需要调用时,要如何加入到构建过程中以及使用它呢?
- 本篇余下部分将使用CMake提供的
add_library,add_subdirectory等函式进行演示与讲解。
- 本篇余下部分将使用CMake提供的
- 本文涉及到链接,链接是什么有什么作用呢?
- 简而言之如果A文件需要B文件,那么编译时,应该把B文件链接到A文件中,形成一个整体的上下文,这样用起来才不会出错,如果没有链接成完整的上下文,那么将会报出错误
某某没有被定义,某某找不到,空指针,大概率连编译过程都跑不通啦
- 简而言之如果A文件需要B文件,那么编译时,应该把B文件链接到A文件中,形成一个整体的上下文,这样用起来才不会出错,如果没有链接成完整的上下文,那么将会报出错误
开始
Step1: 看代码
- 目录结构

MathFunctions是我们抽象出来的Lib,稍后修改完毕要在tutorial.cxx中引用的。
- MathFunctions/CMakeLists.txt
MathFunctions这个库也是一个独立可用的,所以也需要有CMakeLists.txt来描述项目。
- MathFunctions/mysqrt.h
// 声明自定义平方根函数的头文件。
// 编译器指令,用于防止头文件被重复包含。它确保头文件内容只会被编译一次。
#pragma once
// 定义了一个命名空间 mathfunctions,用于组织代码,避免命名冲突。
namespace mathfunctions
{
// namespace detail:在 mathfunctions 命名空间内定义了一个子命名空间 detail,通常用于表示内部实现细节。
namespace detail
{
// 在 detail 命名空间内声明了一个名为 mysqrt 的函数,接受一个 double 类型的参数 x,返回一个 double 类型的结果。
double mysqrt(double x);
}
}
- MathFunctions/mysqrt.cxx
这个cpp实现了mysqrt.h的定义,主要作用是计算平方根。
#include "mysqrt.h"
#include <iostream>
namespace mathfunctions
{
namespace detail
{
double mysqrt(double x)
{
if (x <= 0)
{
return 0;
}
double result = x;
for (int i = 0; i < 10; ++i)
{
if (result <= 0)
{
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
}
return result;
}
}
}
- MathFunctions/MathFunctions.h
// 编译器指令,用于防止头文件被重复包含。它确保头文件内容只会被编译一次。
#pragma once
// 命名空间,用于防止命名冲突。
namespace mathfunctions
{
// 计算平方根
double sqrt(double x);
}
- MathFunctions/MathFunctions.cxx
#include "MathFunctions.h"
// 引入同级目录下的mysqrt.h平方根函数的实现
#include "mysqrt.h"
// 实现MathFunctions.h库的函数
namespace mathfunctions
{
double sqrt(double x)
{
// 调用mysqrt.h中的mysqrt函数
return detail::mysqrt(x);
}
}
- /tutorial.cxx
- /TutorialConfig.h.in
- /CMakeLists.txt
这三个文件和前一章的内容大致相同所以不在阐述
接下来我们要修改代码将tutorial.cxx中的
const double outputValue = sqrt(inputValue);- 替换为
const double outputValue = mathfunctions::sqrt(inputValue) - 并保证编译正常运行结果正确
Step2: 链接可执行文件和库代码
- 在
MathFunctions/CMakeLists.txt中使用add_library来描述库。
# 用于创建库的命令。
# 定义一个新的库目标,并指定构成该库的源文件。
# 语法:add_library(<name> [<type>] [EXCLUDE_FROM_ALL] <sources>...)
# 参数:
# <name>:库的名称。
# <type>(可选):库的类型,可以是以下之一:
# STATIC:静态库,编译时将所有代码打包到一个库文件中。
# SHARED:动态库,运行时链接。
# MODULE:模块库,通常用于插件,运行时动态加载。
# 如果没有指定 <type>,则默认为 STATIC 或 SHARED,具体取决于 BUILD_SHARED_LIBS 变量的值。
add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)
- 修改根目录
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(Tutorial VERSION 1.0)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
configure_file(TutorialConfig.h.in TutorialConfig.h)
add_executable(Tutorial tutorial.cxx)
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")
# 向构建添加一个子目录。
# add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL] [SYSTEM])
# source_dir 指定源 CMakeLists.txt 和代码文件所在的目录。
# binary_dir 指定放置输出文件的目录。
add_subdirectory(MathFunctions)
# 指定目标链接库的命令。它将一个或多个库链接到指定的目标(如可执行文件或另一个库)。
# target_link_libraries(<target> ... <item>... ...)
# Tutorial 是目标名称。
# MathFunctions 是要链接的库名称。
# PUBLIC 修饰符表示 MathFunctions 库对 Tutorial 目标自身和依赖于 Tutorial 的其他目标都是可见的。
# 除了PUBLIC还有PRIVATE:链接库仅对目标自身可见。
# 还有INTERFACE:链接库仅对依赖于该目标的其他目标可见,目标自身不可见。
target_link_libraries(Tutorial PUBLIC MathFunctions)
# 扫一下头文件。
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/MathFunctions"
)
Step3: 修改代码,使用库代码
使用我们自己的计算平方根库来替换cmath中提供的平方根函数。
// 注释掉数学库的引入
// #include <cmath>
#include <iostream>
#include <string>
#include "TutorialConfig.h"
// 引入MathFunctions.h库
#include "MathFunctions.h"
int main(int argc, char *argv[])
{
if (argc < 2)
{
std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
<< Tutorial_VERSION_MINOR << std::endl;
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}
const double inputValue = std::stod(argv[1]);
// 使用我们自己的库替换标准库的sqrt函数
// const double outputValue = sqrt(inputValue);
const double outputValue = mathfunctions::sqrt(inputValue);
std::cout << "The square root of " << inputValue << " is " << outputValue
<< std::endl;
return 0;
}
- 打包测试

Step4: 可选平方根实现
- 这里我们还需要补充几个函数,这些函数将CMake编译一些库时可以配置成可选的。
- 关于
if,option,target_compile_definitions,target_link_libraries。
- MathFunctions/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
# 用于创建库的命令。
# 定义一个新的库目标,并指定构成该库的源文件。
# 语法:add_library(<name> [<type>] [EXCLUDE_FROM_ALL] <sources>...)
# 参数:
# <name>:库的名称。
# <type>(可选):库的类型,可以是以下之一:
# STATIC:静态库,编译时将所有代码打包到一个库文件中。
# SHARED:动态库,运行时链接。
# MODULE:模块库,通常用于插件,运行时动态加载。
# 如果没有指定 <type>,则默认为 STATIC 或 SHARED,具体取决于 BUILD_SHARED_LIBS 变量的值。
# add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)
add_library(MathFunctions MathFunctions.cxx)
# 定义变量,提供一个布尔值,并附带上一个描述性的字符串。
# option(<variable> "<help_text>" [value])
option(USE_MYMATH "Use tutorial provided math implementation" ON)
# 如果 USE_MYMATH 为真,则为 MathFunctions 添加一个编译定义。
if (USE_MYMATH)
# 通常用于在编译时向编译器传递预处理器宏。
# 后续程序使用 #ifdef USE_MYMATH 来进行条件编译。
# target_compile_definitions(<target> <INTERFACE|PUBLIC|PRIVATE> [items1...] [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
# 前面提到STATIC类型的库,编译时将所有代码打包到一个库文件中。
# 静态库在编译时将所有代码打包到一个库文件中,最终生成的可执行文件会包含这些代码。
# 与动态库不同,静态库在运行时不需要额外的库文件。
add_library(SqrtLibrary STATIC mysqrt.cxx)
# 将一个或多个库链接到指定的目标(如可执行文件或另一个库)。
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()
option(USE_MYMATH "Use tutorial provided math implementation" ON)设置了一个变量/宏为USE_MYMATH。if (USE_MYMATH)通过if进行编译时的流程控制。- 如果为开启状态
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH"),如果USE_MYMATH开启,那么在编译MathFunctions这个Library的时候,将传递USE_MYMATH宏进入其代码。add_library(SqrtLibrary STATIC mysqrt.cxx)添加一个本地库,因为USE_MYMATH为真也就是使用了我们提供的平方根函数。target_link_libraries(MathFunctions PRIVATE SqrtLibrary)将上句的SqrtLibrary链接到MathFunctions中endif()
- 如果为关闭状态
- 无事发生
endif()
- MathFunctions.cxx
#include "MathFunctions.h"
#include <cmath>
#ifdef USE_MYMATH
# include "mysqrt.h"
#endif
namespace mathfunctions {
double sqrt(double x)
{
#ifdef USE_MYMATH
return detail::mysqrt(x);
#else
return std::sqrt(x);
#endif
}
}
这里很清晰的可以看到使用USE_MYMATH宏来做了一些事情,比如
- 如果
USE_MYMATH为真则包含我们实现的函数头文件等等,不赘述

打包时我们可以动态通过-DUSE_MYMATH=OFF来动态设置使用那些函数实现。
- 根目录CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(Tutorial VERSION 1.0)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
configure_file(TutorialConfig.h.in TutorialConfig.h)
add_subdirectory(MathFunctions)
add_executable(Tutorial tutorial.cxx)
# list 扫描 MathFunctions 目录,将其添加/追加(APPEND)到 YAHAHA 列表变量中。
list(APPEND YAHAHA "${PROJECT_SOURCE_DIR}/MathFunctions")
# 将 YAHAHA 列表变量添加到 Tutorial 的包含路径中,用于找到头文件。
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}" ${YAHAHA})
# 将 MathFunctions 库链接到 Tutorial 可执行文件中。
target_link_libraries(Tutorial PUBLIC MathFunctions)
总结
- 其实A菌有种错觉啦,关于链来链去的就好像国中一本编译原理中讲到的文件链接了啦。
- 好啦,我们在来看一下今天又新学了几个API呢?
add_library(): 设定一个程式为Lib库。add_subdirectory(): 为项目增加一个子目录。target_link_libraries(): 将一个或多个库链接到指定的目标(如可执行文件或另一个库)。PROJECT_SOURCE_DIR: 程式源码的目录。if()endif(): 条件判断。list(),APPEND: 扫描和追加。option(): 设定选项。target_compile_definitions(): 向程式添加预处理器宏。cmake -D[OPTION_NAME]=: 为CMakeLists.txt中的option填充值。