一、多态性
静态联编所支持的多态性称为编译时的多态性,当调用重载函数时,编译器可以根据调用时所使用的实参在编译时就确定应该调用哪个函数;动态联编所支持的多态性称为运行时的多态性,这由虚函数来支持。虚函数类似于重载函数,但与重载函数的实现策略不同,即对虚函数的调用使用动态联编。
1、静态联编中的赋值兼容性及名字支配规律
派生一个类的原因并非总是为了添加新的数据成员或成员函数,有时是为了重新定义基类的成员函数。先看一个示例如下:
const double PI = 3.14159;
class Point {
private:
double x, y;
public:
Point(double a, double b) :
x(a), y(b) {
}
double area() {
return 0;
}
};
class Circle: public Point {
private:
double radius;
public:
Circle(double a, double b, double c) :Point(a, b) {
this->radius = c;
}
double area() {
return PI * radius * radius;
}
};
#include "Example1.h"
void example1();
int main() {
example1();
return 0;
}
void example1() {
Point a(10.5, 12.3);
cout << a.area() << endl; //0 名字支配规律决定它们只调用自己的area()函数
Circle c(10.5, 12.3, 13.5);
cout << c.area() << endl; //572.555 同上
Point *p1 = &c;
cout << p1->area() << endl; //0 根据赋值兼容规则,Point类的指针指向的是基类Point的area()
Point &p2 = c;
cout << p2.area() << endl; //0 根据赋值兼容规则,Point类的引用跟指针一样
}
对象的内存地址空间中只包含数据成员,并不存储有关成员函数的信息,这些成员函数的地址翻译过程与其对象的内存地址无关,编译器只根据数据类型来翻译成员函数的地址并判断其调用的合法性,这是由静态联编决定的。
声明的基类指针只能指向基类,派生类指针只能指向派生类,它们的原始类型决定它们只能调用各自的同名函数,除非派生类没有基类的同名函数,派生类的指针才根据继承调用基类的成员函数。
2、动态联编的多态性
如果让编译器进行动态联编,这就需要使用到关键字virtual来声明虚函数。当编译系统编译含有虚函数的类时,将为它建立一个虚函数表,表中的每一个元素都指向一个虚函数的地址。此外,编译器也为类增加一个数据成员,这个数据成员是一个指向该虚函数表的指针,通常称为vptr。
虚函数的地址翻译取决于对象的内存地址,编译器为含有虚函数的对象首先建立一个入口地址,这个地址用来存放指向虚函数表的指针vptr,然后按照类中虚函数的声明次序,一一填入