多态性---虚函数的使用


预习和提问
什么是多态? / 为什么使用多态?
什么是虚函数?
特点,注意?
什么是虚函数表?
纯虚函数和抽象类又是什么?
面试怎么问?

虚函数使用方法

定义
函数返回类型之前使用virtual
只在成员函数的声明中添加,实现中不需要
继承
某个成员函数被声明为虚函数,那么它的子类,以及子类的子类中,所继承的这个成员函数,也自动是虚函数
子类中重写这个虚函数,可以不用在写virtual,但建议写,可读性更好

多态性的解释

(1)体现在具有继承关系的父类和子类之间.子类重新定义(覆盖/重写)父类的成员函数,同时父类和子类中又把这个函数声明为了virtual函数
(2)通过父类的指针,只有到了程序运行时期,根据具体执行到的代码行,才能找到动态绑定到父类指针上的对象(new的是哪个),这个对象是某个子类对象,或者父类对象,系统内部实际上是要查类的虚函数表,根据虚函数表找到函数的入口地址,从而调用父类或者子类的虚函数

理解多态

简单说就是想要实现多态这种效果, 就需要使用虚函数
多态理解为一种效果,形式,而虚函数是实际功能,手段,机制

示例

Main

#include "Son.h"
#include "Father.h"
#include <iostream>

int main()
{
	Father father;
	Son son;

	Father* tmp;

	tmp = &father;
	tmp->play();     //Father---play()

	tmp = &son;
	tmp->play();     //Son---play()

	return 0;
}


Father.h

#pragma once
#include <iostream>

using namespace std;

class Father
{
public:
	virtual void play();

};


Father.cpp

#include "Father.h"

void Father::play()
{
	cout << "Father---play()"<< endl;
}

Son.h

#include "Son.h"


void Son::play()
{

	cout << "Son---play()" << endl;
}

Son.cpp

#pragma once
#include "Father.h"

class Son : public Father
{
public:
	void play();
};

在这里插入图片描述

示例中是写死的一个&father或&son,实际项目中可以根据用户的输入,实现过程中的各种状态来创建对象,根据调用时的指针
根据执行时子类对象进来就调用子类方法,父类对象进来就调用父类方法

总结:形式上,原本调用父类的方法,但是,实际上会调用子类的同名方法

纯虚函数和抽象类

纯虚函数:在基类中声明的虚函数,但是他在基类中没有函数体,或者说没有实现,只有一个声明,但它要求任何派生类都要定义该虚函数自己的实现方法
定义
在该虚函数的函数声明末尾的分号之前加"=0"
示例
Father.cpp

//此示例继续上面代码
class Father
{
public:
	virtual void play2() = 0;  //声明了一个纯虚函数
};

Main.cpp

int main()
{
	//Father father; //报错  不允许使用抽象类类型 "Father" 的对象
	//Father* father = new Father;  //报错,函数体都没有,肯定报错
	//Son* son = new Father;  //同样也是不允许的
	
	//Father* father = new Son;  //必须在子类中定义这个纯虚函数的函数体,否则报错
	//不允许使用抽象类类型 "Son" 的对象:	


	
	return 0;
}

注意
(1)纯虚函数没有函数体,只有函数声明
(2)一旦一个类中有纯虚函数,那就不能生成这个类的对象了(有纯虚函数的类就叫抽象类)
(3)抽象类不能用来生成对象,主要目的是用来统一管理子类对象;换句话说,就是主要用于当做父类来生成子类用的.
(4)子类中必须实现该父类中的纯虚函数,那上面例子说就是,定义了一个play2纯虚函数,必须在子类中定义play2函数体,如下

class Son : public Father
{
public:
	void play2(){};
	//Father* father = new Son    //main.cpp中,这一行就不会报错了
};

父类的析构一般写成虚函数

先说结论:用基类指针new子类的对象,在delete的时候系统不会调用子类的析构函数
示例

//main
	Son son;

在这里插入图片描述

	Son* tmp_son = new Son;
	delete tmp_son;

在这里插入图片描述
到这里,还都是正常释放,看下面示例

	Father* tmp_father = new Son;
	delete tmp_father;  //没有执行子类的析构函数


但是在用父类new子类对象的时候,没有释放"Son"的析构,这段示例可以自己尝试一下.
这肯定就有问题了,只删除了一半
解决
把父类的析构写成虚函数

	virtual ~Father();

在这里插入图片描述
完美解决这个问题,只需要在父类的析构上加了一个virtual ,这个问题就不存在了,子类的析构成功执行
结论
(1)delete tmp_father时,肯定是要调用的父类的析构函数, 但是父类析构函数中,要想调用子类(Son)的析构函数,父类(Father)的析构就要声明成virtual, 也就是说C++中为了获得运行时的多态行为,所调用的成员函数必须得是virtual的.
因为虚函数特性,只有虚函数才能做到用父类指针调用子类的虚函数

(2)父类中的析构函数的虚属性会继承给子类,这意味着子类的析构函数也是虚析构,就算不用virtual修饰也可.虽然名字不同

面试常问

为什么父类的析构一定要写成虚函数?
只有这样,当delete一个指向子类对象的父类指针时,才能保证系统能够依次调用子类的析构函数和父类的析构函数,从而保证对象(父类指针指向子对象)的内存被正确释放

多态的原理?
多态是用虚函数表实现的。
有虚函数的类都会生成一个虚函数表,这个表在编译时生成。

动态绑定与静态绑定?
静态绑定是程序编译时确定程序行为。
动态绑定是程序运行时根据具体的对象确定程序行为。

静态成员可以是虚函数吗?

不能, 因为静态成员函数没有this指针, 因为有this指针才能访问到虚表指针,有虚表指针才能找到虚表从而调用实际应该调用的函数。

构造函数可以是虚函数吗?/虚函数指针在什么时候生成的的?
不能,因为对象中的虚表指针是在构造函数初始化列表阶段才初始化的

对象访问普通函数快还是虚函数更快?
首先如果是普通对象,是一样快的,如果是指针对象或者是引用对象,则调用的普通函数快,因为普通对象在编译时就确定地址了,虚函数构成多态,运行时调用虚函数需要到虚函数表中去查找

注:
对象布局,对象模型,虚函数表,后续会用大篇幅补充…

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值