浅谈虚函数、多态

目录

一、多态

什么是多态

什么情况下发生多态的调用

二、虚函数

什么是虚函数

成为虚函数的条件

虚函数的作用

如果虚函数是非常有效的,我们是否可以把每个函数都声明为虚函数

纯虚函数

为什么要有纯虚函数

什么情况下使用纯虚函数


一、多态

什么是多态

同一接口,不同形态

静多态:函数重载和模板(包括函数模板和类模板)

              静态的多态指的是编译时期的多态,函数的重载和模板的实例化都是发生在编译阶段的,因此它们称作静态的多态或静多态       //编译阶段确定 函数的调用 静态绑定 早绑定

动多态:继承和虚继承

            动态的多态指的是运行时期的多态,也涉及静态绑定和动态绑定的概念      //运行阶段确定 函数的调用 动态绑定 晚绑定

#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)这个方法必须在派生类中被实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值