C++多态——后传

1.c++ 11中override 和 final

C++对函数的重写要求是比较严格的(三同),对于使用者来说很有可能因为一些疏忽无法满足重写条件,这种错误在编译期间是不会报出来的,只有在程序运行时没有得到预期的结构,去调试时才能发现。
因此C++提供了 override 和 final 两个关键字,帮助用户检测是否重写

1.1fina

修饰虚函数,表示该虚函数不能再被继承
在这里插入图片描述

1.2 overrride

检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
在这里插入图片描述

2.重载、覆盖(重写)、隐藏(重定义)的对比

在这里插入图片描述

3.抽象类

在虚函数的后面写上 =0,则这个函数为纯虚函数,包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象
派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

3.1什么是抽象类

通俗一点的将,抽象类就是不能用来具体描述某一种对象,没有对应的实体 :比如一个抽象类叫做"植物",这个植物就是一个抽象类,没有具体对应的实体,如果重写这个类,比如重写这个植物是蒲公英,就有了对应的实体

3.2演示

在这里插入图片描述

3.3接口继承和实现继承

普通函数的继承是一种实现继承(主要是继承方法),派生类继承了基类函数,可以使用函数,继承的是函数的实现。
虚函数的继承是一种接口继承,派生类继承的是基类函数的接口,目的是为了重写,达成多态,继承的是接口。
所以如果不实现多态不要把函数定义成虚函数

4.多态的原理

4.1虚函数表

在这里插入图片描述
在这里插入图片描述

如上图所示:b对象之中除了成员_a之外,还多了一个_vfptr指针放在对象的前面(有些放在后面,和平台有关系),这个指针就是虚函数表指针(virtual function)
一个含有虚函数的类中都至少有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表
虚函数表本质是一个函数指针数组
虚表指针本质是一个数组指针

在这里插入图片描述
在这里插入图片描述
总结:
1.派生类对象中也有一个虚表指针,指向的虚表与基类指向的虚表是不同的;虚表之中包含了继承下来的虚函数,和本身定义的虚函数

2.上图之中,可以看到对于重写的Func1,在派生类的虚表之中保存的是派生类的Func1,所以虚函数的重写也叫做覆盖;覆盖的意思即是虚表之中虚函数地址的覆盖,重写是语法上面的叫法,覆盖式原理层的叫法

3.虚函数才会放入虚表之中,所以Func3没有放入虚表之中

4.虚函数本质是一个村虚函数指针的指针数组,这个数组最后以null结尾(由编译器自己决定)

5.派生类虚表生成过程:基类将自己的虚表内容拷贝至派生类之中,派生类将自己重写的虚函数覆盖掉原来的,再按照自己生命虚函数的顺序将虚函数指针添加进去

6.虚表是一个数组,存函数指针的数组,即函数指针数组,虚表指针则是一个指向虚表的指针,即是函数指针数组指针
虚函数和普通函数一样都是存在代码段的,它的指针存在虚表之中,虚表也存在代码段

4.2原理分析

在这里插入图片描述

5.动态绑定与静态绑定

1.静态绑定又称前期绑定(早绑定),在程序编译期间确定了程序的行为(确定了函数是谁的),也称为静态多态,比如函数重载
2.动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态

6.什么是多态

6.1静态多态

静态多态就是函数重载,编译时绑定
比如 :
int a; double b;
cout<<a ; cout<<b;
输出的就是整形和浮点型,这里采用的是函数重载,称之为静态多态

作用:减少代码冗余

6.2动态多态

在运行时确定调用什么函数,通过虚表确定调用那个函数。虚函数重写之后,父类的指针或引用调用重写虚函数,指向谁调用的就是谁的虚函数

作用:调用相同接口,不同的结果(抢红包,抢票),使程序更加灵活

7.单继承和多继承关系的虚函数表

7.1单继承中的虚函数表

在这里插入图片描述

7.2用打印的方式查看虚表中的函数指针:

虚表是函数指针数组,我们可以拿到的参数是指向虚表的指针 - 》函数指针数组指针即虚表这个数组的指针
应该怎么样传参呢?下面分析一下:
数组传参,发生降维,降维成指针,因此传入函数指针数组指针,参数也应该是函数指针数组指针

数组指针(int*)p[]
指针数组 int *p[]
函数指针 void(*p)()
函数指针数组 void( * p[])()
函数指针数组指针 void ( * ( * p)[])()

class Base
{
public:
	virtual void Func1()
	{
		cout << "Base:Func1()" << endl;
	}

	virtual void Func2()
	{
		cout << "Base:Func2()" << endl;
	}

private:
	int _a = 100;
};

class Derive :public Base
{
public:
	virtual void Func1()//对Func1进行重写
	{
		cout << "Dervie Func1" << endl;
	}

	virtual void Func3()//增加一个虚函数
	{
		cout << "Dervie Func4" << endl;
	}

private:
	int _d=200;
};
/*
数组指针(int*)p[]
指针数组 int *p[]
函数指针 void(*p)()
函数指针数组 void(*p[])()
函数指针数组指针 void (*(*p)[])()	*/	 



void PrintVFT1(void(*p[])())//函数指针数组
{
	printf("虚表指针:%p\n", p);
	for (int i = 0; p[i] != nullptr; i++)
	{
		printf("p[%d]:%p\n", i, p[i]);
	}
}



void PrintVFT2(void(*(*p))())//函数指针数组指针
{
	printf("虚表指针:%p\n", p);
	for (int i = 0; p[i] != nullptr; i++)
	{
		printf("p[%d]:%p\n", i, p[i]);
	}
}

typedef void(*VFT)();//将函数指针重定义为VFT

void PrintVFT3(VFT p[])//函数指针数组
{
	printf("虚表指针:%p\n", p);
	for (int i = 0; p[i] != nullptr; i++)
	{
		printf("p[%d]:%p\n", i, p[i]);
	}
}

void PrintVFT4(VFT *p)//int arr[] ->降维成int * -》 VFT arr[] -> 降维成 VFT*
{
	printf("虚表指针:%p\n", p);
	for (int i = 0; p[i] != nullptr; i++)
	{
		printf("p[%d]:%p\n", i, p[i]);
	}
}

int main()
{
	Base b;
	Derive d;
	PrintVFT1((void(*(*))())(*((int*)&d)));//传入虚基表指针,并且强转一下类型,强转成函数指针数组指针
	cout << "___________" << endl;
	PrintVFT2((void(*(*))())(*((int*)&d)));//传入虚基表指针,并且强转一下类型,强转成函数指针数组指针
	cout << "___________" << endl;
	PrintVFT3((VFT *)(*((int*)&d)));//传入虚基表指针,并且强转一下类型,强转成函数指针数组指针(二级函数指针)
	cout << "___________" << endl;
	PrintVFT4((VFT *)(*((int*)&d)));//传入虚基表指针,并且强转一下类型,强转成函数指针数组指针(二级函数指针)




	int a = 10;
	system("pause");
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.2多继承中的虚函数表

多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中

typedef void(*VFT)();

void PrintVFT(VFT *vftarr)
{
	printf("虚表指针:%p\n", vftarr);
	for (int i = 0; vftarr[i] != nullptr; i++)
	{
		printf("vftarr[%d]:%p->", i, vftarr[i]);
		VFT f=vftarr[i];
		f();
		cout << endl;
	}
}

//多继承

#include "test.h"

class base1 {
public:
	virtual void func1() { cout << "base1::func1" << endl; }
	virtual void func2() { cout << "base1::func2" << endl; }
private:
	int b1;
};
class base2 {
public:
	virtual void func1() { cout << "base2::func1" << endl; }
	virtual void func2() { cout << "base2::func2" << endl; }
private:
	int b2;
};

class derive : public base1, public base2 {
public:
	//重写func1,继承func2,自己写一个func3
	virtual void func1() { cout << "derive::func1" << endl; }
	virtual void func3() { cout << "derive::func3" << endl; }
private:
	int d1;
};

int main()
{
	derive d;

	cout <<"大小"<<sizeof(d) << endl;// 8 + 8 +4 =20
	
	printf("func1真实地址:%p\n", &derive::func1);//成员函数取地址要加&

	PrintVFT((VFT*)(*(int*)&d));//取b1基表指针
	cout << "___________" << endl;
	PrintVFT((VFT*)(*(int*)((char*)&d + sizeof(base1))));//取b2基表指针

	printf("func1真实地址:%p\n", &derive::func1);//成员函数取地址要加&

	system("pause");
	return 0;
}

在这里插入图片描述

8.多态常见问题

1.什么是多态
多态分为静态多态和动态多态、静态多态在编译时就已经确定好了,动态多态在运行时才会确定
常见静态多态是函数重载,动态多态则是通过对父类虚函数的重写,通过虚表,达到传入谁的指针就调用谁的函数的目的

2.什么是重载、重写(覆盖)、重定义(隐藏):
重载:同一作用域内,函数名相同参数不同
重写:子类和父类的虚函数,名称、返回值、参数都相同,称子类重写了父类的虚函数
重定义:子类和父类的函数名相同,称子类隐藏了父类的某个函数

3.多态的实现原理:
父类和子类之中保存的虚表指针是不一样的,通过传入指针或者引用(本质也是指针)确定去子类还是父类之中去寻找虚表指针,最后达到调用不同虚函数的目的

4.inline可以是虚函数吗:
可以、在VS之下,编译器会舍弃inline属性,这个函数就不是内联函数了,因为内联函数会被展开,是没有地址的。而虚函数会将其地址放入至虚表之中

5.静态成员可以是虚函数吗:
不能,静态成员没有this指针,也就是没有对象,没有对象代表没有虚表指针 ->没有虚表(虚表指针是存储在对象之中的)

6.构造函数可以是虚函数吗:
不可以
1.构成了多态调用的时候要么调用父类,要么调用子类,而构造函数之中,子类会去调用父类的构造函数
2.因为虚函数表指针是在构造函数初始化阶段才初始化的(将虚函数地址填入虚函数表之中)

7.析构函数可以是虚函数吗:
建议将析构函数定义成虚函数

8.对象访问普通函数快还是虚函数更快:
不构成多态的时候,在编译时就确定了如何调用,因此是一样快的
构成多态时,访问普通函数快,因为访问虚函数首先需要去对象之中找到指针,然后通过指针找到虚表之中去寻找函数地址

9.虚函数表示在什么阶段、在哪里生成的:
虚函数表是在编译阶段生成的,一般情况下存在代码段

10.虚继承的原理:
菱形虚拟继承中,通过虚基表指针,查找虚基类,再通过偏移量拿到对应的变量

11.什么是抽象类,抽象类的作用:
抽象类不能实例化出对象,描述的事物没有对应的实体,另外抽象类体现出了接口继承的关系

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值