改善C++ 程序的150个建议学习之建议4:小心宏#define使用中的陷阱
来源:互联网 发布:国家电网数据运维招人 编辑:程序博客网 时间:2024/04/27 21:11
建议4:小心宏#define使用中的陷阱
C语言宏因为缺少必要的类型检查,通常被C++程序员认为是“万恶之首”,但就像硬币的两面一样,任何事物都是利与弊的矛盾混合体,宏也不例外。宏的强大作用在于在编译期自动地为我们产生代码。如果说模板可以通过类型替换来为我们产生类型层面上多样的代码,那么宏就可以通过符号替换在符号层面上产生的多样代码。正确合理地使用宏,可以有效地提高代码的可读性,减少代码的维护成本。不过,宏的使用中存在着诸多的陷阱,如果不注意,宏就有可能真的变成C++代码的“万恶之首”。
(1)用宏定义表达式时,要使用完备的括号。
由于宏只是简单的字符替换,宏的参数如果是复合结构,那么替换之后要是不用括号保护各个宏参数,可能会由于各个参数之间的操作符优先级高于单个参数内部各部分之间相互作用的操作符优先级,而产生意想不到的情形。但并不是使用了括号就一定能避免出错,我们需要完备的括号去完备地保护宏参数。如下代码片段所定义的宏要实现参数a和参数b的求和,但是这三种定义都存在一定风险:
#define ADD( a, b ) a + b
#define ADD( a, b ) (a + b)
#define ADD( a, b ) (a) + (b)
例如,ADD(a,b) * ADD(c,d)的本意是对(a+b)*(c+d)求值,在采用了上面定义的宏之后,代码展开却变成了如下形式,其中只有第2种方式“碰巧”实现了原本意图:
a + b * c + d
(a + b) * (c + d)
(a) + (b) * (c) + (d)
之所以说“碰巧”,是因为第2种方式中括号的使用也非完备的。例如:
#define MULTIPLE( a, b ) (a * b)
在计算(a+b)×c时,如果采用上述宏MULTIPLE(a+b,c),代码展开后,我们得到的却是a+b×c的结果。要避免这些问题,要做的就是:用完备的括号完备地保护各个宏参数。正确的定义应为:
#define ADD( a, b ) ((a)+(b))
#define MULTIPLE( a, b ) ((a)*(b))
(2)使用宏时,不允许参数发生变化。宏参数始终是一个比较敏感、容易引发错误的东西。有很多人认为,在某种程度上带参的宏定义与函数有几分类似。但是必须注意它们的区别,正如下面代码片段所示:
#define SQUARE( a ) ((a) * (a))
int Square(int a)
{
return a*a;
}
int nValue1 = 10, nValue2 = 10;
int nSquare1 = SQUARE(nValue1++); // nSquare1=110, nValue1=12
int nSquare2 = Square(nValue2++);// nSquare2=100, nValue2=11
类似的定义,却产生了不同的结果,究其原因还是宏的字符替换问题。正如上面的示例一样,两处的a都被参数nValue1++替换了,所以nValue1自增操作也就被执行了两回。这就是宏在展开时对其参数的多次取值替换所带来的副作用。为了避免出现这样的副作用,最简单有效的方法就是保证宏参数不发生变化,如下所示。
#define SQUARE( a ) ((a) * (a))
int nValue1 = 10;
int nSquare1 = SQUARE(nValue1); // nSquare1=100
nValue1++; // nValue1=11
(3)用大括号将宏所定义的多条表达式括起来。如果宏定义包含多条表达式,一定要用大括号将其括起来。如果没有这个大括号,宏定义中的多条表达式很有可能只有第一句会被执行,正如下面的代码片段:
#define CLEAR_CUBE_VALUE( l, w, h )\
l = 0;\
w = 0;\
h = 0;
int i = 0;
for (i = 0; i < CUBE_ACOUNT; i++)
CLEAR_CUBE_VALUE( Cubes[i].l, Cubes[i].w, Cubes[i].h );
简单的字符替代,并不能保证多条表达式都会放入for循环的循环体内,因为没有将它包围在循环体内的大括号中。正确的做法应该是用大括号将多条表达式括起来,这样就能保证多条表达式全部执行了,如下面的代码片段所示:
#define CLEAR_CUBE_VALUE( l, w, h )\
{\
l = 0;\
w = 0;\
h = 0;\
C语言宏因为缺少必要的类型检查,通常被C++程序员认为是“万恶之首”,但就像硬币的两面一样,任何事物都是利与弊的矛盾混合体,宏也不例外。宏的强大作用在于在编译期自动地为我们产生代码。如果说模板可以通过类型替换来为我们产生类型层面上多样的代码,那么宏就可以通过符号替换在符号层面上产生的多样代码。正确合理地使用宏,可以有效地提高代码的可读性,减少代码的维护成本。不过,宏的使用中存在着诸多的陷阱,如果不注意,宏就有可能真的变成C++代码的“万恶之首”。
(1)用宏定义表达式时,要使用完备的括号。
由于宏只是简单的字符替换,宏的参数如果是复合结构,那么替换之后要是不用括号保护各个宏参数,可能会由于各个参数之间的操作符优先级高于单个参数内部各部分之间相互作用的操作符优先级,而产生意想不到的情形。但并不是使用了括号就一定能避免出错,我们需要完备的括号去完备地保护宏参数。如下代码片段所定义的宏要实现参数a和参数b的求和,但是这三种定义都存在一定风险:
#define ADD( a, b ) a + b
#define ADD( a, b ) (a + b)
#define ADD( a, b ) (a) + (b)
例如,ADD(a,b) * ADD(c,d)的本意是对(a+b)*(c+d)求值,在采用了上面定义的宏之后,代码展开却变成了如下形式,其中只有第2种方式“碰巧”实现了原本意图:
a + b * c + d
(a + b) * (c + d)
(a) + (b) * (c) + (d)
之所以说“碰巧”,是因为第2种方式中括号的使用也非完备的。例如:
#define MULTIPLE( a, b ) (a * b)
在计算(a+b)×c时,如果采用上述宏MULTIPLE(a+b,c),代码展开后,我们得到的却是a+b×c的结果。要避免这些问题,要做的就是:用完备的括号完备地保护各个宏参数。正确的定义应为:
#define ADD( a, b ) ((a)+(b))
#define MULTIPLE( a, b ) ((a)*(b))
(2)使用宏时,不允许参数发生变化。宏参数始终是一个比较敏感、容易引发错误的东西。有很多人认为,在某种程度上带参的宏定义与函数有几分类似。但是必须注意它们的区别,正如下面代码片段所示:
#define SQUARE( a ) ((a) * (a))
int Square(int a)
{
return a*a;
}
int nValue1 = 10, nValue2 = 10;
int nSquare1 = SQUARE(nValue1++); // nSquare1=110, nValue1=12
int nSquare2 = Square(nValue2++);// nSquare2=100, nValue2=11
类似的定义,却产生了不同的结果,究其原因还是宏的字符替换问题。正如上面的示例一样,两处的a都被参数nValue1++替换了,所以nValue1自增操作也就被执行了两回。这就是宏在展开时对其参数的多次取值替换所带来的副作用。为了避免出现这样的副作用,最简单有效的方法就是保证宏参数不发生变化,如下所示。
#define SQUARE( a ) ((a) * (a))
int nValue1 = 10;
int nSquare1 = SQUARE(nValue1); // nSquare1=100
nValue1++; // nValue1=11
(3)用大括号将宏所定义的多条表达式括起来。如果宏定义包含多条表达式,一定要用大括号将其括起来。如果没有这个大括号,宏定义中的多条表达式很有可能只有第一句会被执行,正如下面的代码片段:
#define CLEAR_CUBE_VALUE( l, w, h )\
l = 0;\
w = 0;\
h = 0;
int i = 0;
for (i = 0; i < CUBE_ACOUNT; i++)
CLEAR_CUBE_VALUE( Cubes[i].l, Cubes[i].w, Cubes[i].h );
简单的字符替代,并不能保证多条表达式都会放入for循环的循环体内,因为没有将它包围在循环体内的大括号中。正确的做法应该是用大括号将多条表达式括起来,这样就能保证多条表达式全部执行了,如下面的代码片段所示:
#define CLEAR_CUBE_VALUE( l, w, h )\
{\
l = 0;\
w = 0;\
h = 0;\
}
- 改善C++ 程序的150个建议学习之建议4:小心宏#define使用中的陷阱
- 改善C++ 程序的150个建议学习之建议14:小心typedef使用中的陷阱
- 建议4:小心宏#define使用中的陷阱
- 改善C++ 程序的150个建议学习之建议20:使用memcpy()系列函数时要足够小心
- 改善C++ 程序的150个建议学习之建议20:使用memcpy()系列函数时要足够小心
- 改善C++ 程序的150个建议学习之建议19:明白在C++中如何使用C
- 改善C++ 程序的150个建议学习之建议25:尽量用const、enum、inline替换#define
- 改善C++ 程序的150个建议学习之建议12:优先使用前缀操作符
- 改善C++ 程序的150个建议学习之建议15:尽量不要使用可变参数
- 改善C++ 程序的150个建议学习之建议22:灵活地使用不同风格的注释
- 改善C++ 程序的150个建议学习之建议23:尽量使用C++标准的iostream
- 改善C++ 程序的150个建议学习之建议28:new/delete与new[]/delete[]必须配对使用
- 改善C++ 程序的150个建议学习之建议27:区分内存分配的方式
- 改善C++ 程序的150个建议学习之建议31:了解new_handler的所作所为
- 改善C++ 程序的150个建议学习之建议7:时刻提防内存溢出
- 改善C++ 程序的150个建议学习之建议 9:防止重复包含头文件
- 改善C++ 程序的150个建议学习之建议11:将强制转型减到最少
- 改善C++ 程序的150个建议学习之建议16:慎用goto
- Scikit Learn: 在python中机器学习
- synchronized与volatile异同
- java String 转码
- PL/SQL 使用
- 优化代码(Python)
- 改善C++ 程序的150个建议学习之建议4:小心宏#define使用中的陷阱
- Smarty目录结构和子目录路径问题
- android pull 解析
- 男人如何减掉啤酒肚
- 使用spinner实现省市二级级联
- Scipy:高端科学计算
- x264参数中文详解(X264 Settings)
- Multipath
- RTSP详细介绍