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

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 ;


...

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

...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容