Effective C++,rule 2,Prefer const,enum and inlines to #define

前言

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.

精华

  1. 若要在类外使用常量,推荐使用const变量。
  2. 若要在类内使用带封装的常量,那么肯定不能用#define,只能使用const或者enum.
  3. enum hack只支持整形常量,但是它即提供封装性又提供不能取其地址的双重特性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值