1.概述
C/C++ 的宏定义和宏函数非常的有用,由于最近看Android 的jni代码时老是会看见如下图所示的宏定义:
定义完后使用的时候直接如下使用就行了:
JMI_DECLARE_CLASS(Context,android.content);
这样就能很简洁的将一些逻辑重复的代码管理起来了,这样修改的时候也只需要修改宏定义的部分即可。上面的这种方式就是宏函数的使用方式,宏定义其实就是C/C++中使用#define 来定义的语句,如下所示:
#define PI 3.14 // 宏替换
#define MAX(a,b) ((a)>(b)?(a):(b))// 宏函数
如上面所示,宏定义分为宏替换和宏函数,本文将会介绍这两类的区别以及如何使用。
2.宏替换
我们都知道,在C语言中,#
号开头的都是预处理指令,我们宏替换就是使用#define 数值或者算式来定义,需要注意的是宏替换就是单纯的替换,直接替换。我们可以通过一个例子来介绍宏替换:
假设我们需要定义一个CONST的常量,这个常量是的值是:1+2,代码如下所示:
#include<iostream>
#define CONST 1+2
using namespace std;
int main() {
int res = 3 * CONST;
cout << "the result is: " << res << endl;
}
很多读者可能会认为这里的结果是9,但是运行的结果却是5:
为啥会出现这种情况呢,这就是我们说的宏替换,宏替换只是单纯的替换,不会有运算的功能,这里的CONST后面的1+2
会被直接替换到我们的表达式:int res = 3*CONST;
中,替换后的样子应该是:int res = 3*1+2;
根据算术运算的优先级,所以结果就为5,如果要想按照我们想要让结果为9时,需要在宏定义后面的表达式加括号,保持优先级,如:#define CONST (1+2)
如果想要正确的使用宏替换,我们一定要注意理解宏替换的本质。就是就是单纯的替换。
注意:宏替换后的语法需要满足C/C++的语法,替换之后的语句,不能违背C/C++的语法,否则会编译不通过。其次宏不是语句,所以末尾没有分号
3.宏函数
3.1 基本使用
宏函数也是宏替换的一种,重点也是替换,只是不同的是,宏函数后面的表达式需要计算出来.如下所示:
#include<iostream>
#define MAX(a,b) ((a)>(b)?(a):(b))
using namespace std;
int main() {
int res = MAX(1,3);
cout << "the result is: " << res << endl;
}
运行结果:
在上面的代码中,我们使用宏函数的方式定义了一个求最大值的宏函数,在前面我们看到了做替换的时候会把我们定义的宏替换到代码中,这时候优先级就会发生改变,所以当我们定义宏函数时尽量在每个参数上加上括号。另外宏函数可以忽略类型:MAX(1,3),MAX(1.1,1.3),MAX('A','B')
都是合法的。
3.2 宏替换的多行写法
前面我们介绍的宏函数都是单行定义的,那么假设我们的宏函数有多行应该怎么写呢?写法和实际写函数的时候有一点区别,换行的时候需要加反斜杠"\"
分隔,如下所示:
#include<iostream>
#define SWAP(a,b)do{\
int temp = (a);\
(a) = (b);\
(b) = temp;\
}while(0);
using namespace std;
int main() {
int a = 1;
int b = 3;
cout << "before swap: a=" << a <<" b="<< b << endl;
SWAP(a,b);
cout << "after swap: a=" << a << " b=" << b << endl;
}
运行结果:
在上面的代码中,我们定义宏函数时有多行时需要使用反斜杠隔开,在代码中我们还能看到有
do{}while(0)的这种写法,也可以使用if(1){}代替
以前看开源库中的代码时比较好奇为啥这样写,后面发现这样写的原因是因为防止做宏替换的时候和其他的代码产生冲突。
4.针对宏替换做的条件编译
条件编译主要是用来按照我们事先设置好的条件编译我们的代码,在实际的应用场景中主要是为了做兼容性处理,我们先看一段代码:
#include<iostream>
using namespace std;
int main() {
#if 1
cout << "条件编译" << endl;
#else
cout << "不成立,不编译" << endl;
#endif
}
我们通过if后面的条件可以让编译器在编译期间判断条件是否成立,从而进入对应的代码块执行代码条件编译是在程序运行之前完成的,充当条件编译的变量必须是全局变量或者宏,因为局部变量在编译时是没有分配内存的
另外,如果需要判断宏定义xxx不存在可以使用:#ifndef xxx
判断存在则用:#ifdef xxx
这种写法可以防止重复定义宏导致冲突。如果我们想取消宏的定义,可以使用:#undef xxx
,这样就可以取消宏xxx的定义了,取消后xxx自然也就无法使用了。
5.特殊命令和特殊宏
在读别人写的大型项目代码时,我们可能会在宏函数定义中看到两个符号"#" 和“##”
,这两个符号都具有特殊的作用,#
号可以将输入的内容转换成字符串,如下所示:
#include<iostream>
#define toString(x) #x
using namespace std;
enum Person {
MAN,
WOMAN
};
int main() {
cout << "男人: " << toString(MAN) << " 女人:" << toString(WOMAN) << endl;
}
在上面的代码中,我们将枚举类型的数据转换成立字符串的数据并且输出。读者可以运行看效果
技巧: 我们可以使用这种方式将其他类型的数据转换成字符串数据
接下来就是##
,这个命令是连接符,看下面的例子:
上面是我在项目中看到的实际应用,就是拼接生成一个具有标识作用的名字,用起来特别方便:
然后我们可能还会看到一些特殊宏,如下面示例:
#include<iostream>
using namespace std;
int main() {
cout << "日期: " << __DATE__ <<endl<< "时间: "
<< __TIME__<<endl << "当前行号:" << __LINE__ << endl;
}