前言
rule2主要讲的是#define相关的一些东西,由标题可知,这里说的是#define的在某些方面的不足以及一些可行的建议。另外,本节一个重要的内容就是enum hack技术。enum hack 既有 const int(char) “变量”的特性(可被访问控制),又有#define的特征(无法取其地址,从很大程度上杜绝妄图非法修改常量的行为)
#define 常量与const 常量的对比
先看两段代码
#include <stdio.h>
#define PI (3.1415926)
const double pi = 3.1415926;
int main()
{
double a = PI,b;
a = PI*PI;
b = pi;
return 0;
}
汇编:
_main:
01221380 push ebp
01221381 mov ebp,esp
01221383 sub esp,0E0h
01221389 push ebx
0122138A push esi
0122138B push edi
0122138C lea edi,[ebp-0E0h]
01221392 mov ecx,38h
01221397 mov eax,0CCCCCCCCh
0122139C rep stos dword ptr es:[edi]
0122139E movsd xmm0,mmword ptr ds:[1225868h]
012213A6 movsd mmword ptr [a],xmm0
012213AB movsd xmm0,mmword ptr ds:[1225878h]
012213B3 movsd mmword ptr [a],xmm0
012213B8 movsd xmm0,mmword ptr ds:[1225858h]
012213C0 movsd mmword ptr [b],xmm0
012213C5 xor eax,eax
012213C7 pop edi
012213C8 pop esi
012213C9 pop ebx
012213CA mov esp,ebp
012213CC pop ebp
012213CD ret
这两种方式都是定义PI的很常见的方式,说不上谁一定更好或更坏。但是,#define这种方法的原理是在所有 下文中出现 PI的地方都把字符“PI”替换成”3.1415926”了,这个工作是在预编译的阶段完成的。这种的缺点是:
1. 所有PI都会换成了3.1415926,那么这会导致预编译后的代码长度变长(比如说,一个程序有100个地方使用了PI,那么这个100个地方的PI全部换成了(3.1415926)这样长长的一串。然而 这一点无伤大雅。因为 在高级一些的编译器中实际的汇编代码中,3.1415926这个常量是会被编译器放在统一的常量区的。如上面的PI的值(3.1415926) 就放在ds:[1225868h]内,pi的值3.1415926放在ds:[1225858h]内,在汇编语言中都是间接寻址,而不是直接把这个长长的常量写在mov指令里面。
2. 调试的过程中,如果与PI相关的地方出了错误,那么报错信息就不会”XXX在PI 有错误”,而是写成”xxx在3.1415926有错误”,显然后面直接给个数字的就不好寻找问题源了,特别是跨文件的时候···。但是如果就使用pi则可以避免这个的问题,如果出错,就会报:“XXX在pi有错误”类似的错误,这样方便调试。
另外,由define定义的常量没有作用域与访问控制一说的。如下代码所示:
#include <stdio.h>
class Super
{
private:
#define PI (3.1415926)
public:
const double pi = 3.1415926;
};
int main()
{
Super s;
double a = PI,b;
a = PI*PI;
b = s.pi;
double *p1 = &(PI);
double *p2 = (double *)&(s.pi);
*p2 = 0;
return 0;
}
在类外依旧可以使用PI这个符号。同时在 doule *p1=&(PI)是会报错的,它妄图取PI这个符号的地址。
其错误为:
3 IntelliSense: expression must be an lvalue or a function designator
那有没有办法,既让常量得到封装又让常量无法被取地址的呢?
也就是 说 让 double * p2=(double*) &(s.pi)错误,否则正如上面最后两句代码所示, 这个常量其实已经被修改了。这就需要使用到enum hack 技术
enum hack
直接看代码吧!
#include <stdio.h>
class Super
{
public:
enum MyEnum
{
pi = 3
};
};
int main()
{
Super s;
double b;
b = s.pi;
double *p2 = (double *)&(s.pi);
*p2 = 0;
return 0;
}
在编译的时候,在double p2 = (double )&(s.pi);会报:
2 IntelliSense: expression must be an lvalue or a function designator
的错误。
这是因为 enum里面枚举值首先是const int,同时enum结构里面的各个
const int是无法取得地址。
这样就达到了既可以封装,又防止得到这个常量被取地址的目的。
但是enum hack的局限在于 ,只支持const int.
精华
- 若要在类外使用常量,推荐使用const变量。
- 若要在类内使用带封装的常量,那么肯定不能用#define,只能使用const或者enum.
- enum hack只支持整形常量,但是它即提供封装性又提供不能取其地址的双重特性。