尽量避免#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;
}
输出结果:
代码语言:javascript复制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;
}
输出结果:
代码语言:javascript复制main A = 10
func1 A = 20
func2 A = 10
从这个例子我们可以看出,在编译器的预处理阶段,#define
确实是按照顺序来全局进行替换,初始定义A
的值为10
,因此main
函数中的A
和func2
中的A均被替换为10
,而最后在处理到func1
的函数体的时候,A
重新被定义为20
,所以func1
中的A
被替换为20
。
由于宏定义只做替换,所以没有名称的概念,而且宏在编译器预处理的时候就被替换了,因此在代码调试过程中更不容易发现问题。例如上例中,在预编译阶段A
全部被替换为数字10
和20
,编译器在编译的时候根本就感知不到A
的存在,假如代码确实在这个宏定义A
的地方出现了问题,我们debug的时候,只能看到相应的数字10
或20
,并不知道从哪里追踪它们的来源,增加我们定位问题的难度。
因此,在C 中我们尽量避免使用#define来定义一个常量,应使用const
和enum
来定义常量。
尽量避免形似函数的宏
#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;
}
输出结果:
代码语言:javascript复制a = 1
T = 2
TT = 2
func1 = 3
func1 = 4
func1 = 20
a = 6
输出结果可能与我们的预期存在出入,例如我们可能会认为TT
的输出应该为0
,MAXF
的输出可能与预期的不太一致。实际上,在上例中预编译阶段,把所有的宏替换为相应的表达式。其中:
- 对于
T
替换为a a
,T
的输出结果为2
,TT
替换为a a-a a
,TT
的输出结果也为2
。 - 对于
MAXF( a, b);
,首先被替换为func( a > b ? a : b);
,由于a
是先递增再比较,2
比0
大,因此func
的参数应为a
,a
累加了两次,因此MAXF( a, b);
输出的结果为3
。 - 对于
MAXF(a , b);
,首先被替换为func(a > b ? a : b);
,由于a
在这里是先比较再递增,3
比0
大,因此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;
}
输出结果:
代码语言:javascript复制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
。