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 知识体系总览
下面这张图帮你理清本章的核心逻辑:
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()的查找、替换、大小写转换、正则匹配是日常高频操作- 正则匹配时注意转义特殊字符,尤其是点号
. - 两个命令经常配合使用,能解决很多构建脚本里的数据处理问题