C++中的静态和动态多态

  • Post author:
  • Post category:其他


之前学过继承,子类继承父类的属性,多态就是基于继承而来的,我们在如果只用继承,那么子类继承父类的各种属性在编译环节,就已经被确认了,导致代码不灵活。如果继承下来的某个子类不支持某种问题的解决,那么父类就需要重新编写代码,这样这个子类ok了,其他子类却有可能出现问题。

多态的好处:灵活,提高代码的可复用性。


1、多态的分类

1、静态多态:函数重载和运算符重载属于静态多态,复用函数名(编译期多态);

2、动态多态:派生类和虚函数实现运行时多态(运行期多态)。

函数重载就不必多说了,就是函数名的复用;运算符重载也类似于函数重载,相当于把符号的功能扩大了,同一个符号或者函数,能够实现多种功能,它们都是在编译的时候绑定了,以后都不能改变了,所以叫编译器多太。


2、动态多态的实现

我们以汉堡为例,汉堡为父类,鸡腿堡和牛肉保都继承了汉堡类。

代码:

#include<iostream>
using namespace std;

class baseHamburger//父类------汉堡类
{
public:

	void Material()
	{
		cout << "汉堡的配方为:面包、酱汁、蔬菜、肉饼"<<endl ;
	}
};

class chickenHamburger:public baseHamburger//子类1------鸡腿堡类
{
public:

	void Material()
	{
		cout << "汉堡的配方为:面包、酱汁、蔬菜、鸡肉饼" << endl;
	}
};

class beefHamburger:public baseHamburger//子类2------牛肉堡类
{
public:

	void Material()
	{
		cout << "汉堡的配方为:面包、酱汁、蔬菜、牛肉饼" << endl;
	}
};

int main()
{
	chickenHamburger p1;
	p1.Material();

	beefHamburger p2;
	p2.Material();
	
}

从代码可知,我们父类和2个子类内存在3个同名的函数

Material(),

我们创建p1和p2,他们都是默认调用类内的Material()函数,而不是调用父类的Material()函数,在程序编译的时候就确认了

Material()

函数的地址。如果我们想要调用父类的Material()函数,那么只需要加作用域就行。这也是我们最常见的情况。

如果我们这样写

#include<iostream>
using namespace std;

class baseHamburger//父类------汉堡类
{
public:

	void Material()
	{
		cout << "汉堡配方为:面包、酱汁、蔬菜、肉饼" << endl;
	}
};

class chickenHamburger:public baseHamburger//子类1------鸡腿堡类
{
public:

	void Material()
	{
		cout << "汉堡配方为:面包、酱汁、蔬菜、鸡肉饼" << endl;
	}
};

class beefHamburger :public baseHamburger//子类2------牛肉堡类
{
public:

	void Material()
	{
		cout << "汉堡配方为:面包、酱汁、蔬菜、牛肉饼" <<endl;
	}
};

//创建一个吃的函数
void eat(baseHamburger & Hamburger)
{
	cout << "我吃的"; 
	Hamburger.Material();
	
}

int main()
{
	chickenHamburger p1;
	eat(p1);

	beefHamburger p2;
	eat(p2);		
}

我们创建一个吃的

eat()

函数,函数参数为父类对象,函数体为父类对象指向和子类同名的函数,

此时,编译器默认调用了父类的

Material()

函数,为什么呢?

1、我们明明创建的是子类对象,那应该调用的是子类的

Material()呀!

2、传入

eat()

函数的参数是父类对象,那应该就是调用父类的

Material()呀!

是不是感觉有歧义?

那怎么解决呢?

只需要在父类的

Material()

函数前加上

virtual

关键字就可以解决问题。

class baseHamburger//父类------汉堡类
{
public:

	virtual void Material()//虚函数
	{
		cout << "汉堡配方为:面包、酱汁、蔬菜、肉饼" << endl;
	}
};

加上

virtual

关键字后函数就变成了虚函数,程序运行的时候才确定

Material()函数的地址。

有的同学可以说,我直接创建几个不同的函数名不就行了?

这样确实可以,但是你别忘了这个问题是在同名函数,我们要复用函数名前提下提出来的。

而且就算你创建很多个不同的函数名,那代码量不就上来了?且每一个eat()函数的参数都要额外写一个子类对象,函数体实现也就变得复杂了。

我们的目的是传入什么对象,那么就调用什么对象的函数,这也是动态多态的好处。


3、 动态多态的满足条件

1、有继承关系;

2、子类重写父类中的虚函数。

动态多态使用:

父类指针或引用指向子类对象,就像我们的

eat()函数

一样。


4、多态内部原理

当我们继承了父类中的函数的时候,那仅仅是继承了这个函数,其内部是通过

虚函数表指针(vfptr)

的和

虚函数表(vftable)

来控制的,

vfptr

又指向

vftable



vftable

内存放了所有父类的函数地址,

vfptr

就指向这些地址,所以子类的内部就固定存在父类的一份

vfptr



vftable

拷贝,这是最一般的情况。

但是当我们将父类的函数改为虚函数的时候,且在子类重写父类的虚函数,那就不一样了,当我们用父类指针指向子类对象的时候,

vftable

中同名函数的作用域就从父类变成的子类,相当于把父同名类函数的地址改为子类同名函数的地址。



版权声明:本文为weixin_56745394原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。