多态
动态联编和静态联编
- 静态联编:在编译时才知道执行哪段代码
举例理解:今天我让几名同学来协助我完成一个项目,已经给各个同学分配好任务,这个分配任务的动作即为编译,”编译“后,各个同学就知道做什么了,这个就叫做静态联编。 - 动态联编:在运行时才知道执行哪段代码
举例理解:今晚我告诉同学们,我将打电话给一名同学,但是还不知道打给哪位同学,直到打电话的时候才知道,这个就为动态编译。
动态编译的条件:
- 必须有类,且有继承关系;
- 通过基类指针或引用指向派生类对象或基类对象;
- 动态联编的行为是虚函数。
在以下的示例中均有体现。
多态
多态的概念
多态:同一个接口,可能会对应不同的函数。
前提条件:
- 必须在继承中。派生类对象是基类对象。
- 通过基类的对象指针,可以指向基类对象,也可以指向派生类对象。
- 类中的函数必须是虚函数。
观察以下代码:
以上代码出现错误,解决的方法:引入虚函数
虚函数
虚函数,存在于类中,通过virtual关键字修饰的函数。
访问方式:通过对象访问 或 通过对象地址访问。
举例:
#include<iostream>
class A
{
public:
virtual void fun() { printf("A\n"); } //虚函数
};
class B
{
public:
void fun(){ printf("B\n"); }
};
typedef void(*pFun)(); //定义一个函数指针的类型
void main()
{
A a;
a.fun(); //第一种访问方式:通过对象访问
pFun p; //表示一个指向void返回值,无参函数的指针
p = (pFun)(*(int *)*(int *)&a); //只有虚函数才能用此操作
p(); //第二种访问方式:通过对象地址访问
}
下面来说说继承中虚函数的原理:
现在来观察以下代码:当更换pa指针的地址为b时,最终调用的fun还是A类中的fun函数。
当我们加上虚函数时,就不会出现这种情况了:
虚函数的特点
- 在类中定义了一个虚函数,系统会为这个类维护一个虚函数列表(函数指针数组)
- 基类中存在虚函数,派生类中的同名函数(返回值类型、函数名、参数列表都相同)自动成为虚函数,写不写关键字无所谓。
- 如果类中有多个虚函数,按声明顺序从上往下,把虚函数的首地址放入虚函数列表。
- 虚函数列表不会被继承,列表中的表项会被继承。
- 如果派生类中存在同名虚函数,会在里列表中替换掉继承过来的父类的虚函数。
- 如果派生类中有虚函数且不和父类同名,会在列表的尾部添加该函数的首地址。
多态的实现
通过类的继承和虚函数实现多态。
举例:
#include<iostream>
class A
{
public:
virtual void fun() { printf("A::fun\n"); }
A(){ printf("A的构造\n"); }
~A(){ printf("A的析构\n"); }
};
class B:public A
{
public:
void fun(){ printf("B::fun\n"); }
B(){ printf("B的构造\n"); }
~B(){ printf("B的析构\n"); }
};
class C :public A
{
public:
void fun(){ printf("C::fun\n"); }
C(){ printf("C的构造\n"); }
~C(){ printf("C的析构\n"); }
};
// void fun(boss基类指针)
//{
// 玩家.攻击(boss基类指针->对象)
//}
void main()
{
A *pa = new B;
pa->fun();
delete pa;
//pa = new C;
//pa->fun();
//delete pa;
}
解决bug的方法:类中有虚函数,析构函数必须是虚析构。
(构造、析构不会被继承,析构函数也会放到函数列表当中)
纯虚函数
纯虚函数,是虚函数,没有函数定义。
- 有纯虚函数的类叫抽象基类,抽象基类是无法定义对象的。
- 抽象基类必须派生出子类,并重写纯虚函数。如果派生类没有重写虚函数,该派生类继承作为抽象基类存在。
- 可以定义抽象基类为指针。
final
父类的虚函数或纯虚函数在子类中依然是虚函数。
有时我们并不希望父类的某个函数在子类中被重写。
在C++11 及以后可以用关键字final来避免该函数再次被重写。