本周內容算是Part1的收尾了,通過這三周的課程會對面向對象編程這個概念有了更為深刻的理解,信息量有些大,可能還需要之后再復習。
- 首先是OOP里面對于class這個概念的延伸,主要講了三種class之間的關系,有的教材上將類與類之間的關系講的像人生哲學一樣,但是只要理解這三種關系就能繼續之后的系列課程。
對象之間的關系可以概括為繼承(Inheritance)、組合(Composition)和代理(Delegation)3種。
- Composition組合, 表示has-a, 一個class里面完整包含另一個class
Composition概念早就存在,在c語言的struct中,使用了別的struct或者string等,都是組合(c語言實現面向對象就是用了這個特性)。
舉例來說,queue中含有deque,即queue和deque是復合關系。(即只要A含有B,則就是一種復合關系)。但看代碼,queue中的功能,都由dequeue完成。Dequeu的功能比較強大,就比較特殊了,成為一種adapter模式。
composition的構造是由內而外的:B的構造函數會首先調用A的默認構造函數(編譯器自己調用,如果需要傳遞參數,需要在初始化列表顯示調用),然后在調用自己的構造函數
Conainter::Container():: Component() {…} //編譯器會自己去構造Component對象
container的構造函數首先調用component的default構造函數
composition的析構是由外而內的:B的析構函數首先執行自己的,然后才調用A的析構函數。Container的析構先調用自己,然后再調用Component的析構函數(編譯器幫助完成,我們只要管理好Container的構造和析構就可以)。
如果Component的構造函數有多個,則需要顯式的調用構造函數。
- Delegation委托,Composition by reference
Tips:復合是兩者生命一起出現,委托(又稱Composition by reference)Container先創建起來,等到需要Component的時候,才創建Component,即不同步。
a.設計模式:Handle/Body(pointer to implementation:我有一個指針,去指向為我實現所有功能的那一個類。——指針可以指向不同的實現類,所以又稱編譯防火墻:(下圖)左邊的永遠不用再編譯,要編譯的只是(下圖)右邊的。
b.共享
如上圖紅圈所示,a,b,c三個指針指向同一個字符串,即共享一個字符串,這樣的好處是節省內存空間(當然,共享的內容必須完全一樣),但是需要注意,當想改變一個指針指向的內容時,其余兩個指針指向的值也就隨之改變,也就是牽一發而動全身。
- Inheritance,類的繼承,是新的類從已有類那里得到已有的特性。或從已有類產生新類的過程就是類的派生。原有的類稱為基類或父類,產生的新類稱為派生類或子類。
Inheritance的聲明:
class 派生類名:繼承方式 基類名1, 繼承方式 基類名2,...,繼承方式 基類名n
{
subclass成員聲明;
};
- 一個派生類可以同時有多個基類,這種情況稱為多重繼承,派生類只有一個基類,稱為單繼承。直接派生,間接派生。
- 繼承方式規定了如何訪問基類繼承的成員。繼承方式有public, private, protected。如果不顯示給出繼承方式,默認為private繼承。繼承方式指定了派生類成員以及類外對象對于從基類繼承來的成員的訪問權限。
- 派生類繼承基類中除構造和析構函數以外的所有成員。
- 派生類生成:
吸收基類成員(除構造析構函數以外的所有成員);
改造基類成員(根據繼承方式調整基類成員的訪問,函數在子類中的覆蓋,以及虛函數在子類中的覆蓋)
Tips:C++類的3種繼承方式,分別是public繼承,protected繼承,private繼承。
最常用的還是public繼承。class默認的是private繼承,它的member如果沒寫權限也是默認.
3個繼承權限的區別:
class ex0
{
private:
void showPrivate(){cout<<"this is private function!";}
public:
void showPublic(){cout<<"this is public function!";}
protected:
void showProtected(){cout<<"this is protected function!";}
};
class ex1:public ex0
{
public:
void func()
{
showPrivate();
//錯誤因為此函數訪問權限只有基類 ex0 自己有
showPublic();
showProtected();
}
};
class ex2:protected ex0
{
public:
void func()
{
showPrivate();
//錯誤因為此函數訪問權限只有基類 ex0 自己有
showPublic();
//正確, 但是此函數由于 ex2 的保護繼承這個函數的訪問權限已經變成了 protected,
//也就是說對于外部類來說已經不具備訪問這個函數的權限了
showProtected();
}
};
class ex3:private ex0
{
public:
void func()
{
showPrivate();
//錯誤因為此函數訪問權限只有基類 ex0 自己有
showPublic();
//正確, 但是此函數由于 ex3 的私有繼承這個函數的訪問權限已經變成了 private,
//也就是說對于外部類和派生類來說已經不具備訪問這個函數的權限了
showProtected();
//正確, 但是此函數由于 ex3 的私有繼承這個函數的訪問權限已經變成了 private,
//也就是說對于外部類和派生類來說已經不具備訪問這個函數的權限了
}
};
- Inheritance(繼承)with virtual functions
繼承搭配著虛函數。
子類可以繼承父類的所有內容,包括數據和函數。從內存角度,可以繼承數據;對于函數,不能從內存角度理解,而是子類繼承了父類的函數調用權。
函數分為三種情況:
- non virtual function:你不希望derived class重新定義override
- virtual function:希望derived class的重新定義override。已有默認定義。
- pure virtual:你希望derived class一定要重新定義。沒有默認定義。
虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的。簡稱為V-Table。
在這個表中,主是要一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其真實反應實際的函數。這樣,在有虛函數的類的實例中這個表被分配在了 這個實例的內存中,所以,當我們用父類的指針來操作一個子類的時候,這張虛函數表就顯得由為重要了,它就像一個地圖一樣,指明了實際所應該調用的函數。
編譯器必需要保證虛函數表的指針存在于對象實例中最前面的位置(這是為了保證正確取到虛函數的偏移量)。 這意味著我們通過對象實例的地址得到這張虛函數表,然后就可以遍歷其中函數指針,并調用相應的函數。
假設有下面這個類:
class Base {
public:
virtual void f( ) { cout << "Base::f" << endl; }
virtual void g( ) { cout << "Base::g" << endl; }
virtual void h( ) { cout << "Base::h" << endl; }
};
我們可以通過Base的實例來得到虛函數表。
Base b;
cout << "虛函數表地址:" << (int*)(&b) << endl;
cout << "虛函數表 — 第一個函數地址:" << (int*)*(int*)(&b) << endl;
可得到:
虛函數表地址:0012FED4
虛函數表 — 第一個函數地址:0044F148
-
設計模式:Prototype(原型模式)
用原型實例制定創建對象的種類,并且通過拷貝創建新的對象;
類圖:
核心是克隆函數的運用
組成元素:
Prototype:聲明克隆自身的接口;
ConcretePrototype:實現克隆自身的操作;