之前在使用指標的時候,常常對指標的類型感到困惑,既然所有的指標都只佔4個位元組,那麼他所對應的指標類型在指標轉化過程中有什麼作用呢。最近,終於弄懂了他們的關係,相關說明如下:
1.前提
首先定義了一個ZooAnimal類如下:
class ZooAnimal{ public:ZooAnimal();virtual ~ZooAnimal();virtual void rotate();protected:int loc;String name;};
2.說明
定義如下三個指標:
ZooAnimal *px;int *pi;Array<String >*pta;
以記憶體需求的觀點來說,沒什麼不同,他們三個都需要有足夠的記憶體來放置一個機器地址(32位機器上是4位元組)。“指向不同類型之各指標”間的差異,既不在於其指標的表示不同,也不在其內容不同,而是在其所定址出來的object類型不同。也就是說,“指標類型”會教導編譯器如何解釋某個特定地址中的記憶體及其大小:
1.一個指向地址1000的整數指標,在32位機器上,將涵蓋地址空間1000--1003.
2.如果String是傳統的8-bytes(包括一個 4-bytes的字元指標和一個用來表示字串長度的整數),那麼一個ZooAnimal指標將橫跨地址空間1000--1015。如所示:
一個指向地址1000而類型為void*的指標,將涵蓋怎樣的地址空間呢?是的,我們不知道!這就是為什麼一個,類型為void *的指標只能夠含有一個地址,而不能夠通過它操作所指之object的緣故。
所以,轉型(cast)其實是一種編譯器指令,大部分情況下它並不改變一個指標所含的真正地址,它隻影響“被指出之記憶體的大小和其內容”的解釋方式。
3.加上多態之後
現在,讓我們定義一個Bear,作為一種ZooAnimal。當然,經由"public 繼承"可以完成這件任務:
class Bear:public ZooAnimal{public:Bear();~Bear();void rotate();virtual void dance();protected:enum Dances{...};Dances dances_known;int cell_block;};Bear b("Yogi");Bear *pb=&b;Bear &rb=*pb;
b,pb,rb 會有怎樣的記憶體需求呢?不管是pointer或reference都只需要一個word的空間(在32位機器上是4-bytes)。Bear object需要24bytes,也就是ZooAnimal的16bytes加上Bear所帶來的8bytes。展示了可能的記憶體布局:
好,假設我們的Bear object 放在地址1000處,一個Bear指標和一個ZooAnimal指標有什麼不同?
Bear b;ZooAnimal *pz=&bl;Bear *pb=&b;
它們每個都指向Bear object 的第一個byte。其間的差別是,pb所涵蓋的地址包含整個Bear object,而pz所涵蓋的地址只包含Bear object中的ZooAnimal subobject。
除了ZooAnimal subobject 中出現的members,你不能夠實用pz來直接處理Bear的任何menbers。唯一例外是通過virtual機制:
//不合法:cell_back不是ZooAnimal的一個member,雖然我們知道pz當前指向一個 Bear object。pz->cell_back;//ok:經過一個明白的downcast操作就沒有問題 !((Bear *)pz)->cell_block;//下面這樣更好,但是它是一個run-time operation (成本較高)if(Bear *pb2=dynamic_cast<Bear *>(pz))pb2->cell_block;//ok,因為cell_block是Bear 的一個member。pb->cell_block;
當我們寫:
pz->rotate();
時,pz的類型將在編譯時間期決定以下兩點:
1.固定的可用介面。也就是說,pz只能夠調用ZooAnimal的public 介面。
2.該介面的access level(例如rotate()是ZooAnimal的一個public member)。
在每一個執行點,pz所指的object類型可用決定rotate()所調用的實體,類型資訊的封裝並不是維護於pz之中,而是維護於link之中,此link存在於“object 的vptr”和“vptr 所指之virtual table之間”
另外,C++的多態就是通過指標和引用實現的,例如:
Bear b;ZooAnimal za=b;//調用ZooAnimal::rotate()za.rotate();
在以上代碼中,使用Bear類型對象初始化ZooAnimal對象,必然會對Bear類型對象進行裁剪,只是保留跟ZooAnimal有關的資訊。所以za.rotate()必然是調用ZooAnimal中的rotate(),所以多態不能夠在"直接存取objects"上發揮作用。
參考書目:
《深度探索C++物件模型》