C++多态

概念:多态是不同的的对象去调用同一份代码,会产生不同的结果
构成多态的条件:

1.必须要有继承

2.必须通过基类的指针或这引用调用虚函数

3.在子类中必须对基类虚函数进行重写,虚函数是指在基类中加上virtual修饰的函数,重写是指在子类有和基类的虚函数完全相同的函数名,返回值,参数列表,函数内容可以不同

class Person
{
public:
	virtual void work()函数必须是虚函数
	{
		cout << "Person::work" << endl;
	}
	virtual void fun()
	{
		cout << "Person::fun()" << endl;
	}
};
class Child:public Person必须有继承
{
public:
	void work()必须重写
	{
		cout << "Child::work()" << endl;
	}
	void fun()
	{
		cout << "Child::fun()" << endl;
	}
};
class Parent:public Person  
{
public:
	void work()
	{
		cout << "Parent::work()" << endl;
	}
	void fun()
	{
		cout << "Parent::fun()" << endl;
	}
};
int main()
{
	Child c;
	Parent p;
	Person*per = &c;必须通过基类指针或引用调用该虚函数
	Person&rper = p;
	per->fun();
	per->work();
	rper.fun();
	rper.work();
}

代码运行效果:
在这里插入图片描述

重写的特例:

当子类中重写的函数返回值与基类不同时,子类的函数返回的是子类的引用或者指针,基类函数返回的是基类的引用或者指针,这种现象叫做协变。

class A
{
public:
	A&show()
	{
		cout << "A::show()" << endl;
		return *this;
	}
};
class B :public A
{
public:
	B* show()
	{
		cout << "B::show()" << endl;
		return this;
	}
};
不会出错,而且如果通过基类的指针或引用调用show方法,调动的还是子类的方法

C++11关键字final,override

final:修饰一个虚函数,表明该虚函数不能被继承,即在子类不能被重写

class A
{
public:
	virtual void show()final 
	{
		cout << "A::show()" << endl;
	}
};
class B :public A
{
public:
	void show()
	{
		cout << "B::show()" << endl;
	}
};

出错,B类里无法重写A::show()函数

override:检查派生类中是否重写了基类的某个虚函数,没有则会报错

class A
{
public:
	void show()
	{
		cout << "A::show()" << endl;
	}
};
class B :public A
{
public:
	void show()override
	{
		cout << "B::show()" << endl;
	}
};
会出错,B::show()没有重写基类的任何方法

抽象类

当一个基类的虚函数后面添加"=0"时,那么此时这个类就变成了抽象类,抽象类是不能实例化一个对象的。它的子类也不能实例化一个对象。只有当子类重写了基类的虚函数,子类才能实例化对象。基类仍然无法实例化对象,不过可以通过基类的指针和引用来调动基类的方法。

class A
{
public:
	virtual void fun() = 0
	{
		cout << "A::fun()" << endl;
	}
};
class B :public A
{
public:
	void fun()
	{
		cout << "B::fun()" << endl;
	}
};
int main()
{
	B b;
	A*pa = &b;
	A&ra = b;
	return 0;
}

多态实现原理

实现一个多态的例子

class A
{
public:
	A()
	{}
	virtual void fun()
	{
		cout << "A::fun()" << endl;
	}
	void show()
	{
		cout << "A::show()" << endl;
	}
private:
	int m_a;
};
class B :public A
{
public:
	B()
	{}
	void fun()
	{
		cout << "B::fun()" << endl;
	}
	void show()
	{
		cout << "B::show()" << endl;
	}
private:
	int m_b;
};
int main()
{
	B b;
	return 0;
}

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

在实例化一个B对象时,会先构造A对象,通过监视可以看到构造A对象时,除了有m_a成员,发现还有一个__vfptr,而且这个还是在第一位的(比m_a先构造),__vfptr的0下标指的是A::fun()函数。当A构造完了,再来构造B,发现此时A中的__vfptr和构造A时的__vfptr不一样了。首先,两个地址不相同,其次,此时的__vfptr的0下标指的是b::fun().
那么现在有问题了?
1.为什么会有__vfptr?
2.为什么两个__vfptr的地址不一样?
3.为什么两个__vfptr零下标的所指向的函数不一样?

答:如果一个类里有虚函数,那么在构造这个类时,会先定义一个虚函数表指针,即__vfptr,这个虚函数表指针会指向一个虚函数表,表实际上是一个函数指针数组,数组中的元素是指向该类中的虚函数,数组最后一个元素为nullptr,标志着数组的结束。不是虚函数的函数并不会在这个表中。在有继承时,在子类中会有父类的成员,当然也包括虚函数表指针,子类会将父类的虚函数表拷贝一个,如果子类重写了父类的方法,就会将虚函数表中的元素(本身是指向父类虚函数的地址)替换为指向子类的重写函数。如果子类没有重写父类的函数,那么虚函数表钟依然保存的是父类的方法。所以这就解释了上面的问题。

为什么会有两个__vfptr?
答:构造父类时会创建一个,构造子类时也会创建一个
为什么两个__vfptr的地址不一样?
答:子类拷贝父类的虚函数表,,并拿__vfptr指向,所以地址会不一样
为什么两个__vfptr零下标的所指向的函数不一样?
答:当子类重写了父类的虚函数,就会在虚函数表中将原本执行父类函数的地址替换为指向子类的重写函数,所以0下标所指向的函数会不一样。这也就是重写的本质,是更改虚函数表中对应数组下标指针的指向。

面试题

1.什么是多态?

多态是不同对象去调用同一份代码会产生不同的结果。

2.什么是重载,重写,重定义?

重载:在同一个类中,C++允许可以有同名函数,但参数列表和返回值不同,这叫做函数重载
重写:在不同类中,必须有继承,当子类有和父类相同的函数名,参数列表,返回值(协变除外),且两个函数都是虚函数,就会发生重写
重定义:当子类有和父类相同的函数名时,返回值,参数列表可以不相同,且函数不是虚函数,在子类中就会隐藏父类的同名函数,这叫做同名隐藏,也叫重定义

3.多态的实现原理?

当子类重写了父类的虚函数时,就会在自己的虚函数表更改原本指向父类的虚函数的指针,并指向子类重写的虚函数。通过更改指针的指向来形成多态。

4.为什么普通函数的调用比虚函数的调用效率要高?
普通函数是在编译的时候就确定了函数的地址,直接调用
虚函数首先要通过虚函数表指针找到虚函数表,再查询虚函数表才能找到虚函数,最后进行调用
普通函数要调用的操作要比虚函数调用的操作简单的多,所以效率要高。

总结:多态是C++三大特性之一,通过使用多态,可以使代码更加简洁和明确,熟悉多态的底层原理,对写代码是有极大的帮助的。共勉。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值