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 ;
...
在各种设计模式中有很多抽象思考需要构思,在不断写代码中进行进步
...