一.多态简介
多态是C++面向对象三大特性之一,当我们有一个基类,并且从这个基类中派生出其他的类(类之间是
继承关系
),多态可以让我们用基类的指针或引用直接指向任何一个派生类的对象。
多态分为两类,静态多态和动态多态。
静态多态:函数地址早绑定——编译阶段确定函数地址
动态多态:函数地址晚绑定——运行阶段确定函数地址
我们可以从基类中派生出多个类:
class Car
{
public:
void accelerate()
{
cout << "Car is accelerating" << endl;
}
};
class SUV :public Car
{
public:
void accelerate()
{
cout << "SUV is accelerating" << endl;
}
};
class sportscar :public Car
{
public:
void accelerate()
{
cout << "sportscar is accelerating" << endl;
}
};
class SUV200 :public SUV
{
public:
void accelerate()
{
cout << "SUV200 is accelerating" << endl;
}
};
因为C++是强类型语言,所以我们不能用一个派生类的指针指向另一个派生类的对象,但是C++允许派生类的指针隐试的转换为基类的指针(也允许基类的引用接受一个派生类的对象),这意味着以基类指针(引用)为参数的函数可以调用任意派生类中的函数(能用一个函数调用不同类中的函数,无疑大大节约了程序员的工作量,同时这也是C++接口的基础)。
二.虚方法
我们现在可以定义一个函数,函数的参数是一个Car类的指针:
void begin(Car* car)
{
car->accelerate();
}
我们希望能用这个函数执行派生类中的accelerate,但是如果我们直接通过这个函数访问派生类的实例,我们得到的将是基类方法的实现:
int main()
{
SUV s1;
SUV200 s3;
SUV* s2=&s1;
begin(s2);
s2 = &s3;
begin(s2);
return 0;
}
运行结果:
出现这种结果的原因是调用的accelerate()函数被编译器设置为基类的版本,这就是静态多态——即
在编译阶段就确定了函数地址
,所以不管后来传入的对象是什么,绑定的都是基类中的函数。
如果我们想继续用这个函数调用派生类中的accelerate()函数,那么这个函数的地址就不能提前绑定,需要在运行阶段根据传入的对象进行绑定,即
地址晚绑定。
为了实现这一目的,我们就需要用到
虚方法
,即用virtual关键字进行标记。现在我们对基类Car做如下修改:
class Car
{
public:
virtual void accelerate()
{
cout << "car is accelerating" << endl;
}
};
运行结果如下:
可以看到,我们已经可以用begin()函数调用派生类中的accelerate()。同时我们也注意到类SUV200并不是从基类Car中继承的,相反,它是从类SUV继承而来,该类本身就是Car的子类,但是虚方法同样也适用于类SUV200的实例,这说明派生类将继承并自动采用基类中的虚方法,而不必在重写过的方法上继续使用关键字virtual。
需要强调的是,我们在使用虚方法时,必须重写基类中的虚函数。
三.多态原理
在刨析多态原理之前,我们先来看一看基类在使用关键字virtual前后,这些类在内存上有什么变化。
int main()
{
Car c1;
SUV s1;
SUV200 s2;
cout << sizeof(c1) << endl;
cout << sizeof(s1) << endl;
cout << sizeof(s2) << endl;
}
在加virtual关键字之前,运行结果为:
我们给Car类加上virtual之后再来运行一下,结果为:
可以看到在使用关键字virtual后,类占用的内存由1字节变为了4字节,出现这一现象的原因是在加上关键字virtual之后,类中多出来一个虚指针vptr(virtual pointer),它指向一个名为vtable的虚函数表(virtual table),虚函数表中最开始是储存着基类中虚函数的地址,当我们运行程序时,派生类中重写的虚函数地址会替换原来的地址,因此最终执行的是子类中重写的函数,而不是基类的方法。