16-CMake 入门:从单文件到多目标工程¶
目录页:见 C++ 小白教程目录
目标¶
- 理解 CMake 在工程里到底负责什么、不负责什么
- 会写最小
CMakeLists.txt,把单文件程序编译出来 - 会把工程拆成“库 + 可执行文件”两个目标,并设置 C++ 标准版本
先决知识(建议)¶
- 会写一个能编译运行的 C++ 程序(见 01 章)
1. CMake 是什么:它不是编译器¶
- 专业名称:CMake(构建系统生成器)
- 类比:翻译的“组织者”:决定用哪些源文件、怎么分组、用什么编译参数
- 作用:生成你平台对应的构建文件(例如 Ninja 文件、Visual Studio 工程、Makefile)
- 规则/坑点:真正把代码编译成可执行文件的是编译器(GCC/Clang/MSVC),CMake 只是把“怎么编译”描述清楚
一句话记忆:
- 编译器:把
.cpp变成.exe/.o - CMake:把“工程怎么组织、怎么编译”写成一份跨平台配置
2. 你需要认识的 5 个词¶
- 源码目录(source dir):放
CMakeLists.txt和源码的目录 - 构建目录(build dir):CMake 生成的中间文件与产物目录
- 配置(configure):CMake 读取
CMakeLists.txt生成构建系统文件 - 构建(build):调用具体构建工具去编译/链接
- 目标(target):可执行文件或库(这是 CMake 里最重要的抽象)
推荐习惯:永远“源目录 + 构建目录”分离(out-of-source build)。
3. 最小工程:单文件 + 一个可执行目标¶
假设你的目录结构是:
3.1 最小 CMakeLists.txt¶
cmake_minimum_required(VERSION 3.20)
project(demo LANGUAGES CXX)
add_executable(demo main.cpp)
target_compile_features(demo PRIVATE cxx_std_20)
直觉解释:
project:给工程起名,并声明用 C++add_executable:生成一个叫demo的可执行目标target_compile_features:对这个目标声明“至少需要 C++20”
3.2 最常用的构建命令(跨平台)¶
在工程根目录执行:
这两句分别对应:
- 生成构建系统文件到
build/ - 编译并链接
4. 拆成“库 + 可执行文件”:工程化第一步¶
当代码不止一个文件时,推荐的拆法是:
- 把可复用的逻辑放进库(
add_library) main.cpp只做“调度”
一个常见结构:
4.1 对应的 CMakeLists.txt¶
cmake_minimum_required(VERSION 3.20)
project(demo LANGUAGES CXX)
add_library(mylib lib/add.cpp)
target_include_directories(mylib PUBLIC lib)
target_compile_features(mylib PUBLIC cxx_std_20)
add_executable(app app/main.cpp)
target_link_libraries(app PRIVATE mylib)
直觉解释:
mylib是库目标;PUBLIC的 include 目录与编译特性会“传给”依赖它的目标app链接mylib,所以app自动能包含lib/add.h
5. 多目录工程:用 add_subdirectory 管理模块¶
当工程变大时,把所有 target 都写在一个 CMakeLists.txt 里会越来越难维护。更常见的组织方式是“每个模块一个子目录”:
根目录 CMakeLists.txt 只负责“拼装工程”:
cmake_minimum_required(VERSION 3.20)
project(demo LANGUAGES CXX)
add_subdirectory(lib)
add_subdirectory(app)
lib/CMakeLists.txt 只负责库自身:
add_library(mylib add.cpp)
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_features(mylib PUBLIC cxx_std_20)
app/CMakeLists.txt 只负责可执行文件:
这套写法的直觉是:根目录不关心细节,每个模块自己把“我需要什么”描述清楚。
6. Debug / Release:配置类型与构建目录¶
你会经常听到“Debug 版本”和“Release 版本”,它们最常见的差异是:
- Debug:便于调试,通常不开最高级优化
- Release:开启更多优化,运行更快,但调试信息可能少
在 CMake 里你要分清两种情况:
- 单配置生成器(常见于 Ninja/Make):用
-DCMAKE_BUILD_TYPE=Debug选择配置 - 多配置生成器(常见于 Visual Studio):构建时用
--config Debug选择配置
示例(单配置):
示例(多配置):
推荐习惯:
- Debug/Release 用不同 build 目录,互不污染
7. 编译选项:把警告开起来(更早发现问题)¶
在学习阶段,把警告打开会让你更快发现“潜在 bug”。更现代的做法是把编译选项写到 target 上:
add_executable(app main.cpp)
if (MSVC)
target_compile_options(app PRIVATE /W4)
else()
target_compile_options(app PRIVATE -Wall -Wextra -Wpedantic)
endif()
规则/坑点:
- 选项是“编译器相关”的,不同编译器写法不同
- 不要全局乱
add_compile_options(...),优先写在具体 target 上
8. 常见坑(新手最容易踩)¶
- 错误:在源目录里直接生成构建文件 → 结果:目录变得很乱 → 正确:用
-B build建独立构建目录 - 错误:全局
set(CMAKE_CXX_STANDARD 20)然后以为都生效 → 结果:多目标时混乱 → 正确:优先用target_compile_features - 错误:把 include 目录到处
include_directories(...)全局加 → 结果:依赖关系不清楚 → 正确:用target_include_directories写在目标上 - 错误:新增
.cpp文件但没加进目标 → 结果:链接报“未定义引用” → 正确:把源文件加到add_library/add_executable里 - 错误:把 Debug/Release 产物混在一个 build 目录里 → 结果:难定位问题、缓存互相影响 → 正确:分开 build 目录或用正确的配置方式
- 错误:在 CMake 里硬编码绝对路径 → 结果:换电脑就炸 → 正确:用相对路径、target include、find_package 等机制
小练习¶
练习 1:单文件编译¶
- 输入/输出:写一个
main.cpp,输出sum=... - 约束:用 CMake 配置并构建出可执行文件
- 提示:照抄 3.1 的
CMakeLists.txt - 目标:熟悉 configure 与 build 两步
练习 2:库与可执行拆分¶
- 输入/输出:把
add(a, b)放进库,main 调用并输出 - 约束:必须用
add_library+target_link_libraries - 提示:头文件放
lib/,并用target_include_directories - 目标:建立 target 思维
小结¶
- CMake 的核心是 target:把“源文件、头文件路径、编译特性、依赖库”组织清楚
- 新手阶段先掌握:
add_executable、add_library、target_link_libraries、target_include_directories - 保持 out-of-source build,会让工程干净很多