C++避坑之#define常量和形似函数的宏

文章首发公众号:iDoitnow

尽量避免#define定义常量

在C++中,定义常量应该尽量避免使用#define来定义一个常量,主要原因宏定义只做替换,不做类型检查和计算,不仅没有作用域限制,而且容易产生错误。例如:

#include <iostream>
#include <string>
using namespace std;

#define A 10

void func1() {
    #define A 20
    cout << "func1 A = " << A << endl;
}

void func2() { cout << "func2 A = " << A << endl; }

int main() {
    cout << "main A = " << A << endl;
    func1();
    func2();

    return 0;
}

输出结果:

main A = 20
func1 A = 20
func2 A = 20

从上一个例子我们可以看出,输出结果全为20。由于#define只做字面上的替换,且全局有效,因此不管定义在哪里都会在预处理的时候全部替换掉,因此带来的效果就是定义的变量貌似在全局均可访问。上例子中,在func1中重新定义了变量A,导致后面所有的A都变成了20。我们不妨将func1的实现放在main函数之后,看看有什么结果,如下例所示:

#include <iostream>
#include <string>
using namespace std;

#define A 10

void func1();

void func2() { cout << "func2 A = " << A << endl; }

int main() {
  cout << "main A = " << A << endl;
  func1();
  func2();

  return 0;
}

void func1() {
#define A 20
  cout << "func1 A = " << A << endl;
}

输出结果:

main A = 10
func1 A = 20
func2 A = 10

从这个例子我们可以看出,在编译器的预处理阶段,#define确实是按照顺序来全局进行替换,初始定义A的值为10,因此main函数中的Afunc2中的A均被替换为10,而最后在处理到func1的函数体的时候,A重新被定义为20,所以func1中的A被替换为20

由于宏定义只做替换,所以没有名称的概念,而且宏在编译器预处理的时候就被替换了,因此在代码调试过程中更不容易发现问题。例如上例中,在预编译阶段A全部被替换为数字1020,编译器在编译的时候根本就感知不到A的存在,假如代码确实在这个宏定义A的地方出现了问题,我们debug的时候,只能看到相应的数字1020,并不知道从哪里追踪它们的来源,增加我们定位问题的难度。

因此,在C++中我们尽量避免使用#define来定义一个常量,应使用constenum来定义常量。

尽量避免形似函数的宏

#define的另外一个需要注意的地方就是,尽量减少形似函数宏的使用。例如下面的例子:

#include <iostream>
#include <string>
using namespace std;

#define T a + a
#define TT T - T
#define MAXF(a, b) func(a > b ? a : b)

void func(int m) { cout << "func1 = " << m << endl; }

int main() {
    int a = 1;
    cout << "a = " << a << endl;
    cout << "T = " << T << endl;
    cout << "TT = " << TT << endl;
    int b = 0;
    MAXF(++a, b);       // a被累加2次
    MAXF(a++, b);       // a被累加2次
    MAXF(++a, b + 20);  // a被累加1次
    cout << "a = " << a << endl;
    return 0;
}

输出结果:

a = 1
T = 2
TT = 2
func1 = 3
func1 = 4
func1 = 20
a = 6

输出结果可能与我们的预期存在出入,例如我们可能会认为TT的输出应该为0MAXF的输出可能与预期的不太一致。实际上,在上例中预编译阶段,把所有的宏替换为相应的表达式。其中:

  • 对于T替换为a+aT的输出结果为2TT替换为a+a-a+aTT的输出结果也为2
  • 对于MAXF(++a, b);,首先被替换为func(++a > b ? ++a : b);,由于++a是先递增再比较,20大,因此func的参数应为++aa累加了两次,因此MAXF(++a, b);输出的结果为3
  • 对于MAXF(a++, b);,首先被替换为func(a++ > b ? a++ : b);,由于a++在这里是先比较再递增,30大,因此func的参数为a++,这时候a应先将值传递给func,然后再累加,因此func打出来的结果为4。实际上此时a的值已经变为5
  • 对于MAXF(++a, b + 20);a在比较大小的时候累加了一次,6没有20大,因此传入func的参数是20,因此打印输出结果为20
  • 最终a总共累加了5次,最终结果为6

使用形似函数的宏有时候的确会给我们带来方便,但有时候在直观上也会带来使用上的歧义,实际上也不是宏的错,大部分情况是我们把情况简单化、直观化了,实际上如果将其展开并替换后,我们也能及时发现问题,但问题是按照宏的逻辑再次展开分析,已经把我们的工作变得更复杂了,背离了当初我们简单化的初衷。那我们如何防止这些意外的发生呢?对于一些简单的宏表达式,我们可以通过添加括号等方法,强化我们的逻辑,避免不必要的歧义发生,对于形似函数的宏,尽量使用inline函数来替换上面的宏定义,具体的实现如下所示:

#include <iostream>
#include <string>
using namespace std;

#define T a + a
#define TT (T) - (T)

template <typename F>
void func(F m) {
    cout << "func1 = " << m << endl;
}

template <typename F>
inline void MAXF(F a, F b) {
    func(a > b ? a : b);
}

int main() {
    int a = 1;
    cout << "a = " << a << endl;
    cout << "T = " << T << endl;
    cout << "TT = " << TT << endl;
    int b = 0;
    MAXF(++a, b);
    MAXF(a++, b);
    MAXF(++a, b + 20);
    cout << "a = " << a << endl;
    return 0;
}

输出结果:

a = 1
T = 2
TT = 0
func1 = 2
func1 = 2
func1 = 20
a = 4

使用inline函数替代形似函数的宏,使得代码更加易用,同时也实现了类似define的效果。同时,因为我们使用了函数,因此也遵守了作用域和访问的规则,使得我们的代码更具标准性和规则性。

总结

在C++中,尽量避免#define常量和形似函数宏的使用。对于一些简单的表达式的宏,要避免宏嵌套宏,尽量做到简单,对于嵌套宏要做好运算符优先级检查和每一层的嵌套隔离,避免歧义的产生。引用《Effective C++》中的话来做总结就是:

对于单纯常量,最好以const对象和enum替换#define

对于形似函数的宏,最好改用inline函数替换#define

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艰默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值