1 對象的概念
面向對象(Object Oriented Analysis Design,OOAD)的思想把整個世界看成是由具有某種特征行為功能的基本單元——對象構成的。OOAD把一個對象的特征稱為屬性,把其行為稱為一種方法。一個對象,可以接受外部信息,也可以向外部提供某種服務,我們可以將參數傳遞給對象,請求對象處理之后返回給我們,即提供了服務。
2 信息的隱藏與封裝
C++與C基本的改變就是把函數和數據放進了結構之中,即C++類。為了達到隱藏的目的,C++提供了private、protected和public聲明哪些東西能否以及如何被訪問。
雖然C++類很好用,當也不能一股腦兒都把不相干的數據和函數放到類中,要遵循最小化原則。
3 類的繼承特性
什么是對象?對象就是類的一個實例(instance),如果把類比作房子的圖紙,那么類就是建好的房子。
什么是派生類和基類?派生類繼承了基類的全部數據和函數,並具有自己私有的數據和函數,是“is a”的關系。比如人是一個基類,那么男人就是他的派生類,男人也是人。
4 動態特性
絕大部分情況下,程序的功能在編譯之后固定下來了,我們稱之為靜態特性。相對的,如果是運行時才確定下來的,則稱之為動態特性。
動態特性主要包括:虛函數、抽象基類、動態綁定和多態(polymorphism)等等。
4.1 虛函數
有時候為了結構統一和管理方便,在基類定義一個接口函數,派生類分別繼承並覆蓋了這一接口函數。在運行時,動態的確定該用哪一個派生類的函數。這樣的函數稱為虛函數。一旦一個類里的某個函數被聲明成了虛函數,那么其派生類的對應函數也會成為虛函數。
為了提高程序的清晰性,最好在每個派生類顯示的聲明虛函數(加virtual關鍵字)。
4.2 抽象基類
如果將基類的虛函數聲明為純虛函數后該類就變成了抽象基類。方法是將虛函數“初始化”為0(在后邊添加“=0”),這樣告訴編譯器:不要為該函數編址,阻止類的實例化。
抽象基類的設計思想是“接口與實現分離”,因為抽象基類將數據和實現都隱藏在了實現類中,所以又被稱為接口類。
4.3 動態綁定
理解動態綁定首先需要知道以下概念:
- 綁定:將函數體和函數調用關聯起來(一般通過函數指針)
- 早綁定:在程序運行之前,即編譯和鏈接是執行的綁定
- 晚綁定:即動態綁定。在運行時,基於不同類型的對象,對函數調用不同的函數體是發生的綁定。
如果一個語言要實現動態綁定必須有某種機制去確定對象的具體類型,然后調用適當的成員函數。為了達到動態綁定的效果,派生類和基類中同名的函數必須要有相同的原型。
4.4 運行時多態
因為派生類和基類是“is a”的關系,所以一個派生類也可以當成一個基類來使用。因此這些派生類對象在面對同一個函數調用的時候會有不同的反應,這就是運行時的多態。
C++實現運行時多態的方法有:
- 經過隱式轉換,令一個基類指針指向它的一個派生類對象
- 通過這個指針調用基類的虛函數
- 使用dynamic_cast<>和typeid運算符
綜合使用C++的虛函數和多態,有如下突出的優點:
- 只需要完成一次基類的調用,而不是完成每一個派生類的調用
- 派生類的功能可以被基類指針引用,即向后兼容
4.5 多態數組
不要在數組中直接存放多態對象,換之以基類指針。
- 通過基類指針刪除一個優派生類對象組成的數組,結果將是未定義的!(內存空間問題)
- 多態和指針算術運算不可混用,因此多態和數組不可混用
5 C++對象的內存模型
5.1 對象的內存映象
以兩個簡單的多態類為例
//基類A class A{ public: A(){...}; ~A(){...}; virtual void func1() = 0; private: int a; static int b; }; //派生類B class B : public A{ public: B(){...}; ~B(){...}; virtual void func1(){...}; void func2(){...}; private: int c; static int d; };
一個基本的對象模型有如下幾個規則:
- 非靜態數據成員被放在每一個對象體內,為對象所專有
- 靜態數據成員放在程序的靜態存儲區內被該類所有對象共享,因此只存在一份
- 靜態或非靜態成員函數放在程序的代碼段內被該類所有對象共享,因此也只存在一份
- 類類嵌套定義的各種類型(如typedef、class、struct、union、enum等)和放在類外面的定義除了作用域不同外沒有本質的區別
構成對象本身的只有數據,任何成員函數都不屬於任何一個對象。非靜態成員函數與對象的關系就是綁定,綁定的中介是this指針。
- 派生類繼承基類的非靜態成員
- 每一個多態類都創建一個虛函數指針數組(vtable),該類所以虛函數(繼承的或新增的)的地址都保存在這張表里
- 多態類的每一個對象都有一個類型為指向函數指針的指針——vptr,它總是指向所屬類的vtable
- 如果基類已經插入了vptr,則派生類將繼承和重用vptr
- 如果派生類從多個多態基類繼承,則派生類將在每個繼承分支上繼承多個vptr,生成對應的多個vtable數組
- vptr在派生類對象中的相對位置不會因為繼承層次的增加而改變
- 為了支持RTTI,為每一個多態類創建一個type_info對象,並將其地址保存在vtable中的固定位置(一般在首部)
派生類B的對象模型如下圖所示: