封装->继承->多态
- 普通虚函数
- 虚析构函数 check
- 纯虚函数
- 抽象类
- 接口类
- RTTI
- 异常处理
- 隐藏 覆盖
- 早绑定 晚绑定 check
- 虚函数表
静态多态(早绑定)
函数重构
1 2 3 4 5 6 7 8 9 10 11 12
| class Rect{ public: int calcArea(int width); int calcAera(int width, int height); };
int main(){ Rect rect; rect.calcAera(10); rect.calcAera(10, 20); return 0; }
|
程序在运行之前,在编译阶段就已经确定下来要使用哪个calsAera()
函数了。很早地就将函数编译进去了。此情况叫做早绑定,即静态多态。
动态多态(晚绑定)
不同的对象下达相同的指令,做着不同的操作,为动态多态。有前提的:它必须以封装,继承为基础。
也就是说,有了封装继承之后,才能谈动态多态。动态多态至少由两个类,父子类,三个类时动态多态才表现的更加明显。
注意只有当一个类需要有多态的的性质时,才体现多态。此时的base classes
需要将其析构函数声明为virtual
。当没有声明virtual
时,看下例:
父类:
1 2 3 4 5 6 7 8 9 10 11
| class Shape{ public: Shape(){} ~Shape(){ std::cout<<"~Shape()"<<std::endl; } double calcArea(){ std::cout<<"Shape->calc area"<<std::endl; return 0; } };
|
子类Circle:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Circle : public Shape{ public: Circle(double r){ m_dR = r; } ~Circle(){ std::cout<<"~Circle()"<<std::endl; } double calcArea(){ return 3.14*m_dR*m_dR; } private: double m_dR; };
|
子类Rect:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Rect : public Shape{ public: Rect(double width, double height){ m_dWidth = width; m_dHeight = height; } ~Rect(){ std::cout<<"~Rect()"<<std::endl; } double calcAera(){ return m_dHeight*m_dWidth; } private: double m_dWidth; double m_dHeight; };
|
含有多态性质的base class
的设计目的是为了通过base class
的接口来使用derived class
,所以,如果用两个base class指针分别指向两个不同derived class,后分别调用两个derived class
的计算面积方法:
1 2 3 4 5 6 7 8 9 10
| Shape *shape1 = new Circle(1.0); Shape *shape2 = new Rect(3.0, 5.0);
cout<<shape1->calcArea()<<endl; cout<<shape2->calcArea()<<endl;
delete shape1; shape1 = NULL; delete shape2; shape2 = NULL;
|
结果并不是我们想要的:
1 2 3 4 5 6
| Shape->calc area 0 Shape->calc area 0 ~Shape() ~Shape()
|
都调用了父类的calcArea()
方法。且都调用了父类的析构函数。此现象称为隐藏.
WHY:我们不希望这样,我们希望通过父类指针可以调用子类的方法。如何解决?使用virtual
关键字。
解决方法:在父类中想要实现多态的函数前加virtual,同时在子类的相同函数前也加上virtual
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Shape{ public: ... virtual double calcArea(){ std::cout<<"calc area..."<<std::endl; } ... };
class Circle : public Shape{ public: ... double calcArea(){ return 3.14*m_dR*m_dR; } ... };
class Rect : public Shape{ public: ... double calcArea(){ return m_dHeight*m_dWidth; } ... };
|
此时的结果:
1 2 3 4
| 3.14 15 ~Shape() ~Shape()
|
只有base class的同名函数被声明为virtual。
很自然的情况是:当使用derived class指针指向derived class对象时,不管是不是virtual
,都会由正确执行结果:
1 2 3 4 5 6 7 8
| ... Circle *circle1 = new Circle(1.0); Rect *rect1 = new Rect(3.0, 5.0);
delete circle1; circle1 = NULL; delete rect1; rect1 = NULL;
|
输出:
1 2 3 4 5 6
| 3.14 15 ~Circle() ~Shape() ~Rect() ~Shape()
|
敲黑板
- 父类子类要实现多态的方法前,父类要声明为
virtual
。
- 当父类的析构函数不是虚函数时,类指针指向子类对象时,只调用父类析构函数。
- 当父类的析构函数不是虚函数时,
delete
后跟父类指针,只执行父类析构函数,如果delete
后跟子类指针,执行父类和子类析构函数。
- 只将要作为父类的类的析构函数声明为
virtual
。
虚析构函数
为啥要有虚析构函数?如果要将这个class作为基类而且是有多态性质的基类,应该把其析构函数定义成virtual
!看以下例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Circle : public Shape{ public: Circle(double r, int x, int y){ m_pCenter = new Coordinate(x, y) m_dR = r; } ~Circle(){ delete m_pCenter; m_pCenter = NULL; } double calcArea(){ return 3.14*m_dR*m_dR; } private: double m_dR; Coordinate* m_pCenter; };
|
在析构函数中释放了坐标对象,不会内存泄露。可是!
在多态时:
1 2 3 4 5
| Shape *shape1 = new Circle(1.0); cout<<shape1->calcArea()<<endl;
delete shape1; shape1 = NULL;
|
使用父类指针销毁子类对象时,只调用了父类析构函数~Shape()
,而子类~Circle()
未被调用。坐标对象不会被释放,此时内存泄漏。这就不合理了,虚构函数设计就是要被执行的
如何解决?
虚析构函数!在父类的析构函数前加virtual
:
1 2 3 4 5 6 7 8
| virtual ~Shape(){ ... }
~Circle(){ delete m_pCenter; m_pCenter = NULL; }
|
此时,使用父类指针销毁子类对象时,既调用了父类析构函数~Shape()
,又调用子类~Circle()
。父类子类的析构函数都被执行。情理上就对了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Shape{ public: virtual ~Shape(){ std::cout<<"~Shape()"<<std::endl; } };
class Circle : public Shape{ public: ~Circle(){ std::cout<<"~Circle()"<<std::endl; } };
class Rect : public Shape{ public: ~Rect(){ std::cout<<"~Rect()"<<std::endl; } };
|
父类子类的析构函数都被执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Shape *shape1 = new Circle(1.0); Shape *shape2 = new Rect(3.0, 5.0);
delete shape1; shape1 = NULL; delete shape2; shape2 = NULL;
OUTPUT: 3.14 15 ~Circle() ~Shape() ~Rect() ~Shape()
|
执行完子类虚析构函数,再执行父类虚析构函数。
virtual使用限制
敲黑板
任何时候都应该为含有多态性质的基类(父类)声明virtual
析构函数。如果一个class含有任何virual
函数,就一定要有一个virtual
析构函数。