多态

203 阅读2分钟

多态的概念

顾名思义,多态即多种形态。

定义:不同继承关系的类对象,调用同一函数,产生不同行为的现象就是多态。

我理解的多态,就是一种现象。

这个现象即,当不同的对象执行同一个行为时,产生的结果不一样

举个在西安乘坐公交车车费的例子:

付费方式票价
现金2元
支付宝/微信扫码1元
普通卡1元
学生卡0.6元
老年卡0元

这就属于生活中的多态,都是付车费,不同身份/不同支付方式,应该支付的车费是不一样的。

多态的构成条件

在继承中要构成多态要满足以下条件:

1.不同继承关系的类对象调用同一函数 2.必须通过基类的指针或者引用调用虚函数 3.被调用的函数必须是虚函数,并且派生类对基类的虚函数进行了重写

虚函数

virtual修饰的类成员函数,再次强调一定是类成员函数

例:

class Person{
public:
	virtual void ByBus(){ cout<<"票价:2元"<<endl; }
};

虚函数的重写(覆盖)

派生类中有一个成员函数与基类的一个虚函数三个东西相同:

1.返回值类型相同 2.函数名字相同 3.参数列表完全相同

这种行为称派生类的虚函数重写了基类的虚函数

例:

class Person{
public:
	virtual void ByBus(){ cout<<"票价:2元"<<endl; }
};

class Student: public Person{
public:
	virtual void ByBus(){ cout<<"票价:0.6元"<<endl; }
};

class Elder: public Person{
public:
	virtual void ByBus(){ cout<<"票价:0元"<<endl;  }
};

虽然派生类在对基类的虚函数进行重写时,不加virtual修饰成员函数,仍可完成重写工作,效果不受影响

但这种写法不建议,坚持良好的编程风格与代码的易读性有益于与他人合作。

多态的原理

首先来看一张图片 在这里插入图片描述

虚函数表指针及虚函数表

当我们实例化一个Base类对象时,该对象的成员中不仅有我们定义的私有成员_b,还出现了一个根本不是我们定义的__vfptr成员。

更令人疑惑的是,你想看看这个成员什么情况,刚敲下b.__vfptr,编译器就会给你错误警示!说这个Base里没有__vfptr成员!

这是怎么回事?

在这里插入图片描述

原来,这个__vfptr就是实现多态的核心机制之一! 而__vfptr的出现是因为virtual的出现, 如果去掉virtual,则__vfptr则会消失。

在这里插入图片描述 因此我们可以得出结论,一旦类中出现virtual修饰的成员函数,那么该类实例化后的对象必然有一个我们不能访问的成员__vfptr

__vfptr叫作==虚函数表指针==,这个指针==指向一个表,这个表里存储着对象的虚函数地址==。

简单的程序展示一下: 在这里插入图片描述 上图中的类中有4个虚函数,因此__vfptr指向的表里存储着4个函数指针,分别指向虚函数

Base::Func1()
Base::Test()
Base::Print()
Base::Show()

对象b的内存结构如下图所示: 在这里插入图片描述 上图中的红色表格就是==虚函数表==,这个表里存储所有对象b的虚函数地址,最后一个元素为nullptr作为表结束的标志。

派生类的虚函数表

以下面这份简单代码为例,讲讲派生类虚函数指针与基类虚函数指针的差别

#include <iostream>
using namespace std;
class Base {
public:
	virtual void Func1() { cout << "Base::Func1()" << endl; }
	virtual void Test() { cout << "Base::Test()" << endl; }
	virtual void Print() { cout << "Base::Print()" << endl; }
private:
	int _b = 1;
};
class D :public Base{
public:
	virtual void Func1() { cout << "D::Func1()" << endl; }
	virtual void Print() { cout << "D::Print()" << endl; }
private:
	int _c = 1;
};
int main() {
	Base b;
	D p;
	return 0;
}

运行后,监视变量区如下图: 在这里插入图片描述 我们可以看到,基类对象b的虚函数表里面有三个函数指针,指向

Base::Func1()
Base::Test()
Base::Print()

而派生类对象虚函数表也有三个虚函数指针,指向

D::Func1()
Base::Test()
D::Print()

对比即可发现,基类中只要被派生类重写的虚函数,在虚函数表里其地址全被派生类的虚函数覆盖了!

好一招狸猫换太子!竟然把基类的虚函数偷偷换成自己的虚函数。那这么做有什么用呢?

请看下图: 在这里插入图片描述 我们定义了一个基类指针pt1,令其指向派生类对象,然后利用这指针调用方法,可以发现,基类指针调用的方法有的竟然是派生类的方法!这是怎么回事?基类对派生类的引用也发生了这种现象。仔细观察下图: 在这里插入图片描述 可以发现,就是因为基类指针指向的派生类对象中的虚函数表发生了变化,本应是基类的函数指针被换成了派生类的函数指针,当发生方法调用时,自然而然,指针pt1与引用p2会调用被重写的虚函数。

当我们给派生类中增加一个基类并没有的虚函数后,因为pt1p2都是派生类对象强转过来的变量,我想调用派生类的方法没有问题吧?

因为Base* pt1 = new D;本来就是生成了一个派生类对象,然后把这个对象地址强转成为了基类指针,这个对象必然是有MyOwn()方法的。我想通过这个指针调用MyOwn(),我觉得不过分。

可是当我们敲下代码时,杯具了!编译器说,这玩意儿没有MyOwn()方法!怎么回事?我看派生类对象p分明可以调用MyOwn()啊!这是怎么回事?

在这里插入图片描述 请看下图: 在这里插入图片描述

其实派生类对象内存模型里该有的都有,只是当我们用基类指针去指向它时,这对象的地址会发生强制类型转换,使得==基类指针只能看见基类才有的东西==(红色方框)。因此pt1调用不了MyOwn()方法,但实质上是有,只是pt1看不到,访问不了而已。

如何验证?看内存监视就可以。(不过这里为啥多了一个我还没弄清楚,不过的确看到,三个函数地址后还有好几个数据,并且以nullptr<00 00 00 00>结尾) 在这里插入图片描述 因此,多态的实现原理就是通过virtual声明存在多态,之后生成虚表指针,管理虚函数。派生类通过重写基类的虚函数覆盖虚表中基类中三同的虚函数,不同的对象再通过调用同一函数,呈现不同的结果。

上面这段话可能一时半会不太好理解,举个坐公交的栗子:

#include <iostream>
using namespace std;
class Person {
public:
	virtual void ByBus() { cout << "票价:2元" << endl; }
};
class Student :public Person{
public:
	virtual void ByBus() { cout << "票价:0.6元" << endl; }
};
class Elder :public Person {
public:
	virtual void ByBus() { cout << "票价:0元" << endl; }
};
class Youth :public Person {
public:
	virtual void ByBus() { cout << "票价:1元" << endl; }
};
void Func(Person& p) {
	p.ByBus();
}
int main() {
	Person Wang;
	Func(Wang);

	Student Li;
	Func(Li);

	Elder Ma;
	Func(Ma);

	Youth Zhao;
	Func(Zhao);
	return 0;
}

4个不同身份的对象使用同一函数Func(),呈现不同结果: 在这里插入图片描述 我对多态原理的理解目前大抵就是这些了,如有不足,请各位指正!