cmake 语法

参考原文链接:https://zhuanlan.zhihu.com/p/653282782

在前文已经学习了CMake作为一门语言的最基本内容,接下来是进一步的内容

  • 流程控制(条件语句,循环语句)
  • 函数和宏
  • cmake脚本/模块(不是CMakeLists,而是.cmake文件)
  • string字符串处理

流程控制

条件语句:if语句

最完整的if语法结构如下

if(<condition>)<commands>
elseif(<condition>) # optional block, can be repeated<commands>
else()              # optional block<commands>
endif()

其中的elseif和else都是可选的,例如

if(WIN32)message(STATUS "Now is windows")
elseif(APPLE)message(STATUS "Now is Apple systens.")
elseif(UNIX)message(STATUS "Now is UNIX-like OS's.")
endif()

正如前文中提到的,在if后面的变量,不需要使用${Var}的形式获取Var的值,而是直接使用Var。

条件语法

在if中条件,也就是if(P)中的命题P可以实现丰富的功能,因此值得详细讨论

{%note info%} 因为if语句出现的太早了,导致if(P)的语法看起来非常奇怪: 尝试对一个变量名称自动求值,if(${P})。 如果希望处理一个可能是变量名的字符串,建议使用双引号if("${P}"),这会抑制if的自动求值。总之要么用if(P)要么用if("${P}") {%endnote%}

基本变量

P可以是最基本的常量,字符串或者变量名。

  1. if(<constant>): P是有意义的常量
  • 1, ON, YES, TRUE, Y $\Rightarrow$ True
  • 非零的数,甚至浮点数 $\Rightarrow$ True
  • 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, *-NOTFOUND $\Rightarrow$ False
  • 空字符串 $\Rightarrow$ False
  • 这里的布尔变量不区分大小写,例如True,true,TRUE等都是可以的
  • 其它情形会被视作一个变量或一个字符串进行处理
  1. if(<variable>): P是一个变量的名称(而非变量的值)
  • 变量已定义,并且变量的值不是上述False常量的情形 $\Rightarrow$ True
  • 变量已定义,但是变量的值是上述False常量的情形 $\Rightarrow$ False
  • 变量未定义 $\Rightarrow$ False
  • 上述规则对宏结构不使用,对环境变量也不使用(环境变量的名称总是得到False)
  1. if(<string>): P是字符串
  • 可以被解析为True常量的字符串 $\Rightarrow$ True
  • 通常情形下,其它的字符串 $\Rightarrow$ False

常见的例如

set(A "") # 空字符串
...if(A)# 如果仍然是空字符串
else()# 如果被改动,被定义并解析为True
endif()

逻辑判断

P可以是一些简单的逻辑判断

# 取反运算
if(NOT <condition>)# 与运算
if(<cond1> AND <cond2>)# 或运算
if(<cond1> OR <cond2>)

可以支持基于与或非的复杂组合,例如

if((condition1) AND (condition2 OR (condition3)))

注意:

  • 可以使用小括号改变计算优先级
  • NOT的优先级比AND/OR更高
  • 对于AND、OR计算顺序从左到右,并且不使用任何短路操作:x AND y在x不正确时仍然解析y

存在性判断

  • if(COMMAND command-name): 判断这个command-name是否属于命令、可调用的宏或者函数的名称,则返回True
  • if(TARGET target-name): 判断这个target是否已经被add_executable(), add_library(), add_custom_target()这类命令创建,即使target不在当前目录下
  • if(DEFINED <name>|CACHE{<name>}|ENV{<name>}): 判断这个变量是否已定义
  • if(<variable|string> IN_LIST <variable>): 判断这个变量或字符串是否在列表中,见下文的列表操作

常见的比较

  • 数字的比较
# 小于
if(<variable|string> LESS <variable|string>)
# 大于
if(<variable|string> GREATER <variable|string>)
# 等于
if(<variable|string> EQUAL <variable|string>)
# 小于或等于
if(<variable|string> LESS_EQUAL <variable|string>)
# 大于或等于
if(<variable|string> GREATER_EQUAL <variable|string>)
  • 字符串的比较(字典序)
if(<variable|string> STRLESS <variable|string>)
if(<variable|string> STRGREATER <variable|string>)
if(<variable|string> STREQUAL <variable|string>)
if(<variable|string> STRLESS_EQUAL <variable|string>)
if(<variable|string> STRGREATER_EQUAL <variable|string>)
  • 版本号比较(版本号格式为major[.minor[.patch[.tweak]]],省略的部分被视为零)
if(<variable|string> VERSION_LESS <variable|string>)
if(<variable|string> VERSION_GREATER <variable|string>)
if(<variable|string> VERSION_EQUAL <variable|string>)
if(<variable|string> VERSION_LESS_EQUAL <variable|string>)
if(<variable|string> VERSION_GREATER_EQUAL <variable|string>)

路径与文件判断

比较简略,因为目录和文件需要考虑平台和格式等,细节比较多,用到再查文档吧

# 完整路径是否存在,这里~开头的还不行
if(EXISTS path-to-file-or-directory)# 两个完整路径下的文件比较时间戳
if(file1 IS_NEWER_THAN file2)# 完整路径是否是一个目录
if(IS_DIRECTORY path-to-directory)# 完整路径是不是绝对路径
if(IS_ABSOLUTE path)
# 对于windows,要求路径以盘符开始
# 对于linux,要求路径以~开始
# 空路径视作false

循环语句

在 CMake 中,循环语句用于在构建过程中重复执行一系列命令或操作。
目前,CMake 支持两种主要的循环语句:foreach 和 while。

foreach循环

基本用法

foreach循环最基本的用法就是遍历一个列表中的所有变量,此时需要使用${Var}先解析列表的值,例如

set(A 1;2;3;4)foreach(X ${A})message("X=${X}")
endforeach()
# 1,2,3,4

由于我们已经把列表解析了,所以下面的用法也是一样的

foreach(x a b c)message("x=${x}")
endforeach()
# a b c

更本质地说,foreach基本用法中,cmake会把第一个位置的字符串定义为循环变量,把剩下的字符串数组视作迭代的列表。

等差数列遍历

foreach支持基于等差数列的遍历,可以使用foreach(... RANGE ...)命令, 其中range n包括从0到n的自然数序列,range a b c可以控制起点终点和步长,语法为

foreach(<loop_var> RANGE <stop>)
foreach(<loop_var> RANGE <start> <stop> [<step>])

例如

foreach(X RANGE 10)message("X=${X}")
endforeach()
# 0,1,...,10foreach(X RANGE 0 10 2)message(STATUS "X=${X}")
endforeach()
# 0,2,4,8,10
遍历多个列表

foreach可以同时在多个列表中遍历,需要使用关键词foreach(... IN ZIP_LISTS ...),总的迭代次数以最长的列表为准,此时较短的列表对应取空值。

方式一,要求循环变量和列表的个数一致,此时每一个循环变量对应在一个列表中遍历,互不干扰

foreach(<loop_vars> IN ZIP_LISTS <lists>)

例如

foreach(en ba IN ZIP_LISTS English Bahasa)message("en=${en}, ba=${ba}")
endforeach()# en=one, ba=satu
# en=two, ba=dua
# en=three, ba=tiga
# en=four, ba=

方式二,通过单个变量loop_var的多个分量loop_var_N实现

foreach(<loop_var> IN ZIP_LISTS <lists>)

这里只用一个loop_var,来承接对后面多个列表的遍历,它通过分量loop_var_N变量记录对应列表的当前项,例如

list(APPEND English one two three four)
list(APPEND Bahasa satu dua tiga)foreach(num IN ZIP_LISTS English Bahasa)message("num_0=${num_0}, num_1=${num_1}")
endforeach()# num_0=one, num_1=satu
# num_0=two, num_1=dua
# num_0=three, num_1=tiga
# num_0=four, num_1=

while循环

和其它语言一样,cmake支持使用break()命令提前终止循环,使用continue()命令可用于立即开始下一次迭代。

cmake还支持while循环,但是感觉用处不大,因为cmake不需要很复杂的循环逻辑,foreach提供基本的遍历已经足够了。

【1】基本语法

while(<condition>)<commands>
endwhile()

【2】参数含义
<condition> 是一个 CMake 表达式,只有<condition>在为真(非空)时,循环体才会执行。
在循环体内,可以使用 break() 来提前跳出循环。

【3】示例

set(i 0)
while(i LESS <n>)# ...math(EXPR i "${i} + 1")
endwhile()

其他:break() 与 continue()

此外,与绝大多数编程语言一样,CMake也可以通过 break() 跳出循环,通过 continue() 结束本次循环并继续下次循环。

函数

函数的语法结构如下:定义名为<name>的函数,该函数接收名为<arg1>...的参数,<commands>表示函数定义的功能,在调用函数之前不会被执行。

function(<name> [<arg1> ...])<commands>
endfunction()

可以使用<name>(...)调用执行函数

  • 函数体的内部存在一个独立的作用域,在内部定义和使用的变量默认都是"局部"的,不会影响到函数体外
  • 调用函数时,对函数名称的大小写不敏感,Foo()FOO()foo()效果一样,建议使用全小写
  • 函数的参数并没有形式和个数的要求,可以传入任意多的参数
  • 函数会把传入的参数视作普通的字符串,把它作为指定名称的参数变量的值,而不是视作传入一个变量进行复制
  • 函数执行时,会首先对函数体内部使用到的函数参数进行一轮替换,然后逐个执行命令

cmake语法设计非常混乱,最基本的对于函数的参数都有很多种调用的方法,这些方法可以混合使用,不会互相冲突。

参数方式(一)

第一种方式,使用cmake默认提供的函数参数代词(注意,它们不是通常的变量,因为在调用函数时会直接全部替换掉)

  • ARGC: 实际传入的参数的个数,与函数定义时预期的参数个数无关
  • ARGV: 实际传入的参数全体(以列表的形式)
  • ARGV#: 实际传入的第#个参数,计数从0开始的每一个参数,例如ARGV0为第一个参数,ARGV1为第二个参数,依次类推(注意,对于计数超过ARGCARGV#,属于未定义的量)
  • ARGN: 预料之外的参数,定义宏(函数)时参数为2个,实际传了4个,则ARGN代表剩下的两个

例如,定义函数如下

function(test)message(STATUS "ARGC=${ARGC}")message(STATUS "ARGV=${ARGV}")message(STATUS "ARGV2=${ARGV2}")message(STATUS "ARGN=${ARGN}")
endfunction(test)

没有给出一个预期的参数,三组测试结果如下

test(1 2 3)
# -- ARGC=3
# -- ARGV=1;2;3
# -- ARGV2=3
# -- ARGN=1;2;3test()
# -- ARGC=0
# -- ARGV=
# -- ARGV2=
# -- ARGN=test(4 5)
# -- ARGC=2
# -- ARGV=4;5
# -- ARGV2=
# -- ARGN=4;5

参数方式(二)

第二种方式,给function提供默认的形参名,此时传入的参数会依次分配给对应的形参,多的部分还是只能通过ARGN获取。

function(test2 var)message("var=${var}")
endfunction()

测试结果如下

test2("abc") # var=abc
test2("abc" "d") # var=abc 第二个参数丢失了,只能使用ARGN获取set(a 10)
test2(a) # var=a
test2(${a}) # var=10
# 这个例子说明了函数只会把传入的参数视作纯粹的字符串常量,而不是视作变量的重命名
# 如果坚持使用test2(a),那么在test2内部,必须使用${${var}}双层调用,来获取a的值

注意,${var}不同于"${var}"(其中的var是一个列表),在函数调用时因为解析的逻辑导致不同的结果

function(PrintVar var)message("${var}")
endfunction()PrintVar(${specialStr}) # aaa
# 这里分开传入,视作多个参数PrintVar("${specialStr}") # aaa;bbb
# 这里作为一个整体传入,视作一个参数

即列表作为参数时

列表var作为参数传入函数时:

  • 调用时写成 ${var},会被展开识别为多个参数
  • 调用时写成 "${var}",会被打包作为一个列表整体,分配给一个参数
  • 调用时写成 var,在函数内需要两层解析${${var}}才能得到值列表
function(hello var)message("var=${var}|${${var}}")
endfunction()set(A x y z)hello(A)
hello(${A})
hello("${A}")# var=A|x;y;z
# var=x|
# var=x|y;z,

参数方式(三)

推荐在复杂参数情形下,使用cmake_parse_arguments命令来解析函数或宏的参数,因为这个命令非常强大。 (不推荐与其他参数方式混用,不要在function命令中指定参数名称,而是放到函数体内部,使用cmake_parse_arguments命令实现)

语法形式为

cmake_parse_arguments(<prefix> <options> <one_value_keywords><multi_value_keywords> <args>...)

我们通过例子来解释这个命令

function(test)# 选项(布尔参数)的关键词列表set(argop "OA;OB;OC")# 单值参数的关键词列表set(arg "SA;SB;SC")# 列表参数的关键词列表set(arglist "LA;LB;LC")cmake_parse_arguments("ARG" "${argop}" "${arg}" "${arglist}" ${ARGN})message("-----------")message("${ARGV}")message("-----------")message("ARG_OA=${ARG_OA}")message("ARG_OB=${ARG_OB}")message("ARG_OC=${ARG_OC}")message("ARG_SA=${ARG_SA}")message("ARG_SB=${ARG_SB}")message("ARG_SC=${ARG_SC}")message("ARG_LA=${ARG_LA}")message("ARG_LB=${ARG_LB}")message("ARG_LC=${ARG_LC}")message("-----------")
endfunction()

其中的"ARG"是我们指定的前缀,避免名称冲突,此时cmake会自动创建若干名称为ARG_XXX的变量,来承接传入的参数。

  • 调用函数时,如果传入了OA或者其它的argop中的内容,则定义ARG_OA变量为真
test(OA ...)# ARG_OA=True
  • 调用函数时,如果传入了SA并且附带一个值value,则定义ARG_SA变量,它的值即为value
test(... SA "abc")# ARG_SA="abc"
  • 调用函数时,如果传入了LA并且附带一个列表values,则定义ARG_LA变量,它的值即为values
test(... LA "a;b;c")# ARG_SA=a;b;c
  • 习惯在命令的最后使用${ARGN},承接一些额外的参数

测试结果如下

# 第一个,不使用任何参数
# test()
----------------------
ARG_OA=FALSE
ARG_OB=FALSE
ARG_OC=FALSE
ARG_SA=
ARG_SB=
ARG_SC=
ARG_LA=
ARG_LB=
ARG_LC=
-----------# 第二个,使用选项OA,传入LA对应的值列表
# test(OA LA a;b;c)
-----------
OA;LA;a;b;c
-----------
ARG_OA=TRUE
ARG_OB=FALSE
ARG_OC=FALSE
ARG_SA=
ARG_SB=
ARG_SC=
ARG_LA=a;b;c
ARG_LB=
ARG_LC=
-----------# 第三个,使用选项OB,传入LA的列表,再传入SA的值
# test(OB LA "a;b;c" SA "e")
-----------
OB;LA;a;b;c;SA;e
-----------
ARG_OA=FALSE
ARG_OB=TRUE
ARG_OC=FALSE
ARG_SA=e
ARG_SB=
ARG_SC=
ARG_LA=a;b;c
ARG_LB=
ARG_LC=
-----------

以上是正确使用三类参数的情形,可以发现我们并不需要按照选项、单值参数和列表参数的顺序,只需要在正确的关键字后面跟上值或者列表即可。

换言之,cmake_parse_arguments命令使得我们可以用无序的参数键值对的形式,向调用的函数传入指定名称指定值的参数。

cmake_parse_arguments(<prefix> <options> <one_value_keywords><multi_value_keywords> <args>...)

cmake_parse_arguments会解析传入的所有参数,按照指定的关键词和逻辑去分析捕获参数,并产生带有指定前缀的一组新变量:

  • <prefix>是我们希望给捕获产生的所有新变量添加的前缀
  • 如果遇到<options>中的关键词,则对应产生的新变量为true;否则默认为false
  • 如果遇到<one_value_keywords>中的关键词,则把随后的一个值赋给产生的新变量;否则默认为空(未定义)
  • 如果遇到<multi_value_keywords>中的关键词,则把随后的一个列表赋给产生的新变量,直到遇见下一个关键词;否则默认为空(未定义)
  • 最后的<args>...部分,习惯上都使用${ARGN}
  • 如果给的参数无法被cmake按照上述逻辑捕获,仍然可以通过传统的ARGN方式获得:由于我们没有在function命令指定任何参数名,其实所有的参数都被扔进了ARGNARGV

变量作用域

在函数内部直接使用set命令不会影响到函数体之外,在退出函数体之后变量即失效

function(foo)set(A 100)
endfunction()
# 在内部对A的定义无效,对于外层来说,无事发生

可以使用PARENT_SCOPE,表明退出这一层作用域后,变量仍然存在

function(foo)set(A 100 PARENT_SCOPE)
endfunction()
# 在内部对A的定义仍然有效

例如

message("(1)A=${A}")
foo1()
message("(2)A=${A}")function(foo2)set(A 200 PARENT_SCOPE)
endfunction()message("(3)A=${A}")
foo2()
message("(4)A=${A}")# (1)A=10
# (2)A=10
# (3)A=10
# (4)A=200

这里体现的不仅仅是函数变量,而是所有的普通变量都具有的变量作用域机制:(与之相对的,缓存变量是全局的)

  • 通过add_subdirectory()进入新的CMakeLists.txt具有独立的变量作用域。
  • 通过include导入的.cmake文件没有独立的变量作用域。
  • 自定义函数具有独立的变量作用域。
  • 自定义的宏没有独立的变量作用域。

普通变量对子作用域可见,但是子作用域中的变量不会影响到父作用域,相当于定义了同名变量并覆盖,除非使用PARENT_SCOPE选项。

函数返回值

cmake的函数可以在函数体的任何地方直接返回,使用return()即可,但是cmake的函数语法有一个致命的问题,居然不支持返回值!在CMake中为了得到函数的返回值,我们必须把一个变量传给函数,然后通过提升作用域的方式实现,例如

function(test_return rst arg)math(EXPR argnew "${arg}+1")set(${rst} ${argnew} PARENT_SCOPE)
endfunction()set(b 10)
test_return(b 1)
message("b=${b}") # b=2

还可以这么干,直接使用一个变量负责传入传出,需要两层解析来获取传入的值,再使用PARENT_SCOPE把变量传回去

function(test_return2 rst)set(tmp "${${rst}}")math(EXPR tmp "${tmp}+1")set(${rst} ${tmp} PARENT_SCOPE)
endfunction()set(b 10)
test_return2(b)
message("b=${b}") # b=11

函数执行状态

在函数体内,可以使用如下的特殊变量,获取当前的函数执行状态

  • CMAKE_CURRENT_FUNCTION 当前执行的函数名
  • CMAKE_CURRENT_FUNCTION_LIST_DIR 当前执行的函数所在CMakeLists.txt文件的目录
  • CMAKE_CURRENT_FUNCTION_LIST_FILE 当前执行的函数所在CMakeLists.txt文件名(完整带路径)
  • CMAKE_CURRENT_FUNCTION_LIST_LINE 当前执行的函数所在的行数(function语句的行号)

宏的标准语法如下,定义名为<name>的宏,接收名为<arg1>...的参数,<commands>表示宏定义的功能,在调用宏之前不会被执行。

macro(<name> [<arg1> ...])<commands>
endmacro()

宏和函数很相似,宏的名称是大小写不敏感的,建议完全小写。宏也支持使用ARGV之类的默认参数名称,或者使用指定参数名称的方式,再或者cmake_parse_arguments的方式。

我们重点关注宏和函数的不同点:

  • 宏并没有像函数一样单独开辟一个作用域,而是简单地执行了两步操作
  1. 完成对参数的字符串替换
  2. 把命令部分拷贝过来执行
  • 在宏的内部禁止使用return()命令,因为这个命令的效果不是退出宏,而是退出上一层
  • 在宏的内部没有单独作用域,而且ARGV0不是通常的变量,只是类似于单纯的字符串替换,因此不能使用如下的语法
if(ARGV0) # 在函数中可以,但是在宏内部错误,if不会解析ARGV0
if(${ARGV0}) # 正确,注意在进入宏之后,这里就完成了对ARGV0的字符串展开

模块

模块就是以xxx.cmake结尾的文件,通常将一些通用的函数或宏封装到到一个指定的文件中,然后通过include(xxx)方式引用,可以达到代码复用的目的(但是并不会创建独立的变量作用域)。 模块既可以被CMakeLists.txt引用,也可以被其它模块引用。

模块的查找可以是从当前位置出发的相对路径,此时需要带文件后缀,例如include(cmake/xxx.cmake);也可以从CMAKE_MODULE_PATH查找,此时只需要include(xxx),在查找前可以修改这个变量,让CMake顺利找到模块。

string字符串处理

原文链接:https://blog.csdn.net/jidushanbojueA/article/details/137595128

一、字符串处理指令string

在CMake中,string 指令提供了一系列的操作来处理字符串。这些操作包括但不限于比较、替换、连接、长度计算等等。

基本语法
string 指令的基本语法如下:

string(<command> ...)

参数含义:

<command> : 是您希望执行的操作类型,比如 LENGTH,APPEND,REPLACE 等。LENGTH:计算字符串的长度。APPEND:将一个字符串附加到另一个字符串后面。REPLACE:替换字符串中的一部分内容。SUBSTRING:提取字符串的子字符串。COMPARE:比较两个字符串大小,是否相等。TOUPPER/TOLOWER:将字符串转换为大写/小写。REGEX REPLACE:字符串查找和替换,使用正则表达式匹配和替换字符串的某些部分。CONCAT:字符串连接。FIND:用于在字符串中查找一个子字符串位置。REGEX MATCH:用于在字符串中查找正则表达式匹配的内容。…: 后面的形参根据不同的指令各有差异,下面我们会根据command类型一一示例进行介绍。

二、字符串运算符

下面我们根据command类型一一示例进行介绍:

字符串比较

字符串比较有两种方法。
一种方法是通过条件语句使用 STREQUAL,STRLESS 和 STRGREATER 来比较字符串。

STREQUAL:字符串相等比较。如果相等,则表达式结果为 TRUE,否则为 FALSE。
STRLESS:用于判断一个字符串是否在字典排序上小于另一个字符串。如果是,则表达式结果为 TRUE,否则为 FALSE。
STRGREATER:用于判断一个字符串是否在字典排序上大于另一个字符串。如果是,则表达式结果为 TRUE,否则为 FALSE。

set(var1 "Hello")
set(var2 "World")if("${var1}" STRLESS "${var2}")message(STATUS "var1 is less than var2.")
else()message(STATUS "var1 is not less than var2.")
endif()if("${var1}" STRGREATER "${var2}")message(STATUS "var1 is greater than var2.")
else()message(STATUS "var1 is not greater than var2")
endif()if("${var1}" STREQUAL "${var2}")message(STATUS "var1 is equal to var2.")
else()message(STATUS "var1 is not equal to var2.")
endif()

另一种比较字符串的方法是:string(COMPARE <op> <string1> <string2> <output variable>)。

这里的<op>是比较操作符,
<op>是:EQUAL、LESS、GREATER,
比较结果的返回值同上面:STREQUAL、STRLESS、STRGREATER。
<string1>和<string2>是:要比较的字符串,
<output variable>是:用来存储比较结果的变量。

比如:

set(var1 "Hello")
set(var2 "World")string(COMPARE EQUAL "${var1}" "${var2}" result)
if(result)message(STATUS "The strings are equal")
else()message(STATUS "The strings are not equal")
endif()string(COMPARE LESS "${var1}" "${var2}" result)
if(result)message(STATUS "var1 is less than var2")
else()message(STATUS "var1 is not less than var2")
endif()string(COMPARE GREATER "${var1}" "${var2}" result)
if(result)message(STATUS "var1 is greater than var2")
else()message(STATUS "var1 is not greater than var2")


字符串连接

字符串连接也有两种方法。
一种方法是通过 set 命令直接将多个字符串变量或值连接起来。

set(STR1 "Hello, ")
set(STR2 "world!")
set(FULL_STR "${STR1}${STR2}")
message(STATUS "完整字符串:${FULL_STR}")

另一个连接字符串的方法是使用string(CONCAT <output_variable> <input> …)命令。

将所有 <input> 字符串拼接起来,结果存储在 <output_variable> 中

set(STR1 "Hello, ")
set(STR2 "World!")
string(CONCAT CONNECTED_STR ${STR1} ${STR2})
message(STATUS "Connected string: ${CONNECTED_STR}")


字符串替换

使用string(REPLACE <match_string> <replace_string> <output_variable> <input>) 替换字符串中的某部分。

在<input>字符串中搜索 <match_string> 并将其替换为<replace_string>,结果存储在 <output_variable> 中。

set(MY_STRING "Hello CMake world")
string(REPLACE "CMake" "awesome" MY_STRING_MODIFIED ${MY_STRING})
message(STATUS "Modified String: ${MY_STRING_MODIFIED}")


字符串长度

获取字符串长度使用 string(LENGTH <string> <output_variable>)。

用来计算字符串<string> 的长度,并将结果存储在<output_variable> 中。

set(MY_STR "Hello, world!")
string(LENGTH "${MY_STR}" MY_STR_LENGTH)
message(STATUS "字符串长度: ${MY_STR_LENGTH}")


字符串截取

提取子字符串使用 string(SUBSTRING <string> <begin> <length> <output_variable>)。

提取 <string> 的子字符串,从 <begin> 开始,长度为 <length>,结果存储在 <output_variable> 中。

set(MY_STR "Hello, world!")
string(SUBSTRING "${MY_STR}" 0 5 SUB_STR)
message(STATUS "子字符串: ${SUB_STR}")  # 输出应为 "Hello"


字符串转换大小写

把字符串转换为全部大写string(TOUPPER <string> <output_variable>),将 <string> 转换为大写,结果存储在<output_variable>中。

把字符串转换为全部小写string(TOLOWER <string> <output_variable>),将 <string> 转换为小写,结果存储在<output_variable>中。

set(MY_STR "Hello, World!")
string(TOUPPER "${MY_STR}" MY_STR_UPPER)
string(TOLOWER "${MY_STR}" MY_STR_LOWER)
message(STATUS "大写: ${MY_STR_UPPER}")  # 输出全部大写
message(STATUS "小写: ${MY_STR_LOWER}")  # 输出全部小写


字符串正则表达式匹配和替换

使用正则表达式匹配和替换字符串的某些部分string(REGEX REPLACE <regex> <replace_expression> <output_variable> <input>)。

对 <input> 使用正则表达式 <regex> 进行查找,并用<replace_expression> 进行替换,结果存储在 <output_variable> 中。

set(MY_STR "The quick brown fox")
string(REGEX REPLACE "quick brown" "slow red" MY_NEW_STR ${MY_STR})
message(STATUS "替换后的字符串: ${MY_NEW_STR}")  # 输出 "The slow red fox"


字符串查找子字符串

在字符串中查找一个子字符串string(FIND <string> <substring> <output_variable> [REVERSE])。

在 <string> 中查找 <substring> 的位置,位置基础是从0开始,结果是<substring> 开始的索引,存储在 <output_variable> 中,如果没找到,输出 -1。可选的[REVERSE]参数表示从字符串的末尾开始向前搜索。使用REVERSE选项对于查找最后一次出现的位置特别有用,尤其是在处理包含多个相同子字符串的长字符串时。这提供了一种灵活的方式来从不同的方向分析和处理字符串数据。

set(MY_STRING "Hello World")
string(FIND "${MY_STRING}" "World" MY_INDEX)
message(STATUS "Index: ${MY_INDEX}") # 输出为Index: 6set(MY_STRING "Hello World, Hello Universe")
string(FIND "${MY_STRING}" "Hello" MY_INDEX REVERSE)
message(STATUS "Last index: ${MY_INDEX}") # 输出应该是13


字符串中查找正则表达式匹配的内容

在字符串中查找正则表达式匹配的内容string(REGEX MATCH <regular_expression> <output variable> <input_string> )。

使用正则表达式 <regular_expression> 匹配 <input_string> 字符串,结果存储在<output_variable> 中。如果有匹配,该变量<output_variable>将包含匹配的文本;否则,它将是空的。

或者是用于匹配所有匹配项的变种:string(REGEX MATCHALL <regular_expression> <output_variable> <input_string>)

set(INPUT_STRING "The quick brown fox jumps over the lazy dog")
string(REGEX MATCH "quick.*fox" MATCHED_STRING ${INPUT_STRING})
message(STATUS "Matched string: '${MATCHED_STRING}'")

这段代码会把 MATCHED_STRING 设置为 quick brown fox,因为这是 <input_string> 中第一个匹配 <regular_expression> 的部分。REGEX MATCHALL 的行为类似,但它会找到所有匹配的实例而不仅仅是第一个。这很有用,特别是当你需要从输入字符串中提取一系列的匹配数据时。

# 设置包含数字的文本字符串
set(input_text "The 2 quick brown foxes jump over 13 lazy dogs 8 times.")# 使用正则表达式匹配所有数字
string(REGEX MATCHALL "[0-9]+" matched_numbers ${input_text})# 打印匹配到的所有数字
message("Matched numbers are: ${matched_numbers}")


字符串附加

字符串附加另一个字符串string(APPEND <variable> <string> [<string>...])。

将一个或多个 <string> 附加到变量 <variable> 的值上

# 初始为空的编译标志
set(COMPILER_FLAGS "")# 根据需要附加标志
string(APPEND COMPILER_FLAGS "-Wall ")
string(APPEND COMPILER_FLAGS "-Werror ")
string(APPEND COMPILER_FLAGS "-O3")# 打印最终的编译标志
message(STATUS "Compiler flags: ${COMPILER_FLAGS}")

注意事项:

1、在指定多个值进行追加时,string(APPEND …)不会在这些值之间自动添加任何分隔符。如果需要分隔符(如空格或其他字符),你必须自己包括它们在值中。
2、和大部分CMake命令一样,string(APPEND …)修改变量的作用是在原地执行的,意味着它直接改变了传入的变量而不是返回一个新的修改后的副本。

字符串连接CONCAT和字符串附加APPEND的区别

CMake中的string(APPEND ...)和string(CONCAT ...)命令都是用于处理字符串的,但它们在功能上有一些区别:
1、string(APPEND …)
string(APPEND …)命令用于向一个已经存在的变量追加内容。使用这个命令,我们可以向一个字符串变量的末尾添加更多的文本。这个操作是就地完成的,即对原有变量的内容直接进行扩展。

语法:

string(APPEND <variable> <value> [<value>...])

例子:

set(MY_STRING "Hello")
string(APPEND MY_STRING ", World!")
# 在这里,MY_STRING的值变成了"Hello, World!"

2、string(CONCAT …)
相比之下,string(CONCAT …)命令用于将多个字符串值合并为一个新的字符串。它不修改任何原有的字符串变量,而是创建一个新的字符串结果。
语法:

string(CONCAT <variable> <value> [<value>...])

例子:

set(STR1 "Hello")
set(STR2 ", ")
set(STR3 "World!")
string(CONCAT MY_NEW_STRING ${STR1} ${STR2} ${STR3})
# 在这里,创建了一个新的变量MY_NEW_STRING,其值为"Hello, World!"

总结:

1、string(APPEND …)是用来修改已存在变量的内容,通过追加方式扩展该变量的字符串值。
2、string(CONCAT …)是用于合并多个字符串值创建一个新的字符串变量,原有变量的值不会改变。
3、在选择使用哪一个的时候,可以根据你的具体需求来决定:如果你想要修改已有变量的值,可以使用APPEND;如果你想保留原有变量不变,而创建一个新的字符串,那么CONCAT会是更好的选择。

以上为一些基础的示例,实际应用中可能需要根据具体需求组合使用这些命令。CMake 的字符串操作十分灵活,掌握好这些基础操作,可以帮助你更好地管理和处理项目中的字符串数据。

                     
原文链接:https://blog.csdn.net/jidushanbojueA/article/details/137595128

math数学运算

有时候我们需要对cmake变量之间进行数学运算,这时候cmake提供了math()这个命令,命令格式如下:

math(EXPR outVar mathExpr [OUTPUT_FORMAT format])

这个命令也是很简单:直接通过cmake变量结合数学运算符组成mathExpr,然后将结果保存到outVar中。

【参数解释】

EXPR:标识,

outVar:存储计算结果变量,

mathExpr:要进行运算的表达式

OUTPUT_FORMAT 是可选参数,表示 输出结果的格式。

可以是:

        HEXADECIMAL:输出16进制结果.
        DECIMAL:输出10进制结果


【示例】

message("-------------------------------")
set(x 3)
set(y 7)# 将表达式计算结果存入zDec
math(EXPR zDec "(${x} + ${y}) * 2")
# 输出结果zDec日志
message("zDec = ${zDec}")# 将表达式计算结果,以“十进制”方式存入zDec
math(EXPR zDec "(${x} + ${y}) * 2" OUTPUT_FORMAT DECIMAL)
message("DECIMAL zDec = ${zDec}")# 将表达式计算结果,以“十六进制”方式存入zDec
math(EXPR zDec "(${x} + ${y}) * 2" OUTPUT_FORMAT HEXADECIMAL)
message("HEXADECIMAL zDec = ${zDec}")

编辑

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/460926.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从 TCP 友好性看传输优化

再看一遍最传统 TCP AIMD 吞吐的推导&#xff1a; 这个积分用离散求和表示很简单&#xff0c;一个锯齿发送的报文总数为&#xff1a; ( W 2 0 ) ( W 2 1 ) ( W 2 2 ) . . . ( W 2 ( W − W 2 ) ) ≈ 3 ⋅ W 2 8 (\dfrac{W}{2}0)(\dfrac{W}{2}1)(\dfrac{W}{2}2)...(\df…

css 对称按钮,中间斜平行间隔,两头半圆

序&#xff1a;稍一看&#xff0c;挺好看&#xff0c;看也简单&#xff0c;实现起来应该也是一样&#xff0c;没什么难度&#xff0c;分分钟完成。后面将其他的UI做了七七八八后&#xff0c;到这个按钮的时候&#xff0c;不知怎么&#xff0c;突然卡机了&#xff0c;想不起来怎…

DDRPHY数字IC后端设计实现系列专题之后端设计导入,IO Ring设计

本章详细分析和论述了 LPDDR3 物理层接口模块的布图和布局规划的设计和实 现过程&#xff0c;包括设计环境的建立&#xff0c;布图规划包括模块尺寸的确定&#xff0c;IO 单元、宏单元以及 特殊单元的摆放。由于布图规划中的电源规划环节较为重要&#xff0c; 影响芯片的布线资…

几种因素对磁控溅射AlN薄膜择优取向的影响

几种因素对磁控溅射AlN薄膜择优取向的影响 AlN材料在微电子产业中有广泛的应用&#xff0c;AlN薄膜在多种器件中都扮演着重要角色。 晶体生长中的择优取向是指在多晶材料中&#xff0c;晶粒沿着某一特定晶向或晶面生长得更快或更优先的现象。这种取向通常与材料的制备工艺和生长…

基于Java SpringBoot和Vue社区医院诊所医疗挂号管理系统设计

摘要 本文旨在设计并实现一个基于Java SpringBoot和Vue的社区医院管理系统&#xff0c;以解决当前社区医院管理中存在的效率低下、数据安全性差等问题。通过采用前后端分离架构&#xff0c;系统实现了用户信息管理、挂号管理、医生管理等功能模块&#xff0c;有效提升了医院的…

Linux 基础io_ELF_虚拟物理地址_动态库加载

1.可执行程序格式 ELF [wwshcss-ecs-178e myshell]$ ll total 56 -rw-rw-r-- 1 wws wws 92 Oct 17 19:14 file -rw-rw-r-- 1 wws wws 82 Oct 12 16:51 makefile -rw-r--r-- 1 wws wws 90 Oct 17 19:13 myfile -rwxrwxr-x 1 wws wws 20128 Oct 16 21:02 myshell -rw-r…

Java如何实现PDF转高质量图片

大家好&#xff0c;我是 V 哥。在Java中&#xff0c;将PDF文件转换为高质量的图片可以使用不同的库&#xff0c;其中最常用的库之一是 Apache PDFBox。通过该库&#xff0c;你可以读取PDF文件&#xff0c;并将每一页转换为图像文件。为了提高图像的质量&#xff0c;你可以指定分…

【HarmonyOS NEXT】使用 Navigation 对折叠屏设备页面进行分栏展示,优化 UI 交互

关键词&#xff1a;折叠屏、navigation、router、路由、分栏、UI 随着科技的发展&#xff0c;手机设备形态也由一面屏向多面屏进行发展&#xff0c;那么软件的UI适配也面临着问题&#xff0c;本篇文章主要解决大屏设备的页面 UI 适配问题&#xff0c;如折叠屏&#xff0c;平板&…

Coppelia Sim (v-REP)仿真 机器人3D相机手眼标定与实时视觉追踪 (二)

coppelia sim[V-REP]仿真实现 机器人于3D相机手眼标定与实时视觉追踪 二 zmq API接口python调用python获取3D相机的数据获取彩色相机的数据获取深度相机的数据用matpolit显示 python控制机器人运动直接控制轴的位置用IK运动学直接移动到末端姿态 相机内参的标定记录拍照点的位置…

Linux 安装nacos

1.下载版本 https://github.com/alibaba/nacos/tags 2.解压压缩包&#xff0c;启动 (1)将压缩包放到/usr/local目录下&#xff0c;解压 tar -xvf nacos-server-2.0.0-BETA.tar.gz(2)删除压缩包 rm -f nacos-server-2.0.0-BETA.tar.gz(3) 找到nacos的mysql的数据库脚本,在数…

sqoop问题汇总记录

此篇博客仅记录在使用sqoop时遇到的各种问题。持续更新&#xff0c;有问题评论区一起探讨&#xff0c;写得有不足之处见谅。 Oracle_to_hive 1. main ERROR Could not register mbeans java.security.AccessControlException: access denied ("javax.management.MBeanTr…

基于微信小程序的小区管理系统设计与实现(lw+演示+源码+运行)

摘 要 社会发展日新月异&#xff0c;用计算机应用实现数据管理功能已经算是很完善的了&#xff0c;但是随着移动互联网的到来&#xff0c;处理信息不再受制于地理位置的限制&#xff0c;处理信息及时高效&#xff0c;备受人们的喜爱。所以各大互联网厂商都瞄准移动互联网这个潮…

window11使用wsl2安装Ubuntu22.04

目录 1、快速了解wsl2 安装子系统linux流程&#xff08;B站视频&#xff09; 2、wsl2常用命令 3、windows与子系统Linux文件访问方法 4、子系统linux使用windows网络代理、网络配置&#xff08;镜像网络&#xff0c;非NAT&#xff09; 5、wsl2 Ubuntu miniconda 安装 6、…

【K8S系列】Kubernetes 中 NodePort 类型的 Service 无法访问的问题【已解决】

在 Kubernetes 中&#xff0c;NodePort 类型的 Service 允许用户通过每个节点的 IP 地址和指定的端口访问应用程序。如果 NodePort 类型的 Service 无法通过节点的 IP 地址和指定端口进行访问&#xff0c;可能会导致用户无法访问应用。本文将详细分析该问题的常见原因及其解决方…

Cyber​​Panel filemanager/upload 远程命令执行漏洞复现

0x01 产品简介 CyberPanel是一个开源的Web控制面板,它提供了一个用户友好的界面,用于管理网站、电子邮件、数据库、FTP账户等。CyberPanel旨在简化网站管理任务,使非技术用户也能轻松管理自己的在线资源。 0x02 漏洞概述 该漏洞源于filemanager/upload接口未做身份验证和…

(C#面向初学者的 .NET 的生成 AI) 第 2 部分-什么是 AI 和 ML?

从本部分开始Luis Quintanilla介绍AI和机器学习&#xff0c;需要学习的一些东西是什么是AI和ML&#xff1f;作为一名.net开发人员如何学习使用AI和ML。 1、首先什么是AI 和 ML&#xff1f; 你可以把它看作是基本相同事物的不同层次。 在顶层的是AI&#xff08;人工智能&#xf…

Swarm-LIO: Decentralized Swarm LiDAR-inertial Odometry论文翻译

文章目录 前言一、介绍二、相关工作三、方法A. 问题表述B. 框架概述C. 群体系统的初始化D. 去中心化激光雷达-惯性状态估计 四. 实验A. 室内飞行B. 退化环境飞行C. 去中心化部署 五. 结论和未来工作 前言 原文&#xff1a;原文 准确的自我状态和相对状态估计是完成群体任务的关…

wsl 使用docker 部署oracle11g数据库

wsl 使用docker 部署oracle11g数据库 1. 下载oracle11g sudo docker pull registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g2. 运行oracle11g容器&#xff08;docker-compose&#xff09; services:oracle-1.0:container_name: oracle11gimage: oracle11g:1.0restart:…

IDEA集成JProfiler

目录 下载并安装JProfiler下载安装包管理员身份运行配置许可证邮箱复制注册码配置永久许可证选择IDE集成 在IDEA中下载并安装JProfiler插件启动并使用JProfiler进行性能分析启动Java应用程序&#xff1a;自动运行JProfiler 知识扩充功能 下载并安装JProfiler 下载安装包 官网…

Tomcat 和 Docker部署Java项目的区别

在 Java 项目部署中&#xff0c;Tomcat 和 Docker 是两种常见的选择。虽然它们都可以用来运行 Java 应用&#xff0c;但它们在定位、部署方式、依赖环境、资源隔离、扩展性和适用场景等方面有显著区别。 1. 功能定位 1.1 Tomcat Apache Tomcat 是一种轻量级的 Java 应用服务器…