技術在於交流、溝通,轉載請注明出處並保持作品的完整性。
本節主要介紹一下類與類之間的關系,也就是面向對象編程先介紹兩個術語
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.類圖

那么上面的queue與deque的類圖為
![]()
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; ... };
圖示

所以是queue的內存為40bit
4.構造與析構
未了方便我們的理解,我們可以將組合關系聯想成下圖

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.類圖
![]()
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.類圖

3.內存管理
無太大關聯,拋去成員變量,子類比父類多一個虛函數表 4bit
4.構造與析構
子類含有父類的成分,可以理解成

構造由內而外
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; }
輸出結果

說明繼承由內而外的構造,和由外而內的析構
繼續看
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 }
輸出結果

你會返現為什么我用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中找到了所以調用.
四 組合+繼承
組合和繼承共同使用它們的它們的創建順序會是什么樣子
第一種

Component構造 > Base構造 > 子類構造 析構相反
第二種

組合和繼承的構造順序都是由內而外,析構順序都是由外而內,那上面的構造析構順序呢
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; };
輸出結果

Base構造 > Component構造 > 子類構造 析構相反
Derived 的構造函數首先調用 Base 的 default 構造函數, 然后調用 Component 的 default 構造函數, 然后執行自己
Derived::Derived(...): Base(),Component() { ... };
Derived 的析構函數首先執行自己, 然后調用 Component 的 析構函數,然后調用 Base 的析構函數
Derived::~Derived(...){ ... ~Component(), ~Base() };
五 聚合 + 繼承
這個我用一種設計模式來做實例
觀察者模式(主要介紹聚合+繼承的實現,詳細的觀察者模式我會在設計模式中介紹)
假設有一個txt文件,我用三個不同的閱讀軟件同時讀取這一個txt文件,那么當txt內容發生改變時,這三個閱讀器的內容都應做出相應的變化,其實現代碼大致如下
用類圖描述一下

大致實現如下
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.類圖

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++面向對象高級編程>>
