标签:
面向对象程序设计中的多态性是指向不同的对象发送同一个消息,不同对象对应同一消息产生不同行为。在程序中消息就是调用函数,不同的行为就是指不同的实现方法,即执行不同的函数体。也可以这样说就是实现了“一个接口,多种方法”。
从实现的角度来讲,多态可以分为两类:编译时的多态性和运行时的多态性。前者是通过静态联编来实现的,比如C++中通过函数的重载和运算符的重载。后者则是通过动态联编来实现的,在C++中运行时的多态性主要是通过虚函数来实现的。
赋值兼容
不过在说虚函数之前,先介绍一个有关于基类与派生类对象之间的复制兼容关系的内容。它也是之后学习虚函数的基础。(1)派生类对象直接向基类赋值,赋值效果,基类数据成员和派生类中数据成员的值相同;
(2)派生类对象可以初始化基类对象引用;
(3)派生类对象的地址可以赋给基类对象指针;
(4)函数形参是基类对象或基类对象的引用,在调用函数时,可以用派生类的对象作为实参;
看下例
#include "stdafx.h" #include<iostream> #include<string> class ABCBase { private: std::string ABC; public: ABCBase(std::string abc) { ABC=abc; } void showABC(); }; void ABCBase::showABC() { std::cout<<"字母ABC=>"<<ABC<<std::endl; } class X:public ABCBase { public: X(std::string x):ABCBase(x){} }; void function(ABCBase &base) { base.showABC(); } int main() { ABCBase base("A"); base.showABC(); X x("B"); base=x; base.showABC(); ABCBase &base1=x; base1.showABC(); ABCBase *base2=&x; base2->showABC(); function(x); return0; }</span>
要注意的是:
第一,在基类和派生类对象的赋值时,该派生类必须是公有继承的。
第二,只允许派生类对象向基类对象赋值,反过来不允许;
虚函数
虚函数,允许函数调用与函数体之间的联系在运行时才建立,即在运行时才决定如何动作。
虚函数声明的格式:
virtual 返回类型 函数名(形参表)
{
函数体
}
那么定义虚函数有什么用呢?让我们先来看看下面这个示例:
#include "stdafx.h" #include <iostream> #include <string> class Graph { protected: double x; double y; public: Graph(double x,double y); void showArea(); }; Graph::Graph(double x,double y) { this->x=x; this->y=y; } void Graph::showArea() { std::cout<<"计算图形面积"<<std::endl; } class Rectangle:public Graph { public: Rectangle(double x,double y):Graph(x,y){}; void showArea(); }; void Rectangle::showArea() { std::cout<<"矩形面积为:"<<x*y<<std::endl; } class Triangle:public Graph { public: Triangle(double d,double h):Graph(d,h){}; void showArea(); }; void Triangle::showArea() { std::cout<<"三角形面积为:"<<x*y*0.5<<std::endl; } class Circle:public Graph { public: Circle(double r):Graph(r,r){}; void showArea(); }; void Circle::showArea() { std::cout<<"圆形面积为:"<<3.14*x*y<<std::endl; } int main() { Graph *graph; Rectangle rectangle(10,5); graph=&rectangle; graph->showArea(); Triangle triangle(5,2.4); graph=? graph->showArea(); Circle circle(2); graph=&circle; graph->showArea(); return0; }
结果似乎和我们想象的不一样,既然Graph类(图形类)的对象graph指针分别指向了Rectangle类(矩形类)对象,Triangle类(三角类)对象,以及Circle类(圆类)对象,那么就应该执行它们自己所对应成员函数showArea(),怎么结果会是Graph类(图形类)的对象graph里的成员函数呢?
其实当基类对象指针指向公有派生类的对象时,它只能访问从基类继承下来的成员,而不能访问派生类中定义的成员。但是使用动态指针就是为了表达一种动态调用的性质即当前指针指向哪个对象,就调用那个对象对应类的成员函数。那要怎么来解决的,这时虚函数就体现出了它的作用。其实我们只需要对上一个示例代码中所有的类里出现的showArea()函数声明之前加一个关键字virtual:
#include "stdafx.h" #include <iostream> #include <string> class Graph { protected: double x; double y; public: Graph(double x,double y); voidvirtual showArea();//定义为虚函数或virtual void showArea() }; Graph::Graph(double x,double y) { this->x=x; this->y=y; } void Graph::showArea() { std::cout<<"计算图形面积"<<std::endl; } class Rectangle:public Graph { public: Rectangle(double x,double y):Graph(x,y){}; virtualvoid showArea();//定义为虚函数 }; void Rectangle::showArea() { std::cout<<"矩形面积为:"<<x*y<<std::endl; } class Triangle:public Graph { public: Triangle(double d,double h):Graph(d,h){}; virtualvoid showArea();//定义为虚函数 }; void Triangle::showArea() { std::cout<<"三角形面积为:"<<x*y*0.5<<std::endl; } class Circle:public Graph { public: Circle(double r):Graph(r,r){}; virtualvoid showArea();//定义为虚函数 }; void Circle::showArea() { std::cout<<"圆形面积为:"<<3.14*x*y<<std::endl; } int main() { Graph *graph; Rectangle rectangle(10,5); graph=&rectangle; graph->showArea(); Triangle triangle(5,2.4); graph=? graph->showArea(); Circle circle(2); graph=&circle; graph->showArea(); return0; }
在基类中的某成员函数被声明为虚函数后,在之后的派生类中可以重新来定义它。但定义时,其函数原型,包括返回类型、函数名、参数个数、参数类型的顺序,都必须和基类中的原型完全相同。其实在上述修改后的示例代码里,只要在基类中显式声明了虚函数,那么在之后的派生类中就需要用virtual来显式声明了,可以略去,因为系统会根据其是否和基类中虚函数原型完全相同来判断是不是虚函数。因此,上述派生类中的虚函数如果不显式声明也还是虚函数。
最后对虚函数做几点补充说明:
(1)因为虚函数使用的基础是赋值兼容,而赋值兼容成立的条件是派生类之从基类公有派生而来。所以使用虚函数,派生类必须是基类公有派生的;
(2)定义虚函数,不一定要在最高层的类中,而是看在需要动态多态性的几个层次中的最高层类中声明虚函数;
(3)虽然在上述示例代码中main()主函数实现部分,我们也可以使用相应图形对象和点运算符的方式来访问虚函数,如:rectangcle.showArea(),但是这种调用在编译时进行静态联编,它没有充分利用虚函数的特性。只有通过基类对象来访问虚函数才能获得动态联编的特性;
(4)一个虚函数无论配公有继承了多少次,它仍然是虚函数;
(5)虚函数必须是所在类的成员函数,而不能是友元函数,也不能是静态成员函数。因为虚函数调用要靠特定的对象类决定该激活哪一个函数;
(6)内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的即使虚函数在类内部定义,编译时将其看作非内联;
(7)构造函数不能是虚函数,但析构函数可以是虚函数;
版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:
原文地址:http://blog.csdn.net/tfygg/article/details/47083517