3、基础语法与变量:注释方式、变量定义与引用、set()与message()命令、变量作用域基础

各位同学,欢迎来到第三章。这一章我们开始真正动手写CMake代码了。

说实话,很多初学者觉得CMake语法很怪——既不像C++,也不像Python。我当年刚接触时也有同感。但别担心,这一章我会带你把这些基础语法掰开揉碎讲清楚。

3.1 注释方式:给代码加点说明

写代码不写注释,三个月后连自己都看不懂。CMake支持三种注释方式,我一个个说。

3.1.1 单行注释

# 开头,整行都会被忽略。这是最常用的方式。

# 这是一个单行注释
set(MY_VAR "hello")  # 行尾也可以加注释

3.1.2 块注释(CMake 3.0+)

#[[]] 包裹。适合临时屏蔽一大段代码。

#[[
这是一段块注释
可以跨多行
set(TEMP_VAR "不会被执行")
]]
我的习惯:日常开发用单行注释就够了。块注释我一般只在调试时临时屏蔽代码块用,正式提交前会清理掉。

3.1.3 文档注释(CMake 3.0+)

#! 开头,配合 cmake --help-command 使用。说实话,这个我很少用,了解一下就行。

3.2 变量定义与引用:CMake的"内存小盒子"

变量就是存数据的小盒子。CMake里所有变量本质上都是字符串,这一点要记住。

3.2.1 变量命名规则

  • 大小写敏感:MY_VARmy_var 是两个变量
  • 只能包含字母、数字、下划线
  • 不能以数字开头
  • 建议全大写+下划线风格(我个人的偏好)

3.2.2 变量引用

${变量名} 来取值。这是CMake最核心的语法之一。

set(NAME "CMake教程")
message("课程名称是:${NAME}")

你想想看,如果没有 ${},我们怎么知道哪个是变量名哪个是普通文本?

我曾经踩过的坑:变量名拼写错误不会报错,只会得到一个空字符串。比如你写了 ${NAM} 但实际定义的是 ${NAME},CMake不会提醒你,只会默默返回空值。排查这种bug特别费时间。

3.3 set() 命令:给变量赋值

set() 是CMake里最常用的命令之一。它的基本语法是:

set(变量名 值1 值2 ...)

3.3.1 普通变量

set(MY_NAME "张三")
set(MY_AGE 25)          # 数字也是字符串
set(MY_LIST a b c)      # 多个值会变成列表

3.3.2 缓存变量

CACHE 关键字,变量会持久化到CMakeCache.txt中。

set(MY_OPTION "ON" CACHE BOOL "是否启用某个功能")

这个在大型项目中特别有用。用户可以通过 cmake -DMY_OPTION=OFF 来覆盖默认值。

3.3.3 环境变量

$ENV{变量名} 读取系统环境变量。

message("PATH环境变量:$ENV{PATH}")

3.4 message() 命令:调试利器

message() 就是CMake的 printf。我写CMake时,每写几行就要加个message看看变量值对不对。

3.4.1 基本用法

message("你好,CMake")           # 普通消息
message(STATUS "正在配置...")    # 带状态前缀
message(WARNING "这可能有风险")  # 警告
message(FATAL_ERROR "出错了")    # 致命错误,会终止配置

3.4.2 调试技巧

我个人最常用的是 STATUS 级别,它会在输出前加 -- 前缀,看起来更清晰。

set(SRC_FILES main.cpp utils.cpp)
message(STATUS "源文件列表:${SRC_FILES}")
重要:FATAL_ERROR 会立即终止CMake执行。我在项目中用它来做前置条件检查,比如检测到编译器版本太低就直接报错退出。

3.5 变量作用域基础:变量能活多久?

变量的作用域,说白了就是变量在哪些地方能被访问到。CMake的作用域规则其实很简单。

3.5.1 全局作用域

在顶层CMakeLists.txt中定义的变量,默认是全局的。所有子目录都能访问。

# 顶层 CMakeLists.txt
set(GLOBAL_VAR "我是全局的")
add_subdirectory(src)

3.5.2 局部作用域

function()macro() 内部定义的变量,默认是局部的。函数退出后变量就没了。

function(my_func)
    set(LOCAL_VAR "我是局部的")
    message("内部:${LOCAL_VAR}")  # 能访问
endfunction()

my_func()
message("外部:${LOCAL_VAR}")  # 空字符串

3.5.3 子目录作用域

add_subdirectory() 进入子目录时,CMake会创建一个新的作用域。子目录可以访问父目录的变量,但反过来不行。

# 父目录
set(PARENT_VAR "来自父目录")
add_subdirectory(child)

# child/CMakeLists.txt
message("${PARENT_VAR}")  # 能访问
set(CHILD_VAR "子目录的")
# 父目录看不到 CHILD_VAR
避坑指南:我曾经在一个项目里,子目录修改了父目录的变量,结果导致父目录的行为变得不可预测。后来我养成了习惯:在子目录里尽量用 set(... PARENT_SCOPE) 来显式向上传递变量。

3.6 知识体系总览

下面这张图把本章的核心知识点串起来了。你可以把它当作一个快速参考地图。

CMake基础语法与变量 注释方式 # 单行注释 #[[ 块注释 ]] #! 文档注释 变量定义与引用 ${变量名} 引用 大小写敏感 全大写命名风格 set() & message() set(变量 值) CACHE 缓存变量 STATUS/WARNING/ERROR 变量作用域基础 全局作用域 函数局部作用域 子目录作用域

3.7 本章小结

这一章我们学了CMake最基础的语法元素。说白了就是三件事:怎么加注释、怎么定义变量、怎么打印调试信息。再加上作用域的概念,你已经可以看懂大部分简单的CMakeLists.txt了。

嗯,这里要提醒一下:变量作用域是初学者最容易搞混的地方。我的建议是——先记住"子目录能看父目录,父目录看不到子目录"这条铁律。等后面学到函数和宏时,我们再深入展开。

核心要点回顾:
  • 注释用 #,块注释用 #[[ ]]
  • 变量引用用 ${},拼写错误不会报错
  • set() 定义变量,message() 打印信息
  • 作用域遵循"向下可见,向上不可见"原则

公众号:蓝海资料掘金营,微信deep3321