版权声明:本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名elloop(包含链接)
前言
这篇文章分享一下CMake中函数:function, 和宏:macro的使用。本文先从二者区别说起,由于二者区别很小,所以后文就仅对函数的用法进行讨论,因为函数有作用域的概念,适用范围更广。后文分享一个很实用的递归函数用于包含指定目录的所有子目录。
CMake中function和macro的区别
从其官方文档的描述并不会看出二者有什么大的区别,除了在function的定义中提到了Scope的概念。
下面以StackOverflow上的一个例子来直观的了解一下二者的区别:
输出结果是:
可以看到,Moo这个宏的表现与C语言中的宏类似,仅仅是做字符串的替换; Foo函数里arg则是被赋值为var的值,在Foo内部可以修改这个arg变量的值。
个人感觉, 对于CMake里的函数和宏的使用原则可以以C语言里函数和宏的使用原则来作为参考。下面就着重说一下我在组织工程的时候对于function的常见用法。
function 的使用技巧
如何按引用来传递参数?(在function中修改外部作用域的值)
答:通过名字来传递变量
例如:有一个var变量,在函数外部定义,要通过调用一个函数f1, 来修改var的值
结果:
需要注意的两点:
试试写成f1(${var})
:
输出是:
如果写成了 f1(${var})
, 那么先计算表达式${var}
, 即相当于调用f1(abc), 调用结果是在函数的父作用域定义了一个abc变量.
其实在了解了参数展开之后,这个问题很显而易见,本质上就是调用了一个set(<var-name> <var-value> <var-scope>)
, 只不过如果需要通过函数来包装他的话就要注意传参传过来的东西是个变量名还是变量的值。
如何传递列表类型的参数?
如果我要打印一个列表要怎么写?
输出:
在调试CMake脚本的时候,可能经常用到这种打印列表的代码,于是很自然的我需要写一个打印列表的函数:print_list
实现很简单,只要把上面那个foreach丢到一个函数体里面就好了
现在来调用一下这个函数:
输出结果:
应该是hello world才对,怎么会只有一个hello ?
这几乎是初学者必犯的错误,问题出在对print_list
的调用方式上:
print_list(${arg})
展开来看就是 print_list(hello world)
, 因此,传递给print_list
的第一个参数只有hello。
正确的调用方式应该是下面这样,使用双引号把参数括起来:
输出:
函数里几个有用的隐含参数:
name |
description |
ARGC |
函数实参的个数 |
ARGV |
所有实参列表 |
ARGN |
所有额外实参列表, 即ARGV去掉函数声明时显示指定的实参,剩余的实参 |
ARGV0 |
函数第1个实参 |
ARGV1 |
函数第2个实参 |
ARGV2 |
函数第3个实参 |
依次类推 |
依次类推 |
使用上面表格里的几个隐含参数,通过下面这个例子可以更好的说明上面两种传递参数的方式,函数内部发生了什么。
输出:
从两个输出结果里可以看到:
1.使用引号包裹参数时
参数个数:1, 即hello world
额外参数个数: 0
打印第一个参数的内容 = 要打印的列表内容
2.不使用引号包裹参数时
参数个数:2, 分别是 hello 和 world
额外参数个数: 1, world
打印第一个参数的内容 = hello
在不使用括号包裹的情况下,因为函数只需要一个参数,列表里除了第一个元素的其它元素被当做额外的参数传给函数了,当我打印第一个参数的时候,就仅仅把列表的第一个元素打印出来了。
通过这个例子可以看到,在不使用括号来包裹列表类型的参数作为函数实参时,列表参数内部的空格(或者分号)会使得这个列表的内容被当做多个参数传递给函数。
CMake里的函数支持递归调用吗?
经过我的测试,答案是肯定的。
CMake里面有个命令就带有递归的含义:
file(GLOB_RECURSE cpp_list ./*.cpp)
这个file命令,使用GLOB_RECURSE
参数的时候即表示递归搜索的意思,上面这句话的意思是递归搜索当前目录及其子目录下的所有.cpp文件,把其完整路径放入列表cpp_list
中。
通常情况下,确定了所有源文件的路径,对于一个工程的构建来说就已经完成了一大半,剩下的问题就是库和头文件的搜索路径。而库的搜索路径通常很简单,因为通常不需要链接很多的库,并且库可以统一存放。最后的问题就是头文件的搜索路径问题,在一个组织良好的项目里,公用的头文件通常放在一个公共的include路径,业务逻辑里的头文件通常和其源文件放在相同的路径下,此时在其源文件中使用#include
时候,即使没有写完整的包含路径,仅仅写#include "header.h"
也能够编译通过。然而在某些情况下,例如如在的目录树结构:
某程序员放错了a.h的位置,并且他的编码习惯也很差,在a.cpp里的头部,他这样编写了include:
此时,在构建项目的时候就必须把 src/b/bb, src/c/cc/ccc, src/d/dd/ddd/dddd 都放入头文件包含路径,否则a.cpp的编译肯定会报错找不到这几个头文件。
也许你觉得添加这几个路径挺容易的,但是考虑一下更惨的情况,数百个cpp文件,每个cpp不知道包含了哪个.h, 不知道被包含的.h分散在某个子目录下,我如果挨个找头文件,挨个添加包含目录会不会很惨?
所以,我需要一个函数,递归的搜索指定目录的子目录,把所有的子目录添加到include路径里。简单粗暴!
下面就是我要分享的函数:
对于刚才的目录结构,
使用我这个函数来解决包含问题:include_sub_directories_recursively(${CMAKE_CURRENT_LIST_DIR}/src)
这句话,会把当前CMakeLists.txt所在目录下src的所有子目录(包括src目录)加入到包含路径。
输出结果是:
可以看到所有的子目录都被加入到包含路径了。
为什么自己写,而不用file(GLOB_RECURSE … ) ?
因为file命令搜到的是带文件名的完整路径,我需要的是目录。
对于这个函数在实际组织项目中的应用,请参考我这个项目的CMake脚本,在下一篇CMake文章里,我将分享一下这个项目使用CMake来组织的过程。
参考链接
在这里也能看到这篇文章:github博客, CSDN博客, 欢迎访问