天天看點

C++面向對象進階程式設計(五)類與類之間的關系

技術在于交流、溝通,轉載請注明出處并保持作品的完整性。

本節主要介紹一下類與類之間的關系,也就是面向對象程式設計先介紹兩個術語

Object Oriented Programming   OOP面向對象程式設計

Object Oriented Design  OOD面向對象設計

對于類與類之間的關系有很多種,但是我認為了解3種足夠

1.Inheritance (繼承)

2.Composition (組合) 

3.Delegation (委託)  該種關系也可以了解成聚合

 一.組合

1.定義: has-a的關系,一個類中有包含另外一個類 (類中的成員變量之一是類),是包含一個對象,而不是包含一個指針,如果你組合了這個類,那麼你就将擁有你包含的類的全部功能

 下面我介紹一個組合的實際應用

#include<deque>
#include <queue>
template <class T>
class queue {
    ...
protected:
    std::deque<T> c; // 底層容器       has-a的關系
public:
    // 以下完全利用 c 的操作函數完成
    bool empty() const { return c.empty(); }//利用deque的功能來實作queue新定義的功能
    size_t size() const { return c.size(); }
    reference front() { return c.front(); }
    reference back() { return c.back(); }
    
    void push(const value_type& x) { c.push_back(x); }
    void pop() { c.pop_front(); }
};      

 queue是一種隊列操作,單方向操作先進先出

 deque是兩端都可進出,是以說deque的功能較強大與quque,但是如果我queue組合deque(包含 has-a)那麼我們就可以利用deque的功能來實作queue新定義的功能

 這就是組合關系的一種實際應用,同時它也是adapter設計模式

 2.類圖

C++面向對象進階程式設計(五)類與類之間的關系

那麼上面的queue與deque的類圖為

C++面向對象進階程式設計(五)類與類之間的關系

queue包含deque

3.記憶體管理

template <class T>
class queue {
protected: 
    deque<T> c; 
    ... 
};


template <class T>
class deque {
protected:
    Itr<T> start; Itr<T> start;//16 bit
    Itr<T> finish; Itr<T> finish; //16 bit
    T** map; T** map; //4bit
    unsigned int map_size;  //4bit
};


template <class T>
struct Itr { struct Itr {
    T* cur; T* cur;  //4bit
    T* first; T* first;
    T* last; T* last;
    T** node; 
    ...
};      

 圖示

C++面向對象進階程式設計(五)類與類之間的關系

是以是queue的記憶體為40bit

4.構造與析構

未了友善我們的了解,我們可以将組合關系聯想成下圖

C++面向對象進階程式設計(五)類與類之間的關系

a.構造由内而外

Container 的構造函數首先調用 Component 的 default 構造函數,然後才執行自己 的構造函數,可以了解成這樣

Container::Container(...): Component() { ... };       

b.析構由外而内

Container 的析構函數首先執行自己的,然後調用 Component 的 析構函數,可以了解成這樣

Container::~Container(...){ ... ~Component() };      

5.生命周期

 Container于Component具有相同的生命周期

二.聚合 也就是委托關系

1.定義has-a pointer,一個類中包含另一個類的指針,你也同樣擁有被包含類的全部功能,他有一個重要的使用方法handle/body(pImpl)(我在格式工廠(六)shared_ptr中有介紹)

class StringRep;

class String {//handle
public:
    String();
    String(const char* s);
    String &operator=(const String& s); ~String();
    ....
private:
    StringRep* rep; // pimpl
};


class StringRep { //body
    friend class String;
    StringRep(const char* s);
    ~StringRep();
    int count;
    char* rep;
};      

 功能其實與組合非常相似

2.類圖

C++面向對象進階程式設計(五)類與類之間的關系

3.記憶體管理

包含一個指針    4bit

4.構造與析構

不發生影響

5.生命周期

生命周期可以不相同

三.繼承

1.定義is-a的關系,分為父類(Base)和子類(Drived),可以了解成孩子繼承父親的财産,就是父類有的子類都可以有,也可以了解成子類有父類的成分

class _List_node_base
{
    ...
    _List_node_base* _M_next;
    _List_node_base* _M_prev;
    ...
};

template<typename _Tp>
class _List_node: public _List_node_base
{
    _Tp _M_data;
};      

2.類圖

C++面向對象進階程式設計(五)類與類之間的關系

3.記憶體管理 

無太大關聯,抛去成員變量,子類比父類多一個虛函數表 4bit

4.構造與析構

子類含有父類的成分,可以了解成

C++面向對象進階程式設計(五)類與類之間的關系

構造由内而外

Derived 的構造函數首先調用Base 的 default 構造函數, 然後執行自己的

Derived::Derived(...): Base() { ... };       

析構由外而内

Derived 的析構函數首先執行自己的,然後調用用 Base 的析構函數。 

Derived::~Derived(...){ ... ~Base() };       

 5.繼承真正的使用是與虛函數的搭配

虛函數:用virtual聲明的函數,它有三種形式

non-virtual  即普通函數,你不希望子類重新定義它(重新定義override)

virtual 函數(虛函數):你希望 derived class 重新定義 它,且你對這個函數有預設定義

pure virtual 函數(純虛函數):你希望 derived class 一定要重新定義它,你對它沒有預設定義

void func_1();//non-virtual
virtual void func_2();//virtual
virtual void func_3() = 0;//pure virtual      

 下面我們來驗證一下上面的繼承規則

class A
{
public:
    A()
    {
        cout<< "A ctor" << endl;
    }
    virtual ~A()
    {
        cout<< "A dctor" << endl;
    }
    
    void func()
    {
        cout<< "A::func()"<<endl;
    }
    
    virtual void func_virtual()
    {
        cout<< "A::func_virtual()"<<endl;
    }
};

class B : public A
{
public:
    B()
    {
        cout<< "B ctor"<<endl;
    }
    ~B()
    {
        cout<< "B dctor"<<endl;
    }
    
    void func_virtual()
    {
        cout<< "B::func_virtual()"<<endl;
    }
};      

我們先建立一個B對象看看都能輸出什麼

int main(int argc, const char * argv[]) 
{
    B b;
    return 0;
}         

輸出結果

C++面向對象進階程式設計(五)類與類之間的關系

說明繼承由内而外的構造,和由外而内的析構

繼續看

1 int main(int argc, const char * argv[]) {
2     A* a = new B(); //父類指針可以指向子類對象(一般情況下子類的記憶體占用會大于父類,是以父類指針指向子類是可以的,那麼反過來 子類指針指向父類就不行了)
3     a->func();
4     a->func_virtual();
5     delete a;//誰申請誰釋放
6     a = nullptr;
7     return 0;
8 }      

 輸出結果

C++面向對象進階程式設計(五)類與類之間的關系

你會返現為什麼我用a調用func_virtual() 會調用到B的該函數,這個就是繼承的好處之一了,他能動态識别是誰調用

用虛函數表來解釋動态識别想必大家都會知道,現在我來介紹一下我的了解---this指針

在C++類中除了靜态變量都有this指針,在上面第2行 A* a = new B(); 其實 a是一個b對象

在第3行 a->func(),編譯器會編譯成a->func(&a),(我在之前的文章中介紹過誰調用誰就是this,那麼治理的&a 就相當于this),然後會在B中找func(),發現沒有就去父類的A中去找

在第4行 a->func_virtual() => a->func_virtual(&a) 在B中找到了是以調用.

 四 組合+繼承

組合和繼承共同使用它們的它們的建立順序會是什麼樣子

 第一種

C++面向對象進階程式設計(五)類與類之間的關系

Component構造 > Base構造 > 子類構造  析構相反

第二種

C++面向對象進階程式設計(五)類與類之間的關系

組合和繼承的構造順序都是由内而外,析構順序都是由外而内,那上面的構造析構順序呢

class A
{
public:
    A(){cout<< "A ctor" << endl;}
    virtual ~A(){cout<< "A dctor" << endl;}
    void func(){cout<< "A::func()"<<endl;}
    virtual void func_virtual(){cout<< "A::func_virtual()"<<endl;}
};

class C
{
public:
    C(){cout<< "C ctor"<<endl;}
    ~C(){cout<< "C dctor"<<endl;}
};

class B : public A
{
public:
    B(){cout<< "B ctor"<<endl;}
    ~B(){cout<< "B dctor"<<endl;}
    void func_virtual(){cout<< "B::func_virtual()"<<endl;}
private:
    C c;
};      

 輸出結果

C++面向對象進階程式設計(五)類與類之間的關系

Base構造 > Component構造 > 子類構造  析構相反

Derived 的構造函數首先調用 Base 的 default 構造函數, 然後調用 Component 的 default 構造函數, 然後執行自己 

Derived::Derived(...): Base(),Component() { ... }; 

Derived 的析構函數首先執行自己, 然後調用 Component 的 析構函數,然後調用 Base 的析構函數 

Derived::~Derived(...){ ... ~Component(), ~Base() }; 

五 聚合 + 繼承

 這個我用一種設計模式來做執行個體

觀察者模式(主要介紹聚合+繼承的實作,詳細的觀察者模式我會在設計模式中介紹)

 假設有一個txt檔案,我用三個不同的閱讀軟體同時讀取這一個txt檔案,那麼當txt内容發生改變時,這三個閱讀器的内容都應做出相應的變化,其實作代碼大緻如下

 用類圖描述一下

C++面向對象進階程式設計(五)類與類之間的關系

 大緻實作如下

class Subject {
    String m_value;
    vector<Observer*> m_views;//包含指針
public:
    void attach(Observer* obs) {
        m_views.push_back(obs);//捕獲Observe子類
    }
    void set_val(int value) {//目前内容發生改變
        m_value = value;
        notify();
    }
    void notify() {//通知所有子類發生改變,通過其繼承關系調用相應的方法
        for (int i = 0; i < m_views.size(); ++i) m_views[i]->update(this, m_value);
    }
};


class Observer {
public:
    virtual void update(Subject* sub, int value) = 0;
};

class Observer_Sub : public  Observer //不同的閱讀工具 同時觀察Subject中的m_value
{
  void update(){...;}
};      

五 聚合 + 繼承

下面這個例子有點難了解且非常抽象,

現在我以原型模式來實作一個自動建立建立子類的方法

1.類圖

C++面向對象進階程式設計(五)類與類之間的關系

2.實作如下

1 #include <iostream>
 2 using namespace std;
 3 
 4 enum imageType
 5 {
 6     LSAT, SPOT
 7 };
 8 
 9 class Image
10 {
11 public:
12     virtual void draw() = 0;
13     static Image *findAndClone(imageType);
14 protected:
15     virtual imageType returnType() = 0;
16     virtual Image *clone() = 0;
17     // As each subclass of Image is declared, it registers its prototype
18     static void addPrototype(Image *image)
19     {
20         _prototypes[_nextSlot++] = image; }
21 private:
22     // addPrototype() saves each registered prototype here
23     static Image *_prototypes[10];
24     static int _nextSlot;
25 };
26 
27 Image *Image::_prototypes[];
28 int Image::_nextSlot;
29 
30 // Client calls this public static member function when it needs an instance // of an Image subclass
31 Image *Image::findAndClone(imageType type)
32 {
33     for (int i = 0; i < _nextSlot; i++)
34     {
35         if (_prototypes[i]->returnType() == type)
36         {
37             return _prototypes[i]->clone();
38         }
39     }
40     return nullptr;
41 }      

子類SpotImage

1 class SpotImage: public Image
 2 {
 3 public:
 4     imageType returnType()    {
 5         return SPOT;
 6     }
 7     void draw()
 8     {
 9         cout << "SpotImage::draw " << _id << endl;
10     }
11     Image *clone()    {
12         return new SpotImage(1);
13     }
14 protected:
15     SpotImage(int dummy)
16     {
17         _id = _count++;
18     }
19 
20 
21 private:
22     SpotImage()
23     {
24         addPrototype(this);
25         cout<< "static init SpotImage" << endl;
26     }
27     static SpotImage _spotImage;
28     int _id;
29     static int _count;
30 };
31 SpotImage SpotImage::_spotImage;
32 int SpotImage::_count = 1;      

子類LandSatImage

1 class LandSatImage: public Image
 2 {
 3 public:
 4     imageType returnType()
 5     {
 6         return LSAT;
 7     }
 8     void draw()
 9     {
10         cout << "LandSatImage::draw " << _id << endl;
11     }
12     // When clone() is called, call the one-argument ctor with a dummy arg
13     Image *clone()
14     {
15         return new LandSatImage(1);
16     }
17 
18 protected:
19 // This is only called from clone()
20     LandSatImage(int dummy)
21     {
22         _id = _count++;
23     }
24 private:
25 // Mechanism for initializing an Image subclass - this causes the
26 // default ctor to be called, which registers the subclass's prototype
27     static LandSatImage _landSatImage;
28 // This is only called when the private static data member is inited
29     LandSatImage()
30     {
31         addPrototype(this);
32         cout<< "static init LandSatImage" << endl;
33     }
34 // Nominal "state" per instance mechanism
35     int _id;
36     static int _count;
37 };
38 // Register the subclass's prototype
39 LandSatImage LandSatImage::_landSatImage;
40 // Initialize the "state" per instance mechanism
41 int LandSatImage::_count = 1;      

調用

1 // Simulated stream of creation requests
 2 const int NUM_IMAGES = 8;
 3 imageType input[NUM_IMAGES] =
 4 {
 5     LSAT, LSAT, LSAT, SPOT, LSAT, SPOT, SPOT, LSAT
 6 };
 7 
 8 
 9 int main() {
10     
11     Image *images[NUM_IMAGES];
12     // Given an image type, find the right prototype, and return a clone
13     
14     
15     for (int i = 0; i < NUM_IMAGES; i++)
16         
17         
18     images[i] = Image::findAndClone(input[i]);
19     
20     
21     // Demonstrate that correct image objects have been cloned
22     for (int i = 0; i < NUM_IMAGES; i++)
23         
24         
25     images[i]->draw();
26     
27     
28     // Free the dynamic memory
29     for (int i = 0; i < NUM_IMAGES; i++)
30         delete images[i];
31     
32     return 0;
33 }      

其實主要難了解的地方有兩個

a.靜态變量率先初始化  a.SpotImage初始化其預設構造函數調用 Image::addPrototype()

           b.LandSatImage 初始化其預設構造函數調用 Image::addPrototype()

               這兩步使Image::_nextSlot == 2  并使這兩個子類注冊在Image::_prototypes[]中

b.SpotImage和LandSatImage其clone()函數調用帶參數的構造函數,預設構造函數留給靜态變量初始化使用

如有不正确的地方請指正

參照<<侯捷 C++面向對象進階程式設計>>