CMakeList 知识系统学习系列(四):条件与循环

《CMakeList 知识系统学习系列(四):条件与循环》

前言:你是否在CMake中反复踩变量作用域的坑?是否因循环分割错误导致构建失败?本文将详细讲解CMake条件判断与循环的核心技巧!

CMake 条件判断与循环详解

一、条件判断(if/elseif/else

CMake 的条件判断语句用于根据变量值、文件状态或逻辑表达式动态调整构建逻辑。其核心语法如下:

scss 复制代码
if(条件表达式)
    # 条件成立时执行的代码
elseif(其他条件)
    # 其他条件成立时执行的代码
else()
    # 默认执行的代码
endif()

1. 基本条件表达式

类型 语法示例 说明
变量存在性 if(DEFINED VAR) 检查变量 VAR 是否定义
布尔值判断 if(VAR) VARON/1/TRUE 时成立。直接使用未定义变量会导致CMake警告
字符串比较 if("${VAR}" STREQUAL "value") 字符串相等性判断(区分大小写)
数值比较 if(NUM LESS 10) 支持 LESS/GREATER/EQUAL
文件/目录存在 if(EXISTS "path/to/file") 检查文件或目录是否存在

示例:根据编译类型设置优化选项

scss 复制代码
if(CMAKE_BUILD_TYPE STREQUAL "Release")
    add_compile_options(-O3)
elseif(CMAKE_BUILD_TYPE STREQUAL "Debug")
    add_compile_options(-O0 -g)
else()
    message(WARNING "Unknown build type")
endif()

判断变量和数值示例:

kotlin 复制代码
if(age)
    message(STATUS "age is not empty")
else()
    message(STATUS "age is empty")
endif()
# 条件判断
set(age 10)
if(age)
    message(STATUS "age is not empty")
else()
    message(STATUS "age is empty")
endif()
function(show_if)
    if(${age} LESS 10)
        message(STATUS "age < 10")
    elseif(${age} EQUAL 10)
        message(STATUS "age = 10")
    elseif(${age} GREATER 10)
        message(STATUS "age > 10")
    endif()
endfunction()
show_if()
set(age 11)
show_if()
set(age 9)
show_if()

输出:
-- age is empty
-- age is not empty
-- age = 10
-- age > 10
-- age < 10

2. 逻辑运算符

运算符 语法 说明
if(条件1 AND 条件2) 同时成立时返回真
if(条件1 OR 条件2) 任一成立时返回真
if(NOT 条件) 条件不成立时返回真
组合逻辑 if((A AND B) OR (C)) 支持括号嵌套优先级

示例:多条件判断

scss 复制代码
if(WIN32 AND MSVC AND NOT CMAKE_CL_64)
    message("32位 Windows MSVC 编译器")
endif()

3. 高级条件判断

  • 正则表达式匹配 :使用 MATCHES 判断字符串模式

    scss 复制代码
    if("${CMAKE_SYSTEM_NAME}" MATCHES "Linux|Darwin")
        message("当前为类 Unix 系统")
    endif()
    
    function(match_name name)
    if("${name}" MATCHES "worl.*")
        message(STATUS "name match")
    endif()
    endfunction()
    match_name("world")

二、循环(foreach/while

1. foreach 循环

用于遍历列表或范围,支持多种迭代模式:

  • 遍历列表

    1. 直接展开变量
    • 语法foreach(var ${my_list})
    • 行为 :直接展开变量${my_list}为字符串,并根据CMake的默认分隔符(分号或空格)分割成多个元素。
    • 风险:若变量值中包含空格或特殊字符,可能导致元素被错误分割。
    kotlin 复制代码
    set(my_list "a;b c" d)  # 元素为 ["a", "b c", "d"]
    foreach(var ${my_list})
        message("${var}")  # 输出:a; b; c; d(错误分割)
    endforeach()
    1. IN LISTS

      语法foreach(var IN LISTS my_list)

      行为IN LISTS:显式指定列表变量名,直接遍历列表中的每个元素,保留元素完整性,避免空格或分号导致的错误分割

    scss 复制代码
    set(my_list "a;b c" d)
    foreach(var IN LISTS my_list)
        message("${var}")  # 正确输出:a;b c; d
    endforeach()

    3 IN ITEMS

    语法foreach(var IN ITEMS a b c)

    行为: IN ITEMS:直接遍历后续的显式项列表,适用于硬编码元素。

    kotlin 复制代码
    foreach(name IN ITEMS "John Doe" "Alice")  # 显式遍历硬编码项
    endforeach()

优先使用IN LISTS处理列表变量,避免元素分割问题。

使用IN ITEMS显式遍历硬编码项,提高代码清晰度

这里还有个小插曲,我在for LISTS的时候如下,结果输出不是想要的"a;b c" "d"。而是 in LISTS my_list

kotlin 复制代码
set(my_list "a;b c" d) 
foreach(var in LISTS my_list)
    message("in lists ${var}")  
endforeach()

输出
in lists in
in lists LISTS
in lists my_list

刚开始还纳闷呢。这是怎么输出的?

后面看in LISTS my_list不正是var后面的,那为题肯定就是in的大小写问题。

之前在CMakeLists.txt中写message和MESSAGE不是效果等同,之前就错误的认为CMakeLists中大小写不敏感。

查了下资料才知道:CMake的"大小写不敏感"仅针对命令关键字 ,而变量、生成器表达式、文件名等仍严格区分大小写。 那为什么命令关键字大小不敏感? 此设计源于历史兼容性,早期CMake版本使用全大写命令,后续版本逐渐转向小写风格,但保留对大小写的兼容性。原来是历史包袱。O(∩_∩)O哈哈~

  • 范围循环

foreach循环的RANGE关键字用于生成一个整数序列,并遍历该序列中的每个数值。

支持三种参数形式:

  1. 单参数 :生成从 0N-1 的整数序列(包含 0N-1)。
  2. 双参数 :生成从 startend 的整数序列(包含 startend)。
  3. 三参数 :生成从 startend 的整数序列,步长为 step
sql 复制代码
foreach(loop_var RANGE <N>)               # 单参数:0, 1, 2, ..., N-1
foreach(loop_var RANGE <start> <end>)     # 双参数:start, start+1, ..., end
foreach(loop_var RANGE <start> <end> <step>) # 三参数:start, start+step, ..., end(步长可为正/负)

参数详解

  1. 单参数形式
  • 语法RANGE <N>

  • 范围 :生成 0N-1 的整数序列。

  • 示例

    css 复制代码
    foreach(i RANGE 3)
      message("i = ${i}")  # 输出:i=0; i=1; i=2
    endforeach()
  1. 双参数形式
  • 语法RANGE <start> <end>

  • 范围 :生成从 startend 的整数序列(包含两端)。

  • 示例

    css 复制代码
    foreach(i RANGE 2 4)
      message("i = ${i}")  # 输出:i=2; i=3; i=4
    endforeach()
  1. 三参数形式
  • 语法RANGE <start> <end> <step>

  • 范围 :生成从 startend 的整数序列,步长为 step

    • step > 0,则要求 start ≤ end
    • step < 0,则要求 start ≥ end
  • 示例

    css 复制代码
    foreach(i RANGE 5 1 -2)
      message("i = ${i}")  # 输出:i=5; i=3; i=1
    endforeach()

注意事项

  1. 数值类型

    RANGE仅支持整数。如果参数包含非整数,CMake会报错。

  2. 步长的方向

    • 当步长为正数(step > 0)时,必须满足 start ≤ end,否则循环不会执行。
    • 当步长为负数(step < 0)时,必须满足 start ≥ end,否则循环不会执行。
  3. 包含结束值

    RANGE生成的序列包含结束值(end),例如 RANGE 2 4 包含 4

  4. CMake版本要求

    • 单参数和双参数形式:CMake 3.0及以上。
    • 三参数形式(带步长):CMake 3.3及以上。
  • 多列表合并遍历

    scss 复制代码
    set(list1 a b)
    set(list2 "c d" e)
    foreach(item IN LISTS list1 list2)
        message("Item: ${item}")  # 输出:a → b → c d → e
    endforeach()
  • 键值对遍历 ZIP_LISTS(CMake 3.17+)

    kotlin 复制代码
    list(APPEND keys a b c)
    list(APPEND values 1 2 3)
    foreach(key value IN ZIP_LISTS keys values)
        message("Key: ${key}, Value: ${value}")  # 输出:a→1 → b→2 → c→3
    endforeach()

ZIP_LISTS需要3.17版本才支持

2. while 循环

基于条件表达式重复执行代码块,需手动更新终止条件:

scss 复制代码
set(counter 0)
#如果counter变量小于5
while(counter LESS 5)
		#打印
    message("当前计数: ${counter}")
    # counter=counter+1
    math(EXPR counter "${counter} + 1")
endwhile()

逻辑差不多可以看懂。就是当counter小于5,就执行while里面的语句,打印counter值,并且把counter变量累加1。

可以看到和我们正常程序里面写的有点不一样。LESS之前if里面学过。比较整数大小的。把变量累加1.用到math。之前我们都是用set是直接给变量赋值

有人说。我们能不说直接把counter变量+1的值再赋值给counter?

不能。下面语句,输出的不是2.而是0+1了。所以计算并赋值得用math命令。至于math详细介绍,后面在细说

kotlin 复制代码
set(counter 0)
set(temp ${counter}+1)
message(STATUS "temp:${temp}")

//输出
-- temp:0+1

3. 循环控制语句

  • break():立即退出循环

    scss 复制代码
    foreach(i RANGE 10)
        if(i EQUAL 5)
            break()
        endif()
        message("i=${i}")
    endforeach()
  • continue():跳过当前迭代

    scss 复制代码
    foreach(i RANGE 10)
        if(i MOD 2 EQUAL 0)
            continue()
        endif()
        message("奇数: ${i}")
    endforeach()

三、最佳实践

1. 条件判断优化

  • 避免在循环内重复计算复杂条件,可将结果缓存到变量。
  • 优先使用 if(DEFINED VAR) 而非 if(VAR) 防止未定义变量报错。

2. 循环性能优化

  • 减少在循环内部调用耗时操作(如 file(GLOB))。
  • 使用 list(APPEND) 替代多次 set 操作合并结果。

3. 常见错误处理

  • 空列表循环:若列表可能为空,需添加保护逻辑:

    scss 复制代码
    if(NOT source_list)
        message(WARNING "列表为空")
    else()
        foreach(src IN LISTS source_list)
            # ...
        endforeach()
    endif()
  • 无限 while 循环:确保条件最终会变为假。

四、总结

通过合理使用条件判断和循环,CMake 脚本可实现高度动态化和模块化的构建流程,显著提升跨平台项目的可维护性。

相关推荐
虾球xz30 分钟前
游戏引擎学习第271天:生成可行走的点
c++·学习·游戏引擎
qq_4335545433 分钟前
C++ STL编程 vector空间预留、vector高效删除、vector数据排序、vector代码练习
开发语言·c++
XiaoCCCcCCccCcccC33 分钟前
Linux网络基础 -- 局域网,广域网,网络协议,网络传输的基本流程,端口号,网络字节序
linux·c语言·网络·c++·网络协议
菜狗想要变强2 小时前
C++ STL入门:vecto容器
开发语言·c++
五花肉村长2 小时前
Linux-Ext系列文件系统
linux·运维·服务器·c++·笔记·visual studio
weixin_428498492 小时前
在Lua中使用轻量级userdata在C/C++之间传递数据和调用函数
c语言·c++·lua
爱看书的小沐2 小时前
【小沐学GIS】基于C++绘制二维瓦片地图2D Map(QT、OpenGL、GIS)
c++·qt·gis·opengl·glfw·glut·二维地图
coding_rui3 小时前
C++模板笔记
c++·模板·类模板
C++ 老炮儿的技术栈3 小时前
C++中什么是函数指针?
c语言·c++·笔记·学习·算法
再睡一夏就好3 小时前
C语言常见的文件操作函数总结
c语言·开发语言·c++·笔记·学习笔记