【C++第十二课-多态】多态概念、多态原理

多态的概念

概念

具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。-- 这种场景就可以使用多态
多态调用的情况下,是实现重写

多态形成的条件

虚函数的重写

virtual在继承中是虚继承,用这个关键字修饰函数就是虚函数
virtual只能修饰成员函数

父子类中两个虚函数,三同(函数名,参数,返回值),此时构成虚函数的重新
父类的指针或引用去调用虚函数,传的是父类掉父类的,传子类掉子类的(切片)

class Person
{
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student :public Person
{
public:
	virtual void BuyTicket() { cout << "买票-75折" << endl; }
};

class Soldier :public Person
{
public:
	virtual void BuyTicket() { cout << "买票-免费" << endl; }
};
void Func(Person& p)
{
	p.BuyTicket();
}


int main()
{
	Person p1;
	cout << "Person:";
	Func(p1);
	Student stu;
	cout << "Student:";
	Func(stu);
	Soldier sol;
	cout << "Soldier:";
	Func(sol);
	return 0;
}

在这里插入图片描述

虚函数重写的两个例外

(1)协变:虚函数返回值可以不同,这个返回值要求必须是父子类关系的指针或引用

不满足重写就是隐藏
重载:在同一作用域,函数名相同参数不同,与返回值无关

class A {

};
class B :public A
{

};
class Person
{
public:
	virtual A* BuyTicket() 
	{
		cout << "买票-全价" << endl;
		return new A;
	}
};
class Student :public Person
{
public:
	virtual B* BuyTicket() 
	{ 
		cout << "买票-75折" << endl;
		return new B;
	}
};

void f2()
{
	Person* p = new Student;
	p->BuyTicket();
}

int main()
{
	f2();
	return 0;
}

在这里插入图片描述
(2)析构函数的重写(基类和派生类析构函数的名字不同)
析构函数的名字的特殊处理,及其析构函数建议加virtual
下面这样的析构是正确的
在这里插入图片描述
下面这种情况按道理来说应该是
在这里插入图片描述

在这里插入图片描述

这样会造成p2所指向的内容会有内存泄漏的风险

普通调用:看指针或引用或对象的类型
多态调用:看指针或引用指向的对象
上图的情况适合多态调用,看指针指向的对象去进行delete

在这里插入图片描述
(3)虚函数重写时,父类函数加了virtual,子类不加也构成重写

BuyTicket()在父类中没有virtual,在子类中有virtual。但这样不是重写,只构成了隐藏

在这里插入图片描述
父类的BuyTicket()有virtual,子类的没有,也可以构成重写
函数的接口继承了下来,只重写了实现,与父类形成了重写
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

多态的题目

1、
在这里插入图片描述

C++11增加的关于多态的关键字

final

1、修饰虚函数,表示该虚函数不能再被重写
在这里插入图片描述

2、实现一共类,这个类不能被继承
(1)父类构造函数私有化,派生实例化不出对象

只对成员函数等私有化是不可以的,公有继承碰私有,只是不可见,还是可以被继承,只有再下一代才不能被继承

B类对象实例化的时候需要调用A类的构造函数,但A类构造函数私有化

在这里插入图片描述
(2)C++11,final修饰的类为最终类,不能被继承
在这里插入图片描述

override

检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

在这里插入图片描述

多态原理

虚函数表指针 vfptr

Person类的成员变量只有int,但其大小确实8?
在这里插入图片描述

因为Person类里面有虚函数,类里面会多一个虚函数表指针(虚表指针)指向虚函数表

虚函数表实际上是个指针数组

在这里插入图片描述

多态的实现

对于子类来说,他对BuyTicket函数进行了重写,同时更改了虚函数表里对应的内容

虚函数的重写也叫做覆盖
重写是语法层的概念
覆盖是原理层的概念

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

多态调用:运行时去指向对象的虚函数表中找函数的地址,进行调用,所以指向父类调的是父类虚函数,指向子类调用的是子类虚函数
普通调用:编译时,符号表找到函数地址,通过调用者类型确定函数地址

静态绑定和动态绑定

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

打印虚函数表

class Base
{
public:
	virtual void func1()
	{
		cout << "Base::func1()" << endl;
	}
	virtual void func2()
	{
		cout << "Base::func2()" << endl;
	}
private:
	int _i = 1;
};
class Drive :public Base
{
public:
	virtual void func1()
	{
		cout << "Drive::func1()" << endl;
	}
	virtual void func3()
	{
		cout << "Drive::func3()" << endl;
	}
private:
	int _j = 1;
};

typedef void(*VF_ptr)();
void PrintVFT(VF_ptr vft[])
{
	printf("%p\n", vft);
	for (size_t i = 0; vft[i] != nullptr; i++)
	{
		printf("[%d]:%p", i, vft[i]);
		VF_ptr f = vft[i];
		f();
	}
}

int main()
{
	Base bb;
	Drive dd;
	Base* pb = &bb;
	Drive* pd = &dd;
	PrintVFT((VF_ptr*)(*(int*)pd));
	return 0;
}

在这里插入图片描述

补充

1、虚函数表存放在哪个区域(栈、堆、静态区/全局数据段、常量区/只读数据段)?常量区
2、虚表是编译时生成的
3、虚表指针构造函数的初始化列表最开始时生成的
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
Base1里面有个虚表指针,Base2里面有个虚表指针。Drive里面共有两张虚表。
func3()倾向于放在Base1的虚表或者两个虚表都放

菱形继承和菱形虚拟继承(了解)

菱形继承就往多继承看
菱形虚拟继承就了解

抽象类

概念

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

纯虚函数的使用
1、某些情况下不希望父类生成派生类
2、强制要求派生类对于某个虚函数进行重写

在这里插入图片描述

class car
{
public:
	virtual void drive() = 0;
};

class BMW :public car
{
public:
	virtual void drive() { cout << "BMW->操作" << endl; }
};

class Benz :public car
{
public:
	virtual void drive() { cout << "Benz->舒适" << endl; }
};

接口继承和实现继承

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值