[[nodiscard]] 是 C++17 引入的一个属性(attribute),用于提示编译器在调用某个函数时,其返回值不应该被忽略。如果调用者忽略了该函数的返回值,编译器会发出警告。command_name(arg1 arg2 ...),命令名不区分大小写(但推荐使用小写)。参数用空格或分号 ; 分隔(分号用于列表)# 这是单行注释
#[[
这是
多行注释
]]
set(MY_VAR "Hello, CMake!") # 定义变量,区分大小写
message(STATUS ${MY_VAR}) # 使用变量 ${VAR}
set(MY_LIST "a" "b" "c") # 定义列表
list(APPEND MY_LIST "d") # 追加元素
cmake_minimum_required:必须放在 CMakeLists.txt 的第一行。cmake_minimum_required(VERSION 3.10) # 指定最低 CMake 版本
project(MyProject # 项目名称
VERSION 1.0 # 版本号
LANGUAGES CXX # 语言(CXX 表示 C++)
)
生成可执行文件,add_executable(my_app main.cpp)
my_app:生成的可执行文件名。main.cpp:源文件列表(可多个)。生成库
STATIC:静态库(.a/.lib)。SHARED:动态库(.so/.dll)。INTERFACE:仅头文件库(无 .cpp 文件)。add_library(my_lib STATIC src/lib.cpp) # 静态库
add_library(my_shared_lib SHARED src/lib.cpp) # 动态库
链接库 target_link_libraries(my_app PRIVATE my_lib)
PRIVATE:仅当前目标使用(不传递依赖)。PUBLIC:当前目标及其依赖者都使用(传递依赖)。INTERFACE:仅依赖者使用(当前目标不使用)。包含头文件 target_include_directories(my_lib PUBLIC include)
PUBLIC:头文件对依赖者可见。PRIVATE:仅当前目标使用。查找外部库
REQUIRED:如果找不到库,则报错。QUIET:静默查找(不报错)。find_package(OpenCV REQUIRED) # 查找 OpenCV
target_link_libraries(my_app PRIVATE OpenCV::OpenCV)
if(WIN32)
message(STATUS "Running on Windows")
elseif(UNIX)
message(STATUS "Running on Linux/macOS")
endif()
foreach(file IN ITEMS file1.cpp file2.cpp)
message(STATUS "Processing ${file}")
endforeach()
# add_custom_target 定义了一个名为 clean_extra 的自定义目标。
add_custom_target(clean_extra
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/extra_files
COMMENT "Cleaning extra files..."
)
# 将自定义清理目标与默认清理关联,确保运行 clean 时也会触发 clean_extra。
add_dependencies(clean clean_extra)
install() 命令用于定义项目的安装规则,指定哪些文件、目标或目录需要被安装到系统路径中。常见用法:# 安装可执行文件
install(TARGETS my_app # 目标名称
DESTINATION bin # 安装路径(默认前缀为 /usr/local)
)
# 安装库文件
install(TARGETS my_lib # 静态库/动态库
ARCHIVE DESTINATION lib # 静态库(.a/.lib)
LIBRARY DESTINATION lib # 动态库(.so/.dll)
RUNTIME DESTINATION bin # Windows 的 .dll
)
# 安装头文件
install(DIRECTORY include/ # 头文件目录
DESTINATION include # 安装到 /usr/local/include
FILES_MATCHING PATTERN "*.h"
)
# 安装其他文件
install(FILES config.txt # 单个文件
DESTINATION etc
)
--prefix 指定安装前缀(覆盖 CMAKE_INSTALL_PREFIX)--config 指定构建类型(如 --config Release)cmake --build build --target install # 先编译,后安装
# 或直接安装(需提前构建)
cmake --install build --prefix "/my/install/path"
cmake --install build 实际会调用对应的构建工具(如make,ninja)执行 make install 或 ninja install,使用cmake 这种方式更加通用cmake --build --target install 先编译代码,再执行安装(两步合一)cmake --install build 仅执行安装(不重新编译),需确保构建已完成CMake 的安装(install)本质上就是将构建生成的文件(如可执行文件、库、头文件等)复制到指定的目录中,通常是系统标准路径(如 /usr/local)或自定义路径
| 变量 | 说明 |
|---|---|
| CMAKE_BUILD_TYPE | 构建类型(Debug/Release) |
| CMAKE_CXX_STANDARD | C++ 标准(如 11/14/17) |
| CMAKE_INSTALL_PREFIX | 安装路径(默认 /usr/local) |
| PROJECT_SOURCE_DIR | 项目源码目录 |
| PROJECT_BINARY_DIR | 构建目录(build/) |
| 命令 | 说明 |
|---|---|
cmake -S . -B build | 指定源码路径(-S)和构建路径(-B) |
cmake --build build --config Release | 指定构建类型(Debug/Release) |
cmake -DCMAKE_BUILD_TYPE=Debug .. | 设置变量(-D) |
cmake --install . --prefix "install" | 安装到指定目录 |
cmake -G "Ninja" .. | 指定生成器(Ninja/Unix Makefiles/Visual Studio/MinGW Makefiles) |
cmake -B build -S . 是配置阶段,指定源码目录和构建目录,生成Makefile等cmake --build build 是编译cmake --install build 是安装,可以灵活指定选项,包括 --config,--prefixcmake --build build --target install 是将编译和安装合二为一,这里需要提前在配置阶段指定选项,像 -DCMAKE_INSTALL_PREFIX=/my/install/path, -DCMAKE_BUILD_TYPE=Releasecmake --install build,那么不需要在配置阶段指定 -DCMAKE_INSTALL_PREFIX,-DCMAKE_BUILD_TYPE 等,可以在安装阶段再指定:cmake --install build --prefix /my/install/path --config Releasetarget: dependecy1 dependency2 ...
command1
command2
$(VAR) 来引用。特殊变量:$@: 当前规则的目标名$<: 第一个依赖文件$^: 所有依赖文件列表$(COLOR)、$(MAKE) 和 $(MAKESILENT) 等这些看起来没定义的变量,有些是 make 自带的$(MAKE),有些是 CMake 为了灵活控制输出而设计的,可以通过命令行或环境变量传入,让你更方便地控制构建过程的行为。$(COLOR) 是一个可选环境变量或命令行参数$(MAKE) 是 make 内置的一个变量,表示当前 make 程序本身。通常用于多层 Makefile 构建系统中(如 CMake 生成的 Makefile)$(MAKESILENT): $(VERBOSE)MAKESILENT = -s,这是一条条件赋值语句:如果 $(VERBOSE) 为空,则 MAKESILENT = -s,如果你设置了 VERBOSE=1,则这一行不会定义 MAKESILENT,类似条件语句,为空才定义CC = gcc
CFLAGS = -Wall -g
hello: hello.o
$(CC) $(CFLAGS) -o hello hello.o
hello: hello.o util.o
gcc -o $@ $^
.c 到 .o,这样所有 .c 文件都可以自动编译成 .o 文件。%.o: %.c
$(CC) -c $< -o $@
make 这些目标不是实际的文件名:防止当前目录下有叫 clean 的文件导致命令不执行。.PHONY: clean
clean:
rm -f *.o hello
make:构建默认目标(第一个目标)make target:构建指定目标make -f filename:指定不同的 Makefilemake -n target 或 make --just-print target:干运行,只打印命令,不执行make -j N:并行构建(加速编译)make -B:强制重建所有目标make -n install 不执行安装,打印命令

# 进入项目目录
cd /d D:\path\to\my_project
# 创建构建目录并生成 Ninja 构建文件
mkdir build && cd build
cmake -G "Ninja" -DCMAKE_C_COMPILER=D:\MinGW64\mingw64\bin\gcc.exe -DCMAKE_CXX_COMPILER=D:\MinGW64\mingw64\bin\g++.exe ..
# 编译项目(自动调用 ninja.exe)
cmake --build . # 等效于运行 ninja, cmake --build . 跨平台统一,适合脚本化构建
# 生成 Makefile
cmake -G "MinGW Makefiles" -DCMAKE_C_COMPILER=D:\MinGW64\mingw64\bin\gcc.exe -DCMAKE_CXX_COMPILER=D:\MinGW64\mingw64\bin\g++.exe ..
# 编译项目(调用 mingw32-make.exe)
mingw32-make
# 或(如果重命名了 make.exe)
make # 或者 cmake --build .
cmake --build . --target clean
# 或直接删除 build 目录
rm -rf build
| 阶段 | 作用 | 输入 | 输出 | 工具/命令 |
|---|---|---|---|---|
| 1.预处理(Preprocessing) | 展开宏、包含头文件、条件编译等 | .c/.cpp 文件 | .i 文件 | g++ -E main.cpp -o main.i |
| 2.编译(Compilation) | 将预处理后的代码翻译成汇编语言 | .i 文件 | .s 文件 | g++ -S main.i -o main.s |
| 3.汇编(Assembly) | 将汇编代码转换为目标机器码 | .s 文件 | .o/.obj 文件 | as main.s -o main.o或g++ -c main.s -o main.o |
| 4.链接(Linking) | 将多个目标文件和库链接为可执行文件 | .o/.obj + 库文件 | 可执行文件(.exe) | g++ main.o utils.o -o Test.exe或g++ @linkLibs.rsp -o Test.exe |
| 静态库打包 | 打包为静态库 | 多个 .obj | .a | ar qc objects.a main.obj utils.obj |
.obj/.o 文件:.a/.lib 文件:g++ libmylib.a main.obj -o Test.exeg++ main.obj libmylib.a -o Test.exe,或使用 --start-group 或 --end-group 来强制多次扫描库文件:g++ main.obj --start-group libmylib.a --end-group -o Test.exe# 创建动态库(Linux)必须命名为libxxx.so
g++ -fPIC -c foo.cpp -o foo.o
g++ -fPIC -c bar.cpp -o bar.o
g++ -shared -o libmylib.so foo.o bar.o
# 创建动态库(Windows) .dll
cl /EHsc /D_USRDLL /MD dllmain.cpp myfunc.cpp /LD /Femylib.dll
在 Windows 上,.dll 是动态库本体,而 .lib 是“导入库”,用于链接阶段告诉链接器这个 .dll 提供了哪些函数
# 查看依赖关系(Linux)
ldd Test.exe # 查看依赖的 .so 文件
readelf -d Test.exe # 查看 ELF 的动态信息
nm libmylib.so # 查看符号表
# 查看依赖关系(Windows)
dumpbin /dependents Test.exe
Dependency Walker # 图形化查看 DLL 依赖
g++ main.cpp -L. -lfoo -o myapp-L.:在当前目录下查找库-lfoo:表示链接 libfoo.so 或 libfoo.a
void* handle = dlopen("./libfoo.so", RTLD_LAZY);
if (!handle) {
// 错误处理
}
/usr/lib/、/usr/local/lib/LD_LIBRARY_PATHexport LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./myapp
cl main.cpp foo.lib
HMODULE hModule = LoadLibrary("foo.dll");
if (hModule) {
FARPROC pFunc = GetProcAddress(hModule, "myfunction");
if (pFunc) ((void(*)())pFunc)();
FreeLibrary(hModule);
}
| 意图 | 推荐签名 | 适用对象 | 说明 |
|---|---|---|---|
| 只读输入(不修改) | const T& | 类类型、容器、大结构体、std::string 等 | 避免拷贝,保留语义清晰 |
| T | 内置类型(int,double)、小 POD、std::shared_ptr、智能指针 | 传值开销 ≤ 传引用,且避免引用悬垂 | |
| 修改输入(In-Out) | T& | 任何需要原地修改的对象 | 明确告知调用方“我会改你” |
| 获取所有权(Sink/消耗型) | T | 任何可移动类型 | 现代 C++ 首选。调用处自动拷贝或移动,函数体内 std::move 即可 |
| T&& | 仅用于模板完美转发或显式移动语义 | 普通函数中慎用,易误导 | |
| 只读视图(C++17/20) | std::string_view std::span<const T> | 字符串、连续序列 | 零拷贝、不持有生命周期,替代 const std::string& |
// ❌ 传统重载(代码重复,维护成本高)
void set_name(const std::string& s) { name_ = s; }
void set_name(std::string&& s) { name_ = std::move(s); }
// ✅ 现代 C++ 推荐(单一接口,编译器自动选择拷贝或移动)
void set_name(std::string s) { name_ = std::move(s); }
注意:若 T 拷贝极昂贵且你希望调用者对左值避免拷贝,可保留双重载;否则单一 T 是 Herb Sutter 等专家推荐的默认做法。
| 场景 | 推荐返回类型 | 说明 |
|---|---|---|
| 生成新对象 | T(传值) | C++17 起强制复制消除(RVO),几乎零开销;C++11 也会自动移动 |
| 返回已有对象引用 | const T& 或 T& | 仅限:容器元素、类成员、单例、静态变量。严禁返回局部变量引用 |
| 可能失败/可选值 | std::optional<T> | 传值返回,语义安全 |
| 转移唯一所有权 | std::unique_ptr<T> | 明确所有权移交 |
| 意图 | 存储方式 | 注意事项 |
|---|---|---|
| 拥有数据 | T(值语义) | 默认首选,拷贝/移动语义清晰 |
| 只读非拥有 | const T& 或 std::reference_wrapper<const T> | 必须保证引用对象生命周期 ≥ 类实例 |
| 可修改非拥有 | T& 或 std::reference_wrapper<T> | 极易引发悬垂引用,慎用 |
| 初始化后不可变 | const T | 构造函数初始化列表赋值,后续不可改 |
| 可选/延迟构造 | std::optional<T> 或 std::unique_ptr<T> | 避免空指针/未定义状态 |
T&& 不是万能引用:仅在函数模板 template<class T> void f(T&&) 中才是通用引用;普通函数 void f(std::string&&) 只能绑定右值。const T& 会延长临时对象生命周期:const std::string& r = std::string("tmp"); 是合法的,但不要将其存入成员变量。const T& + 拷贝更快,且代码更简洁。std::unique_ptr<T>:按值传递 = 转移所有权std::shared_ptr<T>:按 const T& 传递 = 共享观察,不增加引用计数sizeof(T) ≤ 3× 指针大小(64位约 24 字节)可传值;更准确的做法是结合 std::is_trivially_copyable_v<T> 判断。