7. 数学运算与字符串操作:math() 和 string() 命令

这一章我们聊聊 CMake 里两个非常实用的命令——math()string()。说实话,刚开始用 CMake 的时候,我总觉得它就是个「项目配置工具」,跟数学运算、字符串处理这些八竿子打不着。后来项目做多了才发现,这两个命令在构建脚本里简直是救火队员,尤其是跨平台编译的时候,各种路径拼接、版本号解析,全靠它们撑场面。

7.1 math() 命令:让 CMake 算个数

先说说 math()。它的语法很简单:

math(EXPR <output_variable> <expression>)

说白了,就是让 CMake 帮你算个表达式,结果存到变量里。支持的操作符有 + - * / % | & ^ ~ << >>,跟 C 语言差不多。

举个例子:

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

嗯,这里要注意:表达式必须用双引号括起来,否则 CMake 会报错。我刚开始写的时候经常忘,后来养成了习惯,只要用 math() 就顺手加引号。

小技巧:如果你需要做浮点数运算,math() 帮不上忙——它只支持整数。我在项目中遇到过需要计算帧率的情况,最后是用 Python 脚本配合 execute_process() 搞定的。

7.2 string() 命令:字符串操作的瑞士军刀

string() 命令功能非常丰富,我挑几个最常用的来讲。它的基本语法是:

string(SUBSTRING <str> <begin> <length> <output_variable>)
string(REPLACE <match_string> <replace_string> <output_variable> <input>...)
string(TOLOWER <string> <output_variable>)
string(TOUPPER <string> <output_variable>)
string(REGEX MATCH <regular_expression> <output_variable> <input>...)

是不是看着有点晕?别急,我一个一个说。

7.2.1 查找与替换

先看查找:

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

替换也很直观:

string(REPLACE "World" "CMake" NEW_STRING "${MY_STRING}")
message("替换后: ${NEW_STRING}")  # 输出: Hello, CMake CMake!

我个人习惯在替换路径分隔符时用这个命令。比如 Windows 下路径是反斜杠,Linux 下是正斜杠,用 string(REPLACE "\\" "/" ...) 一键搞定。

7.2.2 大小写转换

这个最简单,直接看代码:

string(TOLOWER "Hello" LOWER_STR)
string(TOUPPER "World" UPPER_STR)
message("小写: ${LOWER_STR}, 大写: ${UPPER_STR}")

输出:小写: hello, 大写: WORLD

你想想看,什么时候会用这个?比如你要比较用户输入的选项,不管用户写 ON 还是 on,统一转成大写再比较,省心多了。

7.2.3 正则匹配

正则匹配是 string() 里最强大的功能。我曾在项目中用它解析第三方库的版本号:

set(VERSION_STRING "v3.14.159")
string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" _ "${VERSION_STRING}")
message("匹配结果: ${CMAKE_MATCH_0}")  # 输出: 3.14.159
message("主版本: ${CMAKE_MATCH_1}")   # 输出: 3
message("次版本: ${CMAKE_MATCH_2}")   # 输出: 14
message("修订号: ${CMAKE_MATCH_3}")   # 输出: 159
避坑指南:我曾经在正则匹配时忘了转义点号 .,结果匹配到了 v3a14b159 这种奇怪的东西。记住,在正则表达式里,点号匹配任意字符,要匹配字面意义上的点号,必须写成 \\.

另外,string(REGEX MATCHALL ...) 可以匹配所有出现的位置,返回一个列表。比如:

set(TEXT "apple, banana, apple pie")
string(REGEX MATCHALL "apple" MATCHES "${TEXT}")
message("匹配到 ${MATCHES} 次")  # 输出: apple;apple

7.3 知识体系总览

下面这张图帮你理清本章的核心逻辑:

CMake 数学运算与字符串操作 math() 命令 string() 命令 整数运算:+ - * / % 位运算:| & ^ ~ << >> ⚠ 不支持浮点数 查找与替换:FIND / REPLACE 大小写转换:TOLOWER / TOUPPER 正则匹配:REGEX MATCH / MATCHALL 核心:构建脚本中的数据处理利器

7.4 实战:版本号自动递增

最后分享一个我在项目中实际用过的场景。我们需要在每次构建时自动递增补丁版本号:

# 读取当前版本
file(READ "version.txt" VERSION)
string(STRIP "${VERSION}" VERSION)

# 解析版本号
string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" _ "${VERSION}")
set(MAJOR ${CMAKE_MATCH_1})
set(MINOR ${CMAKE_MATCH_2})
set(PATCH ${CMAKE_MATCH_3})

# 补丁号加1
math(EXPR PATCH "${PATCH} + 1")

# 写回文件
set(NEW_VERSION "${MAJOR}.${MINOR}.${PATCH}")
file(WRITE "version.txt" "${NEW_VERSION}")
message("版本已更新: ${VERSION} → ${NEW_VERSION}")

这段代码把 math()string() 结合得恰到好处。你想想看,如果没有这两个命令,你恐怕得写个外部脚本,再通过 execute_process() 调用,多麻烦。

核心要点回顾:
  • math() 只做整数运算,浮点数请另寻他法
  • string() 的查找、替换、大小写转换、正则匹配是日常高频操作
  • 正则匹配时注意转义特殊字符,尤其是点号 .
  • 两个命令经常配合使用,能解决很多构建脚本里的数据处理问题
公众号:蓝海资料掘金营,微信deep3321