zoukankan      html  css  js  c++  java
  • 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++面向对象高级编程>>

     
  • 相关阅读:
    poj 3321 Apple Tree
    hdu 1520 Anniversary party
    Light OJ 1089 Points in Segments (II)
    Timus 1018 Binary Apple Tree
    zoj 3299 Fall the Brick
    HFUT 1287 法默尔的农场
    Codeforces 159C String Manipulation 1.0
    GraphQL + React Apollo + React Hook 大型项目实战(32 个视频)
    使用 TypeScript & mocha & chai 写测试代码实战(17 个视频)
    GraphQL + React Apollo + React Hook + Express + Mongodb 大型前后端分离项目实战之后端(19 个视频)
  • 原文地址:https://www.cnblogs.com/LearningTheLoad/p/7309817.html
Copyright © 2011-2022 走看看