多态性
多态性是面向对象程序设计的关键技术之一。若程序设计语言不支持多态性,不能称为面向对象的语言。利用多态性技术,可以调用同一个函数名的函数,实现完全不同的功能。
多态性(polymorphism)多态性是考虑在不同层次的类中,以及在同一类中,同名的成员函数之间的关系问题。函数的重载,运算符的重载,属于编译时的多态性。以虚基类为基础的运行时的多态性是面向对象程序设计的标志性特征。体现了类推和比喻的思想方法。
虚函数
虚函数是一个类的成员函数,定义格式如下:
virtual 返回类型 函数名(参数表)
关键字virtual指明该成员函数为虚函数。virtual仅用于类定义中,如虚函数在类外定义,不可加virtual(但是类内部声明时要加virtual)。
当某一个类的一个类成员函数被定义为虚函数,则由该类派生出来的所有派生类中,该函数始终保持虚函数的特征。
最后一句话如何理解?
#include<iostream> using namespace std; class Base { public: virtual void show() { cout << "This is Base show" << endl; } }; class D :public Base { public: void show() { cout << "This is D show" << endl; } }; class C :public D { public: void show() { cout << "This is C show" << endl; } }; void main() { C c; D *pd = &c; pd->show(); }
倘若D的show不是虚函数,那么pd->show();访问的就是类D的show方法。但结果却是C的show方法,所以类D的show方法也是虚的。
在整个虚函数继承体系中,父类 子类的虚函数需要做到3同。函数名相同,函数返回值相同,函数参数相同(包括参数个数,参数类型)
当在派生类中重新定义虚函数(overriding a virtual function,亦译作覆盖)时,不必加关键字virtual。但重新定义时不仅要同名,而且它的参数表和返回类型全部与基类中的虚函数一样,否则链接时出错。
多态演示代码
#include<iostream> using namespace std; class Animal { public: void Eat() { cout << "Animal Eat" << endl; } void Sleep() { cout << "Animal Sleep" << endl; } }; class People :public Animal { public: void Eat() { cout << "People Eat" << endl; } void Sleep() { cout << "People Sleep" << endl; } }; class Dog :public Animal { public: void Eat() { cout << "Dog Eat" << endl; } void Sleep() { cout << "Dog Sleep" << endl; } }; class Pig :public Animal { public: void Eat() { cout << "Pig Eat" << endl; } void Sleep() { cout << "Pig Sleep" << endl; } }; void fun(Animal *a) { a->Eat(); a->Sleep(); } void main() { People p; Dog dog; Pig pig; fun(&p); fun(&dog); fun(&pig); }
由于赋值兼容规则,导致都是Animal的Eat和Sleep
#include<iostream> using namespace std; class Animal { public: virtual void Eat() { cout << "Animal Eat" << endl; } virtual void Sleep() { cout << "Animal Sleep" << endl; } }; class People :public Animal { public: void Eat() { cout << "People Eat" << endl; } void Sleep() { cout << "People Sleep" << endl; } }; class Dog :public Animal { public: void Eat() { cout << "Dog Eat" << endl; } void Sleep() { cout << "Dog Sleep" << endl; } }; class Pig :public Animal { public: void Eat() { cout << "Pig Eat" << endl; } void Sleep() { cout << "Pig Sleep" << endl; } }; void fun(Animal *a) { a->Eat(); a->Sleep(); } void main() { People p; Dog dog; Pig pig; fun(&p); fun(&dog); fun(&pig); }
虚函数注意事项
成员函数应尽可能地设置为虚函数,但必须注意以下几条:
1.派生类中定义虚函数必须与基类中的虚函数同名外,还必须同参数表,同返回类型。否则被认为是同名隐藏,而不是虚函数。如基类中返回基类指针,派生类中返回派生类指针是允许的,这是一个例外。
#include<iostream> using namespace std; class Base { public: virtual Base* Show() { cout << "This is Base Show()" << endl; return this; } }; class Son :public Base { public: virtual Son* Show() { cout << "This is Son Show()" << endl; return this; } }; void main() { Son son; Base *pb = &son; pb->Show(); }
注:上述代码需要在VC6.0以上版本允许,VC6.0对C++语法支持不完善
2.只有类的成员函数才能说明为虚函数。这是因为虚函数仅适用于有继承关系的类对象。
3.静态成员函数,是所有同一类对象共有,不受限于某个对象,不能作为虚函数。
4.一个类对象的静态和动态类型是相对的,实现动态多态性时,必须使用基类类型的指针变量或引用,使该指针指向该基类的不同派生类的对象,并通过该指针指向虚函数,才能实现动态的多态性。
5.内联函数每个对象一个拷贝,无映射关系,不能作为虚函数。还有一个,全局函数也不能是虚函数
6.析构函数可定义为虚函数,构造函数不能定义虚函数,因为在调用构造函数时对象还没有完成实例化。在基类中及其派生类中都动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤消对象时的多态性。
#include<iostream> using namespace std; class Base { public: virtual Base* Show() { cout << "This is Base Show()" << endl; return this; } Base() { cout << "Create Base" << endl; } ~Base() { cout << "Free Base" << endl; } }; class Son :public Base { public: virtual Son* Show() { cout << "This is Son Show()" << endl; return this; } Son() { cout << "Create Son" << endl; } ~Son() { cout << "Free Son" << endl; } }; void main() { Base *pb = new Son; delete pb; }
构造的时候父类、子类都构造了。析构的时候只析构了父类,导致内存泄漏。改进代码,父类析构函数变成虚函数,代码如下
#include<iostream> using namespace std; class Base { public: virtual Base* Show() { cout << "This is Base Show()" << endl; return this; } Base() { cout << "Create Base" << endl; } virtual ~Base() { cout << "Free Base" << endl; } }; class Son :public Base { public: virtual Son* Show() { cout << "This is Son Show()" << endl; return this; } Son() { cout << "Create Son" << endl; } ~Son() { cout << "Free Son" << endl; } }; void main() { Base *pb = new Son; delete pb; }
父类指针在指向子类对象的时候,之所以能够指向是因为在子类里面隐藏包含一个父类。pb指向的是整个空间的起始位置,而这个起始位置也恰恰是父类的起始位置。由于是Base类型,在delete的时候,编译器会认为你要删除的只是Base那么大的内存,而不会释放子类对象内存,导致内存泄漏。一旦把父类的析构函数声明为析构函数之后,这种释放就会达到一个多态的释放。即先检查一下我是否是因为创建子类而创建的父类,如果是则先释放子类对象,然后释放父类对象,达到级联式删除。
7.虚函数执行速度要稍慢一些。为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现。所以多态性总是要付出一定代价,但通用性是一个更高的目标。
8.如果定义放在类外,virtual只能加在函数声明前面,不能(再)加在函数定义前面。正确的定义必须不包括virtual。
重载、隐藏、覆盖
重载是针对一个类内部情况而言的,作为重载的依据:参数列表不同(包括参数的个数不同、类型不同或顺序不同,仅仅参数名称不同是不可以的);函数返回值也不能作为重载的依据。
隐藏是针对继承体系而言,父类子类有相同名字的成员函数,这里只要求一同即函数名字。并且不能使用virtual关键字,子类方法会隐藏父类所有同名方法
覆盖也是针对继承体系而言,但是需要加关键字virtual