首先,在C++中,多态有两种,函数重载和虚函数。(python中的多态貌似就是只有函数重载..)
粗俗地说:继承意味着,子类可以使用父类的方法,而多态,则是父类可以使用子类的方法。
网上有一句话很好地解释了多态:
多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数(Virtual Function)实现的。
而为什么要使用多态呢?
举个栗子,一个士兵类(父类),在一场战役中,需要变成大刀兵作战,那么这时,他可以用大刀兵类(子类之一)的方法,例如砍杀方法,刺刀方法。然后这名士兵,再另外异常战役中,需要充当弓箭手,那么他可以用弓箭手类(子类之一)的方法,例如拉弓方法,射箭方法等等。官方一点的说法,多态就是解耦。
那多态如何实现呢?
1.在父类中创建虚函数。
2.在子类中创建虚函数。
3.父类的指针指向子类指针的引用
下面以代码展示:
#include<iostream>
using namespace std;
class A
{
public:
virtual void print()
{
cout<<"thisis A"<<endl;
}
};
class B:public A
{
public:
void print()
{
cout<<"thisis B"<<endl;
}
};
int main()
{
A*a;
Bb;
a= &b;
a->print();
return0;
}
打印结果是:this is B
虚函数和纯虚函数:
上面已经显示出,虚函数,其实就是用 virtual 修饰的函数,而纯虚函数则是用 virtual 修饰,且 =0 的函数,它本身没有定义的。有一个或多个纯虚函数的类就叫做抽象类。虽然纯虚函数自身没有定义,但,它是用来提示使用者,它的子类们会有相应的定义,当然,这里就是函数的重载了。
#include<iostream>
using namespace std;
class Soilder
{
public:
virtual void OutSoilder()=0;
};
class Tank_Soilder:public Soilder
{
public:
void OutSoilder()
{
cout<<"CreateTankSoilder!"<<endl;
}
};
class Fly_Soilder:public Soilder
{
public:
void OutSoilder()
{
cout<<"CreateFly_Soilder!"<<endl;
}
};
int main()
{
Soilder*so1;
Soilder*so2;
Tank_Soildertank1;
Fly_Soilderfly1;
so1= &tank1;
so2= &fly1;
so1->OutSoilder();
so2->OutSoilder();
return0;
}
打印结果:Create TankSoilder
Create FlySoilder
这片代码显示了 纯虚函数的作用:例如 一个兵营(父类),只能产生特殊兵种,不能产生赤手空拳的士兵(纯虚函数),所以只能使用子类的方法来决定产生什么兵。
虚函数是如何实现多态的?(下文的虚函数表 = 虚表)
首先要介绍虚表的概念,当一个类中有虚函数时,编译器就会给这个类创建一个虚表,虚表上记载着,类中的虚函数的首地址。此外,编译器还会给这个类自动新增一个成员变量,是一个指向虚表的指针(vptr),因此,在创建实例时,也会有一个指向虚表的指针(cptr),个人理解,vptr和cptr其实是一样的,只不过称呼不同。
注意几点:
1.虚表都放在每个类对象的首地址。例如: (int*)(&b) 表示虚表的首地址,b为一个对象。 而 ((int*)*(int*)(&b))+0)表示虚表的第一个虚函数地址
2.虚函数(首地址)在虚表中的排列顺序是按照虚函数的声明顺序排的。
3.虚表中,父类的虚函数放在前面,子类的虚函数放在后面。
4.虚表中的虚函数可以被覆盖,(这正是多态的本质。)看下图:
. (子类的虚表中,覆盖的f()函数被放到了虚表中原来父类虚函数的位置。没有被覆盖的函数依旧。)
(又因为虚表位于类对象的首地址,所以,《若 a 为父类对象指针,&b为子类对象引用》当 a = &b 时,b的首地址赋了给a,所以导致a调用自己的f()时,却会调用了b 中同名的f( ))
需要注意的地方:
1.只有类的成员函数才能被声明为虚函数,全局函数或者静态成员函数不能被声明为虚函数
2.实现多态,并不是反正子类的虚表都会覆盖父类的虚表,所以父类能调用所有子类的虚函数。父类能调用子类的虚函数,必须在父类中,有声明其同名虚函数。