简体中文 繁體中文 English Deutsch 한국 사람 بالعربية TÜRKÇE português คนไทย Français Japanese

站内搜索

搜索

活动公告

通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,将及时处理!
10-23 09:31

深入理解CMake中的列表与数组操作 从基础到高级应用的完整指南

SunJu_FaceMall

3万

主题

166

科技点

3万

积分

大区版主

碾压王

积分
32106
发表于 2025-8-23 21:30:36 | 显示全部楼层 |阅读模式 [标记阅至此楼]

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1. 引言

CMake是一个开源、跨平台的构建自动化工具,它使用配置文件(CMakeLists.txt)来生成标准的构建文件(如Makefile或Visual Studio项目)。CMake被广泛用于C++项目的构建系统管理,但也可以用于其他语言的构建。

在CMake中,列表(list)是一种基本而重要的数据结构。虽然CMake没有传统编程语言中的”数组”概念,但列表功能在大多数情况下可以充当数组的角色。列表是一系列由分号(;)分隔的字符串,CMake提供了丰富的命令来操作这些列表。

理解如何有效操作列表对于编写高效、灵活的CMake脚本至关重要。无论是处理源文件列表、管理编译选项,还是处理依赖项,列表操作都是必不可少的技能。

2. 列表的基础知识

2.1 列表的概念与表示

在CMake中,列表本质上是一个由分号(;)分隔的字符串。例如,”a;b;c”是一个包含三个元素的列表。CMake在内部处理列表时,会自动处理这种分隔。

列表可以显式或隐式地创建:
  1. # 显式创建列表
  2. set(my_list "a" "b" "c")
  3. # 隐式创建列表
  4. set(my_other_list "a;b;c")  # 这与上面的列表等价
复制代码

2.2 创建列表

有几种常见的方法可以创建列表:
  1. # 方法1:使用set命令
  2. set(simple_list "item1" "item2" "item3")
  3. # 方法2:通过分号分隔的字符串创建
  4. set(another_list "item1;item2;item3")
  5. # 方法3:通过变量扩展创建
  6. set(base_list "item1" "item2")
  7. set(extended_list ${base_list} "item3")  # 结果为 "item1;item2;item3"
复制代码

2.3 访问列表元素

CMake提供了list命令来访问和操作列表。要获取列表的长度或特定元素:
  1. set(my_list "apple" "banana" "cherry")
  2. # 获取列表长度
  3. list(LENGTH my_list list_length)
  4. message(STATUS "List length: ${list_length}")  # 输出: List length: 3
  5. # 获取特定索引的元素(索引从0开始)
  6. list(GET my_list 1 first_item)
  7. message(STATUS "Item at index 1: ${first_item}")  # 输出: Item at index 1: banana
  8. # 获取子列表
  9. list(SUBLIST my_list 1 2 sublist)
  10. message(STATUS "Sublist: ${sublist}")  # 输出: Sublist: banana;cherry
复制代码

2.4 列表的字符串表示

当需要将列表作为字符串处理时,可以使用JOIN操作:
  1. set(my_list "apple" "banana" "cherry")
  2. # 用逗号和空格连接列表元素
  3. list(JOIN my_list ", " joined_string)
  4. message(STATUS "Joined string: ${joined_string}")  # 输出: Joined string: apple, banana, cherry
复制代码

3. 列表的基本操作

3.1 添加元素

向列表添加元素是常见的操作:
  1. set(my_list "item1" "item2")
  2. # 在末尾添加元素
  3. list(APPEND my_list "item3")
  4. message(STATUS "After append: ${my_list}")  # 输出: After append: item1;item2;item3
  5. # 在开头添加元素
  6. list(PREPEND my_list "item0")
  7. message(STATUS "After prepend: ${my_list}")  # 输出: After prepend: item0;item1;item2;item3
复制代码

3.2 插入元素

在指定位置插入元素:
  1. set(my_list "item1" "item2" "item3")
  2. # 在索引1处插入新元素
  3. list(INSERT my_list 1 "new_item")
  4. message(STATUS "After insert: ${my_list}")  # 输出: After insert: item1;new_item;item2;item3
复制代码

3.3 删除元素

从列表中删除元素:
  1. set(my_list "item1" "item2" "item3" "item2")
  2. # 按索引删除元素
  3. list(REMOVE_AT my_list 1)
  4. message(STATUS "After remove at: ${my_list}")  # 输出: After remove at: item1;item3;item2
  5. # 按值删除元素(删除所有匹配项)
  6. list(REMOVE_ITEM my_list "item2")
  7. message(STATUS "After remove item: ${my_list}")  # 输出: After remove item: item1;item3
  8. # 删除重复元素
  9. list(REMOVE_DUPLICATES my_list)
  10. message(STATUS "After remove duplicates: ${my_list}")  # 输出: After remove duplicates: item1;item3
复制代码

3.4 修改元素

修改列表中的特定元素:
  1. set(my_list "item1" "item2" "item3")
  2. # 修改特定索引的元素
  3. list(REMOVE_AT my_list 1)
  4. list(INSERT my_list 1 "new_item2")
  5. message(STATUS "After modification: ${my_list}")  # 输出: After modification: item1;new_item2;item3
复制代码

4. 列表的高级操作

4.1 搜索元素

在列表中搜索特定元素:
  1. set(my_list "apple" "banana" "cherry" "date")
  2. # 查找元素的索引
  3. list(FIND my_list "cherry" index)
  4. message(STATUS "Index of 'cherry': ${index}")  # 输出: Index of 'cherry': 2
  5. # 检查元素是否存在
  6. if(index GREATER -1)
  7.     message(STATUS "'cherry' found in the list")
  8. else()
  9.     message(STATUS "'cherry' not found in the list")
  10. endif()
复制代码

4.2 排序列表

对列表进行排序:
  1. set(my_list "banana" "apple" "date" "cherry")
  2. # 按字母顺序升序排序
  3. list(SORT my_list)
  4. message(STATUS "Sorted list: ${my_list}")  # 输出: Sorted list: apple;banana;cherry;date
  5. # 按字母顺序降序排序
  6. list(SORT my_list COMPARE STRING ORDER DESCENDING)
  7. message(STATUS "Reverse sorted list: ${my_list}")  # 输出: Reverse sorted list: date;cherry;banana;apple
  8. # 按自然顺序排序(考虑数字)
  9. set(numeric_list "item10" "item2" "item1")
  10. list(SORT numeric_list COMPARE NATURAL)
  11. message(STATUS "Naturally sorted list: ${numeric_list}")  # 输出: Naturally sorted list: item1;item2;item10
复制代码

4.3 列表转换

将列表转换为其他格式或从其他格式创建列表:
  1. # 将字符串转换为列表
  2. set(string "a b c")
  3. string(REPLACE " " ";" list_from_string "${string}")
  4. message(STATUS "List from string: ${list_from_string}")  # 输出: List from string: a;b;c
  5. # 将列表转换为字符串
  6. set(my_list "a" "b" "c")
  7. string(REPLACE ";" " " string_from_list "${my_list}")
  8. message(STATUS "String from list: ${string_from_list}")  # 输出: String from list: a b c
复制代码

4.4 列表过滤

根据特定条件过滤列表:
  1. set(source_files "main.cpp" "utils.cpp" "test_main.cpp" "utils.h" "test_utils.h")
  2. # 过滤出所有.cpp文件
  3. set(cpp_files "")
  4. foreach(file ${source_files})
  5.     if(file MATCHES "\\.cpp$")
  6.         list(APPEND cpp_files ${file})
  7.     endif()
  8. endforeach()
  9. message(STATUS "C++ files: ${cpp_files}")  # 输出: C++ files: main.cpp;utils.cpp;test_main.cpp
  10. # 使用filter命令(CMake 3.6+)
  11. list(FILTER source_files INCLUDE REGEX "\\.(cpp|h)$")
  12. message(STATUS "Filtered source files: ${source_files}")  # 输出: Filtered source files: main.cpp;utils.cpp;test_main.cpp;utils.h;test_utils.h
复制代码

5. 列表在CMake脚本中的实际应用

5.1 管理源文件

列表最常见的用途之一是管理项目中的源文件:
  1. # 收集所有源文件
  2. set(SOURCES
  3.     src/main.cpp
  4.     src/utils.cpp
  5.     src/graphics/renderer.cpp
  6.     src/graphics/shader.cpp
  7. )
  8. # 收集所有头文件
  9. set(HEADERS
  10.     include/utils.h
  11.     include/graphics/renderer.h
  12.     include/graphics/shader.h
  13. )
  14. # 创建可执行文件
  15. add_executable(my_app ${SOURCES} ${HEADERS})
  16. # 条件性地添加源文件
  17. if(ENABLE_TESTS)
  18.     list(APPEND SOURCES
  19.         test/test_main.cpp
  20.         test/test_utils.cpp
  21.     )
  22.     add_executable(my_app_test ${SOURCES} ${HEADERS})
  23. endif()
复制代码

5.2 管理编译选项

使用列表管理编译选项和定义:
  1. # 基本编译选项
  2. set(COMMON_FLAGS
  3.     -Wall
  4.     -Wextra
  5.     -O2
  6. )
  7. # 调试模式下的额外选项
  8. if(CMAKE_BUILD_TYPE STREQUAL "Debug")
  9.     list(APPEND COMMON_FLAGS
  10.         -g
  11.         -DDEBUG
  12.     )
  13. endif()
  14. # 应用编译选项
  15. target_compile_options(my_app PRIVATE ${COMMON_FLAGS})
  16. # 管理预处理器定义
  17. set(DEFINITIONS
  18.     PLATFORM_DESKTOP
  19.     VERSION_MAJOR=1
  20.     VERSION_MINOR=0
  21. )
  22. if(ENABLE_FEATURE_X)
  23.     list(APPEND DEFINITIONS FEATURE_X)
  24. endif()
  25. target_compile_definitions(my_app PRIVATE ${DEFINITIONS})
复制代码

5.3 管理链接库

使用列表管理项目依赖的库:
  1. # 系统库
  2. set(SYSTEM_LIBS
  3.     pthread
  4.     dl
  5. )
  6. # 第三方库
  7. find_package(Boost REQUIRED COMPONENTS filesystem system)
  8. set(THIRD_PARTY_LIBS
  9.     Boost::filesystem
  10.     Boost::system
  11. )
  12. # 项目内部库
  13. add_subdirectory(libs/utils)
  14. add_subdirectory(libs/graphics)
  15. set(INTERNAL_LIBS
  16.     utils
  17.     graphics
  18. )
  19. # 链接所有库
  20. target_link_libraries(my_app
  21.     PRIVATE
  22.         ${SYSTEM_LIBS}
  23.         ${THIRD_PARTY_LIBS}
  24.         ${INTERNAL_LIBS}
  25. )
复制代码

5.4 处理平台特定代码

使用列表处理不同平台的特定代码:
  1. # 通用源文件
  2. set(COMMON_SOURCES
  3.     src/main.cpp
  4.     src/utils.cpp
  5. )
  6. # 平台特定源文件
  7. if(WIN32)
  8.     list(APPEND COMMON_SOURCES
  9.         src/platform/windows.cpp
  10.     )
  11. elseif(UNIX AND NOT APPLE)
  12.     list(APPEND COMMON_SOURCES
  13.         src/platform/linux.cpp
  14.     )
  15. elseif(APPLE)
  16.     list(APPEND COMMON_SOURCES
  17.         src/platform/macos.cpp
  18.     )
  19. endif()
  20. # 创建可执行文件
  21. add_executable(my_app ${COMMON_SOURCES})
复制代码

6. 列表操作的最佳实践和常见陷阱

6.1 最佳实践
  1. # 好的做法
  2. set(my_list "item1" "item2" "item3")
  3. list(APPEND my_list "item4")
  4. # 不好的做法
  5. set(my_string "item1;item2;item3")
  6. set(my_string "${my_string};item4")  # 容易出错,特别是当元素包含分号时
复制代码
  1. # 好的做法
  2. set(my_list "item with spaces" "another item")
  3. # 不好的做法
  4. set(my_list item with spaces another item)  # 这会创建一个包含4个元素的列表
复制代码
  1. # 好的做法
  2. set(my_list "item1" "item2" "item3")
  3. list(REMOVE_ITEM my_list "item2")
  4. # 不好的做法
  5. set(new_list "")
  6. foreach(item ${my_list})
  7.     if(NOT item STREQUAL "item2")
  8.         list(APPEND new_list ${item})
  9.     endif()
  10. endforeach()
  11. set(my_list ${new_list})
复制代码

6.2 常见陷阱
  1. set(my_list "item1" "" "item3")
  2. list(LENGTH my_list len)
  3. message(STATUS "Length: ${len}")  # 输出: Length: 3,空元素也被计算在内
复制代码
  1. # 问题:元素本身包含分号
  2. set(problematic_list "item1" "item;with;semicolons" "item3")
  3. list(LENGTH problematic_list len)
  4. message(STATUS "Length: ${len}")  # 输出: Length: 5,而不是预期的3
  5. # 解决方案:使用引号
  6. set(correct_list "item1" "item;with;semicolons" "item3")
  7. list(LENGTH correct_list len)
  8. message(STATUS "Length: ${len}")  # 输出: Length: 3
复制代码
  1. set(my_list "item1" "item2" "item3")
  2. # 危险:在迭代过程中修改列表
  3. foreach(item ${my_list})
  4.     if(item STREQUAL "item2")
  5.         list(REMOVE_ITEM my_list ${item})  # 这可能导致意外的行为
  6.     endif()
  7. endforeach()
  8. # 更安全的方法:创建一个新列表
  9. set(my_list "item1" "item2" "item3")
  10. set(new_list "")
  11. foreach(item ${my_list})
  12.     if(NOT item STREQUAL "item2")
  13.         list(APPEND new_list ${item})
  14.     endif()
  15. endforeach()
  16. set(my_list ${new_list})
复制代码

7. 高级技巧和实用示例

7.1 列表转换和映射

将一个列表转换为另一个列表,应用某种转换:
  1. # 将源文件列表转换为目标文件列表
  2. set(source_files "src/main.cpp" "src/utils.cpp" "src/graphics/renderer.cpp")
  3. set(object_files "")
  4. foreach(src ${source_files})
  5.     # 替换扩展名和路径
  6.     string(REPLACE ".cpp" ".o" obj "${src}")
  7.     string(REPLACE "src/" "build/obj/" obj "${obj}")
  8.     list(APPEND object_files ${obj})
  9. endforeach()
  10. message(STATUS "Object files: ${object_files}")
  11. # 输出: Object files: build/obj/main.o;build/obj/utils.o;build/obj/graphics/renderer.o
复制代码

7.2 列表去重和交集

实现列表去重和计算交集:
  1. # 列表去重
  2. set(list_with_duplicates "a" "b" "a" "c" "b" "d")
  3. set(unique_list "")
  4. foreach(item ${list_with_duplicates})
  5.     list(FIND unique_list ${item} index)
  6.     if(index EQUAL -1)
  7.         list(APPEND unique_list ${item})
  8.     endif()
  9. endforeach()
  10. message(STATUS "Unique list: ${unique_list}")
  11. # 输出: Unique list: a;b;c;d
  12. # 计算两个列表的交集
  13. set(list1 "a" "b" "c" "d")
  14. set(list2 "b" "c" "e" "f")
  15. set(intersection "")
  16. foreach(item ${list1})
  17.     list(FIND list2 ${item} index)
  18.     if(NOT index EQUAL -1)
  19.         list(APPEND intersection ${item})
  20.     endif()
  21. endforeach()
  22. message(STATUS "Intersection: ${intersection}")
  23. # 输出: Intersection: b;c
复制代码

7.3 多维列表模拟

虽然CMake没有真正的多维列表,但可以通过特定模式模拟:
  1. # 创建一个"2D列表"(实际上是列表的列表)
  2. set(matrix
  3.     "1;2;3"
  4.     "4;5;6"
  5.     "7;8;9"
  6. )
  7. # 访问"2D列表"的元素
  8. set(row 1)  # 第二行(索引从0开始)
  9. set(col 2)  # 第三列(索引从0开始)
  10. list(GET matrix ${row} row_content)
  11. string(REPLACE ";" " " row_elements "${row_content}")
  12. message(STATUS "Row ${row}: ${row_elements}")
  13. # 将行内容转换为真正的列表
  14. string(REPLACE ";" ";" row_list "${row_content}")
  15. list(GET row_list ${col} element)
  16. message(STATUS "Element at (${row},${col}): ${element}")
  17. # 输出: Element at (1,2): 6
复制代码

7.4 列表操作函数封装

创建自定义函数来封装常见的列表操作:
  1. # 自定义函数:从列表中移除匹配正则表达式的元素
  2. function(remove_regex_matches list_name regex)
  3.     set(input_list ${${list_name}})
  4.     set(result_list "")
  5.     foreach(item ${input_list})
  6.         if(NOT item MATCHES "${regex}")
  7.             list(APPEND result_list ${item})
  8.         endif()
  9.     endforeach()
  10.     set(${list_name} ${result_list} PARENT_SCOPE)
  11. endfunction()
  12. # 使用自定义函数
  13. set(test_list "file1.cpp" "file2.h" "test_file1.cpp" "test_file2.h")
  14. remove_regex_matches(test_list "^test_")
  15. message(STATUS "Filtered list: ${test_list}")
  16. # 输出: Filtered list: file1.cpp;file2.h
  17. # 自定义函数:将列表转换为键值对列表
  18. function(create_key_value_pairs list_name separator)
  19.     set(input_list ${${list_name}})
  20.     set(result_list "")
  21.     foreach(item ${input_list})
  22.         string(REGEX REPLACE "${separator}";";" pair "${item}")
  23.         list(APPEND result_list ${pair})
  24.     endforeach()
  25.     set(${list_name} ${result_list} PARENT_SCOPE)
  26. endfunction()
  27. # 使用自定义函数
  28. set(config_list "DEBUG:TRUE" "LOG_LEVEL:INFO" "MAX_CONNECTIONS:10")
  29. create_key_value_pairs(config_list ":")
  30. message(STATUS "Key-value pairs: ${config_list}")
  31. # 输出: Key-value pairs: DEBUG;TRUE;LOG_LEVEL;INFO;MAX_CONNECTIONS;10
复制代码

7.5 处理命令行参数

使用列表处理传递给CMake脚本的命令行参数:
  1. # 假设CMake通过以下方式调用:
  2. # cmake -DARGS="-O2;-Wall;-DDEBUG" -P process_args.cmake
  3. if(DEFINED ARGS)
  4.     # 将字符串转换为列表
  5.     string(REPLACE ";" " " args_string "${ARGS}")
  6.     message(STATUS "Arguments string: ${args_string}")
  7.    
  8.     # 直接使用列表
  9.     set(args_list ${ARGS})
  10.     message(STATUS "Arguments list: ${args_list}")
  11.    
  12.     # 处理每个参数
  13.     foreach(arg ${args_list})
  14.         if(arg MATCHES "^-D(.+)=(.+)$")
  15.             set(var_name ${CMAKE_MATCH_1})
  16.             set(var_value ${CMAKE_MATCH_2})
  17.             message(STATUS "Define: ${var_name} = ${var_value}")
  18.         elseif(arg MATCHES "^-")
  19.             message(STATUS "Flag: ${arg}")
  20.         else()
  21.             message(STATUS "Other argument: ${arg}")
  22.         endif()
  23.     endforeach()
  24. endif()
复制代码

8. 总结

在CMake中,列表是一种强大而灵活的数据结构,虽然它本质上是由分号分隔的字符串,但CMake提供了丰富的命令来操作这些列表。通过本文,我们深入探讨了CMake中列表操作的各个方面,从基础的创建、访问和修改,到高级的搜索、排序和转换。

我们了解了列表在CMake脚本中的实际应用,包括管理源文件、编译选项、链接库以及处理平台特定代码。同时,我们也讨论了列表操作的最佳实践和常见陷阱,帮助读者避免一些常见的错误。

最后,我们探索了一些高级技巧和实用示例,如列表转换和映射、列表去重和交集、模拟多维列表、封装自定义函数以及处理命令行参数。这些技巧可以帮助读者更有效地利用CMake中的列表功能,编写更加灵活和强大的构建脚本。

掌握CMake中的列表操作是成为CMake专家的重要一步。希望本文能够帮助读者深入理解CMake中的列表与数组操作,从基础到高级应用,为他们的项目构建提供有力支持。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

加入Discord频道

加入Discord频道

加入QQ社群

加入QQ社群

联系我们|小黑屋|TG频道|RSS |网站地图

Powered by Pixtech

© 2025-2026 Pixtech Team.