美文网首页
C++避坑之#define常量和形似函数的宏

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

作者: 艰默 | 来源:发表于2023-03-02 13:43 被阅读0次

文章首发公众号: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

相关文章

  • 尽量以const,enum,inline替换#define

    对于单纯常量,最好以const对象或enums替换#define 对于形似函数的宏,最好改用inline函数替换#...

  • const常量与define宏定义的区别

    在C++ 程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。以下是const和define...

  • [C++面试]宏相关问题

    1. 宏常量与宏函数 C++中用#define <宏名> <字符串>命令定义宏,在代码中将字符串替换宏名出现的位置...

  • C++中的常量

    在C++中,有两种方式定义常量 1.#define 宏常量(通常定义在文件上方)#define 常量名 常量值 ...

  • C语言易忘知识点

    一:宏函数 一般形式:#define 标识符 常量 例如:#define PI ...

  • 一些小链接

    1、iOS 宏(define)与常量(const)的正确使用 iOS宏(define)与常量(const)的正确使...

  • 准备:回顾c

    宏定义 关键字 define 定义一个常量的方法(即宏定义) 带参数的宏定义 宏函数的定义 使用宏函数的好处是,不...

  • #define和const

    c语言只有#define,c++可以用#define和const来定义常量。const比#define更具优势。 ...

  • 【c++】#define(宏定义)和const(常量)

    原文出自【CSDN】,转载请保留原文链接: http://blog.csdn.net/love_gaohz/art...

  • iOS 日常小知识点总结

    define和const常量有什么区别 define在预处理阶段进行替换, const常量在编译阶段使用;宏不做类...

网友评论

      本文标题:C++避坑之#define常量和形似函数的宏

      本文链接:https://www.haomeiwen.com/subject/reejldtx.html