列表操作:list命令详解

列表操作,说白了就是CMake里最常用的数据容器管理。我刚开始用CMake时,总觉得列表这东西不就是个数组嘛,有啥好学的?结果项目一复杂,各种源文件列表、依赖列表、编译选项列表堆在一起,不会操作列表简直寸步难行。

今天我就把list命令的增删改查、排序去重、转字符串这些操作,掰开揉碎了讲清楚。

列表的增删改查

先说说最基本的——怎么往列表里加东西、删东西、改东西、查东西。

追加元素:APPEND

这个最简单,也最常用。我在项目中经常用它来收集源文件:

set(MY_SOURCES)
list(APPEND MY_SOURCES "main.cpp")
list(APPEND MY_SOURCES "utils.cpp" "network.cpp")
message("源文件列表: ${MY_SOURCES}")
# 输出: main.cpp;utils.cpp;network.cpp

注意,CMake列表的分隔符是分号;,不是逗号。嗯,这里容易踩坑。

插入元素:INSERT

有时候需要在指定位置插入元素,比如把某个头文件路径插到最前面:

list(INSERT MY_SOURCES 0 "config.h")
# 现在列表变成: config.h;main.cpp;utils.cpp;network.cpp

索引从0开始,这个和大多数编程语言一致。

删除元素:REMOVE_ITEM / REMOVE_AT / REMOVE_DUPLICATES

删除操作有三种,我分别说说:

  • REMOVE_ITEM:按值删除,删掉所有匹配的元素
  • REMOVE_AT:按索引删除,只删一个
  • REMOVE_DUPLICATES:去重,后面单独讲
list(REMOVE_ITEM MY_SOURCES "utils.cpp")
# 删掉所有等于"utils.cpp"的元素

list(REMOVE_AT MY_SOURCES 1)
# 删掉索引为1的元素(第二个元素)

我曾经在项目中犯过一个错:用REMOVE_ITEM删一个不存在的值,CMake不会报错,只是静默忽略。所以删除前最好先确认元素存在。

查找元素:FIND

查找元素返回的是索引,找不到返回-1:

list(FIND MY_SOURCES "main.cpp" INDEX)
if(INDEX GREATER -1)
    message("main.cpp 在索引 ${INDEX} 位置")
endif()

这个操作我经常用来做条件判断,比如某个源文件是否存在,决定要不要加特定的编译选项。

列表排序

排序用list(SORT),默认按字典序升序排列:

set(FILES "z.cpp" "a.cpp" "m.cpp")
list(SORT FILES)
message("排序后: ${FILES}")
# 输出: a.cpp;m.cpp;z.cpp

如果想降序,加个COMPARE DESCENDING

list(SORT FILES COMPARE DESCENDING)
# 输出: z.cpp;m.cpp;a.cpp

还有个CASE选项,控制大小写是否敏感。默认是CASE SENSITIVE,如果你想要不区分大小写:

list(SORT FILES COMPARE DESCENDING CASE INSENSITIVE)
小技巧:排序前最好先确认列表元素没有空字符串,否则排序结果可能不符合预期。我遇到过空字符串被排到最前面,排查了半天才发现是某个变量没赋值。

列表去重

去重用REMOVE_DUPLICATES,它会保留第一次出现的元素,删掉后续重复的:

set(LIST_WITH_DUPS "a" "b" "a" "c" "b")
list(REMOVE_DUPLICATES LIST_WITH_DUPS)
message("去重后: ${LIST_WITH_DUPS}")
# 输出: a;b;c

这个操作在收集源文件时特别有用。比如你从多个子目录递归收集源文件,可能会有重复,去重一下就能避免重复编译。

注意:REMOVE_DUPLICATES 会修改原列表,不会返回新列表。如果你需要保留原列表,记得先复制一份。

列表转换字符串

把列表转成字符串,最直接的方式就是用${}展开,但这样得到的是分号分隔的字符串。有时候我们需要自定义分隔符,比如逗号或空格。

CMake没有直接的list(JOIN)命令(3.12版本之前),但可以用foreach循环拼接:

set(MY_LIST "a" "b" "c")
set(JOINED_STR "")
foreach(item IN LISTS MY_LIST)
    if(JOINED_STR)
        string(APPEND JOINED_STR ", ")
    endif()
    string(APPEND JOINED_STR "${item}")
endforeach()
message("拼接结果: ${JOINED_STR}")
# 输出: a, b, c

CMake 3.12之后有了list(JOIN),简单多了:

list(JOIN MY_LIST ", " JOINED_STR)
message("拼接结果: ${JOINED_STR}")
# 输出: a, b, c

我个人习惯用list(JOIN),代码更简洁。如果你的CMake版本够新,建议直接用这个。

知识体系总览

下面这张图总结了列表操作的核心逻辑,我画出来方便你记忆:

list 命令 增删改查 APPEND - 追加 INSERT - 插入 REMOVE_ITEM - 按值删 REMOVE_AT - 按索引删 FIND - 查找索引 排序 SORT - 排序 COMPARE ASCENDING/DESCENDING CASE SENSITIVE/INSENSITIVE 去重 REMOVE_DUPLICATES 保留首次出现 修改原列表 转字符串 ${} 展开 - 分号分隔 list(JOIN) - 自定义分隔符 foreach 手动拼接

实战经验总结

说了这么多,我总结几个实际项目中的经验:

  • 列表长度获取:用list(LENGTH),别傻傻地数分号
  • 列表是否为空:判断LENGTH EQUAL 0,或者直接用if(MY_LIST)——空列表在CMake里是假值
  • 列表复制:直接set(NEW_LIST ${OLD_LIST})就行,列表是值传递
  • 小心空格:列表元素如果包含空格,一定要用引号包起来,否则会被当成多个元素

核心要点:列表操作是CMake构建脚本的基石。增删改查是基本功,排序去重是进阶技巧,转字符串是输出和调试的利器。把这些玩熟了,你的CMake脚本会干净很多。

好了,列表操作就讲到这里。记住,多用message()打印列表内容来调试,这是最笨也最有效的方法。我到现在写CMake脚本,还是习惯在每个关键操作后加个message()看看列表长啥样。


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