【CMake】循环——foreach(),while()
目录
一.foreach
1.1.常用基本示例
1.1.1.示例1
1.1.2.示例2
1.1.3.示例3——嵌套foreach
1.1.4.示例4——遮盖外部变量
1.1.5.示例5——break
1.1.6.示例6——continue
1.1.7.示例7——break和continue的结合
1.2.范围循环 (RANGE)
1.2.1.示例1
1.2.2.示例2
1.2.3.示例3——倒序循环
1.3.列表循环 (IN LISTS/ITEMS)
1.3.1.示例 1——最基础的列表遍历
1.3.2.示例2——IN LISTS 遍历多个列表
1.3.3.示例3——IN LISTS + ITEMS
1.3.4.示例4——LISTS A 和 ITEMS ${A} 完全等价
1.4.压缩列表循环 (ZIP_LISTS) - 并行迭代
1.4.1.示例1
1.4.2.示例2
1.4.2.示例3——长短不一
二.while()
2.1.示例1
一.foreach
对列表中的每个值评估一组命令。
基本语法
foreach(<loop_var> <items>)<commands>
endforeach()
其中 <items>
是一个由分号或空格分隔的项目列表。
foreach
和与之匹配的 endforeach
之间的所有命令会被记录下来但不会被立即调用。
一旦评估到 endforeach
,记录的命令列表就会为 <items>
中的每个项目调用一次。
在每次迭代开始时,变量 <loop_var>
将被设置为当前项目的值。
<loop_var>
的作用域被限制在循环范围内。有关详细信息,请参见策略 CMP0124
。
命令 break()
和 continue()
提供了跳出正常控制流的方法。
出于历史遗留原因,endforeach()
命令允许一个可选的 <loop_var>
参数。如果使用,它必须是开头 foreach
命令参数的原样重复。
我们来好好讲讲
foreach(<loop_var> <items>)<commands>
endforeach()
CMake 处理 foreach
循环的过程可以分为两个明确的阶段:录制阶段和执行阶段。
阶段一:录制阶段 (Parsing/Recording Phase)
当 CMake 的解析器首次遇到 foreach(
这一行时,它会立刻进行以下操作:
-
识别循环结构:解析器识别出这是一个循环的开始,并读取循环变量名
<loop_var>
和项目列表<items>
。 -
解析项目列表:将
<items>
这个字符串,无论是由空格还是分号分隔,解析成一个真正的列表(一个内部数组)。例如,将"a b c"
或"a;b;c"
解析成列表['a', 'b', 'c']
。 -
进入录制模式:从
foreach
命令的下一行开始,直到遇到与之匹配的endforeach()
命令,CMake 解析器会逐行读取其中的所有命令,但不会执行它们。 -
存储命令块:解析器将这些命令(
<commands>
里的所有内容)原封不动地存储起来,作为一个等待被多次调用的命令块。同时,它也会记录下循环变量名和之前解析好的项目列表。
这个阶段的核心是:CMake 只是在进行“预习”,它记住了要循环做什么(命令块)、循环变量叫什么、以及要对哪些值(项目列表)进行循环。它还没有开始做任何实际的工作。
阶段二:执行阶段 (Execution Phase)
当 CMake 解析器遇到与 foreach
相匹配的 endforeach()
命令时,录制阶段结束,执行阶段立刻开始。此时,CMake 才会真正去运行这个循环。
foreach(<loop_var> <items>)<commands>
endforeach()
对于项目列表中的每一个元素,CMake 都会按顺序执行以下步骤:
-
设置循环变量:将当前迭代所处理的列表元素的值,赋予循环变量
<loop_var>
。例如,在第一轮迭代中,<loop_var>
的值被设置为列表的第一个元素。 -
进入循环作用域:CMake 会创建一个新的变量作用域。在这个作用域内,循环变量
<loop_var>
被创建或赋值。-
遮盖(Shadowing):如果在外层作用域已经存在一个同名的变量,在当前循环作用域内,那个外层变量将变得不可访问。所有对
${<loop_var>}
的引用都指向当前循环迭代所设置的新值。
-
-
执行命令块:CMake 将录制阶段存储起来的那个命令块,从头到尾完整地执行一遍。在执行这些命令时,任何使用
${<loop_var>}
的地方都会被替换为当前循环变量的值。 -
处理控制流命令(可选):
-
如果在执行命令块时遇到了
continue()
命令,CMake 会立即停止执行当前迭代中剩余的命令,直接跳转到步骤5,开始准备下一次迭代。 -
如果在执行命令块时遇到了
break()
命令,CMake 会立即停止整个循环,跳转到步骤6,彻底结束整个foreach
循环,不再进行任何后续迭代。
-
-
结束本次迭代:如果没有遇到
break()
,当前迭代正常结束。CMake 会销毁当前循环作用域。伴随着作用域的销毁,当前设置的循环变量<loop_var>
也随之被销毁。 -
循环终止或继续:
-
如果是因为
break()
而终止,循环到此完全结束。 -
否则,CMake 会检查项目列表中是否还有下一个元素。如果有,则回到步骤1,开始下一次迭代,但注意,每次迭代都会创建一个全新的作用域。如果所有元素都已处理完毕,则整个
foreach
循环结束。
-
循环结束后
-
当循环完全结束后,CMake 会继续执行
endforeach()
之后的命令。 -
作用域恢复:由于循环自身的作用域已经结束,如果在循环之前存在同名的变量,那么现在那个变量的值将会重新变得可见和可访问,就像循环从未发生过一样。循环变量本身在循环外部是不存在的。
1.1.常用基本示例
1.1.1.示例1
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ForeachDemo1)set(MY_LIST A B C)foreach(item ${MY_LIST})message(STATUS "当前 item: ${item}")
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
1.1.2.示例2
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ForeachDemo1)foreach(color Red Green Blue)message(STATUS "颜色: ${color}")
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
1.1.3.示例3——嵌套foreach
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ForeachDemo1)set(ROWS 1 2 3)
set(COLS A B)foreach(r ${ROWS})foreach(c ${COLS})message(STATUS "坐标: ${r}${c}")endforeach()
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
1.1.4.示例4——遮盖外部变量
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ShadowDemo)# 外层变量
set(VAR "OuterValue")
message(STATUS "循环前外层 VAR = ${VAR}")# 外层循环
foreach(VAR A B C)# 循环内部访问的是循环变量,不再是外层 VARmessage(STATUS "循环内部 VAR = ${VAR}")
endforeach()# 循环结束后,VAR 恢复为外层值
message(STATUS "循环结束后外层 VAR = ${VAR}")
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
1.1.5.示例5——break
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ForeachBreakDemo)set(MY_LIST 1 2 3 4 5)foreach(item ${MY_LIST})if(item EQUAL 3)message(STATUS "遇到 3,结束整个循环")break()endif()message(STATUS "处理 item: ${item}")
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
当 item=3
时,break()
立即停止整个循环,不再进行任何后续迭代。
1.1.6.示例6——continue
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ForeachContinueDemo)set(MY_LIST 1 2 3 4 5)foreach(item ${MY_LIST})if(item EQUAL 3)message(STATUS "遇到 3,跳过本次迭代")continue()endif()message(STATUS "处理 item: ${item}")
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
当 item=3
时,continue()
让循环直接跳到下一次迭代,后面的命令 (message(STATUS "处理 item: ${item}")
) 被跳过。
1.1.7.示例7——break和continue的结合
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(SimpleNestedForeachDemo)set(OUTER_LIST 1 2)
set(INNER_LIST a b c)foreach(outer ${OUTER_LIST})message(STATUS "外层循环 outer=${outer}")foreach(inner ${INNER_LIST})# 内层 continueif(inner STREQUAL "b")message(STATUS " 内层循环遇到 b,跳过本次迭代")continue()endif()# 内层 breakif(inner STREQUAL "c")message(STATUS " 内层循环遇到 c,跳出内层循环")break()endif()message(STATUS " 内层循环处理 inner=${inner}")endforeach()
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
和我们之前学的是不是一样啊。
1.2.范围循环 (RANGE)
foreach(<loop_var> RANGE <stop>)
在此变体中,foreach
遍历数字 0, 1, ... 直到(并包括)非负整数 <stop>
。
foreach(<loop_var> RANGE <start> <stop> [<step>])
在此变体中,foreach
以 <step>
为步长,遍历从 <start>
开始直到最多 <stop>
的数字。
如果未指定 <step>
,则步长大小为 1。
三个参数 <start>
、<stop>
、<step>
必须都是非负整数,且 <stop>
不得小于 <start>
;
否则您将进入未 documented 行为的危险区域,这些行为可能在未来的版本中更改。
1.2.1.示例1
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ForeachRangeDemo1)foreach(i RANGE 5)message(STATUS "i = ${i}")
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
1.2.2.示例2
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ForeachRangeDemo2)foreach(i RANGE 2 10 2)message(STATUS "i = ${i}")
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
-
语法:
RANGE <start> <stop> <step>
-
步长为 2,从 2 到 10(包含 10)。
1.2.3.示例3——倒序循环
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ForeachRangeDemo3)foreach(i RANGE 5 0 -1)message(STATUS "i = ${i}")
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
-
可以用负步长实现倒序循环。
1.3.列表循环 (IN LISTS/ITEMS)
foreach(<loop_var> IN [LISTS [<lists>]] [ITEMS [<items>]])
在此变体中,<lists>
是一个由空格或分号分隔的列表值变量(list-valued variables)的列表。foreach
命令遍历每个给定列表中的每个项目。ITEMS
关键字后面的 <items>
的处理方式与第一种 foreach
命令变体相同。形式 LISTS A
和 ITEMS ${A}
是等价的。
这么说大家肯定看不懂,我们来好好讲讲
我们先把这个完整的语法拆开看:
foreach(<loop_var> IN [LISTS [<lists>]] [ITEMS [<items>]])<commands>
endforeach()
这里有两个可选的关键部分:LISTS [<lists>]
和 ITEMS [<items>]
。你可以只使用其中一个,也可以两个一起使用。
第一部分:LISTS [<lists>]
-
<lists>
是什么? 它是一个或多个列表变量名(List Variable Names)。 -
注意: 这里放的是变量的名字,而不是变量本身的值。你不需要在这里使用
${}
。 -
作用: 告诉
foreach
循环:“请去找到这些名字的变量,把它们里面所有的值都拿出来,作为要遍历的项目。”
举个例子:
假设你有两个变量:
set(MyListA a b c)
set(MyListB 1 2 3)
那么 LISTS MyListA MyListB
的意思就是:“请把变量 MyListA
里的所有值 (a
, b
, c
) 和变量 MyListB
里的所有值 (1
, 2
, 3
) 都合并到一起。”
第二部分:ITEMS [<items>]
-
<items>
是什么? 它是一个或多个直接的值(Direct Values)。 -
作用: 告诉
foreach
循环:“除了从变量里拿值,我还要直接加入这些值。”
接着上面的例子:
ITEMS x y z
的意思就是:“在刚才那些值的基础上,再直接加上 x
, y
, z
这三个值。”
组合起来的工作原理
当 LISTS
和 ITEMS
一起使用时,foreach
循环的执行流程如下:
-
收集阶段(Collection Phase):在循环开始之前,CMake 会先做一件事:
-
找到所有在
LISTS
后面指定的变量名(如MyListA
,MyListB
),获取这些变量的值(即它们所代表的列表)。 -
获取所有在
ITEMS
后面直接指定的值(如x
,y
,z
)。 -
将上述所有值按顺序拼接成一个大的、总的列表。
-
上面例子的最终列表将是:
a
,b
,c
,1
,2
,3
,x
,y
,z
。
-
-
-
执行阶段(Execution Phase):
-
接下来,循环就变得和最基本的
foreach
循环一模一样了。 -
CMake 会遍历这个刚刚生成的总的列表。
-
在每次迭代中,将列表中的当前值赋给循环变量
<loop_var>
,然后执行一次循环体内的命令。
-
所以,这个复杂的语法最终的目的就是为了生成一个用于遍历的总列表。
关键理解:等价形式
文档里说了一句非常关键的话:LISTS A
和 ITEMS ${A}
是等价的。
我们来拆解一下这句话:
-
LISTS A
:意思是“去找到名为A
的变量,把它里面的值都拿来”。 -
ITEMS ${A}
:意思是“直接把这些值拿来”。这里${A}
已经被展开(Expand) 了。如果A
的值是a b c
,那么ITEMS ${A}
就完全等价于ITEMS a b c
。
因此,这两种写法最终会导致循环遍历完全相同的项目序列。 它们只是达到目的的两种不同路径而已。
写法 | 含义 |
---|---|
| “喂,循环,你去把变量 |
| “喂,循环,我直接把 |
通常,使用 LISTS
是更推荐的方式,因为它更直接,不易出错。
1.3.1.示例 1——最基础的列表遍历
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ForeachListsDemo1)set(MY_LIST a b c)foreach(item IN LISTS MY_LIST)message(STATUS "当前 item = ${item}")
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
1.3.2.示例2——IN LISTS
遍历多个列表
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ForeachListsDemo3)set(LIST1 red green)
set(LIST2 blue yellow)foreach(color IN LISTS LIST1 LIST2)message(STATUS "颜色: ${color}")
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
1.3.3.示例3——IN LISTS
+ ITEMS
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ForeachListsItemsDemo)# 定义几个列表变量
set(LIST1 a b c)
set(LIST2 1 2 3)# foreach 同时使用 LISTS 和 ITEMS
foreach(item IN LISTS LIST1 LIST2 ITEMS x y z)message(STATUS "当前 item = ${item}")
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
1.3.4.示例4——LISTS A
和 ITEMS ${A}
完全等价
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ForeachListsVsItemsDemo)# 定义一个列表变量
set(MY_LIST a b c)# 使用 LISTS
message(STATUS "使用 LISTS:")
foreach(item IN LISTS MY_LIST)message(STATUS " item = ${item}")
endforeach()# 使用 ITEMS
message(STATUS "使用 ITEMS:")
foreach(item IN ITEMS ${MY_LIST})message(STATUS " item = ${item}")
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
1.4.压缩列表循环 (ZIP_LISTS) - 并行迭代
foreach(<loop_var>... IN ZIP_LISTS <lists>)
3.17 版本新增。
在此变体中,<lists>
是一个由空格或分号分隔的列表值变量的列表。foreach
命令同时遍历每个列表,并按如下方式设置迭代变量:
-
如果只给定一个
loop_var
,则它会设置一系列loop_var_N
变量(如num_0
,num_1
)为相应列表中的当前项目; -
如果传递了多个变量名,它们的数量应与列表变量的数量匹配;
-
如果任何列表较短,则相应的迭代变量在当前迭代中未定义。
我们这么看其实是有点小困难的,我们现在来好好讲讲
好的,这个语法是 foreach
循环中最高级的一个变体,它用于同步遍历多个列表。它不是在遍历一个由所有列表拼接成的大列表,而是像拉链(Zip)一样,将多个列表的对应元素配对处理。我们把它彻底讲清楚。
核心思想:并行遍历
想象你有两个列表:
-
Names = Alice Bob Charlie
-
IDs = 1001 1002 1003
普通的 foreach
会先遍历完 Names
的所有元素,再遍历 IDs
的所有元素。而 ZIP_LISTS
的目的是让你在同一次迭代中同时访问到这两个列表的第一个元素(Alice
和 1001
),下一次迭代访问第二个元素(Bob
和 1002
),以此类推。
它得名于像“拉链”一样将两个列表咬合在一起。
Names: Alice Bob Charlie| | |
IDs: 1001 1002 1003
语法分解和两种模式
foreach(<loop_var>... IN ZIP_LISTS <lists>)
这里的 <lists>
和之前一样,是一个或多个列表变量名(不需要 ${}
)。
这个语法根据你提供的 <loop_var>
的数量,有两种截然不同的工作模式:
模式一:单个循环变量(自动索引模式)
语法: foreach(<single_var> IN ZIP_LISTS <lists>)
执行流程:
-
录制阶段:CMake 解析器识别该语法结构,记录下需要循环执行的命令块。其中,
<lists>
可以包含一个或多个列表变量名,例如ListA ListB ListC
。 -
执行阶段:
-
循环的总迭代次数由
<lists>
中长度最长的那个列表决定,我们将其长度记为N
。 -
在每一次迭代中(例如第
i
次迭代,i
从0
开始),CMake 会执行以下操作:-
它不会直接将一个值赋给变量
<single_var>
。 -
取而代之的是,它会自动创建一组新的变量。这组变量的命名规则为:
<single_var>_0
,<single_var>_1
,<single_var>_2
, ...,变量的数量与<lists>
中提供的列表数量相同。 -
每个新变量的值有其明确的对应关系:
<single_var>_M
的值来自于<lists>
中第M+1
个列表的第i
个元素。-
具体来说,
<single_var>_0
对应第一个列表的第i
个元素。 -
<single_var>_1
对应第二个列表的第i
个元素。 -
依此类推,
<single_var>_M
对应第M+1
个列表的第i
个元素。
-
-
-
举个例子:
set(Names Alice Bob Charlie)
set(IDs 1001 1002 1003)foreach(person IN ZIP_LISTS Names IDs)message("Name: ${person_0}, ID: ${person_1}")
endforeach()
输出:
Name: Alice, ID: 1001
Name: Bob, ID: 1002
Name: Charlie, ID: 1003
-
在第一次迭代中,CMake 自动创建了
person_0
(值为Alice
)和person_1
(值为1001
)。 -
你通过
person_0
和person_1
来访问并行列表中的当前元素。
模式二:多个循环变量(一一对应模式)
语法: foreach(<var1> <var2> <var3> ... IN ZIP_LISTS <lists>)
执行流程:
-
录制阶段:CMake 解析器会检查关键字
IN ZIP_LISTS
前面的变量名数量。 -
执行阶段:
-
循环的迭代次数同样由
<lists>
中最长的列表决定。 -
在每一次迭代中,CMake 会按顺序将
<lists>
中每个列表的当前元素,直接赋给你提供的循环变量。-
第一个列表的当前元素 ->
<var1>
-
第二个列表的当前元素 ->
<var2>
-
第三个列表的当前元素 ->
<var3>
-
...以此类推。
-
-
举个例子(实现和上面同样的功能):
set(Names Alice Bob Charlie)
set(IDs 1001 1002 1003)foreach(name id IN ZIP_LISTS Names IDs)message("Name: ${name}, ID: ${id}")
endforeach()
输出:
Name: Alice, ID: 1001
Name: Bob, ID: 1002
Name: Charlie, ID: 1003
-
这种模式更直观,你直接为每个列表指定了一个有意义的变量名(
name
,id
),而不是使用自动生成的索引名(person_0
,person_1
)。
处理列表长度不一致的情况
这是 ZIP_LISTS
的一个关键行为。文档指出:“如果任何列表较短,则相应的迭代变量在当前迭代中未定义。”
举个例子:
set(ListA a b c d e) # 最长,有5个元素
set(ListB 1 2 3) # 较短,只有3个元素
set(ListC X Y) # 最短,只有2个元素foreach(a_val b_val c_val IN ZIP_LISTS ListA ListB ListC)message("A: ${a_val}, B: ${b_val}, C: ${c_val}")
endforeach()
输出:
A: a, B: 1, C: X # 第一次迭代,所有变量都有值
A: b, B: 2, C: Y # 第二次迭代,所有变量都有值
A: c, B: 3, C: # 第三次迭代!ListC 用完了,所以 ${c_val} 是未定义的(空字符串)
A: d, B: , C: # 第四次迭代,ListB 和 ListC 都用完了
A: e, B: , C: # 第五次迭代,只有 ListA 还有值
-
因为
ListA
最长(5个元素),所以循环会执行 5 次。 -
当某个列表的元素被取完后,对应那个列表的循环变量将变成“未定义”。在CMake中,引用一个未定义的变量通常会得到一个空字符串(
""
),这可能会在后续逻辑中导致错误。 -
因此,在使用
ZIP_LISTS
时,你通常需要确保列表长度一致,或者在循环体内使用if(DEFINED ...)
来检查变量是否有效,以避免错误。
1.4.1.示例1
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(ForeachZipListsDemo)# 定义两个等长的列表
set(NUMBERS 1 2 3)
set(LETTERS A B C)# 使用 ZIP_LISTS 同时遍历
foreach(pair IN ZIP_LISTS NUMBERS LETTERS)message(STATUS "当前 NUMBERS=${pair_0}, LETTERS=${pair_1}")
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
1.4.2.示例2
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(ForeachZipListsDemo)set(NUMBERS 1 2 3 4)
set(LETTERS A B C D)
set(COLORS Red Green Blue Yellow)foreach(num letter color IN ZIP_LISTS NUMBERS LETTERS COLORS)message(STATUS "num=${num}, letter=${letter}, color=${color}")
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
1.4.2.示例3——长短不一
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(ForeachZipListsDemo)set(NUMBERS 1 2 3 4)
set(LETTERS A B C)
set(COLORS Red Green)foreach(num letter color IN ZIP_LISTS NUMBERS LETTERS COLORS)message(STATUS "num=${num}, letter=${letter}, color=${color}")
endforeach()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果
二.while()
大家可以去官网看看:while — CMake 4.1.1 Documentation
当条件为真时,评估一组命令
while(<condition>)<commands>
endwhile()
在 while 和与之匹配的 endwhile() 之间的所有命令会被记录下来但不会立即调用。一旦评估到 endwhile(),只要 <condition> 条件为真,就会调用所记录的命令列表。
<condition> 条件的语法和评估逻辑与 if() 命令中详细描述的完全相同。
break() 和 continue() 命令提供了跳出正常控制流的方法。
出于历史遗留原因,endwhile() 命令允许一个可选的 <condition> 参数。如果使用该参数,它必须与开头 while 命令的参数完全一致。
这个while循环大家都不陌生吧,我们现在就只需要知道
<condition> 条件的语法和评估逻辑与 if() 命令中详细描述的完全相同。
1.常量表达式(Constant Expressions)
当 if()
命令中的参数是一个常量(即一个被直接写出的值,而非变量引用)时,其真假判断遵循明确的规则:
-
评估为真(True)的常量:包括
1
、ON
、YES
、TRUE
、Y
以及任何非零的数字(无论是整数如2
,-1
,还是浮点数如0.5
,3.14
)。这些值均表示逻辑真。 -
评估为假(False)的常量:包括
0
、OFF
、NO
、FALSE
、N
、IGNORE
、NOTFOUND
、空字符串(""
)以及任何以后缀-NOTFOUND
结尾的字符串(例如MyPackage-NOTFOUND
)。这些值均表示逻辑假。
一个重要特征是这些命名的布尔常量(如 ON/OFF, YES/NO)是不区分大小写的。因此,on
, On
, ON
, yes
, Yes
, YES
都会被视为真。
如果所提供的参数不符合上述任何特定常量,CMake 则不会将其视为常量,而是会尝试将其解释为一个变量名称或一个字符串。
2.变量形式(if(<variable>))
如果参数是一个未被引号包围的名称,CMake 会将其视为一个变量名,并执行变量扩展。其判断逻辑为:
-
真:当且仅当该变量已经被定义,并且其扩展后的值不属于上面1.1节里面说的“假常量”列表。
-
假:在以下两种情况下评估为假:
-
该变量已被定义,但其值是一个“假常量”。包括
0
、OFF
、NO
、FALSE
、N
、IGNORE
、NOTFOUND
、空字符串(""
)以及任何以后缀-NOTFOUND
结尾的字符串(例如MyPackage-NOTFOUND
)。这些值均表示逻辑假。 -
该变量未被定义。访问一个不存在的变量会将其值视为空字符串(一个假常量),因此同样返回假。
-
重要限制:
宏参数(Macro Parameters):在宏(macro
)内部,宏的参数不是传统意义上的 CMake 变量,因此不能以 if(<parameter>)
的形式进行测试。应使用 if(${parameter})
进行变量扩展后再判断其值。
环境变量(Environment Variables):不能使用 if(ENV{some_var})
来测试环境变量是否存在或为其值是否为真。ENV{some_var}
是一个特殊的引用语法,整个表达式 ENV{...}
本身不会被识别为一个变量名。测试环境变量的正确方法是先将其值赋给一个普通变量,例如 set(MY_ENV_VAR "$ENV{SOME_VAR}")
,然后再使用 if(MY_ENV_VAR)
进行判断。
3.字符串形式(if(<string>))
带引号的字符串在 CMake 中通常始终被视为“假”,除非满足以下任一条件:
-
字符串本身是公认的真常量之一,包括:
1
、ON
、YES
、TRUE
、Y
,以及任何非零数值(整数如2
、-1
,或浮点数如0.5
、3.14
)。这些值在逻辑判断中均代表“真”。 -
另一种情况则与策略
CMP0054
有关,具体说明如下:
我们来仔细讲解
-
在 CMake 4.0 之前的版本中,如果策略
CMP0054
未被显式设置为NEW
(即处于OLD
行为下),当引号内的字符串内容恰好是一个已定义变量的名称时,CMake 会执行一种被称为“未引号参数自动变量扩展”的行为。这意味着if("${some_var}")
和if(some_var)
可能会因为变量some_var
的值不同而产生不同的结果,而if("some_var")
甚至可能被解释为去查询一个名为some_var
的变量。这种行为非常容易导致混淆和错误。 -
自 CMake 4.0 起,策略
CMP0054
默认变为NEW
,此行为被禁止。在NEW
行为下,引号内的内容永远被视为字符串值,而不会自动被当作变量名再次展开。因此,if("some_text")
在任何情况下都只判断字符串"some_text"
是否为真常量,而不会去查找一个名为some_text
的变量。这是推荐的做法,它能带来更清晰、更可预测的逻辑。
2.1.示例1
📂 项目目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)project(whileDemo)# 1 初始化变量
set(i 1)# 2 当i <= 5 时 就输出i的值
while(i LESS_EQUAL 5)message(STATUS "i = ${i}")math(EXPR i "${i} +1") # i++
endwhile()
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
运行结果