C++利用对象、引用、指针调用虚函数

C++利用对象、引用、指针调用虚函数

虚函数实现原理说明:

每个类的大小比起所有成员数据多4个字节,表明有虚函数的类的大小还要加上一个紧缩的空指针类型的大小。这说明了该在包含虚函数的类中,编译系统自动加入了一些表明类型的信息。

当一个类中拥有虚函数时,编译系统将为该类创建一个数组VTABLE。VTABLE的元素是虚函数的地址,且同一虚函数的地址在基类和派生类的VTABLE中相对首位置的偏移是一样的。同时,编译系统还加入了相应的调用虚函数的代码。所有这些都是不需要程序员作的工作,由系统自动完成。在初始化该类对象时,将加入一个指向VTABLE的指针,这个指针一般称为VPTR。一般来说,VPTR位于该类对象的存储单元的最开始部位。

这样,当VPTR被正确的初始化之后,便指向了该对象的VTABLE,从而在对象及其特定的虚函数定义间建立了联系。从虚函数调用的意义上来说,VPTR表明了类型信息,因为它使得调用与类型相符合。

本例(见下文)可以看到,不管类层次进行了多少扩充,对于已有类对象的操作都不必作出改动。从这一点来看,虚函数所体现的运行时多态,大大提高了软件的可扩充性。我们知道,系统的设计者在初期集中于整个系统框架的合理构建,而在后期进行具体问题的分析,并逐渐扩充该框架。运行时多态保证了分析、设计、实现和扩充各个阶段的统一,使得系统的设计者在各阶段可以集中于眼前的工作,而不必为了以后不可预见的变化付出代价。

虚函数总结:

  • 虚函数只能是类成员函数,它在基类体内部说明,目的是提供一种接口界面;

  • 虚函数不能是友元函数(即非成员函数),也不能是静态成员函数,因为虚函数调用要靠特定的对象来决定该激活哪个函数。虚函数可以在另一个类中被声明为友元函数;

  • 一旦一个函数定义为虚函数,那么无论它传下多少层,都将保持为虚函数,而不必每次都加关键字virtual;

  • 基类的虚函数可在一个或多个派生类中被重新定义,但其原型与基类必须完全相同(即返回类型、函数名、参数个数、类型及顺序一样),否则系统将认为派生类中的函数是重载的,而非虚函数;如果仅有返回类型不同,那么编译将出错;

  • 要虚函数发挥作用,必须用基类的指针(或引用)指向派生类的对象,并用指针(或引用)调用虚函数。也就是说,只有用地址才能体现运行多态性。因为不论是指向基类还是指向派生类的指针(引用),大小都是一样的,这样才能用基类指针指向派生类对象。这时,指针提供的信息是不完全的,在编译阶段不知道应该调用虚函数的哪个版本。而如果用对象调用虚函数,由于类型已经确定了,因此编译系统很可能采用预绑定;

  • 由于包含虚函数的基类指针可以指向其不同的派生类,并可执行不同版本的虚函数,提供了实现程序运行的多态性方法,因而将包含虚函数的类称为多态类。

虚函数与一般重载函数的区别:

  • 重载函数在类型和参数数量上一定不相同,而重定义的虚函数则要求参数的类型和个数、函数返回类型相同;
  • 虚函数必须是类的成员函数,重载的函数则不一定是这样;
  • 构造函数可以重载,但不能是虚函数,析构函数可以是虚函数。

指针的转换规则:

  • 指向基类的指针,可以指向它的公有派生的对象,但不能指向私有派生的对象;

  • 只能利用它直接访问派生类从基类继承来的成员,不能直接访问公有派生类中特定的成员;

  • 不能将指向派生类对象的指针指向其基类的一个对象。

  • 当虚函数在操作中引用的基类数据成员无法被派生类直接引用时(例如被隐藏的成员),便会出现错误。为了使用虚函数达到最好的动态联编效果,一般应以该虚函数第一次出现的类的引用体或指针作为参数,避免不确定因素。

代码如下:

/************************************************************************
* 利用对象、引用、指针调用虚函数
************************************************************************/
#include <IOSTREAM.H>
//基类
class CBase
{
   int x;
public:
   CBase(int n) {x=n;}
   virtual void PrintX() {cout<<"CBase::PrintX : "<<x<<endl;}
};

//派生类
class CDerive : public CBase
{
   int x;
public:
   CDerive(int n1,int n2):CBase(n1) 
{    x=n2;    }
   void PrintX() 
   {    cout<<"CDerive::PrintX : "<<x<<endl;
       CBase::PrintX();
   }
};

//子派生类
class CSubDerive : public CDerive
{
   int x;
public:
   CSubDerive(int n1,int n2,int n3):CDerive(n1,n2)
   {    x=n3;    }
   void PrintX()
   {    cout<<"CSubDerive::PrintX : "<<x<<endl;
       CDerive::PrintX();
   }
};

void main()
{   cout<<"CBase size = "<<sizeof(CBase)<<endl;    
   cout<<"CDerive size = "<<sizeof(CDerive)<<endl;
   cout<<"CSubDerive size = "<<sizeof(CSubDerive)<<endl;
   cout<<endl;

   CBase obj1(1);    
CDerive obj2(2,3);
CSubDerive obj3(4,5,6);
   obj1.PrintX();
   obj2.PrintX();
   obj3.PrintX();
   cout<<endl;

   CBase *pObj1=&obj1;    
   CDerive *pObj2=&obj2;    
   CSubDerive *pObj3=&obj3;
   pObj1->PrintX();
   pObj2->PrintX();
   pObj3->PrintX();
   cout<<endl;

   CBase *pObj[]={pObj1,pObj2,pObj3};
   pObj[0]->PrintX();
   pObj[1]->PrintX();
   pObj[2]->PrintX();
   cout<<endl;

   CBase &yobj1=obj1;
   CBase &yobj2=obj2;
   CBase &yobj3=obj3;
   yobj1.PrintX();
   yobj2.PrintX();
   yobj3.PrintX();
}

  1. 运行结果如下(4组打印语句的打印结果完全一样,在此只给出前两组打印语句的画面):
    在这里插入图片描述

  2. 对象的内容:
    在这里插入图片描述

  3. 三个对象的地址及内容:

          ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190704212705392.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xvdzUyNTI=,size_16,color_FFFFFF,t_70)
    
  4. 基类指针指向三个对象时,指针的详细内容:

在这里插入图片描述

  1. 基类对象引用三个对象:

             ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190704212717625.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xvdzUyNTI=,size_16,color_FFFFFF,t_70)
    

转自:https://blog.csdn.net/livelylittlefish/article/details/2171504
更多C++相关知识体系,请移步C++知识目录

猜你喜欢

转载自blog.csdn.net/low5252/article/details/94656335