课堂大纲:
1.组合与继承
1.1 Composition 复合
1.2 Delegation 委托
1.3 Inheritance 继承
2.虚函数与多态
2.1虚函数
正文
1.组合与继承
1.1Composition 复合
复合表示has-a,表示一个类里含有另一个类的对象(非指针及引用)。
例如
template<class T>
class queue
{
...
protected:
deque<T> c; //底层容器
};
其中c是该类的一个成员,是c这个对象及其成员变量是占据queue内存的。
UML表示方法是:
黑色实心菱形从queue类指向deque类,一个简单的记忆方法是:菱形是实心的,表示这个queue是真的有这个deque的实体,然后这个菱形指向了deque类,表示这个菱形表示deque。
Composition关系下的构造和析构
构造:
构造是由内而外,就像打包裹,从里往外。先构造components的对象再构造container,这是编译器自动完成的。
Container::Container(...):Component(){...};
析构:
析构是由外而内,就像拆包裹,从外往里。先析构Container再析构components的,这也是编译器自动完成的。
Container::~Container(){~Component();}
1.2 Delegation委托
委托表示composition by reference,表示一个类里含有另一个类的指针或者引用对象。
例如:
class String
{
private:
StringRep* rep; //pimml
};
黑色空心菱形从String类指向StringRep类,一个简单的记忆方法是:菱形是空心的,表示这个String是只有这个StringRep的指针对象或者引用,然后这个菱形指向了StringRep类,表示这个菱形表示StringRep类。
著名的写法:编译防火墙
一个类A只提供接口,具体实现用另一个类B来完成,其中A与B的 关系是委托关系,好处是可以切换具体实现,不影响接口。
a b c共享数据,如果a要改数据,那么系统就会复制一份专门给a修改,而不会影响b和c 的使用。
1.3Inheritance 继承
复合表示is-a
例如:
struct _List_node_base
{
_List_node_base* _M_next;
_List_node_base* _M_prev;
};
template<typename _Tp>
struct _List_node: public _List_node_base
{
_Tp _M_data;
};
UML表示方式为:
图中表示的方式与前面两种略有不同,结合三角形,可以看成是有子类指向父类,这是一个泛化的过程,譬如老虎→动物。
其中继承方式有public、protected、private三种,现简单介绍一下其特性。
public | protected | private | |
---|---|---|---|
公共继承 | public | protected | 不可见 |
私有继承 | private | private | 不可见 |
保护继承 | protected | protected | 不可见 |
在上图中:1)基类成员对派生类都是:共有和保护的成员是可见的,私有的的成员是不可见的。
** 2)基类成员对派生类的对象来说:要看基类的成员在派生类中变成了什么类型的成员。如:私有继承时,基类的共有成员和私有成员都变成了派生类中的私有成员,因此对于派生类中的对象来说基类的共有成员和私有成员就是不可见的。**
父类中的数据会被子类继承下来,但是子类能否直接访问这些数据需要根据上表的特性来考虑。
当继承与虚函数搭配时,能充分发挥出继承的价值。关于虚函数,下文会提到。
继承关系下的构造和析构
构造:
继承关系下的构造还是由内而外地,derived类先构造base类的,再构造自己的,这是编译器自动完成的
Derived::Derived(...):Base(){...}
析构:
析构则是由外而内地,先析构derived的再析构base的,这也是编译器自动完成
Derived::~Derived() {~Base();}
值得注意的是,《Effective C++》条款07中有提到
当derived class 对象经由一个base class指针被删除,而该base class 带有一个non-virtual 析构函数时,其结果未有定义——实际执行时通常发生的是对象的derived成分没被销毁。
消除这个问题的做法很简单:给base class 一个virtual析构函数。此后删除derive class对象就会如你想要的那般。
读者可以简单地写一个类来测试一下,这里笔者就随便写一个例子来说明这个问题。
#include<iostream>
using namespace std;
class Base
{
public:
Base( ) { cout << "I'm Base's Ctor" << endl; }
~Base( ) { cout << "I'm Base's Dtor" << endl;}
};
class Derived: public Base
{
public:
Derived( ) { cout << " I'm Derived's Ctor"<<endl; }
~Derived( ){cout<<"I'm Derived's Dtor"<<endl;}
};
int main()
{
Base *base = new Derived;
delete base;
}
运行结果:
可以清楚看到这就发生了上述内存泄露的问题了。为此
修改程序如下:
#include<iostream>
using namespace std;
class Base
{
public:
Base( ) { cout << "I'm Base's Ctor" << endl; }
virtual ~Base( ) { cout << "I'm Base's Dtor" << endl;}
};
class Derived: public Base
{
public:
Derived( ) { cout << " I'm Derived's Ctor"<<endl; }
~Derived( ){cout<<"I'm Derived's Dtor"<<endl;}
};
int main()
{
Base *base = new Derived;
delete base;
}
运行结果如下:
所以在父类的析构函数中加了virtual关键字后,delete父类指针时可以还调用子类的析构函数,从而避免了内存泄露。
值得提醒一下的时,《Effective C++》07条款中给的提醒是:
无端地将所有classes的析构函数声明为virtual,就像从未声明它们virtual一样,都是错误的。许多人的心得是:只有当class内含至少一个virtual函数才为它声明virtual析构函数
好了,终于进入下一部分了。
2.虚函数与多态
2.1虚函数
首先简单介绍三个关于虚函数的名词:
non-virtual函数:非虚函数,这个函数是你不希望子类重新定义它。
virtual函数:虚函数,你希望子类重新定义,而且父类中已经定义过这个函数。
pure virtual函数: 纯虚函数,你希望子类一定要重新定义且父类中并无默认定义。
例子如下:
class Shape
{
public:
virtual void draw( ) const = 0; //纯虚函数
virtual void error( const std::string& msg);//虚函数
int objectID( ) const;//非虚函数
...
};
class Rectangle: public Shape {...};
class Ellipse:public Shape{...};
```
**继承+复合关系下的构造和析构**
![捕获3.JPG](http://upload-images.jianshu.io/upload_images/2020078-f84f329c06b6ff91.JPG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
三个类的关系如上图所示,那么我们用简单的程序测试一下:
```C++
#include<iostream>
using namespace std;
class Base
{
public:
Base(){cout<<"Base ctor!"<<endl;}
~Base(){cout<<"Base dtor!"<<endl;}
int base;
};
class Component
{
public:
Component(){cout<<"Component ctor!"<<endl;}
~Component(){cout<<"Component dtor!"<<endl;}
int component;
};
class Derived:public Base
{
public:
Derived(){cout<<"Derived ctor!"<<endl;}
~Derived(){cout<<"Derived dtor!"<<endl;}
int derived;
Component cpt;
};
int main()
{
Derived d;
return 0;
}
```
运行结果是:
```
Base ctor!
Component ctor!
Derived ctor!
Derived dtor!
Component dtor!
Base dtor!
```
那么如下图的关系呢,我们再修改一下程序测试一下:
![捕获4.JPG](http://upload-images.jianshu.io/upload_images/2020078-641806665254d409.JPG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
```C++
#include<iostream>
using namespace std;
class Component
{
public:
Component(){cout<<"Component ctor!"<<endl;}
~Component(){cout<<"Component dtor!"<<endl;}
int component;
};
class Base
{
public:
Base(){cout<<"Base ctor!"<<endl;}
~Base(){cout<<"Base dtor!"<<endl;}
int base;
Component cpt;
};
class Derived:public Base
{
public:
Derived(){cout<<"Derived ctor!"<<endl;}
~Derived(){cout<<"Derived dtor!"<<endl;}
int derived;
};
int main()
{
Derived d;
return 0;
}
```
运行结果是:
```C++
Component ctor!
Base ctor!
Derived ctor!
Derived dtor!
Base dtor!
Component dtor!
```
和打包裹的例子是一致的,打包(构造)的时候是先包装最里面的,然后再一层一层装外面的。拆包(析构)都是从最外面的包装开始拆起,直到拆到后面发现盒子里只剩下这么一点空气了:)
**委托+继承**
*待续...*