CMake 完整入门教程(二)

6 脚本流程控制
CMake 也支援一般编程语言常用的流程控制和副程式,因此撰写弹性很大。
6.1 条件语句
CMake 的条件语句为 if elseif else endif
# 当 expr 值为下列其中之一時,执行 command1:
# ON, 1, YES, TRUE, Y
# 当 expr 值为下列其中之一時,执行 command2:
# OFF, 0, NO, FALSE, N, NOTFOUND, *-NOTFOUND, IGNORE 
if(expr)
 command1(arg)
else(expr)
 command2(arg)
endif(expr)

版本较早的 CMake 要求在 else(...) 括号内必须填上对应的条件项目,然而很容易造成误
导,例如
if(WIN32)
...
else(WIN32) command2(arg)
endif(WIN32)
乍看之下会以为 WIN32 TRUE 时执行 command2 ,但原意其实是 WIN32 FALSE 才执行
command2 ,因此在较新的版本中已经不强迫了。
# 以下也合法
if(WIN32)
...
else()
command2(arg)
endif()
6.2 条件式计算规则
条件式的可以透过运算符组合,请参考运算符一章
if((expr) AND (expr OR (expr)))
在条件式当中即使不加 ${} if 也会先尝试解释成变量。
# 下面两行意义相同
if (foo)
if (${foo})
# 下面两行意义相同
if (foo AND bar)
if (${foo} AND ${bar}) 这里用 if 为例, while 亦为同理。
6.3 循环语句
CMake 的循环有两种:
foreach ... endforeach
while ... endwhile
set(V alpha beta gamma)
message(${V})
foreach(i ${V})
message(${i})
endforeach()
Output:
alpha
beta
gamma
6.4 循环范围和步进
FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
start 开始到 stop 结束,以 step 为步进,
举例如下 SET(A 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17)
FOREACH(A RANGE 5 15 3)
MESSAGE(${A})
ENDFOREACH(A)
最终得到的结果是 :
5
8
11
14
这个指令需要注意的是,直到遇到 ENDFOREACH 指令,整个语句块才会得到真正的执行。

在给出的 CMake 代码片段中,FOREACH 循环用于迭代一个范围内的数字。这个范围从 start 到 stop,步长为 step

  • loop_var 是循环变量,代表当前迭代的数字。
  • RANGE 是关键字,表示我们要迭代的范围。
  • start 是范围的起始值。
  • stop 是范围的结束值。
  • step 是可选的,表示迭代的步长。如果没有提供 step,那么步长默认为1。

在给定的例子中:

SET(A 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17)
FOREACH(A RANGE 5 15 3)
MESSAGE(${A})
ENDFOREACH(A)

这里,我们迭代变量 A 从5到15,步长为3。因此,循环会执行以下操作:

  1. A=5 -> MESSAGE(5)
  2. A=8 -> MESSAGE(8)
  3. A=11 -> MESSAGE(11)
  4. A=14 -> MESSAGE(14)

注意,SET(A 1 2 3 ... 17) 这行代码在这里并没有实际的作用,因为 A 在 FOREACH 循环中被重新定义,并且只在这个循环内部有效。循环外部的 A 的值不会改变。

总之,这个循环会按照指定的步长打印出从5到14的数字。

 

7 运算符
表达式中可以包含运算符,运算符包括:
一元运算符,例如: EXISTS COMMAND DEFINED
二元运算符,例如: EQUAL LESS GREATER STRLESS STRGREATER
NOT (非运算符)
AND (与运算符)、 OR (或运算符)
运算符优先级:一元运算符 > 二元运算符 > NOT > AND OR
  1. 一元运算符

    • EXISTS: 用于检查文件或目录是否存在。
    • COMMAND: 用于检查指定的命令是否存在。
    • DEFINED: 检查变量是否已定义。
  2. 二元运算符

    • EQUAL: 检查两个值是否相等。
    • LESS: 检查左边的值是否小于右边的值。
    • GREATER: 检查左边的值是否大于右边的值。
    • STRLESS: 字符串小于比较。
    • STRGREATER: 字符串大于比较。
  3. NOT(非运算符):对后面的条件进行否定。

  4. AND(与运算符):前后两个条件都必须满足。

  5. OR(或运算符):前后两个条件只要满足一个即可。

7.1 逻辑运算
expr 值为 FALSE 时成立。
if(NOT )
expr1 expr2 同时为 TRUE 时成立。
if( AND )
expr1 expr2 至少其中之一为 TRUE 时成立。
if( OR )
7.2 比较运算
7.2.1 数值比较
if(variable LESS number)
if(string LESS number)
if(variable GREATER number)
if(string GREATER number)
if(variable EQUAL number)
if(string EQUAL number)
7.2.2 字串比较
if(variable STRLESS string)
if(string STRLESS string)
if(variable STRGREATER string)
if(string STRGREATER string)
if(variable STREQUAL string)
if(string STREQUAL string) 字串比较依照 lexicographically order 决定大小。
LESS GREATER EQUAL STRLESS STRGREATER STREQUAL 会分别检查左右算子是否为
已定义过的变量,若是则采用变量值,否则采用字面值。

字符串比较是按照字典序(lexicographically order)进行的。这意味着“abc”小于“abd”,“apple”小于“banana”。

如果您想要基于变量的值进行比较,您需要确保变量已经被定义并且包含了一个字符串值。

7.2.3 Regular Expression 比对
if(variable MATCHES regex)
if(string MATCHES regex)
MATCHS 会先检查左方算子是否为已定义过的变量,若是则会比对变量储存的字串值,否
则将整串符号当成字串处理。
7.3 档案相关
判断档案和资料夹是否存在。行为只对完整路径是 well-defined
if(EXISTS file-name)
if(EXISTS directory-name)
file1 file2 新,或者其中一个档案不存在时。行为只对完整路径是 well-defined
if(file1 IS_NEWER_THAN file2)
判断给定的 path 是否是绝对路径。
if(IS_ABSOLUTE path)
  1. EXISTS 判断

    • if(EXISTS file-name):这个条件判断名为 file-name 的文件是否存在。
    • if(EXISTS directory-name):这个条件判断名为 directory-name 的目录是否存在。这些条件只对完整的、明确的路径有效。
  2. IS_NEWER_THAN 判断

    • if(file1 IS_NEWER_THAN file2):这个条件判断 file1 是否比 file2 新。如果 file1 的修改日期比 file2 新,或者 file1 不存在而 file2 存在,那么这个条件为真。同样,这些行为只对完整的、明确的路径有效。
  3. IS_ABSOLUTE 判断

    • if(IS_ABSOLUTE path):这个条件判断给定的 path 是否是绝对路径。在Unix系统中,绝对路径从根目录开始(例如 /usr/local/bin),而相对路径则不是(例如 ../bin)。在Windows系统中,绝对路径可能是 C:\Users\Username\Documents 这样的形式。

这些条件在CMake的配置过程中非常有用,可以帮助您根据文件和目录的存在与否、新旧以及是否是绝对路径来执行不同的操作。

7.4 其他
判断给定的 command-name 是否属于指令、 function macro
if(COMMAND command-name)
判断给定的 variable-name 是否已经被定义过。
if(DEFINED variable-name)
8 自定义宏和函数
CMake 有两种设计子程序的方式:
macro ... endmacro
function ... endfunction
  1. 宏(Macro):

    • 宏在CMake中是通过macroendmacro来定义的。
    • 宏类似于简单的命令替换:它们将宏内的所有命令替换为其结果。
    • 宏不会保留其内部的变量和函数的上下文,这意味着在宏内部定义的变量或函数在宏外部是不可见的。
    • 宏通常用于执行一次性操作或进行简单的命令替换。
macro(myMacro)
# 这里是宏的代码
endmacro()
  1. 函数(Function):

    • 函数在CMake中是通过functionendfunction来定义的。
    • 函数具有自己的作用域,可以在函数内部定义变量和调用其他函数,这些都不会影响到函数外部的上下文。
    • 函数提供了更好的封装和组织代码的能力,并允许您编写可重用的代码块。
function(myFunction)
# 这里是函数的代码
endfunction()

比较:

  • 宏在很多情况下更简单和直接,但可能导致代码更难以理解和维护,特别是当涉及到复杂的逻辑或需要封装更多内容时。
  • 函数提供了更好的封装和组织,使得代码更加模块化和可重用。它们更适合用于编写逻辑复杂或需要重复使用的代码块
8.1 基本语法
基本语法如下:
macro( [arg1 [arg2 [arg3 ...]]])
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endmacro()
function( [arg1 [arg2 [arg3 ...]]])
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endfunction()
下面是一个例子:
# 定义名为 print1 macro
macro(print1 MESSAGE)
message(${MESSAGE})
endmacro(print1)
# 定义名为 print2 function
function(print2 MESSAGE)
message(${MESSAGE})
endfunction(print2)
print1("from print1")
print2("from print2")
8.2 处理不定数目参数
以简单的求和函数为例,我们来看宏的一个示例:
macro(sum outvar)  
  # 获取所有传递给宏的参数,并将其存储在变量_args中  
  set(_args ${ARGN})  
    
  # 初始化结果变量为0  
  set(result 0)  
    
  # 遍历_args中的每一个元素(通过循环变量_var)  
  foreach(_var ${_args})  
    # 对当前的迭代值(_var)进行求和,并更新result的值  
    math(EXPR result "${result}+${_var}")  
  endforeach()  
    
  # 将求和的结果存储在变量${outvar}中  
  set(${outvar} ${result})  
endmacro()  
  
# 调用上面定义的sum宏,并将求和的结果存储在变量addResult中  
sum(addResult 1 2 3 4 5)  
  
# 打印出求和的结果  
message("Result is :${addResult}")

上面是一段求和函数,我们来解读一下代码:
"${ARGN}" CMake 中的一个变量,指代宏中传入的多余参数。因为我们这个宏 sum 中只
定义了一个参数 "outvar" ,其余需要求和的数字都是不定形式传入的,所以需要先将多余
的参数传入一个单独的变量中。当然,在这个示例中,第一行代码显得多余,因为似乎没
必要将额外参数单独放在一个变量中,但是建议这么做。
对上面这个宏再进一步加强:如果我们想限制这个宏中传入的参数数目(尽管在这个宏中
实际上是不必要的),那么可以将宏改写一下:
macro(sum outvar)  
  set(_args ${ARGN})  
  list(LENGTH _args argLength)  
  if(NOT argLength LESS 4) # 限制参数个数不能超过 4  
    message(FATAL_ERROR "参数过多!")  
  endif()  
  set(result 0)  
  foreach(_var ${_args})  
    math(EXPR result "${result}+${_var}")  
  endforeach()  
  set(${outvar} ${result})  
endmacro()  
  
sum(addResult 1 2 3 4 5)  
message("Result is :${addResult}")

8.3 宏和函数的区别
CMake 中的函数 ("function") 与宏唯一的区别就在于,函数不能像宏那样将计算结果传出来
( 也不是完全不能,只是复杂一些 ) ,并且函数中的变量是局部的,而宏中的变量在外面也
可以被访问到,请看下例:
# 定义一个宏  
macro(macroTest)  
  # 在宏内部设置一个变量  
  set(test1 "aaa")  
endmacro()  
  
# 定义一个函数  
function(funTest)  
  # 在函数内部设置一个变量  
  set(test2 "bbb")  
endfunction()  
  
# 调用宏  
macroTest()  
# 打印宏内部的变量  
message("${test1}")  
  
# 调用函数  
funTest()  
# 打印函数内部的变量(注意:在函数调用结束后,变量作用域结束,所以这里可能会得到一个未定义的变量)  
message("${test2}")

运行这段代码后,只会打印出一条信息 "aaa" ,由此可以看到宏与函数的区别。
8.4 综合示例
最后我们来通过一个稍微复杂综合一点的宏来结束本小节。
下面的这个宏是找出指定数值范围内全部素数,并输出。
macro(GetPrime output maxNum)  
  set(extArg ${ARGN})  
  if(NOT ${#extArg} EQUAL 0)  
    message(FATAL_ERROR "To much args!")  
  endif()  
    
  # 判断传入的变量是否为数字类型  
  if(NOT ${maxNum} MATCHES "^[0-9]+$")  
    message(FATAL_ERROR "maxNum must be a positive integer!")  
  endif()  
    
  set(result)  
  foreach(_var RANGE 2 ${maxNum})  
    set(isPrime 1)  
    math(EXPR upplimit ${_var}-1)  
    foreach(_subVar RANGE 2 ${upplimit})  
      math(EXPR _temp "${_var}%${_subVar}")  
      if(_temp EQUAL 0)  
        set(isPrime 0)  
        break()  
      endif()  
    endforeach()  
    if(isPrime)  
      list(APPEND result ${_var})  
    endif()  
  endforeach()  
  set(${output} ${result})  
endmacro()  
  
GetPrime(output 100)  
message("${output}")

你可能感兴趣的:(C++项目开发基础,c++)