C++学习笔记(三十八):深入理解<多态>原理

C++学习笔记(三十八):深入理解<多态>原理,第1张

大家好,我是翼同学!

目录
  • 多态的解释
    • 联编
      • 静态联编
      • 动态联编
    • 什么是多态?
    • 如何实现多态?
  • 多态的示例
  • 多态的原理
  • 写在最后


每篇前言

今天笔记的内容是:

  • 什么是多态?
  • 多态的原理是什么?

我们知道,继承机制使得类对象可以得到重用。而多态性对于继承的意义很大。类的多态可以维护类编程的独立性,而不会干扰到应用编程。在这一篇笔记中主要记录类的多态性以及多态的原理。


多态的解释

多态是面向对象编程中的除数据封装与继承外的第三个基本特征。

想理解多态,就得先了解联编


联编
  • 联编是指程序自身彼此关联的过程,即使一个源程序经过编译、连接,成为一个可执行程序的过程。在联编中,需要确定程序中的 *** 作调用(函数调用)与执行该 *** 作(函数)的代码段之间的映射关系。

  • 比如说程序调用函数时,应该使用哪个可执行代码呢?又因为C++有重载函数的概念,那么编译器就必须查看函数的参数以及函数名才能确定使用哪一个函数。

  • 按照联编所进行的阶段不同,可分为静态联编和动态联编。

静态联编

静态联编指的是在编译时就解决了程序中的 *** 作调用与执行该 *** 作代码间的关系。比如说函数在编译阶段就确定了函数的调用地址,并产生了代码,即地址是早绑定的。那么这就是静态联编,又称早期联编。

静态联编的优点:

  • 调用速度快,效率高

动态联编

动态联编指的是编译程序在编译阶段并不能确切地知道将要调用的函数,只有在程序运行时才能确定将要调用的函数。也就是说联编工作是在程序运行时进行的,即地址时是晚绑定的。这种在程序运行时进行的联编工作被称为动态联编。又称晚期联编或迟后联编。

动态联编的优点:

  • 灵活,易维护性以及问题的抽象性。

什么是多态?

好了,当了解什么是联编后,我们就大致能理解什么是多态了。

概念:

  • 多态(polymorphism):指的是为不同数据类型的实体提供统一的接口

  • C++支持编译时多态(也就是静态联编):如函数重载或运算符重载。

  • C++也支持运行时多态(即动态联编):如派生类以及虚函数的实现。

静态多态和动态多态的区别在于调用函数时函数地址是早绑定还是晚绑定。而我们主要学习的是动态联编。即运行时,根据数据类型判断该调用哪一个函数的能力,我们称之为:多态性。

在开发中,我们提倡对程序的扩展进行开发,而关闭程序的修改。而多态的好处就在于:它能够让代码的结构变得清晰,可读性和扩展性也大大增加了。


如何实现多态?

C++规定:

  • 动态联编是在虚函数的支持下实现的,即成员函数必须声明为virtual

也就是说,为了指明某个成员函数具有多态性,我们用关键字virtual来标志其为虚函数。

调用方式:

  • 通过对象的指针或引用调用成员函数。
  • 即父类的指针或引用可以指向子类的对象,这不会报错。

多态的示例

举个例子

  • 我们定义了一个动物类(Animal)
  • 动物类有一个函数speak()表示叫这个动作。
  • 这时我们由动物类继承出鸡类(Chicken)和狗类(Dog)
  • 虽然鸡类和狗类都源自动物类,但是叫的动作是不一样的
  • 如鸡是啼叫,而狗会吠叫
  • 我们把这种源自同个父类,但各自响应不同的现象,称为多态

在上述例子中,为了实现多态,我们必须将speak()函数设置为虚函数。然后子类在继承父类后,需要在类内重新定义虚函数的函数体,使之具有特有的属性。

代码如下所示:

#include 
using namespace std;
class Animal {
public:
	virtual void speak() {
		cout << "动物在叫..." << endl;
	}
};
class Chicken : public Animal{
public:
	void speak() {
		cout << "鸡在啼叫..." << endl;
	}
};

class Dog : public Animal {
public:
	void speak() {
		cout << "狗在吠叫..." << endl;
	}
};

void doSpeak(Animal& a) {
	a.speak();
}

int main() {
	Animal a;
	Chicken c;
	Dog d;
	doSpeak(a);
	doSpeak(c);
	doSpeak(d);
	return 0;
}


多态的原理

那么,多态的实现原理是什么呢?

回答这个问题,需要了解一下类内部的结构,为此我们可以利用VS的工具来查看类的结构。

首先我们定义的动物类Animal是这样的:

接着,使用VS工具后类结构如下所示:

可以看到,Animal类的结构很简单,而且大小为1.

我们来看看,狗类继承了动物类:

而狗类的结构如下所示:

好了,这时我们再将动物类中的speak()函数定义为虚函数,如下所示:

这时我们再来看Animal类的结构:

很显然,动物类的结构变丰富了,大小也变为4.

以及狗类的内部结构如下:

狗类的内部结构也发生了变化。

那这是怎么一回事?

这是因为,在Animal类Dog类的内部结构中,有一个东西叫做虚函数表指针vfptr,即 virtual function pointer的意思。这个虚函数表指针是用于指向虚函数表vftable,而虚函数表的作用就是记录虚函数的函数入口地址。

  • 当子类重写父类的虚函数时,子类的虚函数表中的入口地址就发生了覆盖(改变)。

比如说,Dog类如果没有重新定义speak()的话,那么Dog类的结构如下:

当狗类重写动物类的虚函数speak()时,狗类的虚函数表中的speak()函数的入口地址就发生了改变。

如下所示:

虚函数表里记录的函数入口就已经改成了Dog类自己定义的函数。

而多态的使用,就是让父类的引用或指针指向子类的对象。

如下所示:


写在最后

本章内容参考书:

《C++程序设计教程》---- 钱能 著


好了,本篇笔记就到写这,欢迎大家到评论区一起讨论!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1353394.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-14
下一篇 2022-06-14

发表评论

登录后才能评论

评论列表(0条)

保存