首页 > 编程语言 >C++ 多态原理

C++ 多态原理

时间:2023-06-04 22:35:50浏览次数:47  
标签:函数 对象 多态 C++ 地址 基类 原理 指针

多态就是多种形态,C++的多态分为静态多态与动态多态。
动态多态就是通过继承重写基类的虚函数实现的多态,在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。

多态的总结:
(1) 用virtual关键字声明的函数叫做虚函数,虚函数肯定是类的成员函数。
(2) 存在虚函数的类都有一个虚函数表叫做虚表。当类中声明虚函数时,编译器会在类中生成一个虚函数表。
(3) 类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的
(4) 虚函数表是一个存储类成员函数指针的数据结构。
(5) 虚函数表是由编译器自动生成与维护的。
(6) virtual成员函数会被编译器放入虚函数表中。
(7) 当存在虚函数时,每个对象中都有一个指向虚函数的指针(C++编译器给父类对象,子类对象提前布局vptr指针),当进行test(parent *base)函数的时候,C++编译器不需要区分子类或者父类对象,只需要在base指针中,找到vptr指针即可)。
(8) vptr一般作为类对象的第一个成员。

为什么调用普通函数比调用虚函数的效率高?
因为普通函数是静态联编的,而调用虚函数是动态联编的。
(1) 静态联编 :在编译的时候就确定了函数的地址,然后call就调用了。
(2) 动态联编 :首先需要取到对象的首地址,然后再解引用取到虚函数表的首地址后,再加上偏移量才能找到要调的虚函数,然后call调用。
明显动态联编要比静态联编做的操作多,肯定就费时间。

为什么要用虚函数表(存函数指针的数组)?
实现多态,父类对象的指针指向父类对象调用的是父类的虚函数,指向子类调用的是子类的虚函数。同一个类的多个对象的虚函数表是同一个,所以这样就可以节省空间,一个类自己的虚函数和继承的虚函数还有重写父类的虚函数都会存在自己的虚函数表。

为什么要把基类的析构函数定义为虚函数?
为了防止内存泄露,如果不用virtual关键字,当基类指针指向派生类对象时,析构时只会析构基类,而不会析构派生类。

#include<iostream>
#include<stdio.h>
using namespace std;

class A
{   //注意:内存对齐原则
    public:
        int i; //4 Bytes
        virtual void func() {} //8 Bytes
        virtual void func2() {} //8 Bytes 与func函数共用8字节内存
};

class B: public A
{
    int j; //4 Bytes
    void func() {} //普通函数不占内存空间
};

int main()
{
  printf("A: %d, B: %d\n", sizeof(A), sizeof(B));
  return 0;
}

在 64位编译模式下,程序的运行结果是:

A: 16, B: 16

如果将程序中的 virtual 关键字去掉,输出结果变为:

A: 4, B: 8

虚函数表是编译器生成的,程序运行时被载入内存。一个类的虚函数表中列出了该类的全部虚函数地址。例如,在上面的程序中,类 A 对象的存储空间以及虚函数表(假定类 A 还有其他虚函数)如图 1 所示。

 

      图1:类A对象的存储空间以及虚函数表

类 B 对象的存储空间以及虚函数表(假定类 B 还有其他虚函数)如图 2 所示。

       图2:类B对象的存储空间以及虚函数表 多态的函数调用语句被编译成根据基类指针所指向的(或基类引用所引用的)对象中存放的虚函数表的地址,在虚函数表中查找虚函数地址,并调用虚函数的一系列指令。  

假设 pa 的类型是 A*,则 pa->func() 这条语句的执行过程如下:

(1) 取出 pa 指针所指位置的前 8 个字节,即对象所属的类的虚函数表的地址。如果 pa 指向的是类 A 的对象,则这个地址就是类 A 的虚函数表的地址;如果 pa 指向的是类 B 的对象,则这个地址就是类 B 的虚函数表的地址。

(2) 根据虚函数表的地址找到虚函数表,在其中查找要调用的虚函数的地址。

(3) 根据找到的虚函数的地址调用虚函数。

由以上过程可以看出,只要是通过基类指针或基类引用调用虚函数的语句,就一定是多态的,也一定会执行上面的查表过程,哪怕这个虚函数仅在基类中有,在派生类中没有。

标签:函数,对象,多态,C++,地址,基类,原理,指针
From: https://www.cnblogs.com/lyfily-p-7439305/p/17456532.html

相关文章

  • QT的特殊命名空间方式和C++对比
    Qt有以下的写法QT_BEGIN_NAMESPACEnamespaceUi{classWidget;}QT_END_NAMESPACE在开始和结束关键字中间进行命名空间的创建及其内部类的声明 而C++则是直接进行创建namespaceMyNamespace{//在MyNamespace命名空间内定义的类、函数、变量等classMyC......
  • C++ 多态 虚函数virtual
    先解释虚函数,对于基类,子类继承基类后可能会调用其某个函数FA,而不同的子类继承了同一个基类后需要基类内某个同样的函数FA但又不是同个作用,此时则会在对应的子类内对应重载派生出FA_B函数和FA_C函数,而这时要求FA为虚函数(virtual)那为什么不各自写成一个函数B和C呢?这就是多态的意......
  • C++程序开发技巧
    引言类(class)的使用分为两种——基于对象(objectBased)和面向对象(objectoriented)基于对象是指,程序设计中单一的类,和其他类没有任何关系单一的类又分为:不带指针的类(classwithoutpointermembers)和带指针的类(classwithpointermembers)面向对象则是类(class)中涉及了类之间的关......
  • Effective Modern C++(四)再探移动语义与完美转发
    移动语义移动语义是c++11最为重要的特性之一,但这不代表着我们可以在任何时候都无脑地使用它。在以下几个情况下,移动语义并没有什么用处。没有移动操作:要移动的对象没有提供移动操作,所以移动的写法也会变成复制操作。比如对于STL库中的array容器而言,他的元素都直接存储在了......
  • QT--C++简学
    2.1C++语言的新特点(对于C语言来说) 赋值:直接------- intx(100) 在定义的时候就可以赋值,相当于x=100;2.2输入(cin)--------输出(cout)  2.2.1   cout<<x<<endl;  //一个变量             --------printf     cout<<x<......
  • Effective Modern C++(三)引用折叠
    template<typenameT>voidfunc(T&&param);对于一个通用引用,只有当实参被用来实例化通用引用形参时,才会推导形参T。编码机制是简单的。当左值实参被传入时,T被推导为左值引用。当右值被传入时,T被推导为非引用。WidgetwidgetFactory();//返回右值的函数Widgetw;......
  • C++学习资源
    项目STL网站zouxiaohang/TinySTL:TinySTLisasubsetofSTL(cutsomecontainersandalgorithms)andalsoasupersetofSTL(addsomeothercontainersandalgorithms)https://github.com/zouxiaohang/TinySTL......
  • 镜头成像原理
    从上面的图里面可以清晰的看到景深产生的机制,虽然任意距离的点都会成像,也就是说无论物体距离镜头什么样的距离,都会形成其光线的汇聚成像,但是像的位置是随着物体到镜头距离的不同而不同的。所以,只有那些像位置恰好在感光器件位置上面的物体可以清晰的成像,而更近和更远的物体在感光......
  • C/C++数据结构设计题[2023-06-04]
    C/C++数据结构设计题[2023-06-04]停车场模拟管理程序的设计与实现1.设计目的理解线性表的逻辑结构和存储结构,进一步提高使用理论知识指导解决实际问题的能力。2.问题描述设停车场只有一个可停放几辆汽车的狭长通道,只有一个大门可供汽车进出。汽车在停车场内按车辆到达的先后顺......
  • C++面试八股文:struct、class和union有哪些区别?
    某日小二参加XXX科技公司的C++工程师开发岗位5面:面试官:struct和class有什么区别?小二:在C++中,struct和class的唯一区别是默认的访问控制。struct默认的成员是public的,而class的默认成员是private的。面试官:struct、class和union有哪些区别?小二:union和struct、class在内存布局上......