虚函数详细理解

1.虚函数的使用?

        1.1虚函数的定义

        在实现c++多态时会用到虚函数。虚函数使用的其核心目的是通过基类访问派生类定义的函数。所谓虚函数就是在基类定义一个未实现的函数名,为了提高程序的可读性,建议后代中虚函数都加上virtual关键字。一般格式:

        上述代码在基类中定义了一个test的虚函数,所有可以在其子类重新定义父类的做法这种行为成为覆盖(override),或者为重写。

        常见用法:声明基类指针,利用指针指向任意一个子类对象,调用相关的虚函数,动态绑定,由于编写代码时不能确定被调用的是基类函数还是那个派生类函数,所以被称为“”虚“”函数。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。

3.虚函数之vptr(虚指针)和vtbl(虚表)
        类在继承中的内存是如何实现管理的?

                在类的继承中,每一个class产生一堆指向virtual function的指针,放在vtbl(虚表)中。对于每一个class object 被添加了一个指针,指向相关的virtual table,这里指针称为vptr(虚指针)。

        下面我们结合上图对vptr和vtbl做进一步的讲解:

        图中定义了三个类,分别为class A、class B、class C;其中class A 是class B的基类,class B 是class C的基类。

        class A 的 公共成员中有四个函数接口,分别为虚函数 virtual void vfunc1(); virtual void vfunc2();普通成员函数void func1();void func2()。class A中有两个私有成员,分别为m_data1和m_data2。
        class B 继承了class A ,但是class B中的虚函数virtual void vfunc1()覆盖了基类的同名虚函数;此外class B的公共成员中还有一个属于自己的函数 void func2(),注意,这里面的函数func2虽然与class A中的函数func2同名,但是他们是互补相关的两个函数,也不存在谁覆盖谁,因为他们并没有将该同名函数申明为virture function(虚函数),所以class B的对象在调用void func2()的时候,它只能调用到class B自身公共成员函数中的void func2()函数,而无法调用到class A 中的void func2()函数。 对由于class B中的私有成员只有一个m_data3。
        class C继承了class B,但是但是class C中的虚函数virtual void vfunc1()覆盖了基类class B中的同名虚函数;此外存在一个公共成员函数:void func2(),其调用原理,参考上一段文字(在此处就不做详细说明了)。

        现在我们知道了A、B、C这三个类的继承关系,以及各自所拥有的公有成员、私有成员以及各自的virtual function 。那么我们接下来就来谈谈,各自对象的虚指针和虚表。

        从图的左上角可以看到,当class中有虚函数时,那么他所创建的对象将会多出一个指针(如图中的黑点所示为一个指针),这也是为什么类中有虚函数比类中没有虚函数在进行对象所占字节的测试时,会多出4个字节,而这多出的四个字节其实就是vptr(虚指针);在图中虚指针对应的地址为0x409004,对于对象a的成分除了包括一个vptr(虚指针)外,还包函两个数据成员               m_data1、m_data2. 对象a通过虚指针指向虚表(A vtbl),表中放的都是函数指针,指向虚函数。从class A中可以看出class A有两个虚函数,所以vtbl中有两个虚指针,分别指向对应的虚函数。(注意同种相同颜色的框框)。

        此处补充一下:对于图中给出的三个类(class A,class B,class C)他们一共有8个函数。对应的四个普通成员函数和四个虚函数,必须要明白的一点是,如果基类中有虚函数,那么子类中一定有虚函数,因为子类继承了基类的成份。并且虚指针只能调用虚函数,而不能调用普通成员函数。

 对于B的对象b,因为class B继承了class A,而class A中有虚函数,那么刚才我们说了继承时,由于子类继承了基类的成分,所以对象b一定也有一个虚指针,指向对应的虚函数。而对象b中由于是继承了基类A,所以对象b的成分按照顺序将会包含:一个虚指针,对象A的m_data1,m_data2(基基类的成份),然后才到自身的m_data3。

        对于C的对象c,他的成分同样是按顺序包括:一个虚指针,类A的m_data1,m_data2,类B的m_data3,和自身的m_data1、m_data4.

        由于是虚指针,各自的虚指针(vptr)只会指向各自对象对应的虚表( vtbl ) ,对于class A的对象a的虚指针指向的虚表有两个虚函数分别为A::vfunc1()和A::vfunc2()。这个比较好理解。

        那么对于class B的对象b,它的虚指针呢。因为class B是class A的子类,子类将会继承基类的成分,而基类有两个虚函数vfunc1()和vfunc2(),这两个虚函数都属于基类的成分,所以继承时都需要继承,但是class B的虚函数virtual void vfunc1()将基类的同名虚函数覆盖掉了,那么实际上对象b只有两个虚函数,分别为自身的虚函数vfunc1(),和来自基类继承的虚函数vfun2()。

   对于class C的对象c而言,其原理可以参考对象b,但是需要说明的一点是,由于class C继承了class B,而class B 又继承了class A,虽然class B中没有写出虚函数vfunc2(),但是实际上class B中时包含了class A的的虚函数vfunc2()的成分的。又因为class C,继承了class B,这时候我们从上面的图中左下角可以看到,class C的对象中其实上是包含了class B从class A中继承过来的成分。所以此时对于class C的对象c来说,它也是包含了两个虚函数的,分别为自身的虚函数vfunc1(),和A::vfunc2()。

4.虚函数与纯虚函数
                首先:强调一个概念

        定义一个函数为虚函数,不代表函数为不被实现的函数。

        定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。

        定义一个函数为纯虚函数,才代表函数没有被实现。

        定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

4.1纯虚函数
4.1.1定义

        纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加 =0:

virtual void funtion1()=0
4.1.2引入原因

        在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

        为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

        声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。

        纯虚函数最显著的特征是:它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。

        定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。

4.2抽象类
包含纯虚函数的类称为抽象类。

                抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。

4.2.1抽象类的作用

         抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。

4.2.2使用抽象类时注意:

        抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。
抽象类是不能定义对象的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在C++中,基类和派生类是面向对象编程的重要概念。基类是派生类的父类,而派生类则是基于基类进行扩展的类。 在基类中声明的函数可以被派生类继承并且可以被派生类重写。派生类也可以定义自己的函数来扩展基类的功能。当派生类覆盖基类的函数时,可以使用关键字`virtual`来实现多态性。 以下是一些关于基类和派生类函数的知识点: 1. 基类中的函数可以被派生类继承。派生类可以通过调用基类中的函数来使用基类的功能。 2. 派生类可以通过重写基类中的函数来改变其行为。当派生类覆盖基类的函数时,必须使用与基类函数相同的函数签名(即函数名称、参数类型和返回类型相同)。 3. 派生类中的函数可以调用基类中的同名函数,使用作用域解析运算符(::)来指定基类函数。 4. 基类中的虚函数可以被派生类覆盖。当一个指向派生类对象的基类指针调用虚函数时,将调用派生类中的实现。 5. 派生类中的虚函数可以覆盖基类中的虚函数。当一个指向派生类对象的基类指针调用该虚函数时,将调用派生类中的实现。 6. 派生类中的非虚函数不能覆盖基类中的同名虚函数。如果派生类声明了一个与基类中的虚函数同名的非虚函数,那么该函数将隐藏基类中的虚函数。 7. 派生类可以通过使用`override`关键字来覆盖基类中的虚函数。这可以帮助编译器检查是否正确地覆盖了基类中的虚函数。 总之,在C++中,基类和派生类函数的继承、重写和多态性是实现面向对象编程的重要机制。理解这些概念并正确使用它们可以使程序更加清晰、可读性更高,也可以更加方便地扩展和维护代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值