前言
rule2主要講的是#define相關的一些東西,由标題可知,這裡說的是#define的在某些方面的不足以及一些可行的建議。另外,本節一個重要的内容就是enum hack技術。enum hack 既有 const int(char) “變量”的特性(可被通路控制),又有#define的特征(無法取其位址,從很大程度上杜絕妄圖非法修改常量的行為)
#define 常量與const 常量的對比
先看兩段代碼
#include <stdio.h>
#define PI (3.1415926)
const double pi = ;
int main()
{
double a = PI,b;
a = PI*PI;
b = pi;
return ;
}
彙編:
_main:
push ebp
mov ebp,esp
sub esp,h
push ebx
A push esi
B push edi
C lea edi,[ebp-h]
mov ecx,h
mov eax,CCCCCCCCh
C rep stos dword ptr es:[edi]
E movsd xmm0,mmword ptr ds:[h]
A6 movsd mmword ptr [a],xmm0
AB movsd xmm0,mmword ptr ds:[h]
B3 movsd mmword ptr [a],xmm0
B8 movsd xmm0,mmword ptr ds:[h]
C0 movsd mmword ptr [b],xmm0
C5 xor eax,eax
C7 pop edi
C8 pop esi
C9 pop ebx
CA mov esp,ebp
CC pop ebp
CD 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 ()
public:
const double pi = ;
};
int main()
{
Super s;
double a = PI,b;
a = PI*PI;
b = s.pi;
double *p1 = &(PI);
double *p2 = (double *)&(s.pi);
*p2 = ;
return ;
}
在類外依舊可以使用PI這個符号。同時在 doule *p1=&(PI)是會報錯的,它妄圖取PI這個符号的位址。
其錯誤為:
那有沒有辦法,既讓常量得到封裝又讓常量無法被取位址的呢?
也就是 說 讓 double * p2=(double*) &(s.pi)錯誤,否則正如上面最後兩句代碼所示, 這個常量其實已經被修改了。這就需要使用到enum hack 技術
enum hack
直接看代碼吧!
#include <stdio.h>
class Super
{
public:
enum MyEnum
{
pi =
};
};
int main()
{
Super s;
double b;
b = s.pi;
double *p2 = (double *)&(s.pi);
*p2 = ;
return ;
}
在編譯的時候,在double p2 = (double )&(s.pi);會報:
的錯誤。
這是因為 enum裡面枚舉值首先是const int,同時enum結構裡面的各個
const int是無法取得位址。
這樣就達到了既可以封裝,又防止得到這個常量被取位址的目的。
但是enum hack的局限在于 ,隻支援const int.
精華
- 若要在類外使用常量,推薦使用const變量。
- 若要在類内使用帶封裝的常量,那麼肯定不能用#define,隻能使用const或者enum.
- enum hack隻支援整形常量,但是它即提供封裝性又提供不能取其位址的雙重特性。