第六章 数学与字符串:math命令、string命令详解

说实话,CMake 里的数学和字符串操作,看着简单,用起来坑不少。我最早接触 CMake 时,觉得这玩意儿不就是个构建工具吗?怎么还要处理字符串?后来项目做多了才发现——构建系统里到处是字符串拼接、版本号比较、路径处理。今天咱们就把这块彻底讲透。

6.1 math 命令:CMake 里的计算器

先聊聊数学运算。CMake 的 math 命令,说白了就是个整数计算器。它支持加减乘除、取模、位运算。语法很简单:

math(EXPR <output_variable> <expression>)

看个例子:

set(A 10)
set(B 3)
math(EXPR RESULT "${A} + ${B} * 2")
message("结果: ${RESULT}")  # 输出 16

注意,CMake 的 math 遵循 C 语言的运算符优先级。乘除优先于加减,括号可以改变优先级。我在项目中遇到过一个问题:有人写 math(EXPR X "10 / 3"),期望得到 3.333,结果却是 3。为什么?因为 CMake 只支持整数运算,小数部分直接截断。

避坑指南:CMake 的 math 不支持浮点数。如果你需要小数运算,得用字符串拼接或者外部工具。我曾经在版本号比较时踩过这个坑——"3.10" 和 "3.9" 比较,直接当字符串比会出错。

位运算也支持,不过用得少:

math(EXPR FLAGS "0xFF & 0x0F")  # 按位与,结果 15

我个人习惯把 math 用在计数器、循环索引、或者简单的配置计算上。复杂的数学逻辑,建议交给 Python 脚本或者外部工具。

6.2 string 命令:字符串处理全家桶

字符串操作才是 CMake 的重头戏。string 命令功能非常丰富,我把它分成几类来讲。

6.2.1 字符串查找与替换

查找子串用 FIND

set(TEXT "Hello, CMake World!")
string(FIND "${TEXT}" "CMake" POS)
message("CMake 的位置: ${POS}")  # 输出 7

替换用 REPLACE

string(REPLACE "World" "CMake" NEW_TEXT "${TEXT}")
message("替换后: ${NEW_TEXT}")  # Hello, CMake CMake!

嗯,这里要注意:REPLACE 是全局替换,不是只替换第一个。如果你只想替换一次,得用正则。

小技巧:路径处理时,我经常用 string(REPLACE "/" "\\" WIN_PATH "${UNIX_PATH}") 来转换路径分隔符。Windows 和 Linux 的路径格式不同,这个操作很实用。

6.2.2 字符串大小写转换

大小写转换用 TOLOWERTOUPPER

string(TOLOWER "Hello CMake" LOW)
string(TOUPPER "Hello CMake" UP)
message("小写: ${LOW}")  # hello cmake
message("大写: ${UP}")   # HELLO CMAKE

这个功能在比较字符串时特别有用。比如用户输入的选项可能是 "ON"、"on"、"On",统一转成大写再比较,省心很多。

6.2.3 字符串正则匹配

正则匹配是 string 命令里最强大的功能。CMake 支持基本的正则表达式语法:

set(VERSION "3.27.1")
string(REGEX MATCH "([0-9]+)\\.([0-9]+)" MATCHED "${VERSION}")
message("匹配结果: ${MATCHED}")  # 3.27

string(REGEX MATCHALL "[0-9]+" ALL_NUMS "${VERSION}")
message("所有数字: ${ALL_NUMS}")  # 3;27;1

正则替换也很常用:

string(REGEX REPLACE "([0-9]+)\\.([0-9]+)" "v\\1_\\2" NEW_VER "${VERSION}")
message("替换后: ${NEW_VER}")  # v3_27.1

我在项目中遇到过最典型的场景:解析编译器版本号。GCC 的输出格式经常变,用正则提取主版本号、次版本号,比硬解析靠谱得多。

核心要点:正则匹配时,CMake 使用 \\1\\2 来引用捕获组。注意是双反斜杠,不是单反斜杠。这个细节坑过不少人。

6.3 字符串其他实用操作

除了上面这些,还有几个常用操作:

  • LENGTH:获取字符串长度
  • SUBSTRING:截取子串
  • STRIP:去除首尾空白
  • COMPARE:比较字符串

看个综合例子:

set(INPUT "  CMake 3.27  ")
string(STRIP "${INPUT}" CLEAN_INPUT)
string(LENGTH "${CLEAN_INPUT}" LEN)
string(SUBSTRING "${CLEAN_INPUT}" 0 5 PREFIX)
message("清理后: '${CLEAN_INPUT}'")  # 'CMake 3.27'
message("长度: ${LEN}")              # 11
message("前缀: ${PREFIX}")           # CMake

字符串比较时,我建议用 STREQUAL 而不是 EQUAL。前者是字符串比较,后者会尝试转成数字比较,容易出问题。

6.4 知识体系总览

下面这张图总结了本章的核心内容:

CMake 数学与字符串命令知识体系 math 命令 string 命令 算术运算 (+ - * / %) 位运算 (& | ^ ~) 仅支持整数 查找替换 (FIND/REPLACE) 大小写转换 正则匹配 (REGEX) 其他 (LENGTH/SUBSTRING/STRIP) 注意:math 仅支持整数运算,浮点数需用其他方式处理

6.5 实战经验总结

最后分享几个我这些年积累的经验:

  1. 版本号比较:不要直接用字符串比较 "3.10" 和 "3.9"。我习惯用 string(REGEX MATCHALL) 提取数字,然后逐个比较。
  2. 路径处理:用 string(REPLACE) 转换路径分隔符时,注意 Windows 下反斜杠需要转义。
  3. 正则调试:CMake 的正则引擎比较弱,复杂正则建议先在在线工具上测试,再移植过来。
  4. 性能考虑:在循环里频繁调用 string 命令会影响构建速度。能提前算好的值,尽量用变量缓存。
个人习惯:我写 CMake 时,会把所有字符串操作集中到一个函数里,加上详细的注释。这样半年后回来看代码,还能一眼看懂当时为什么这么写。

数学和字符串操作,是 CMake 构建脚本的基础能力。掌握好了,写出来的 CMakeLists.txt 会更健壮、更易维护。下一章咱们聊聊文件操作和路径处理,这些在实际项目中同样重要。


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