【04】运算符与条件、循环语句
1. 条件控制:if
条件控制的基本写法为:
if(aaa)
# ...
elseif(bbb)
# ...
else()
# ...
endif()
并且能够支持嵌套条件控制,即
if(aaa)
# ...
if(ccc)
# ...
endif()
# ...
else()
# ...
endif()
2. 运算符
先给出以下例子
# 定义于 <opencv-path>/cmake/OpenCVModule.cmake 中的内容
if(" ${ARGV1}" STREQUAL " INTERNAL" OR " ${ARGV1}" STREQUAL " BINDINGS")
set(OPENCV_MODULE_${the_module}_CLASS "${ARGV1}" CACHE INTERNAL "The category of the module")
set(__ocv_argn__ ${ADD_MODULE_ARGN})
list(REMOVE_AT __ocv_argn__ 0)
ocv_add_dependencies(${the_module} ${__ocv_argn__})
unset(__ocv_argn__)
else()
set(OPENCV_MODULE_${the_module}_CLASS "PUBLIC" CACHE INTERNAL "The category of the module")
ocv_add_dependencies(${the_module} ${ADD_MODULE_ARGN})
if(BUILD_${the_module})
set(OPENCV_MODULES_PUBLIC ${OPENCV_MODULES_PUBLIC} "${the_module}" CACHE INTERNAL "List of OpenCV modules marked for export")
endif()
endif()
在 if()
的小括号中出现了 STREQUAL
以及 OR
的内容,这些属于运算符,我们有必要介绍一些能够产生 BOOL
类型变量的运算符,这些运算符也常用于条件控制中。
2.1 一元运算符
EXISTS
:判断文件或者目录是否存在,存在时为真。需要提供绝对路径;如果文件或者目录是符号链接(例如软连接),则只有当链接的目标存在时返回真。格式为:EXISTS xxx
,例如:# 定义于 <opencv-path>/cmake/OpenCVModule.cmake 中的内容 if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/test")
IS_DIRECTORY
:判断指定内容是否为文件夹,是则为真。需要提供绝对路径。格式为:IS_DIRECTOTY xxx
,例如:if(IS_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/${m})
COMMAND
:如果给定的名称是命令、宏或者函数这类可被调用的对象,则返回真。格式为:COMMAND xxx
,例如:# 定义于 <opencv-path>/cmake/OpenCVModule.cmake 中的内容 if(COMMAND ocv_get_module_external_sources)
DEFINED
:如果给定的变量(普通变量、缓存变量或系统环境变量)存在,则返回真。格式为:DEFINED xxx
,系统环境变量前要加ENV
,即DEFINED ENV xxx
。示例如下:# 定义于 <opencv-path>/cmake/OpenCVModule.cmake 中的内容 if(DEFINED OPENCV_INITIAL_PASS)
TARGET
:如果给定的名称是目标(包括二进制目标、伪目标,可参考【05】目标构建),则返回真。格式为:TARGET xxx
,例如:# 定义于 <opencv-path>/cmake/OpenCVGenABI.cmake 中的内容 if(TARGET opencv_${mod}) # opencv_world
2.2 二元运算符
EQUAL
:左边两个字符串或者变量相等时为真。STREQUAL
:左边与右边的字典顺序相等时为真。MATCHES
:按照正则表达式去匹配,左边是待匹配的值,右边是正则表达式,能匹配为时为真。例如:# 定义于 <opencv-path>/cmake/OpenCVModule.cmake 中的内容 # m 为某个 foreach 中的元素 if(NOT m MATCHES "^opencv_")
2.3 逻辑运算符
与其他语言一致,包含与、或、非:AND
、OR
、NOT
if(BUILD_opencv_world AND m STREQUAL "opencv_world"
OR NOT BUILD_opencv_world
OR NOT OPENCV_MODULE_${m}_IS_PART_OF_WORLD)
逻辑操作符的结合优先级:
NOT
>AND
>OR
提示
运算符结合顺序为:一元运算符 > 二元运算符 > 逻辑运算符
3. 循环控制:foreach
这里介绍最常用的foreach
,while
这里不做介绍
3.1 用法
先给出两个例子:
# 以下均为定义于 <opencv-path>/cmake/OpenCVModule.cmake 中的内容
# Example 1
foreach(subdir ${subdirs})
if(EXISTS "${path}/${subdir}/CMakeLists.txt")
list(APPEND paths "${path}/${subdir}")
list(APPEND names "${subdir}")
endif()
endforeach()
# Example 2
foreach(mod ${OPENCV_MODULES_BUILD} ${OPENCV_MODULES_DISABLED_USER} ${OPENCV_MODULES_DISABLED_AUTO} ${OPENCV_MODULES_DISABLED_FORCE})
if(HAVE_${mod})
unset(HAVE_${mod} CACHE)
endif()
unset(OPENCV_MODULE_${mod}_DEPS CACHE)
unset(OPENCV_MODULE_${mod}_DEPS_EXT CACHE)
# 省略一系列 unset
endforeach()
可以得出,遍历循环控制的基本写法为:
foreach(每个元素 ${列表1} ${列表2} ...)
循环体
endforeach()
对于以上两个例子 Example 1 和 Example 2,有以下注意的地方:
Example 1
${subdirs}
不可写成"${subdirs}"
,我们假设此处${subdirs}
内容有:a
、b
、c
,那么遍历的时候subdir
则分别指代a
、b
、c
。而如果写成"${subdirs}"
,展开则表示"a;b;c"
,那么subdir
则指代a;b;c
这个变量。subdir
是个普通变量,因此在访问(取值)时也需要用${}
的写法。
Example 2
共有 4 个待遍历的列表,遍历则会将这 4 个列表中的所有内容依次访问。
mod
为一个变量,不例外的也需要用${}
来访问。同时我们还能看到这种写法:unset(OPENCV_MODULE_${mod}_DEPS CACHE)
${mod}
用在了一个变量的中间,假设此时${mod}
指的是aa
,那么这个变量则表示为:OPENCV_MODULE_aa_DEPS
注意
条件控制结束标志endif()
和循环控制结束标志endforeach()
括号中的内容可以不写,一旦写,就必须跟if()
和foreach()
括号中的内容一致
3.2 其他写法
foreach
还有其他写法,例如对于
foreach(m ${members})
# ...
endforeach()
有更直观的写法,我们可以写成
foreach(m IN LIST members)
# ...
endforeach()
4. 示例
如果我们想遍历当前目录下的所有文件夹要怎么办?在终端中我们可以使用ls
来列出当前路径下的所有内容,包括文件夹,当然也包括了文件,在 CMake 中我们要如何调用命令行呢?
4.1 列出文件与文件夹
4.1.1 执行子进程:execute_process
先列出官网给出的命令原型:
execute_process(COMMAND <cmd1> [<arguments>]
[COMMAND <cmd2> [<arguments>]]...
[WORKING_DIRECTORY <directory>]
[TIMEOUT <seconds>]
[RESULT_VARIABLE <variable>]
[RESULTS_VARIABLE <variable>]
[OUTPUT_VARIABLE <variable>]
[ERROR_VARIABLE <variable>]
[INPUT_FILE <file>]
[OUTPUT_FILE <file>]
[ERROR_FILE <file>]
[OUTPUT_QUIET]
[ERROR_QUIET]
[COMMAND_ECHO <where>]
[OUTPUT_STRIP_TRAILING_WHITESPACE]
[ERROR_STRIP_TRAILING_WHITESPACE]
[ENCODING <name>]
[ECHO_OUTPUT_VARIABLE]
[ECHO_ERROR_VARIABLE]
[COMMAND_ERROR_IS_FATAL <ANY|LAST>])
这里列出常用的几个选项:
COMMAND
用于指定一个子进程命令行。
CMake直接使用操作系统 API 执行子进程。所有参数都被逐字传递给子进程。没有使用中间shell,所以像
>
这样的shell操作符被视为普通参数。如果需要连续执行多个命令,请使用多个
execute_process()
调用,并使用单个COMMAND
参数。
OUTPUT_VARIABLE
- 变量名将分别用标准输出管道的内容设置。
ERROR_VARIABLE
- 变量名将分别用标准错误管道的内容设置。
因此,列出当前目录下所有文件夹,就可以写为
execute_process(COMMAND ls ${CMAKE_CURRENT_LIST_DIR} OUTPUT_VARIABLE subs)
其中,输出的变量为subs
,这个操作仅能在支持ls
命令的平台上使用,一般是 Linux。
4.1.2 使用 file 操作
为实现同样效果,我们可以使用更加通用的 file
命令
set(cur_path "${CMAKE_CURRENT_LIST_DIR}")
file(GLOB subs RELATIVE "${cur_path}" "${cur_path}/*")
其中,输出的变量为 subs
4.2 用法示例
例如,列出当前目录下所有的文件夹,并添加至 dirs
中
set(dirs)
set(cur_path "${CMAKE_CURRENT_LIST_DIR}")
file(GLOB subs RELATIVE "${cur_path}" "${cur_path}/*")
foreach(_sub ${subs})
if(IS_DIRECTORY ${_sub})
list(APPEND dirs ${_sub})
endif()
foreach()