• 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.类图

    那么上面的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++面向对象高级编程>>

     
  • 相关阅读:
    转:深入 AngularUI Router
    angularJS $scope的$apply方法实现model刷新
    CSS 如何让 height:100%; 起作用
    【AngularJs】---$sce 输出Html
    angular 组件之间传值
    kendo Grid 列添加自定义模板
    关于“内控点”
    关于总结
    咏春
    一只老鼠夹
  • 原文地址:https://www.cnblogs.com/LearningTheLoad/p/7309817.html
Copyright © 2020-2023  润新知