課堂大綱:
1.組合與繼承
1.1 Composition 復(fù)合
1.2 Delegation 委托
1.3 Inheritance 繼承
2.虛函數(shù)與多態(tài)
2.1虛函數(shù)
正文
1.組合與繼承
1.1Composition 復(fù)合
復(fù)合表示has-a,表示一個(gè)類(lèi)里含有另一個(gè)類(lèi)的對(duì)象(非指針及引用)蛛倦。
例如
template<class T>
class queue
{
...
protected:
deque<T> c; //底層容器
};
其中c是該類(lèi)的一個(gè)成員歌懒,是c這個(gè)對(duì)象及其成員變量是占據(jù)queue內(nèi)存的。
UML表示方法是:
黑色實(shí)心菱形從queue類(lèi)指向deque類(lèi)溯壶,一個(gè)簡(jiǎn)單的記憶方法是:菱形是實(shí)心的及皂,表示這個(gè)queue是真的有這個(gè)deque的實(shí)體,然后這個(gè)菱形指向了deque類(lèi)且改,表示這個(gè)菱形表示deque验烧。
Composition關(guān)系下的構(gòu)造和析構(gòu)
構(gòu)造:
構(gòu)造是由內(nèi)而外,就像打包裹又跛,從里往外碍拆。先構(gòu)造components的對(duì)象再構(gòu)造container,這是編譯器自動(dòng)完成的慨蓝。
Container::Container(...):Component(){...};
析構(gòu):
析構(gòu)是由外而內(nèi)感混,就像拆包裹,從外往里礼烈。先析構(gòu)Container再析構(gòu)components的弧满,這也是編譯器自動(dòng)完成的。
Container::~Container(){~Component();}
1.2 Delegation委托
委托表示composition by reference此熬,表示一個(gè)類(lèi)里含有另一個(gè)類(lèi)的指針或者引用對(duì)象庭呜。
例如:
class String
{
private:
StringRep* rep; //pimml
};
黑色空心菱形從String類(lèi)指向StringRep類(lèi),一個(gè)簡(jiǎn)單的記憶方法是:菱形是空心的犀忱,表示這個(gè)String是只有這個(gè)StringRep的指針對(duì)象或者引用疟赊,然后這個(gè)菱形指向了StringRep類(lèi),表示這個(gè)菱形表示StringRep類(lèi)峡碉。
著名的寫(xiě)法:編譯防火墻
一個(gè)類(lèi)A只提供接口近哟,具體實(shí)現(xiàn)用另一個(gè)類(lèi)B來(lái)完成,其中A與B的 關(guān)系是委托關(guān)系鲫寄,好處是可以切換具體實(shí)現(xiàn)吉执,不影響接口。
a b c共享數(shù)據(jù)地来,如果a要改數(shù)據(jù)戳玫,那么系統(tǒng)就會(huì)復(fù)制一份專(zhuān)門(mén)給a修改,而不會(huì)影響b和c 的使用未斑。
1.3Inheritance 繼承
復(fù)合表示is-a
例如:
struct _List_node_base
{
_List_node_base* _M_next;
_List_node_base* _M_prev;
};
template<typename _Tp>
struct _List_node: public _List_node_base
{
_Tp _M_data;
};
UML表示方式為:
圖中表示的方式與前面兩種略有不同咕宿,結(jié)合三角形,可以看成是有子類(lèi)指向父類(lèi),這是一個(gè)泛化的過(guò)程府阀,譬如老虎→動(dòng)物缆镣。
其中繼承方式有public、protected试浙、private三種董瞻,現(xiàn)簡(jiǎn)單介紹一下其特性。
public | protected | private | |
---|---|---|---|
公共繼承 | public | protected | 不可見(jiàn) |
私有繼承 | private | private | 不可見(jiàn) |
保護(hù)繼承 | protected | protected | 不可見(jiàn) |
在上圖中:1)基類(lèi)成員對(duì)派生類(lèi)都是:共有和保護(hù)的成員是可見(jiàn)的田巴,私有的的成員是不可見(jiàn)的钠糊。
** 2)基類(lèi)成員對(duì)派生類(lèi)的對(duì)象來(lái)說(shuō):要看基類(lèi)的成員在派生類(lèi)中變成了什么類(lèi)型的成員。如:私有繼承時(shí)壹哺,基類(lèi)的共有成員和私有成員都變成了派生類(lèi)中的私有成員抄伍,因此對(duì)于派生類(lèi)中的對(duì)象來(lái)說(shuō)基類(lèi)的共有成員和私有成員就是不可見(jiàn)的。**
父類(lèi)中的數(shù)據(jù)會(huì)被子類(lèi)繼承下來(lái)管宵,但是子類(lèi)能否直接訪問(wèn)這些數(shù)據(jù)需要根據(jù)上表的特性來(lái)考慮逝慧。
當(dāng)繼承與虛函數(shù)搭配時(shí),能充分發(fā)揮出繼承的價(jià)值啄糙。關(guān)于虛函數(shù),下文會(huì)提到云稚。
繼承關(guān)系下的構(gòu)造和析構(gòu)
構(gòu)造:
繼承關(guān)系下的構(gòu)造還是由內(nèi)而外地隧饼,derived類(lèi)先構(gòu)造base類(lèi)的,再構(gòu)造自己的静陈,這是編譯器自動(dòng)完成的
Derived::Derived(...):Base(){...}
析構(gòu):
析構(gòu)則是由外而內(nèi)地燕雁,先析構(gòu)derived的再析構(gòu)base的,這也是編譯器自動(dòng)完成
Derived::~Derived() {~Base();}
值得注意的是鲸拥,《Effective C++》條款07中有提到
當(dāng)derived class 對(duì)象經(jīng)由一個(gè)base class指針被刪除拐格,而該base class 帶有一個(gè)non-virtual 析構(gòu)函數(shù)時(shí),其結(jié)果未有定義——實(shí)際執(zhí)行時(shí)通常發(fā)生的是對(duì)象的derived成分沒(méi)被銷(xiāo)毀刑赶。
消除這個(gè)問(wèn)題的做法很簡(jiǎn)單:給base class 一個(gè)virtual析構(gòu)函數(shù)捏浊。此后刪除derive class對(duì)象就會(huì)如你想要的那般。
讀者可以簡(jiǎn)單地寫(xiě)一個(gè)類(lèi)來(lái)測(cè)試一下撞叨,這里筆者就隨便寫(xiě)一個(gè)例子來(lái)說(shuō)明這個(gè)問(wèn)題金踪。
#include<iostream>
using namespace std;
class Base
{
public:
Base( ) { cout << "I'm Base's Ctor" << endl; }
~Base( ) { cout << "I'm Base's Dtor" << endl;}
};
class Derived: public Base
{
public:
Derived( ) { cout << " I'm Derived's Ctor"<<endl; }
~Derived( ){cout<<"I'm Derived's Dtor"<<endl;}
};
int main()
{
Base *base = new Derived;
delete base;
}
運(yùn)行結(jié)果:
可以清楚看到這就發(fā)生了上述內(nèi)存泄露的問(wèn)題了。為此
修改程序如下:
#include<iostream>
using namespace std;
class Base
{
public:
Base( ) { cout << "I'm Base's Ctor" << endl; }
virtual ~Base( ) { cout << "I'm Base's Dtor" << endl;}
};
class Derived: public Base
{
public:
Derived( ) { cout << " I'm Derived's Ctor"<<endl; }
~Derived( ){cout<<"I'm Derived's Dtor"<<endl;}
};
int main()
{
Base *base = new Derived;
delete base;
}
運(yùn)行結(jié)果如下:
所以在父類(lèi)的析構(gòu)函數(shù)中加了virtual關(guān)鍵字后牵敷,delete父類(lèi)指針時(shí)可以還調(diào)用子類(lèi)的析構(gòu)函數(shù)胡岔,從而避免了內(nèi)存泄露。
值得提醒一下的時(shí)枷餐,《Effective C++》07條款中給的提醒是:
無(wú)端地將所有classes的析構(gòu)函數(shù)聲明為virtual靶瘸,就像從未聲明它們virtual一樣,都是錯(cuò)誤的。許多人的心得是:只有當(dāng)class內(nèi)含至少一個(gè)virtual函數(shù)才為它聲明virtual析構(gòu)函數(shù)
好了怨咪,終于進(jìn)入下一部分了屋剑。
2.虛函數(shù)與多態(tài)
2.1虛函數(shù)
首先簡(jiǎn)單介紹三個(gè)關(guān)于虛函數(shù)的名詞:
non-virtual函數(shù):非虛函數(shù),這個(gè)函數(shù)是你不希望子類(lèi)重新定義它惊暴。
virtual函數(shù):虛函數(shù)饼丘,你希望子類(lèi)重新定義,而且父類(lèi)中已經(jīng)定義過(guò)這個(gè)函數(shù)辽话。
pure virtual函數(shù): 純虛函數(shù)肄鸽,你希望子類(lèi)一定要重新定義且父類(lèi)中并無(wú)默認(rèn)定義。
例子如下:
class Shape
{
public:
virtual void draw( ) const = 0; //純虛函數(shù)
virtual void error( const std::string& msg);//虛函數(shù)
int objectID( ) const;//非虛函數(shù)
...
};
class Rectangle: public Shape {...};
class Ellipse:public Shape{...};
```
**繼承+復(fù)合關(guān)系下的構(gòu)造和析構(gòu)**
![捕獲3.JPG](http://upload-images.jianshu.io/upload_images/2020078-f84f329c06b6ff91.JPG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
三個(gè)類(lèi)的關(guān)系如上圖所示油啤,那么我們用簡(jiǎn)單的程序測(cè)試一下:
```C++
#include<iostream>
using namespace std;
class Base
{
public:
Base(){cout<<"Base ctor!"<<endl;}
~Base(){cout<<"Base dtor!"<<endl;}
int base;
};
class Component
{
public:
Component(){cout<<"Component ctor!"<<endl;}
~Component(){cout<<"Component dtor!"<<endl;}
int component;
};
class Derived:public Base
{
public:
Derived(){cout<<"Derived ctor!"<<endl;}
~Derived(){cout<<"Derived dtor!"<<endl;}
int derived;
Component cpt;
};
int main()
{
Derived d;
return 0;
}
```
運(yùn)行結(jié)果是:
```
Base ctor!
Component ctor!
Derived ctor!
Derived dtor!
Component dtor!
Base dtor!
```
那么如下圖的關(guān)系呢典徘,我們?cè)傩薷囊幌鲁绦驕y(cè)試一下:
![捕獲4.JPG](http://upload-images.jianshu.io/upload_images/2020078-641806665254d409.JPG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
```C++
#include<iostream>
using namespace std;
class Component
{
public:
Component(){cout<<"Component ctor!"<<endl;}
~Component(){cout<<"Component dtor!"<<endl;}
int component;
};
class Base
{
public:
Base(){cout<<"Base ctor!"<<endl;}
~Base(){cout<<"Base dtor!"<<endl;}
int base;
Component cpt;
};
class Derived:public Base
{
public:
Derived(){cout<<"Derived ctor!"<<endl;}
~Derived(){cout<<"Derived dtor!"<<endl;}
int derived;
};
int main()
{
Derived d;
return 0;
}
```
運(yùn)行結(jié)果是:
```C++
Component ctor!
Base ctor!
Derived ctor!
Derived dtor!
Base dtor!
Component dtor!
```
和打包裹的例子是一致的,打包(構(gòu)造)的時(shí)候是先包裝最里面的益咬,然后再一層一層裝外面的逮诲。拆包(析構(gòu))都是從最外面的包裝開(kāi)始拆起,直到拆到后面發(fā)現(xiàn)盒子里只剩下這么一點(diǎn)空氣了:)
**委托+繼承**
*待續(xù)...*