課堂大綱:
1.組合與繼承
1.1 Composition 復合
1.2 Delegation 委托
1.3 Inheritance 繼承
2.虛函數與多態
2.1虛函數
正文
1.組合與繼承
1.1Composition 復合
復合表示has-a,表示一個類里含有另一個類的對象(非指針及引用)。
例如
template<class T>
class queue
{
...
protected:
deque<T> c; //底層容器
};
其中c是該類的一個成員,是c這個對象及其成員變量是占據queue內存的。
UML表示方法是:
黑色實心菱形從queue類指向deque類,一個簡單的記憶方法是:菱形是實心的,表示這個queue是真的有這個deque的實體,然后這個菱形指向了deque類,表示這個菱形表示deque。
Composition關系下的構造和析構
構造:
構造是由內而外,就像打包裹,從里往外。先構造components的對象再構造container,這是編譯器自動完成的。
Container::Container(...):Component(){...};
析構:
析構是由外而內,就像拆包裹,從外往里。先析構Container再析構components的,這也是編譯器自動完成的。
Container::~Container(){~Component();}
1.2 Delegation委托
委托表示composition by reference,表示一個類里含有另一個類的指針或者引用對象。
例如:
class String
{
private:
StringRep* rep; //pimml
};
黑色空心菱形從String類指向StringRep類,一個簡單的記憶方法是:菱形是空心的,表示這個String是只有這個StringRep的指針對象或者引用,然后這個菱形指向了StringRep類,表示這個菱形表示StringRep類。
著名的寫法:編譯防火墻
一個類A只提供接口,具體實現用另一個類B來完成,其中A與B的 關系是委托關系,好處是可以切換具體實現,不影響接口。
a b c共享數據,如果a要改數據,那么系統就會復制一份專門給a修改,而不會影響b和c 的使用。
1.3Inheritance 繼承
復合表示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表示方式為:
圖中表示的方式與前面兩種略有不同,結合三角形,可以看成是有子類指向父類,這是一個泛化的過程,譬如老虎→動物。
其中繼承方式有public、protected、private三種,現簡單介紹一下其特性。
public | protected | private | |
---|---|---|---|
公共繼承 | public | protected | 不可見 |
私有繼承 | private | private | 不可見 |
保護繼承 | protected | protected | 不可見 |
在上圖中:1)基類成員對派生類都是:共有和保護的成員是可見的,私有的的成員是不可見的。
** 2)基類成員對派生類的對象來說:要看基類的成員在派生類中變成了什么類型的成員。如:私有繼承時,基類的共有成員和私有成員都變成了派生類中的私有成員,因此對于派生類中的對象來說基類的共有成員和私有成員就是不可見的。**
父類中的數據會被子類繼承下來,但是子類能否直接訪問這些數據需要根據上表的特性來考慮。
當繼承與虛函數搭配時,能充分發揮出繼承的價值。關于虛函數,下文會提到。
繼承關系下的構造和析構
構造:
繼承關系下的構造還是由內而外地,derived類先構造base類的,再構造自己的,這是編譯器自動完成的
Derived::Derived(...):Base(){...}
析構:
析構則是由外而內地,先析構derived的再析構base的,這也是編譯器自動完成
Derived::~Derived() {~Base();}
值得注意的是,《Effective C++》條款07中有提到
當derived class 對象經由一個base class指針被刪除,而該base class 帶有一個non-virtual 析構函數時,其結果未有定義——實際執行時通常發生的是對象的derived成分沒被銷毀。
消除這個問題的做法很簡單:給base class 一個virtual析構函數。此后刪除derive class對象就會如你想要的那般。
讀者可以簡單地寫一個類來測試一下,這里筆者就隨便寫一個例子來說明這個問題。
#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;
}
運行結果:
可以清楚看到這就發生了上述內存泄露的問題了。為此
修改程序如下:
#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;
}
運行結果如下:
所以在父類的析構函數中加了virtual關鍵字后,delete父類指針時可以還調用子類的析構函數,從而避免了內存泄露。
值得提醒一下的時,《Effective C++》07條款中給的提醒是:
無端地將所有classes的析構函數聲明為virtual,就像從未聲明它們virtual一樣,都是錯誤的。許多人的心得是:只有當class內含至少一個virtual函數才為它聲明virtual析構函數
好了,終于進入下一部分了。
2.虛函數與多態
2.1虛函數
首先簡單介紹三個關于虛函數的名詞:
non-virtual函數:非虛函數,這個函數是你不希望子類重新定義它。
virtual函數:虛函數,你希望子類重新定義,而且父類中已經定義過這個函數。
pure virtual函數: 純虛函數,你希望子類一定要重新定義且父類中并無默認定義。
例子如下:
class Shape
{
public:
virtual void draw( ) const = 0; //純虛函數
virtual void error( const std::string& msg);//虛函數
int objectID( ) const;//非虛函數
...
};
class Rectangle: public Shape {...};
class Ellipse:public Shape{...};
```
**繼承+復合關系下的構造和析構**

三個類的關系如上圖所示,那么我們用簡單的程序測試一下:
```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;
}
```
運行結果是:
```
Base ctor!
Component ctor!
Derived ctor!
Derived dtor!
Component dtor!
Base dtor!
```
那么如下圖的關系呢,我們再修改一下程序測試一下:

```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;
}
```
運行結果是:
```C++
Component ctor!
Base ctor!
Derived ctor!
Derived dtor!
Base dtor!
Component dtor!
```
和打包裹的例子是一致的,打包(構造)的時候是先包裝最里面的,然后再一層一層裝外面的。拆包(析構)都是從最外面的包裝開始拆起,直到拆到后面發現盒子里只剩下這么一點空氣了:)
**委托+繼承**
*待續...*