許多學過C的朋友一定還記得C語言中的類型轉換,例如:
float FloatNum = 1.234;
int IntNum = (int)FloatNum;
// IntNum = 1
這是比較正常的類型轉換,稍微危險一點的轉換如下:
float FloatNum = 1.234;
float * pFloatPointer = &FloatNum;
int * pIntPointer = (int *)pFloatPointer;
// *pIntPointer 裡邊就是亂七八糟的東西了
C 的類型轉換雖然很方便,但是卻帶來了更多的問題。比如 C的類型轉換可以允許你進行任意類型之間的轉換。 非常隨意的使用類型轉換很容易造成程式邏輯的混亂,使人看不懂你寫的代碼,或者編譯器不能正確識別你的轉換的意圖,所以做出錯誤的轉換方式。其次,C類型的轉換很難查錯。特別是在大型的工程中,你想找出一個因為 (uint)轉換成 int而產生益出的問題,可能需要查看上千行包含"(int)"的代碼。為了避免諸如此類情況的發生 C++引入了4種類型轉換的方式。請記住,C的類型轉換隻是為C語言設計的,並不適合C++。C++支援C的類型轉換隻不過是為了向下相容的考慮。
讓我們來看看C++的4種類型轉換(關於這4種類型轉換的詳細說明,請參見<C++ Programming Language> 3rd Edition):
static_cast<>()
dynamic_cast<>()
const_cast<>()
reinterpret_cast<>()
我們一個一個來說
================== static_cast ==================
static_cast<>()
static_cast可以用來進行相互關聯類型之見的轉換,比如double 轉成 float, float轉成 int 以及 有關聯的pointer之間,有關聯的 class pointer 之間的轉換。
========比如========
float FloatNum = 1.234;
int IntNum = static_cast<int>(FloatNum); // IntNum = 1;
========或者========
class BaseClass{
public:
BaseClass();
virtual ~BaseClass();
};
class DerivedClass : public BaseClass{
public:
DerivedClass();
~DerivedClass();
void DoSomething(void);
};
BaseClass * pBaseClass = new BaseClass();
// 沒問題,不過call pDerviedCalss->DoSomething()很有可能會crash
DerivedClass * pDerivedClass = static_cast<DerivedClass *>pBaseClass;
DerivedClass * pDerivedClass = new DerivedClass();
// 沒問題,很安全
BaseClass * pBaseClass = static_cast<BaseClass *>(pDerivedClass);
值得注意的是 static_cast是在程式編譯的時候檢查類型轉換是否符合要求,並不在程式運行期間進行檢查,因為沒有 runtime overhead, 對速度的影響比較小。所以在你對類型轉換很有把握的時候,可以盡量的使用static_cast。另外 static_cast在轉換指標的時候並不能保證轉換前的指標地址和轉換後的指標地址相同,特別是在多重繼承的類結構中,指標地址經常會變化。
下面是一些通常比較危險的類型轉換,
========包括========
unsigned 轉 sign 比如 uint 轉成 int
double 轉 float
long 轉 int (64位作業系統有危險,32位無)
這些轉換都是範圍大的轉成範圍小的數,或者無符號轉成有符號。比如:
unsigned int k = 4294967290;
int m = static_cast<int>(k);
這裡k已經超出了int的範圍,所以 m的最後結果是 -6。所以在做以上的類型轉換的時候,要特別的小心。
================== dynamic_cast ==================
dynamic_cast 是用來對相關的class 指標之間進行的類型轉換。由於dynamic_cast在運行過程中對轉換進行安全性檢查,所以在很大程度上影響程式的運行速度,並且在便宜的時候需要開啟runtime type info 的開關 /GR,所以並不推薦使用。
如果要使用dynamic_cast那麼要求轉換的類至少需要含一個虛函數,並且只能對類的指標進行轉換操作,指標不包括 void *。 例如:
class BaseClass{
public:
BaseClass();
virtual ~BaseClass();
virtual void DoSomething(void);
};
class DerivedClass : public BaseClass{
public:
DerivedClass();
~DerivedClass();
void DoSomething(void);
};
DerivedClass * pDerivedClass = new DerivedClass();
// 沒問題
BaseClass * pBaseClass = dynamic_cast<DerivedClass *>(pDerivedClass);
BaseClass * pBaseClass = new BaseCalss();
// 有問題,基類轉成衍生類別,dynamic_cast會返回null 所以 pDerivedClass == NULL
DerivedClass * pDerivedCalss = dynamic_cast<DerivedClass *>(pBaseClass);
================== const_cast ==================
顧名思義,就是把const變數轉換成 non-const變數,或者把volatile轉成non-volatile。這是最不推薦使用的一種轉換,只有在特殊的情況下才會使用。如果你需要大量的使用const_cast,那麼只能說明程式中存在著設計缺陷。
const_cast的使用如下:
float FloatNum = 12;
const float * pConstFloatPointer = &FloatNum;
// ok 這麼用沒問題
float * pFloatPointer = const_cast<float *>(pConstFloatPointer);
// 編譯錯誤,const_cast只能進行const和non-const之間的轉換
int * pFloatPointer = const_cast<int *>(pConstFloatPointer);
================== reinterpret_cast ==================
reinterpret_cast是最危險的一種轉換類型,它並不對資料進行任何實際的轉換操作,而是直接把資料當作另一種類型來使用(跟記憶體拷貝的功能差不多)。比如:
class ClassX{...};
class ClassY{...};
ClassX * pClassX = new ClassX();
// 沒問題,不過除非classX和classY都是結構相同的interface,否則並不安全,程式很容易crash
ClassY * pClassY = reinterpret_cast<ClassY *>(pClassX);
reinterpet_cast比較有用的地方就是函數指標的轉換,比如把一個指標存到一個函數指標數組中:
typedef void (*FuncPtr)();
FuncPtr FuncPtrArray[10];
int DoSomething() {...};
// 這裡使用reinterpret_cast
FuncPtrArray[0] = reinterpret_cast<FuncPtr>(&DoSomething);
總的來說,儘可能的使用這4種轉換來清晰的表達你轉換的目的,盡量不要使用C風格的轉換。
================== Design Pattern ==================
今天的Design Pattern講講另外兩個概念: Interface Inheritance 和 Implementation Inheritance。
上次Aear已經介紹了Inheritance 和 Delegation,並且說如果能用Delegation的地方,就最好不要使用 Inheritance。但是如果必須使用Inheritance怎麼辦?因此就出現了不同使用Inheritance(繼承)的方式。第一種就是 Interface Inheritance。
簡單的說,Interface Inheritance就定義一個 abstract class (抽象類別),把所有提供的類方法都在這個抽象類別中定義成純虛函數,然後在衍生類別中實現這些虛函數。對於這個 abstract class,我們可以稱它為: Interface。 (是不是有點象 COM?)。 比如在遊戲中,所有的敵人都可以移動和攻擊,但是不同的敵人類型,攻擊和移動的具體方式都不一樣。我們就可以用一個抽象類別在表示所有的敵人,在衍生類別中具體實現不同移動和攻擊方式。
下面讓我們來看看代碼:
class Enemy{
public:
virtual void Move(void) = 0;
virtual void Attack(void) = 0;
};
// 人類敵人
class HumanEnemy : public Enemy{
public:
void Move(void) {...}; // 用2條腿走路
void Attack(void) {...}; // 用拳頭或武器攻擊
};
// 怪物敵人
class MonsterEnemy : public Enemy{
public:
void Move(void) {...}; // 用4跳腿走路
void Attack(void) {...}; // 用牙齒和爪子攻擊
};
在運行AI的時候我們不需要具體判斷對方是人類還是敵人,直接調用 move和attack就行了,代碼如下:
void CalculateAction( Enemy * pEnemy)
{
pEnemy->Move();
pEnemy->Attack();
}
main()
{
....
HumanEnemy * pHumanEnemy = new HumanEnemy();
MonsterEnemy * pMonsterEnemy = new MonsterEnemy();
CalculateAction(static_cast<Enemy *>pHumanEnemy);
CalculateAction(static_cast<Enemy *>pMonsterEnemy);
....
}
=========== 小分割線 ===========
關於implementation inheritance,就是Aear在上一章裡舉的關於CBitmap和CTexture的例子。其中CBitmap 只基類,提供了 GetBitmapHeight()方法。 CTexture是CBitmap的衍生類別,提供了GetTextureHeight()的方法。這種結構很容易使人產生程式邏輯結構的混亂,並不推薦使用。Aear的個人觀點是:
能使用 Delegation就用
不能用Delegation就用 Interface Inheritance
用不了Interface Inheritance就重新設計你的類結構,使之能使用前兩種方式
如果重新設計系統或者使用Interface Inheritance開銷太大,再用Implementation Inheritance。
其實所有的Implementation Inheritance都可以通過Interface Inheritance來實現,具體來說,就是定義一個 abstract class基類,在此基礎上,定義衍生類別的abstract class (衍生類別也是interface),如此定義下去,直到所有需要實現的類都擁有自己的interface class為止。