目录
如果虚函数是非常有效的,我们是否可以把每个函数都声明为虚函数
一、多态
什么是多态
同一接口,不同形态
静多态:函数重载和模板(包括函数模板和类模板)
静态的多态指的是编译时期的多态,函数的重载和模板的实例化都是发生在编译阶段的,因此它们称作静态的多态或静多态 //编译阶段确定 函数的调用 静态绑定 早绑定
动多态:继承和虚继承
动态的多态指的是运行时期的多态,也涉及静态绑定和动态绑定的概念 //运行阶段确定 函数的调用 动态绑定 晚绑定
#include<iostream>
using namespace std;
//多态
class Base
{
public:
virtual void func(){}
void fun2(){}
private:
int ma;
int mb;
};
class Derive : public Base
{
public:
virtual void func(){}
private:
int mc;
};
int main()
{
Derive d;
Base *p = &d;
p->fun2(); //静态的多态,调用的是Base类中的fun2函数
p->func(); //动态的多态,调用的是Derive类中的func函数
return 0;
}
什么情况下发生多态的调用
指针或引用调用虚函数
对象完整
二、虚函数
什么是虚函数
虚函数是成员方法前面加了virtual关键字,它的实现依赖于虚函数表,虚函数表中存有函数的入口地址,多态是基于虚函数的一种功能,可以做到动态的调用基类或派生类的函数
编译器处理虚函数的方法是:为每个类对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的序表指针,这种数组称为虚函数表。即,每个类使用一个虚函数表,每个类对象用一个虚表指针
eg:基类对象包含一个虚表指针,指向基类中所有虚函数的地址表。派生类对象也将包含一个虚表指针,指向派生类虚函数表。
//虚函数是在成员方法前面加了virtual关键字
class Base
{
public:
virtual void func(){}
private:
int ma;
int mb;
};
如果func方法不是虚函数,那么Base定义的对象占8个字节的内存;如果func实现成virtual函数,那么Base定义的对象就成了12个字节了,因为多出了一个vfptr虚函数指针这4个字节,vfptr指向的是一张虚函数表vftable,虚函数表中存放的虚函数地址
加入Derive类从Base类继承而来:
lass Derive : public Base
{
public:
private:
int mc;
};
派生类
可以看到派生类Derive处理自己的mc,还从基类Base继承了vfptr,ma和mb。由于Derive没有重写func函数(也就是没有提供同名覆盖函数),因此vfptr指向的vftable里面,存放的还是从基类继承来的func函数的地址。
重写了func函数之后
class Derive : public Base
{
public:
virtual void fun(){}
private:
int mc;
};
可以看到,Derive类对象的虚函数表中的func虚函数地址,已经更改成派生类Derive自己的虚函数func了
vfptr优先级最高
一个类有一个或多个虚函数只生成一个vfptr(向内合并),对象只多了4个字节,不过虚函数越多,虚函数表就越大。
类的虚函数表是在编译阶段就生成好的,一个类型对应一张虚函数表,也就是说,Base类的所有对象的vfptr都指向一张Base的虚函数表,Derive类所有对象的vfptr也都指向同一张Derive类的虚函数表。虚函数表运行时,放在内存的.rodata段,叫做只读数据段,只允许读,而不允许修改。
优先级:非虚基类 > 虚基类
虚基类的处理顺序:继承顺序、
成为虚函数的条件
1.依赖对象
2.取地址
普通全局变量函数 (X)
普通的类成员方法 (V)
静态的类成员方法 (X) 没有this指针,不依赖对象
inline函数 (X) 不能取地址,在调用点直接展开
构造函数 (X) 对象需要构造函数来构造,而成为虚函数必须依赖对象调用
析构函数 (V) 系统用对象来调用
虚函数的作用
实现类的多态性
当子类重新定义了父类的虚函数后,当父类的指针指向子类对象的地址时,【即B b;A a = &b;】父类根据指针赋给它的不同子类指针,动态的调用子类的该函数,而不是父类的函数,且这样的函数调用发生在运行阶段,而不是发生在编译阶段,称之为动态联编。而函数的重载可以认为是多态,只不过是静态的。
注意,非虚函数静态联编,效率要比虚函数高,但是不具备动态联编的能力。
如果使用了virtual关键字,程序将根据引用或指针指向的对象类型来选择方法,否则使用引用类型或指针类型来选择方法。
如果虚函数是非常有效的,我们是否可以把每个函数都声明为虚函数
不可以。因为虚函数是有代价的,由于每个虚函数的对象都必须维护一个虚函数表,因此在使用虚函数的时候都会产生一个系统开销。如果是一个很小的类,且不想派生其他的类,那么根本没必要使用虚函数
纯虚函数
抽象类
1.不能实例化对象
2.当指针或引用
为什么要有纯虚函数
在很多情况下,基类本身生成对象是不合情理的。为了解决这个问题,方便使用类的多态性,引入了纯虚函数的概念,将函数定义为纯虚函数,则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚函数的类称为抽象类,纯虚函数不能生成对象。
什么情况下使用纯虚函数
(1)当想在基类中抽象一个方法,且该基类只能做被继承,而不能被实例化
(2)这个方法必须在派生类中被实现