【C++】多态的细节

多态的构成条件:

多态是指存在继承关系的不同类对象中,在调用同一个函数时所表现的不同行为。
继承中多态的构成还需要两个条件:
1、必须通过基类的指针和引用来调用虚函数。
2、被调用的函数必须是虚函数,派生类需要对虚函数进行重写。
重写的条件是虚函数+三同(函数名,参数,返回值)

虚函数:

虚函数必须要在类作用域中才存在,需要关键字virtual。

class Person
{
public:
	//虚函数
	virtual void fun()
	{
	};
};

不同的对象当成参数传来就可以当成多态。
在这里插入图片描述

一些细节:

重写的条件本来是虚函数+三同,但有一些例外。
1、派生类的重写虚函数可以不加virtual,但建议加上。
2、存在协变的特殊情况,返回值可以不同,但必须是父子关系的指针或引用
在这里插入图片描述
3、对于析构函数而言,要想满足虚函数的重写,在编译器进行编译时需要将析构函数都处理成destructor这一个统一的名字。而析构函数的重写,是为了不发生内存泄漏

Person* p = new person;
delete p;//这个场景没问题

//在不满足多态的情况下就会出现下面的场景
p = new Student;
delete p;//这里相当于p->destructor() + operator delete(p)
//由于是Person*类型的去调用析构函数,所以其不能直接去调用Student类的析构函数,造成内存泄漏

//只要析构函数形成多态,那上面的代码在编译时会自动调用派生类的析构函数
//不再受限于调用者(p),编译器根据多态进行析构

重写失效:

这里需要认识关键字final。
在这里插入图片描述

override:

该关键字是用来检查函数是否完成了重写。
在这里插入图片描述

设计一个不被继承的类:

方法一:
对构造函数进行私有化。
可以在类中写另外的函数来访问构造函数。
不过需要静态化来调取未形成对象的类中函数。
在这里插入图片描述
方法二:
基类加上关键字final来限制。

重载、重写、重定义的区别:

重载:
1、函数在同一个作用域中。
2、函数名/参数不同。

重写(覆盖):
1、两个函数分别在基类和派生类的作用域。
2、两个函数必须是虚函数+三同(协变在返回值上是父子关系的指针或引用)。

重定义(隐藏):
1、两个函数分别在基类和派生类的作用域。
2、函数名相同。
简单地说 两个分别在基类和派生类的同名函数不构成重写那就是重定义

虚函数表:

虚函数本质是放在代码段中,但对象里含有虚函数表来存放虚函数的地址。
在这里插入图片描述
不符合多态时,编译器在进行编译就可以确定函数地址。
符合多态时,运行时会找到指向对象的虚函数表寻求地址。

基类和派生类的虚函数表是可以通过切片来统一做成基类的格式以便在调用时找到虚函数表。
这也是为什么可以基类对象调用基类对象的虚函数表,派生类对象调用派生类对象的虚函数表。

为什么在多态的实现中不能是基类对象?

在涉及虚函数的概念后,回过头来思考多态的条件我们不免发出这样的疑问。
首先要明确的一点是,在多态的实现中需要将虚函数表进行拷贝,然后通过虚函数的重写覆盖了旧虚函数地址,换成派生类虚函数地址或者是新地址
如果是使用基类对象,派生类对象赋值给基类对象的过程中,不会拷贝虚表。

多继承中的虚函数表:

在这里插入图片描述
使用sizeof可以知道,创一个Derive对象所需的内存大小是20。
从何而来?打开监视窗口看发现是继承了两个基类的虚函数表(实际是指针),还有自己的成员。
在这里插入图片描述
虽然是继承了基类的两个虚函数表,派生类自己的虚函数就会被放在两个虚函数表中的一个。
仔细一看,发现虽然通过两个虚函数表分别能调用func1函数,但地址不一样,这涉及到汇编语言,暂时不介绍。

抽象类:

纯虚函数:

在虚函数后面加上 = 0 就是纯虚函数。

class Book
{
public:
	virtual void Print(char* name) = 0;
private:
	char* _name;
};

抽象类的定义:

包含纯虚函数的类叫做抽象类,抽象类不能实例化对象
派生类继承了抽象类后也不能实例化对象,只有重写了纯虚函数,派生类才能实例化对象。

class Ficton:public Book
{
public:
	virtual void Print();
	//这样不能实例化

	virtual void Print(char* name)
	{
		printf("%s\n", name);
	}
};

接口继承:

对抽象类的继承也称为接口继承,使用抽象类,就是强制对虚函数进行重写使不同的类实现不同的功能,可以认为是接口。

总结:

1、什么是多态?
多态可以分为静态多态和动态多态。
静态多态:普通函数重载。例如使用cout时我们不需要像printf函数那样需要特定的格式输出符,事实上cout根据不同类型参数实现了函数的重载。
动态多态:继承中虚函数的重写 + 父类指针调用。父类指针可以指向父类对象,也可以指向子类对象,从而十分灵活地调用虚函数表,获取指向对象的虚函数。
实现原理:静态多态通过函数名修饰规则,动态多态通过虚函数表实现。
2、内联函数可以是虚函数吗?
可以,编译是通过的,但是写上inline并不代表函数就是内联函数,因为虚函数需要有地址存放在虚函数表中,而内联函数类似于宏那般,在编译阶段就写入代码中了,没有地址。
3、静态成员函数可以是虚函数吗?
不行。静态成员函数不和其它成员函数一样拥有this指针,无法通过父类指针方式访问虚函数表,因此无法实现多态。
4、构造函数不能是虚函数。
虚函数表在编译中就已经生成,但虚函数指针是类实例化时,通过构造函数的初始化列表对虚函数表指针进行初始化。
5、析构函数最好是虚函数。
这种情况发生在具有继承关系的类之间,因为使用delete对数据进行清除时难免会根据不同的类进行处理,使用虚函数是为了重写析构函数,实现多态。
6、效率问题:使用普通函数更快还是虚函数更快?
如果是使用普通对象,那二者无区别;如果使用指针(引用)对象,因为虚函数要调用虚函数表,在效率上更低。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
俄罗斯方块是一款经典的游戏,多年来一直受到广大玩家的喜爱。MFC(Microsoft Foundation Class)是一套用于Windows编程的C++类库,可以帮助开发者快速创建Windows界面应用程序。 在俄罗斯方块游戏中,我们可以使用多态调用MFC来实现游戏的各种功能。 首先,我们可以创建一个基类TetrisBlock,其中包含俄罗斯方块的基本属性和方法,比如获取方块形状、旋转方块等。 然后,我们可以创建多个继承自TetrisBlock的子类,每个子类代表不同类型的俄罗斯方块,比如I型、T型、L型等。这些子类可以重写基类的方法,以实现自身特定的功能。 接下来,我们可以使用MFC提供的类和方法来创建游戏界面,比如使用CWnd类创建一个窗口,使用CDC类在窗口中绘制游戏界面等。 在游戏进行过程中,我们可以使用多态调用来实现不同类型的俄罗斯方块的移动和旋转。比如,通过创建一个指向基类TetrisBlock的指针,将其指向任意一个子类对象,然后调用指针所指向对象的方法来进行操作。 此外,我们还可以使用MFC提供的类和方法实现用户交互,比如通过CButton类创建按钮,通过CDialog类创建对话框等。这样,玩家就可以使用鼠标或键盘来控制俄罗斯方块的移动、旋转和其他操作。 总之,通过多态调用MFC,我们可以方便地实现俄罗斯方块游戏的各种功能和界面。这样,开发者可以更加专注于游戏的逻辑和用户体验,而无需过多关注底层的实现细节

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值