1. 宏常量与宏函数
C++中用#define <宏名> <字符串>
命令定义宏,在代码中将字符串替换宏名出现的位置。定义宏的方式根据是否包含参数可以分为两种:
#define <宏名> <字符串>
#define PI 3.1415926
#define <宏名>(<参数列表>) <宏体>
#define A(x) x
2. 使用宏的原因?
在预处理阶段的宏替换仅仅是将目标字符串替换宏名,在代码中对宏的使用必须极其谨慎,否则很容易写出有问题的程序。定义宏的主要有两个场景:
- 通过宏定义常量:在常量变更时仅需要修改宏的定义而不需要修改所有使用到常量的位置
- 带参数的宏可以减少系统调用函数的开销:对于一些特别简单的函数而言,函数的调用开销不可忽视,带参数的宏在预处理阶段就进行了宏展开,提高了程序的运行效率
- 带参数的宏可以实现模板功能
3. C++是否应该避免使用宏,如何避免使用宏?
C++原则:尽量使用
const
、enum
和inline
替换#define
的使用,防止编译错误不够明朗,同时加强编译期间的类型检查,提高代码健壮性和可读性。
3.1 使用const替换#define定义常量
程序编译分为预处理、编译和链接三个阶段。#define
是不被视为语言的一部分,在预处理阶段就会进行宏展开替换所有的宏,因此进入第二步编译阶段是如果遇到了编译错误,那么错误信息可能会提到3.14
而不是PI
,导致错误信息不够明朗。
// 不推荐
#define PI 3.14
// 推荐
const doule Pi = 3.14;
3.2 使用enum替换#define
我们无法使用#define
创建一个class专属常量,因为#define
并不重视作用域。
对于class内定义常量,我们通常使用static
+const
的方式定义:
class Student {
private:
static const int num = 10;
int scores[num];
};
const int Student::num; // static 成员变量,需要进行声明
如果不想外部获取到class
专属常量的内存地址,可以使用enum
的方式定义常量(因为取一个enum
的地址是不合法的):
class Student {
private:
enum { num = 10 };
int scores[num];
};
3.3 使用inline替换#define
通常使用宏定义函数主要是出于如下考虑:
- 实现模板功能
- 减少函数调用带来的开销
另外一个常见的 #define
误用情况是以它实现宏函数,它不会招致函数调用带来的开销,但是用 #define
编写宏函数容易出错,如下用宏定义写的求最大值的函数:
#define MAX(a, b) ( { (a) > (b) ? (a) : (b); } ) // 求最大值
这般长相的宏有着太的缺点,比如在下面调用例子:
int a = 6, b = 5;
int max = MAX(a++, b);
std::cout << max << std::endl;
std::cout << a << std::endl;
输出结果(以下结果是错误的):
7 // 正确的答案是 max 输出 6
8 // 正确的答案是 a 输出 7
要解释出错的原因很简单,我们把 MAX
宏做简单替换:
int max = ( { (a++) > (b) ? (a++) : (b); } ); // a 被累加了2次!
在上述替换后,可以发现 a 被累加了 2 次。我们可以通过改进 MAX
宏,来解决这个问题:
#define MAX(a, b) ({ \
__typeof(a) __a = (a), __b = (b); \
__a > __b ? __a : __b; \
})
简单说明下,上述的 __typeof
可以根据变量的类型来定义一个相同类型的变量。改进后的 MAX
宏,输出的是正确的结果,max
输出 6,a
输出 7。
虽然改进的后 MAX
宏,解决了问题,但是这种宏的长相就让人困惑。我们可以用template inline
的方式,写出短小的函数:
template<typename T>
inline T max(const T& a, const T& b)
{
return a > b? a : b;
}