美文网首页
GeekBand极客班C++面向对象高级编程(上)第三周笔记

GeekBand极客班C++面向对象高级编程(上)第三周笔记

作者: xiaoxii | 来源:发表于2017-01-13 19:27 被阅读0次

    11.组合与继承

    . 遇到复杂问题时,需要类与类相关联,即面向对象思想

    Composition复合

    . 表示has-a, (里面有个类)

    . 复合类似一种类与类的包含关系


    template <class T, class Sequence = deque(T)>

    class queue

    {

    ...

    protected :                         //给子类提供接口

        Sequence c;                 //底层容器

    public :                             //以下操作函数完全利用c的操作函数完成

        bool empty() const {return c.empty() ; }

        size_type size() const {return c.size() ; }

        reference front() {return c.front() ;}

        reference back() {return c.back() ; }        //deque是两端可进出,queue是末端今前端出fifo

        void push (const value_type& x) { c.push_back(x) ; }

        void pop() {c.pop_front() ; }

    } ;        //这时候所有的功能deque都可以完成,则deque借用后,不需要自己写新功能了


    . 图示时,用实心菱形加箭头◆→表示,箭头指向的一端为被包涵的类,实心菱形一端为容器

    . 这时候,容器可以借用另外函数的功能实现自己的功能

    . 复合可以将其他类函数改装成为自己需要的函数,即可看作一种Adapter

    . 复合可以嵌套

    . 从内存角度:复合所占大小Sizeof要把所包涵的类一层一层计算进去

    Composition复合关系下的构造和析构

    . Container◆→Component

    . 构造时,由内而外,Container的构造函数先调用Component的构造函数,再执行自己


    Container::Container(...) : Component() {...} ;             //调用的是Component的默认构造函数


    . 析构时,由外而内,先执行自己,再调用Component的析构函数


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


    . 编译器会帮忙调用Component构造和析构函数,但只能调用默认的

    . 如果不想调用默认构造函数,需要自己写Component构造函数的调用

    Delegation委托. Composition by reference

    . 如果是有指针的类,而指针指向另一个类

    . 图示时,用空心菱形加箭头◇→表示,箭头指向的一端为被类包涵的指针所指向的类


    class StringRep ;

    class String

    {

    public:

        String() ;

        String(const char* s) ;

        String(const String& s) ;                                    //拷贝构造

        String &operator = (const String& s) ;              //拷贝赋值

        ~String() ;                                                         //析构

    ...

    Private :

        StringRep* rep ;                                    //pimpl

    } ;


    . 以指针为方式调用另一个类,即为Delegation,也可成为Composition by reference

    . 当用指针相连时,数据生命不一致,与Composition相区别

    . →一端当需要时才去创建,◇一端只是对外的接口,当需要动作时都调用→一端的类去实现

    . 这种写法叫做pimpl(pointer to implimentation),又叫做Handle/Body

    . 当这样写类时,前面接口可以保持不变,指针后面真正实现的类可以切换,不会影响客户端

    . 又叫做编译防火墙

    . reference counting,例如当有三个object共享同一个rep(改变内容互不影响copy on write)

    Inheritance继承,表示is-a


    struct _List_node_base                              //以struct为例子

    {

        _List_node_base* _M_next ;

        _List_node_base* _M_prev ;

    } ;

    template<typename _Tp>

    struct _List_node  : public _List_node_base         //子类,将struct从父类继承过来

    {

        _Tp _M_data ;

    } ;


    . 图示时,用空心三角形加直线表示◁-,横线一端表示子类,▷一端表示父类

    . 继承方式有三种,public、protected、private

    .. public继承可以被任意实体访问

    .. protected继承只允许子类及本类的成员函数访问

    .. private继承只允许本类的成员函数访问

    . 从内存角度,父类数据被完整继承下来到子类,子类对象中包涵父类成分

    inheritance继承关系下的构造和析构

    . Derived-▷Base

    . 由于是也是包含关系,所以与Component类似

    . 构造由内而外,Derived构造函数先调用Base的默认构造函数,然后再执行自己


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


    . 析构由外而内


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


    . base class的dtor必须是virtual,否则会出现undefined behaviour

    . 也由编译器自动完成

    12.虚函数与多态

    Inheritance with virtual functions 带虚函数的继承

    . 语法形式,函数前加virtual

    . non-virtual函数,不希望derived class派生类(子类)重新定义(override,复写)

    . virtual函数,希望derived class重新定义,但它自己有默认定义

    . pure virtual函数,希望derived class一定要重新定义,它自己没有默认定义


    class Shape

    {

    public :

        virtual void draw() const = 0 ;                                        //pure virtual

        virtual void error(const std :: string& msg) ;                  //impure virtual

        int objectID() const ;                                                     //non-virtual

    ...

    }

    class Rectangle : publicShape{...} ;


    . 有时候纯虚函数也可以有定义

    . 在类中考虑到继承问题时,要考虑搭配虚函数

    . 很多时候在不同软件中,都有某功能相类似,例如文件编辑的软件中的打开功能,这时候写一个父类来解决相同操作步骤,将特殊部分列为虚函数,以此来提高效率

    . 父类可能很久前就写好的,实际运行main时通过子类对象调用父类函数


    CDocument::OnFileOpen()                                     //(1)

    {

    ...

        Serialize()            //函数中做了一些固定动作把其中的一些关键部分延缓到子类去给定,以后由子类写出

    ...

    }

        class CMyDoc :public CDocument                     //(2)

    {

    virtual Serialize() {...}

    } ;

    main()

    {

        CMyDoc myDoc ;

    ...

        myDoc.OnFileOpen() ;                                     //CDocument::OnFileOpen(&myDoc);

    }              //调用顺序,通过(2)进入到(1)开始调用,到S函数时,调用(2)中virtual,再回(1)继续


    . 通过子类调用父类函数

    . 父类中的关键动作可延缓到子类去操作,叫做Template Method (不是指的模板)

    . 在框架中,会设计出同类固定功能,将无法决定的功能留为虚函数留给子类去定义

    . MFC就是一种Template Method

    . 在上面栗子中,调用Serialize时,编译器在做这样的动作:


    this->Serialize() ;

    (*(this->vptr)[n])(this) ;


    Inheritance+Composition关系下的构造和析构

    . Derived既含父类又含Component时,

    . Derived含父类,其父类又含Component时,一层一层构造和析构即可

    Delegation+Inheritance 委托+继承

    . 委托+继承的功能最强大


    class Observer

    {

    public :

        virtual void update(Subject* sub,int value)=0 ;         //将来可以派生不同的子类

    } ;

    class Subject                                //需要很多观察器,与Observer是委托关系

    {

        int m_value ;

        vector<Observer*>m_views ;               //准备一个容器,里面可以放好多Observer的指针

    public :

        void attach(Observer* obs)                  //提供注册功能(还要有注销功能,栗子没给出)

        {                                                           //附着一个Observer

            m_views.push_back(obs) ;             //放到容器中

        }

        void set_val(int value)                         //

        {

            m_value+value;

            notify() ;

        }

        void notify()                                              //遍历并通知所有Observer更新信息

        {

            for(int i=0;i<m_views.size();i++)

            m_views[i]->update(this,m_value);

        }

    }


    13.委托相关设计

    . 若要写一个file system或者window system,先要理清需要构造的层次关系,再考虑需要那些class和关系

    Composite:以file system为例:

    . 先要准备一个primitive,也可称为file

    . 另外要准备一个Composite,一个容器,可以容纳很多file,也可以放很多他自己

    . Composite还需要一个可以添加Primitive也可以添加他自己的一个函数

    . 这时候需要写一个Primitive和Composite共同的父类,即Component

    . Component中可以写一个添加函数,这时候Composite就可以委托他实现添加功能

    . 这种方法即为设计模式Composite,是一个委托+继承模式

    . 代码如下


    class Component

    {

        int value ;

    public :

        Component(int val){value=val;}

        virtual void add(Component*){}          //需要让Composite重新定义add功能,所以写为虚函数

    }                                                             //但不能是纯虚函数,因为Primitive不能有动作

    class Composite :public Component    

    {

        vector<Component*>c;                     //做一个容器存放Component

    public :

        Composite(int val): Component(val){}

        void add(Component* elem) {c.push_back(elem) ; }

    ...

    }

    class Primitive :publicComponent

    {

    public :

        Primitive(int val) :Component(val){}

    } ;


    Prototype

    . 框架被建好的时候,因为定义需要被子类来定义,这时候不能new,需要new的class name被还没创建

    . 这时使派生的子类都可以new一个自己作为Prototype,让框架可以看到Prototype的位置来接收它

    . 创建子类时,安排一个静态对象(图示为加下划线)作为原型,这个原型必须登记到父类框架端

    .. 写代码时候线写typename,再写objectname

    . 父类要留有空间来给子类登记原型

    . 静态对象构造时,需要调用构造函数,做一个private数据(图示为前加负号,protected图示为前加#)

    . 这时构造函数只能被自己调用,这个构造函数需要调用父类添加原型函数把自己登记到父类框架端

    . 父类中添加原型功能可以把得到的原型放入容器

    . 子类还需要自己准备一个clone函数,用来new自己,这时候通过原型对象可以调用clone

    . 所有的子类都需要这样来创建

    . 因为静态函数的调用需要classname,所以需要这样做

    . 代码如下


    #include<iostream>

    enum imageType{LAST , SPOT};

    class Image

    {

    public :

        virtual void draw()=0 ;

        static Image *findAndClone(imageType) ;

    protected :

        virtual imageType returnType()=0 ;                      //纯虚函数,一定要子类来写

        virtual Image *clone()=0 ;                                   

        static void addPrototype(Image *image)            //子类声明后,会将他的原型登记过来

        {_prototypes[_nextSlot++]=image;}

    private :                                                                    //把添加功能登记的每个原型保存到这里

        static Image *_prototypes[10];                           //这个数组是自己用来存放原型的容器

        static int _nextSlot;

    } ;                                                                              //class中静态的data必须在class外进行一次定义

    Image *Image::_prototypes[];

    int Image::_nextSlot;

    Image *Image::findAndClone(imageType type)       //客户需要Image对象实例时候调用这个公开静态函数

    {

        for(int i=0;i<_nextSlot;i++)                                  //在容器中寻找需要的class来clone出来

            if(_prototypes[i]->returnType()==type)

                return _prototypes[i]->clone();

    }


    class LandSatImage :public Image                                              //继承父类

    {

    public :

        imageType returnType(){return LAST ;}

        void draw(){cout<<"LandSatImage::draw"<<_id<<endl ; } 

        Image *clone(){return new LandSatImage(1) ; }                  // 用来new自己 ,调用第二构造,参数任意

    protected :

        LandSatImage(int dummy){_id = _count++; }             //第二个构造函数,用来给clone调用的构造函数

    private :

        static LandSatImage _LandSatImage                       //创建静态原型

        LandSatImage(){addPrototype(this) ; }                     //让原型调用父类添加函数登记到父类端的构造函数

        int _id ;

        static int _count ;

    } ;

    LandSatImage LandSatImage::_landSatImage ;

    int LandSatImage::_count = 1 ;


    ...

    在各种设计模式中有很多抽象思考需要构思,在不断写代码中进行进步

    ...

    相关文章

      网友评论

          本文标题:GeekBand极客班C++面向对象高级编程(上)第三周笔记

          本文链接:https://www.haomeiwen.com/subject/qnehbttx.html