多态:同一操作作用于不同的类的实例,将产生不同的执行结果,即一个接口,多种方法。即不同类的对象收到相同的消息时,得到不同的结果。在程序中消息就是调用函数,不同的行为即执行不同的函数体。
- 编译时的多态性 -> 静态多态性 -> 函数重载
- 运行时的多态性 -> 动态多态性 -> 虚函数
函数重载
同一类内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。用来处理实现功能类似数据类型不同的问题。
虚函数
在基类中声明为virtual的函数,并在一个或多个派生类中被重新定义的成员函数,运行时将会根据对象的实际类型来调用相应的函数。
作用:运行时多态,基类中提供虚函数的实现,为派生类提供默认的函数实现。派生类可以重写基类的虚函数实现派生类的特殊化。
1.虚函数可以通过基类指针或引用来访问基类和派生类中的同名函数
基类指针是用来指向基类的,如果用它来指向派生类对象,则进行指针类型转换,将派生类对象的指针先转换为基类的指针,所以基类指针指向的是派生类对象中的基类部分。虚函数的运用使得在派生类的基类部分中,派生类的虚函数取代了原来的虚函数。
2.同名函数area在基类中定义为虚函数,定义一个指向基类对象的指针变量pt。pt指向基类对象时,pt->area输出基类对象的数据成员;pt指向派生类对象A时,pt->display输出派生类A对象的数据成员;pt指向派生类对象B时,pt->display输出派生类B对象的数据成员。
#include <iostream>
using namespace std;
class Shape
{
public:
Shape(int a=0, int b=0){
width = a; height = b;
}
virtual int area(){
cout << "Parent class area :" <<endl;
return 0;
}
protected:
int width, height;
};
class Rectangle: public Shape
{
public:
Rectangle(int a=0, int b=0):Shape(a, b) { }
int area(){
cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape
{
public:
Triangle(int a=0, int b=0):Shape(a, b){ }
int area(){
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
int main()
{
Rectangle rec(10,7);
Triangle tri(10,5);
Shape *shape;
shape = &rec; // 存储矩形的地址
shape->area(); // 调用矩形的求面积函数 area
shape = &tri; // 存储三角形的地址
shape->area(); // 调用三角形的求面积函数 area
return 0;
}
虚函数表
vtbl,每个类使用一个虚函数表,每个类对象用一个虚表指针指向虚函数表。类中有N个虚函数,那么其虚函数表将有N*4字节。
编译器处理虚函数
为每个类对象添加一个隐藏成员,来保存一个指向函数地址数组(虚函数表)的指针,称为虚表指针(vptr)
基类对象包含一个虚表指针,指向基类中虚函数表。派生类对象包含一个虚表指针,指向派生类虚函数表。
1.如果派生类重写基类的虚方法,派生类虚函数表将保存重写的虚函数地址,不是基类的虚函数地址;
2.如果基类中的虚方法没有在派生类中重写,派生类将继承基类中的虚方法,派生类中虚函数表将保存基类中未被重写的虚函数的地址;
3.如果派生类中定义了新的虚方法,则该虚函数的地址被添加到派生类虚函数表中;
使用虚函数的调用过程:
1.编译器知道pb是类B的指针,不知道它指向的具体对象类型是B的对象或D的对象。对于pb->bar编译器能够确定的是此处operator->的另一个参数是B::bar
2.B::bar和D::bar在各自虚函数表中的偏移位置是相等的。
无论pb指向哪种类型的对象,只要能够确定被调函数在虚函数表中的偏移值,待运行时能够确定具体类型,并能找到相应vptr就能找出真正应该调用的函数。
B::bar是一个虚函数指针, 它的ptr部分内容为9,则它在B的虚函数表中的偏移值为8(8+1=9)
当程序执行到pb->bar()时,已经能够判断pb指向的具体类型了:
pb指向B的对象时,得到B对象的vptr,加上偏移值8((char*)vptr + 8), 可以找到B::bar
pb指向D的对象时,得到D对象的vptr,加上偏移值8((char*)vptr + 8), 可以找到D::bar