白话C++系列(22) -- 虚函数
2016-06-01 14:49 Keiven_LY 阅读(841) 评论(0) 编辑 收藏 举报C++远征之多态篇
多态概述
讲到多态,那什么是多态呢?
所谓多态,简单来说就是,当发出一条命令的时候,不同的对象接收到同样的命令之后,所做出的动作是不同的,那么我们就把这种情况称之为多态。
虚函数及其实现原理
上一小节,我们笼统了解释了什么是多态,现在,我们来看一看书本上是如何来定义多态的,如下:指相同对象收到不同消息或不同对象收到相同消息时产生不同的动作。
为了能够把这个概念说清楚,我们简而言之,其实就是在说两个概念:静态多态和动态多态。
静态多态(早绑定)
所谓静态多态,也称之为早绑定。在我们之前的课程中其实就已经涉及到并且已经应用了,只不过当时怕用这个概念把大家搞晕,就没有给大家提这个概念。那么,这节课就给大家讲一讲什么是静态多态,为什么把静态多态也叫做早绑定。接下来看下面这个例子。
在这个例子中,我们定义了一个矩形类(Rect),并且在这个类中,还定义了两个函数,然而这两个成员函数的名字是相同的,都是叫做calcArea(计算矩形面积),但是这两个函数的参数个数不同,那么对于这两个函数,我们之前称之为互为重载的函数。那么,作为互为重载的两个函数来说,当我们去实例化一个矩形的对象rect之后,我们就可以通过这个对象来分别调用这两个函数。
在调用时,对于第一个函数,我们传入一个参数,对于第二个函数,我们传入两个参数,那么,计算机在编译的时候就会自动的调用相应的函数,那么,大家会发现,程序在运行之前的编译阶段就已经确定下来,到底要使用哪个函数了。可见,很早的就已经将函数编译进去了,那么,我们就把这种情况叫做早绑定,也叫做静态多态。
动态多态(晚绑定)
那么什么是动态多态呢?我们还是从一个例子开始讲起。比如,当前我们下达一道指令----计算面积。于是,就给圆形这个类下达了这道指令,让它来计算面积,同时,我们又向矩形这个类也下达了这道指令,也让它来计算面积。那么,对于圆形和矩形来说,它们分别有自己的计算面积的方法,可见,这两种方法肯定是不同的。
这种情况就是,对于不同的对象下达相同的指令,但是却做着不同的操作。而动态多态是有着前提的,它必须以封装和继承为基础。那么,动态多态起码是有两个类:一个是子类,一个是父类。当然,也可以有三个类,只有使用三个类的时候,动态多态才表现得更加明显。我们还是从代码的角度给大家讲解动态多态的例子。
我们先来看一看,适用我们之前所学习的一些代码是否能够实现动态多态呢?比如,我们在这里定义了一个形状类(Shape),在这个类当中,定义了一个成员函数叫做计算面积(calcArea)。
然后,我们再定义两个类:一个Circle类(圆),一个Rect类(矩形)。这两个类都以public方式继承Shape类,如下:
接下来我们所要讲的重点就是:在main函数中去使用的时候,我们可以使用父类的指针shape1去指向其中的一个子类对象Circle,并且用另外一个父类的指针shape2去指向一个矩形的对象。从而,这两个子类对象都被它的父类指针所指向,而我们进行操作的时候呢,是通过shape1和shape2分别调用计算面积的函数calcArea(),如下所示:
大家想一想,如果你去尝试一下的话,你肯定知道结果,而结果呢,其实并不是我们所想要的,因为要用到的都是父类的计算面积,也就是说,在屏幕上会打印出来年该行计算面积出来,即“calcArea()”的字样。那么,如果想要实现动态多态,看来以前我们所学习到的这些知识是没法实现的了。所以,接下来要学习一个新的知识:虚函数,即采用关键字virtual来修饰类的成员函数。实际当中,怎么来实现呢。我们还是以刚刚的例子来说明问题。我们在刚才的例子中,提到了计算面积(calcArea)这个函数,我们需要在父类(Shape)中去定义成员函数的时候,就把我们想要实现多态的成员函数前面加virtual关键字,使其成员虚函数,如下所示:
然后呢,我们在去定义它的子类的时候,给它计算面积的成员函数前面也要加上关键字virtual(此时这个关键字virtual不是必须要加上的,如果不加,系统会自动给你加上,如果加上了,我们会在后续的适用当中看得更加明显,所以,最好还是加上的好),使其成为虚函数,如下所示:
最后,就是关键的main函数中的使用了。
那么,在main函数当中,用父类指针再去指向子类对象的时候呢,如果用shape1调用calcArea()的时候呢,那么调用的就是Circle这个类自己的calcArea()函数,当然也会计算出实际的圆的面积出来。同理,用shape2调用calcArea()的时候呢,那么调用的就是Recr这个类自己的calcArea()函数,也会计算出矩形的面积来。
虚函数代码实践
题目描述:
/* ************************************************** */
/* 动态多态、虚函数 */
/* 要求:
1. 定义Shape类,成员函数:calcArea(),构造函数,析构函数
2. 定义Rect类,成员函数:calcArea(),构造函数,析构函数
数据成员:m_dWidth,m_dHeight
3. 定义Circle类,成员函数:calcArea(),构造函数,析构函数
数据成员:m_dR
/* ************************************************** */
程序框架:
头文件(Shape.h)
#ifndef SHAPE_H #define SHAPE_H #include <iostream> using namespace std; class Shape { public: Shape(); ~Shape(); double calcArea(); }; #endif
源程序(Shape.cpp)
#include "Shape.h" #include <iostream> using namespace std; Shape::Shape() { cout << "Shape()" << endl; } Shape::~Shape() { cout << "~Shape()" << endl; } double Shape::calcArea() { cout << "Shape---calcArea()" << endl; return 0; }
头文件(Circle.h)
#ifndef CIRCLE_H #define CIRCLE_H #include "Shape.h" class Circle:public Shape { public: Circle(double r); ~Circle(); double calcArea(); protected: double m_dR; }; #endif
源程序(Circle.cpp)
#include "Circle.h" #include <iostream> using namespace std; Circle::Circle(double r) { cout << "Circle()" << endl; m_dR = r; } Circle::~Circle() { cout << "~Circle()" << endl; } double Circle::calcArea() { cout << "Circle----calcArea()" << endl; return 3.14*m_dR*m_dR; }
#include "Circle.h" #include <iostream> using namespace std; Circle::Circle(double r) { cout << "Circle()" << endl; m_dR = r; } Circle::~Circle() { cout << "~Circle()" << endl; } double Circle::calcArea() { cout << "Circle----calcArea()" << endl; return 3.14*m_dR*m_dR; }
头文件(Rect.h)
#ifndef RECT_H #define RECT_H #include "Shape.h" class Rect:public Shape { public: Rect(double width, double height); ~Rect(); double calcArea(); protected: double m_dWidth; double m_dHeight; }; #endif
源程序(Rect.cpp)
#include "Rect.h" #include <iostream> using namespace std; Rect::Rect(double width, double height) { cout << "Rect()" << endl; m_dWidth = width; m_dHeight = height; } Rect::~Rect() { cout << "~Rect()" << endl; } double Rect::calcArea() { cout << "Rect----calcArea()" << endl; return m_dWidth * m_dHeight; }
主调程序(demo.cpp)
在主调程序中定义两个指针,这两个指针都是Shape类的指针:一个指向Rect,一个指向Circle。然后使用指针shape1和shape2分别调用计算面积(calcArea())函数,目的是:看一看调用到的究竟是谁的计算面积的函数。最后,将两个指针对象销毁,目的是:看一看销毁父类指针的时候,能否销毁子类的对象呢?
#include <iostream> #include <stdlib.h> #include "Circle.h" #include "Rect.h" using namespace std; int main() { Shape *shape1 = new Rect(3, 6); //传入宽和高 Shape *shape2 = new Circle(5); //传入半径 shape1 ->calcArea(); shape2 ->calcArea(); delete shape1; shape1 = NULL; delete shape2; shape2 = NULL; system("pause"); return 0; }
运行结果:
结果分析:
我们要实例化一个Rect的对象,显然就要先执行其父类的构造函数,然后再执行其本身的构造函数,同理,要实例化一个Circle的对象,首先也要执行其父类的构造函数,然后再执行其本身的构造函数,这就是前面四行代码的打印结果。接下来的两行就验证了我们之前所讲的一样,通过shape1和shape2去调用calcArea()函数的时候,调用到的都是Shape这个父类的calcArea()函数,并没有像我们想象中那样去调用Rect或Circle中的calcArea()函数,这个问题该如何来解决呢??。最后,在销毁Shape1和shape2的时候,只执行了Shape这个父类的析构函数,而并没有执行Rect和Circle本身的析构函数,这是为什么呢??(这个问题留待下一节课讲)。
针对执行结果中的第一个问题,我们前面已经学过,可以通过虚函数来达到多态的效果。也就是在计算面积(calcArea())函数前加上关键字virtual即可。当我们在三个头文件中的calcArea()函数前加上virtual后的运行结果如下:
这个时候,当用shape1和shape2去调用calcArea()函数的时候,调用到的就分别是Rect和Circle中的calcArea()函数了,这是我们想要的结果。