目录

特性

cmake

# 这是单行注释

#[[
  
  
]]
set(MY_VAR "Hello, CMake!")  # 定义变量,区分大小写
message(STATUS ${MY_VAR})    # 使用变量 ${VAR}
set(MY_LIST "a" "b" "c")  # 定义列表
list(APPEND MY_LIST "d")  # 追加元素

核心 CMake 命令

cmake_minimum_required(VERSION 3.10)  # 指定最低 CMake 版本
project(MyProject                     # 项目名称
        VERSION 1.0                   # 版本号
        LANGUAGES CXX                 # 语言(CXX 表示 C++)
)
add_library(my_lib STATIC src/lib.cpp)  # 静态库
add_library(my_shared_lib SHARED src/lib.cpp)  # 动态库
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(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
)
cmake --build build --target install  # 先编译,后安装
# 或直接安装(需提前构建)
cmake --install build --prefix "/my/install/path"
💡tip

CMake 的安装(install)本质上就是将构建生成的文件(如可执行文件、库、头文件等)复制到指定的目录中,通常是系统标准路径(如 /usr/local)或自定义路径

变量说明
CMAKE_BUILD_TYPE构建类型(Debug/Release)
CMAKE_CXX_STANDARDC++ 标准(如 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)
ℹ️note
  • 整个过程包括配置,编译,安装
  • cmake -B build -S . 是配置阶段,指定源码目录和构建目录,生成Makefile等
  • cmake --build build 是编译
  • cmake --install build 是安装,可以灵活指定选项,包括 --config--prefix
  • cmake --build build --target install 是将编译和安装合二为一,这里需要提前在配置阶段指定选项,像 -DCMAKE_INSTALL_PREFIX=/my/install/path-DCMAKE_BUILD_TYPE=Release
  • 如果只是安装 cmake --install build,那么不需要在配置阶段指定 -DCMAKE_INSTALL_PREFIX-DCMAKE_BUILD_TYPE 等,可以在安装阶段再指定:cmake --install build --prefix /my/install/path --config Release

make

Makefile

target: dependecy1 dependency2 ...
    command1
    command2
CC = gcc
CFLAGS = -Wall -g

hello: hello.o
    $(CC) $(CFLAGS) -o hello hello.o

hello: hello.o util.o
    gcc -o $@ $^
%.o: %.c
    $(CC) -c $< -o $@
.PHONY: clean
clean:
    rm -f *.o hello

返回目录

编译构建

# 进入项目目录
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

编译过程

❗️important
  • Linux 上 .a 是静态库,.so 是动态库
  • Windows 上 MSVC 的静态库是 .lib,MinGW 的静态库是 .a,.dll 是动态库
阶段作用输入输出工具/命令
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.og++ -c main.s -o main.o
4.链接(Linking)将多个目标文件和库链接为可执行文件.o/.obj + 库文件可执行文件(.exe)g++ main.o utils.o -o Test.exeg++ @linkLibs.rsp -o Test.exe
静态库打包打包为静态库多个 .obj.aar qc objects.a main.obj utils.obj
💡tip
  • 链接器的工作流程大致如下: .obj 文件 + .a/.lib 文件 → 解析符号引用 → 分配地址 → 生成可执行文件,(推荐把 .a 放在后面)
  • 链接顺序很重要!有些链接器(如 GNU 的 ld 或 g++)对输入文件的顺序是有要求的:
  • 符号解析是从左到右进行的,如果你在 .a 库后面放 .obj 文件,可能无法正确解析某些符号 g++ libmylib.a main.obj -o Test.exe
  • 如果 main.obj 中调用了 libmylib.a 中的函数,但链接器已经处理完 libmylib.a,它不会回头重新查找符号,导致链接失败
  • 正确顺序:g++ main.obj libmylib.a -o Test.exe,或使用 --start-group--end-group 来强制多次扫描库文件:g++ main.obj --start-group libmylib.a --end-group -o Test.exe
  1. 初始化:创建一个“未定义符号表”
  2. 从左到右依次处理每个输入文件:对每个文件进行扫描和符号解析
  3. 处理目标文件(.obj/.o):如果它定义了某些符号,就添加到“已定义符号表”;如果它引用了某些未定义的符号,就添加到“未定义符号表”
  4. 处理静态库(.a/.lib):遍历其中的所有 .obj 文件,如果某个 .obj 文件定义了当前“未定义符号表”中的某个符号,就提取这个 .obj 并处理它的符号(按需提取)
  5. 结束:如果“未定义符号表”非空 → 报错:找不到符号定义

动态库

# 创建动态库(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
ℹ️note

在 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 依赖

linux 使用动态库

#include <dlfcn.h>
void* handle = dlopen("./libfoo.so", RTLD_LAZY);
if (!handle) {
    // 错误处理
}
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./myapp

Windows使用动态库

#include <windows.h>

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); }
ℹ️note

注意:若 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>避免空指针/未定义状态

高频避坑指南

返回目录