关键词:多态
将多态之前先来说几个概念:
-
联编
联编是指一个程序模块、代码之间互相关联的过程。
联编又分为两种:
- 静态联编(static binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配。重载函数使用静态联编。
- 动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)switch 语句和 if 语句是动态联编的例子。
结合实际,
- C++与C相同,是静态编译型语言
- 在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象;所以编译器认为父类指针指向的是父类对象。
- 由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象
从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成员函数。这种特性就是静态联编。
于是就有思考问题如果子类定义了与父类中原型相同的函数会发生什么?
举个例子:
class A
{
public:
virtual void print() //虚函数
{
cout << "AAAAAAAAAAAAAAAAAA" << endl;
}
};
class B :public A
{
public:
void print()
{
cout << "BBBBBBBBBBBBBBBBBBBB" << endl;
}
void show()
{
cout << "bbbbbbbbbbbbbb" << endl;
}
};
int main()
{
A *pa;
pa = new A; //基类指针指向基类对象
pa->print();
//pa->show(); //基类指针无法指向派生类对象,向下转型有风险
delete pa; //基类指针指向派生类对象
pa = new B;
pa->print(); //若print()未定义为虚函数,编译时已经确定pa 为A类指针,静态联编
//pa->show();
delete pa;
system("pause");
return 0;
}
上述代码中,若基类中函数print()未声明为虚函数,编译时已经确定pa 为A类指针,这就是静态联编,其结果如下:
声明为虚函数后:
这就是多态,C++中通过virtual关键字对多态进行支持,使用virtual声明的函数被重写后即可展现多态特性
所以,显而易见
多态成立的三个条件
- 要有继承
- 要有虚函数重写
- 用父类指针(父类引用)指向子类对象
什么是函数重写?
在子类中定义与父类中原型相同的函数,函数重写只发生在父类与子类之间
重载与重写的区别
重载:
- 同一个作用域;
- 子类无法重载父类函数,父类同名函数将被覆盖;
- 重载是在编译期间根据参数类型和个数决定;
重写:
- 发生于父类、子类之间;
- 父类和子类函数有相同的函数原型;
- 使用virtual关键字声明后能够产生多态;
- 运行期间根据具体对象类型决定调用的函数。
虚析构函数
将析构函数声明为需析构函数的作用:可以通过基类指针释放派生类对象
多态原理
当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类成员函数指针的数据结构,由编译器自动生成与维护的virtual成员函数会被编译器放入虚函数表中,当存在虚函数时,每个对象中都有一个指向虚函数表的指针(C++编译器给父类对象、子类对象提前布局vptr指针;当进行函数调用时,C++编译器不需要区分子类对象或者父类对象,只需要再base指针中,找vptr指针即可。)
VPTR一般作为类对象的第一个成员
纯虚函数
纯虚函数是一个在基类中说明的虚函数,在基类中没有定义,要求任何偶爱盛磊都定义自己的版本,类似于JAVA中的接口
纯虚函数的说明形式:
class Sharp
{
protected:
double x, y; //在圆中,xy都表示半径,矩形中x表示矩形的长,y表示矩形的宽
double s; //面积
public:
virtual double GetArea() = 0; //纯虚函数
Sharp(double _x, double _y);
};
抽象类
一个具有纯虚函数的基类称为抽象类,抽象类不能用于直接创建对象实例,可以声明抽象类的指针和引用
派生类中必须实现基类中的纯虚函数,否则它仍将被看作一个抽象类
几个注意事项:
-
构造函数无法实现多态
-
不要用基类指针指向派生类数组,父类p++与子类p++步长不同
-
抽象类不能用于直接创建对象实例