多态

多态

一.多态概念

百度百科:
多态按字面意思是“多种状态”,在面向对象语言中,接口的多种不同实现方法即多态,引用Charlie Calverts对多态的描述-多态性是允许你将父对象设置成一个或更多的他的子对象相等的技术,赋值之后父对象就可以根据当前赋值给它的子对象的特征以不同方式运作,简单的说就是一句话:允许将子类类型的指针赋值给父类类型指针

简单描述就是指不同对象调用相同的方法,所产生的效果不一样。举个栗子,比如神奇宝贝中的宝可梦,相同的精灵蛋孵化出的精灵会根据主人的身份不同孵化出不同的宝可梦。在举个通俗的栗子,大家应该都坐过公交,公交就是老年人有老年卡2毛,学生有学生卡4毛,成人就是全价…

二.多态的实现

在这里插入图片描述

多态产生条件:

1.如果我们期望实现不同的状态必须以父子以继承的状态为前提,然后加上'virtual' 这个关键字,而且必须要完成对基类的重写。
2.调用函数的对象必须是指针和引用(协变除外)
协变 协变虚函数的返回值可以是父类或子类的指针或者引用

//协变
class A{}; class B : public A {};
 
class Person { public:    virtual A* f() {return new A;} };
 
class Student : public Person { public:    virtual B* f() {return new B;} };

重写如果子类中有一个与父类完全相同的虚函数,就称子类的虚函数重写(覆盖)了父类的虚函数。完全相同指的是:函数名相同,返回值相同,参数列表也相同(这里有一个例外,协变除外)
重写,重载,重定义(隐藏)三者关系

在这里插入图片描述

以买票为例写一个代码 体验一下多态

#include<iostream>
using namespace std;
class Person {
public:
 virtual void BuyTickets() { //virtual Person* BuyTicket() 协变
  cout << "全价票" << endl;
 }
 /*
 ~Person()
 {
  cout << "I am Person" << endl;//普通调用
 }
 */
 virtual ~Person()
 {
  cout << "I am Person" << endl;//多态调用
 }
};


class Student :public Person
{
public:
 void BuyTickets() {   //virtual Student* BuyTickets() 协变是虚函数构成重写的例外,即使返回值不相同
  cout << "半价票" << endl;
 }
 ~Student()
 {
  cout << "I am Student" << endl;
 }
};
void SellTicWin(Person& p)//这里为什么要传指针或者引用?
{
 p.BuyTickets();
}

int main()
{
 /*Student s;
 SellTicWin(s);
 Person p;
 SellTicWin(p);
 */
 Person* p = new Person;
 p->BuyTickets();
 delete p;
p = new Student;
 p->BuyTickets();
 delete p;//在不加virtual的情况下果然默认是调用的person的析构函数,原因是普通调用只跟返回值类型有关系
 return 0;
 }

这里提出了一个问题:为什么要传递基类的指针或者引用呢?这里不得不提到另一个问题—虚函数表

三.虚函数表

1.虚函数表:存放虚函数地址的一个指针数组。
2.虚函数指针:是对象的一部分,它指向了虚函数表。

先来看内存中虚函数表和指针是怎么样的:用下面类定义一个b1对象

class Base
{
public:
	virtual void func1()
	{}

	virtual void func2()
	{}

private:
	int a;
};

void Test1()
{
	Base b1;
}

可以看出有一个a成员变量,还有一个_vfptr(虚函数表指针),这个指针指向的地址有俩个虚函数的地址,模型如下:

在这里插入图片描述

对象模型:我们重写过程就是基于这张虚函数表
在这里插入图片描述

多态的实现原理

在这里插入图片描述

这里提出了一个问题:为什么是一个对象的指针或者引用去调用这个函数?因为只有指针或者引用去指向虚函数表,编译器才知道要调用哪个函数。
为了解释更清楚

class person
{
public:
	virtual void buy()
	{
		cout << "全价" << endl;
	}
};

class student:public person
{
public:
	virtual void buy()
	{
		cout << "半价" << endl;
	}
};

void fun(person& p)
{
	p.buy();
}

int main()
{
	person p;
	person* pp = &p;
	p.buy();//使用p对象调用
	pp->buy();//使用指针模拟多态调用
        system("pause");
	return 0;
} 

打开反汇编:

我们发现,如果使用p对象调用代码只有两行,而模拟多态的原理代码较p对象而言还是非常多的,因为使用p对象调用时,编译器在编译的阶段已经知道我们所需要调用的函数,而模拟多态的原理我们发现,系统需要去虚函数表中去查找我们想调用的函数。

在这里插入图片描述

四.抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
为什么有抽象类(不实例对象的类),因为拿下面的代码来说,人本来就是抽象的东西,人本身应该有自己的特征,比如人应该是一个学生,或者是一个老师。实例出人这个类是没有意义的。

virtual void buy()=0;

抽象类不能实例化出对象,如果派生类没有实例化出对象那么也不能完成对象的实例化,但是可以实例化出指针。

与多态相关的问题?

1.普通调用和多态调用的关系:普通调用只根据返回值类型进行判断(例如:Person p返回值是一个Person所以调用的应该是person这个类 多态调用是根据指针或者引用 引用或指向的对象完成相应调用)
2.同名隐藏和重写的区别?
重写是提前定义了虚函数,并且函数名返回值和参数列表必须相同(再无协变情况下)
隐藏是返回值和参数列表不一定要相同,而且没有定义虚函数,反正就是满足了继承关系的函数下,函数名相同不是重写就必然是隐藏。
3.在delete p语句都相同的情况下,编译器是如何知道调用的是哪个函数??
根据初始化时指针指向的对象,对象完成了任务生命周期结束调用相应的构造函数,在底层实现的时候把名字做了特殊处理叫做
Student::vector deleting destructor
Person::vector deleting destructor
4.析构函数可以是虚函数吗?什么场景下析构函数是虚函数?答:可以,并且最好把基类的析构函数定义成虚函数。(因为可能delete一个父类的指针指向子类,多态原理会帮助我们释放干净)
5.多态的实现原理?答:虚函数表
6.inline函数可以是虚函数吗?答:不能,因为inline函数没有地址,无法把地址放到虚函数表中。
7.静态成员可以是虚函数吗?答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。
8.构造函数可以是虚函数吗?答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。
9.对象访问普通函数快还是虚函数更快?答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
10.虚函数表是在什么阶段生成的,存在哪的?答:虚函数是在编译阶段就生成的,一般情况下存在代码段的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值