对多态的理解

本文探讨了C++中的静态联编与动态联编概念,解释了多态的定义和实现条件,重点讲解了虚函数的使用、虚函数表的生成以及多态在编程中的应用。通过实例和图解深入剖析了为何不能将所有函数声明为虚函数,以及各类函数的特性和限制。
摘要由CSDN通过智能技术生成

一、静态联编和动态联编

1、联编是指一个程序模块,代码之间的相互关联的过程
2、静态联编是程序的匹配、连接在编译阶段实现,也称为早期匹配或早期绑定;例如,函数重载就是静态联编的一种
3、动态联编是指程序联编在运行时才进行,也称为晚绑定;switch语句和if语句是动态联编的例子;

二、什么是多态以及多态的实现条件

1、三连问:

  • 定义一个空的类型,里面没有任何的成员变量或者成员函数,对这个类型进行 sizeof 运算,结果是?
    结果为1,空类型的实例不包含任何信息,按道理sizeof()会得到0;但空类型也能实例化对象,而对象肯定是占用一定空间的,所以编译器会自动赋予空类型一定的内存,具体要看编译器,GCC和msvc都是赋予空类型1字节内存;

  • 如果空类型里面添加构造函数和析构函数,结果又是多少?
    还是1;因为调用构造和析构,知道函数地址即可,而这些地址只和类型相关,和实例化的对象无关,编译器不会为此添加额外的信息;

  • 如果把析构函数变为虚函数,结果是多少?
    C++编译器发现类型定义里面含有虚函数时,就会为此类型生成一个虚函数表,并在该类型的每个实例化对象添加一个指向虚函数表的指针,32位系统内占4字节;

2、什么是多态?

2.1、网上说法:同样的调用语句有多种不同的表现形态 or 在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数 or 如果有几个以上似而不完全相同的对象,有时人们要求在向它们发出一个相同的消息时,他们的反应各不相同,分别执行不同的操作。这就情况就是多态现象。

2.2、我的理解:一个接口多种实现,可以提高代码重用率,给予用户更灵活的使用方法;总言之,是为了方便程序员高效率的编写出优秀的代码,向用户提供更灵活的使用方式;

3、多态的实现条件

  • 要有继承关系;
  • 使用父类指针或引用指向子类对象;
  • 在父类和子类中有相同的虚函数,且子类中对父类的虚函数进行了重写;

三、C++如何实现多态

3.1、原理

当类中声明虚函数时,编译器会在类中生成一个虚函数表(不属于该类实例化对象的内存空间,是全局的),虚函数表是一个存储virtual修饰的类成员函数地址的数据结构,它是有编译器自动生成和维护的;
virtual成员函数的地址会被编译器放入虚表中,每个对象都有一个指向虚表的指针vptr;

3.2、虚指针什么时候初始化?

  • 在构造函数中进行虚表的创建和虚表指针的初始化(必须所有构造函数完成后,虚表和虚指针的内容才最终确定,所以最好不要在父类的构造函数中调用虚函数,否则无法实现多态)
  • 构造子类对象时,先调用父类的构造函数,此时编译器只看到了父类,不知道后面是否还有继承者,它初始化父类对象的虚表指针vptr,该虚表指针指向父类的虚表。当执行子类的构造函数时,子类对象的虚表指针vptr被初始化, 此时 vptr指向自身的虚表。当fish类的fh对象构造完毕后,其内部的虚表指针也就被初始化为指向fish类的虚表。

3.3、虚函数和纯虚函数

纯虚函数就是基类定义了函数体,但不做实现;而继承它的所有子类都必须实现,即纯虚函数是个接口,是个函数声明,在子类可以不重载;

3.4、带有virtual修饰成员函数的类的每个对象内部都有一个指向本类虚表的指针,不管对象类型如何转换,但该对象内部的虚表指针是固定的;

3.5、如果基类有虚函数:

  • 每个类都有一个虚表;

  • 虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然有该函数的地址,只不过该地址是指向基类的虚函数实现;

  • 如果基类有3个虚函数,则基类的虚表至少有3项,派生类也至少有3项,

  • 如果派生类重写了响应的虚函数,则虚表中的地址会改变,指向自身的虚函数实现;

  • 如果派生类还有自己的虚函数,那么虚表也会增加;

3.6、图解

from
1、基类有虚函数,派生类不对基类的虚函数重写,但拥有自己的虚函数
在这里插入图片描述
2、如果派生类中有虚函数重载了父类的虚函数
在这里插入图片描述
3、多重继承(没有虚函数重写)的情况
在这里插入图片描述
从图上我们可以看到

1)每个父类都有自己的虚表。

2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

4、多重继承(有虚函数重写)的情况
在这里插入图片描述
三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类对象,并调用子类的f()了

四、关于多态的“为什么?”

1、什么函数不能声明为虚函数或者是否可以把类的每个成员函数声明为虚函数?

不能声明为虚函数的有:**普通函数(非类成员函数,即类外声明和定义的函数),静态成员函数,内联成员函数,构造函数,友元函数;**具体说明如下:
1)普通函数不能声明为虚函数。普通函数只能被重载,不能被重写,即使强硬声明为virtual也没用,编译器会在编译时绑定函数
2)构造函数不能声明为虚函数。构造函数用于初始化对象,只有对象生成后才能发挥多态作用。构造函数不能被继承。
3)静态成员函数不能声明为虚函数。静态成员函数对每个对象来说都只有一份,是共享的,不归属某个对象,所以没有动态绑定的必要。
4)内联成员函数不能声明为虚函数。内联函数就是为了在代码中直接展开,减少函数调用的开销。而虚函数是为了在继承后,对象能都准确的执行自己的动作,二者不统一;
5)友元函数不属于类的成员函数,不能被继承,所以不能声明为虚函数
6)析构函数可以是虚函数,而且通常声明为虚函数(至于为什么,暂时不做回复);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值