谈谈对多态的理解,什么叫多态,如何理解多态

  谈谈对多态的理解,什么叫多态,如何理解多态

  Yyds干货库存

  了解多态性可能是你第一次接触多态性。在这里,我举一个现实的例子。买火车票的时候会发现,学生有优惠价,军人有优先购票权。如果让你写一个买票的系统,你会怎么做?首先我们学了继承,确定第一步是创建几个角色,每个角色写一个买票的功能。反正可以藏起来。先实现自己的想法吧。

  #包括iostream

  #包含矢量

  #包含字符串

  使用命名空间std

  阶级人士

  {

  公共:

  人员(字符串名称)

  :_name(名称)

  {

  }

  无效购买券()

  {

  Cout _ name普通票价100元’endl;

  }

  受保护:

  string _ name

  };

  班兵:公众人物

  {

  公共:

  士兵(字符串名称)

  :人员(姓名)

  {

  }

  无效购买券()

  {

  Cout _name 士兵优先购买100元票价 endl

  }

  };

  班级学生:公众人物

  {

  公共:

  学生(字符串名称)

  :人员(姓名)

  {

  }

  无效购买券()

  {

  Cout _ name学生半价50元’endl;

  }

  };

  int main()

  {

  while(真)

  {

  cout ====================== endl;

  Cout 1学生2军人3普通人endl

  int ret=-1;

  Cout“请选择”;

  CIN雷特;

  字符串名称;

  请输入cout名称: endl

  cin名字;

  开关(复位)

  {

  案例1:

  {

  学生s(姓名);

  s . buy ticket();

  }

  打破;

  案例二:

  {

  士兵s(姓名);

  s . buy ticket();

  }

  打破;

  案例三:

  {

  人员(姓名);

  s . buy ticket();

  }

  打破;

  默认值:

  Cout 输入错误,请重新输入 endl

  打破;

  }

  }

  返回0;

  }

  什么是多态性?我们已经完成了这个程序,所以你觉得你的代码有问题吗?如前所述,子类的对象可以分配给父类。我们想想,反正父类和子类都有购票功能。能否通过父类的购票功能自动调用子类?这是一个很好的想法,也是我们今天理解多态性的开始。

  关键字virtual我们已经看到了虚拟关键字,它在虚拟继承中。在这里,我们将谈论它的其他用途,修改成员函数。修改后的成员函数称为虚函数。

  在说这个之前,我们先来看一个情况。这种情况就是我们的多态性。所谓多态性,就是父类的引用或者调用虚函数的指针。

  A级

  {

  公共:

  虚拟void函数()

  {

  cout A:void func() endl;

  }

  };

  B类:公共A

  {

  公共:

  虚拟void函数()

  {

  cout B:void func() endl;

  }

  };

  int main()

  {

  B b

  a a=b;

  a . func();

  返回0;

  }

  之前,我们谈到了超载和隐藏。今天,我们需要知道一些东西,重写。所谓重写,就是在亲子类中出现一个同名的函数,并且这个函数的返回值和参数是相同的(有两个特殊之处,后面会讲到。)

  虚函数重写:派生类中有一个虚函数与基类完全相同(即派生类虚函数的返回值类型、函数名、参数列表与基类虚函数完全相同)。说是子类的虚函数重写了基类的虚函数。

  A级

  {

  公共:

  虚拟void函数()

  {

  cout A:func() endl;

  }

  };

  B类:公共A

  {

  公共:

  虚拟void函数()

  {

  cout B:func() endl;

  }

  void func(int i)

  {

  cout B:func(int I) endl;

  }

  };

  int main()

  {

  B b

  a a=b;

  a . func();

  b . func(2);

  返回0;

  }

  他们三个的关系是这样的。我们花了一张照片。

  父类调用子类的方法的多态性的条件是否一定会构成多态性?不是,必须满足以下两个条件才能构成多态性。

  虚函数必须通过基类的指针或引用来调用。被调用的函数必须是虚函数,派生类必须重写基类的虚函数。先测试一下,先看现象。至于原理,这个虚函数表后面再说。

  A级

  {

  公共:

  虚拟void函数()

  {

  cout A:func() endl;

  }

  };

  B类:公共A

  {

  公共:

  虚拟void函数()

  {

  cout B:func() endl;

  }

  };必须是指针或父类放热引用。

  int main()

  {

  B b

  a a=b;

  a * p=b;

  p-func();

  a . func();

  返回0;

  }

  如果只是简单的把子类对象给父类,那就不是多态了。

  int main()

  {

  B b

  a a=b;//简单切片

  a . func();

  返回0;

  }

  虚拟一定要带吗?这里我先说明一下,父类的虚函数一定要带,否则不构成多态条件。

  A级

  {

  公共:

  void func1()

  {

  cout A:func 1() endl;

  }

  私人:

  int _ a;

  };

  B类:公共A

  {

  虚拟void函数1()

  {

  cout B:func 1() endl;

  }

  };

  int main()

  {

  B b

  a a=b;

  a . func 1();

  返回0;

  }

  但是,可以省略覆盖父类的子类的虚函数,但是我们建议采用它们。感觉像c里面的bug。

  A级

  {

  公共:

  虚拟void函数1()

  {

  cout A:func 1() endl;

  }

  私人:

  int _ a;

  };

  B类:公共A

  {

  void func1()

  {

  cout B:func 1() endl;

  }

  };

  int main()

  {

  B b

  a a=b;

  a . func 1();

  返回0;

  }

  C 11覆盖和最终。这是C 11中新增的两个关键词,功能还可以。

  关键字final的主要作用是禁止重写,它有最终的含义,即被它修改的函数不能被子类重写。

  A级

  {

  公共:

  虚拟void func() final

  {

  cout A:func() endl;

  }

  };

  B类:公共A

  {

  公共:

  虚拟void函数()

  {

  cout B:func() endl;

  }

  };

  此外,final还可以用来修饰一个类,以表明它不能被继承。

  甲级决赛

  {

  公共:

  虚拟void函数()

  {

  cout A:func() endl;

  }

  };

  B类:公共A

  {

  公共:

  虚拟void函数()

  {

  cout B:func() endl;

  }

  };

  Override关键字override更简单。就是检查看子类的这个函数是不是父类中的虚函数。如果有,就好了;如果没有,它将报告一个错误。

  A级

  {

  公共:

  虚拟void函数()

  {

  cout A:func() endl;

  }

  };

  B类:公共A

  {

  公共:

  虚拟void func()覆盖

  {

  cout B:func() endl;

  }

  };

  否则,将会出现编译错误。

  A级

  {

  公共:

  void函数()

  {

  cout A:func() endl;

  }

  };

  B类:公共A

  {

  公共:

  虚拟void func()覆盖

  {

  cout B:func() endl;

  }

  };

  我前面说过,重写后的函数必须有完全相同的函数名、返回值和参数,但有两个例外。

  先看现象,返回值会不一样。

  A级

  {

  };

  B类:公共A

  {

  };

  C类

  {

  公共:

  虚拟A* func()

  {

  cout A:func() endl;

  }

  };

  D类:公共C类

  {

  公共:

  虚拟B* func()

  {

  cout B:func() endl;

  }

  };

  当重写基类虚函数时,派生类具有与基类虚函数不同的返回值类型。即当基类的虚函数返回基类对象的指针或引用,派生类的虚函数返回派生类对象的指针或引用时,称为协变。请注意,协变不重要,用处不大。

  析构函数这里我就直接断定,父子类的析构函数也是可以重写的,析构函数会被编译器重命名为Destructor(),所以可以重载。至于它的功能,那就大了。

  类别人员{

  公共:

  ~人()

  {

  cout ~ Person() endl;

  }

  };

  学生:公众人物{

  公共:

  虚拟~学生()

  {

  cout ~ Student() endl;

  删除[]_ name;

  }

  私人:

  char* _name=new char[10]{ j , a , c , k };

  };

  int main()

  {

  Person* ptr=新人;

  删除ptr//ptr- destructor()运算符删除(ptr)

  ptr=新生;

  删除ptr//ptr- destructor()运算符删除(ptr)

  返回0;

  }

  你会发现有问题,内存泄露,严格的编译器会自动检查出来。原因是现在两个析构函数都是隐藏的,我们用父类来调用,所以只能调用父类的析构函数。如果父类的析构函数是虚函数,就可以避免这个问题。

  类别人员{

  公共:

  虚拟~人()

  {

  cout ~ Person() endl;

  }

  };

  学生:公众人物{

  公共:

  虚拟~学生()

  {

  cout ~ Student() endl;

  删除[]_ name;

  }

  私人:

  char* _name=new char[10]{ j , a , c , k };

  };

  int main()

  {

  Person* ptr=新人;

  删除ptr//ptr- destructor()运算符删除(ptr)

  ptr=新生;

  删除ptr//ptr- destructor()运算符删除(ptr)

  返回0;

  }

  这样,如果形成多态性,编译器会自动移除子类的析构函数,子类的析构函数会同时调用父类的析构函数,这也是析构函数的函数名会被统一处理的原因。这里,我们建议把父类的析构函数写成虚函数。

  如果抽象类在虚函数后写=0,则该函数是纯虚函数。包含纯虚函数的类称为抽象类(也叫接口类),抽象类不能实例化对象。派生类不能在继承后实例化对象。只有重写纯虚函数,派生类才能实例化一个对象。纯虚函数规定派生类必须重写,纯虚函数体现接口继承。

  下面的类是一个抽象类,有一个纯虚函数。

  阶级人士

  {

  公共:

  虚拟void func()=0;

  私人:

  };抽象类是不能继承的,是先天继承的,继承的类必须重写所有纯虚函数。

  无法实例化

  int main()

  {

  每人;

  返回0;

  }

  继承的类必须重写纯虚函数。

  阶级人士

  {

  公共:

  虚拟void func()=0;

  私人:

  };

  班级学生:公众人物

  {

  公共:

  void函数()

  {

  }

  私人:

  };

  int main()

  {

  学生stu

  Person per=stu//允许

  返回0;

  }

  接口继承和实现继承先说实现继承。

  普通函数的继承是一种实现继承。派生类继承基类函数,可以使用函数,继承函数的实现。简单理解就是子类可以调用父类的函数,这就是实现继承。所谓实现继承,就是函数名和函数体都被继承。

  A级

  {

  公共:

  void函数()

  {

  cout A:func() endl;

  }

  };

  B类:公共A

  {

  };

  int main()

  {

  B b

  b . func();

  返回0;

  }

  虚函数的继承是一种接口继承,派生类继承基类虚函数的接口,为了重写和实现多态性,继承接口。所以,如果没有实现多态,就不要把函数定义成虚函数。人-语音接口继承意味着只继承函数的声明,而不继承任何实现。

  A级

  {

  公共:

  虚拟void func(int a=10)

  {

  cout A:func() endl;

  cout a= a endl

  }

  };

  B类:公共A

  {

  void func(int a=20)

  {

  cout B:func() endl;

  cout a= a endl

  }

  };

  int main()

  {

  B b

  a a=b;

  a . func();

  返回0;

  }

  这里可以看到,接口继承只是被继承函数的名字(包括默认值),所以一定要记住。

  一般来说,我们可以学习以上内容,但需要深入交谈,这样面试官提问时的印象才能得到改善。

  虚表指针在讲这个之前,我们需要看一个现象。下面的结果是哈?

  #包括iostream

  使用命名空间std

  A级

  {

  公共:

  虚拟void函数()

  {

  }

  私人:

  int _ a;

  };

  int main()

  {

  cout sizeof(A)endl;

  返回0;

  }

  我们对此感到困惑。里面不是有整数成员变量吗?为什么它的尺寸是8?因为有虚函数,所以有指针。

  虚函数表这个指针指向一个表,简称虚函数表。每个虚函数的地址都存储在表中。记住,每个虚函数都会放在虚函数表中。

  A级

  {

  公共:

  虚拟void函数1()

  {

  }

  虚拟void函数2()

  {

  }

  私人:

  int _ a;

  };

  int main()

  {

  A a

  返回0;

  }

  多态原理看到虚函数表,我们可以看看多态的原理,涉及到虚函数表。

  仔细看看子类的虚函数表,这里你会发现它变了。你重写的虚函数表中村的指针地址发生变化,这就是多态的原理。简单来说,子类继承父类的同时也继承了虚函数表,然后编译看子类是否重写了父类中的虚函数。重写后,虚函数中对应的函数指针发生变化,这就是多态性。

  A级

  {

  公共:

  虚拟void函数1()

  {

  }

  虚拟void函数2()

  {

  }

  私人:

  int _ a;

  };

  B类:公共A

  {

  void func1()

  {

  }

  };

  int main()

  {

  B b

  返回0;

  }

  虚函数都在虚函数表里吗?我们现在有一个问题,虚函数表中的每一个虚函数都是吗?我们先来看看监控窗口。

  A级

  {

  公共:

  虚拟void函数1()

  {

  }

  虚拟void函数2()

  {

  }

  私人:

  int _ a;

  };

  B类:公共A

  {

  虚拟void函数1()

  {

  }

  虚拟void函数2()

  {

  }

  虚拟void func3()

  {

  }

  };

  int main()

  {

  B b

  返回0;

  }

  这里有问题。在我们的子类中有三个虚函数。为什么监视器窗口只有两个?我们的结论错了吗?不是,是因为编译器的监视器窗口优化了。我们想看的是实际的内存。

  A级

  {

  公共:

  虚拟void函数1()

  {

  }

  虚拟void函数2()

  {

  }

  私人:

  int _ a;

  };

  B类:公共A

  {

  虚拟void函数1()

  {

  }

  虚拟void函数2()

  {

  }

  虚拟void func3()

  {

  }

  };

  int main()

  {

  B b

  返回0;

  }

  现在我们已经确认虚函数表中有三个指针,而且这三个指针非常相似。关键的是,其中两个已经被证实,我们有理由怀疑第三个指针也是虚函数指针。我们现在再检查一遍,这里的指针会发生变化。毕竟是我重新编译的,不过不用担心,你还是可以理解的。

  注意这是给大家验证的。最好能看懂里面的内容,看不懂也没关系。但是,我们在这里学到的是函数指针和强制类型转换。

  A级

  {

  公共:

  虚拟void函数1()

  {

  }

  虚拟void函数2()

  {

  }

  私人:

  int _ a;

  };

  //一个函数指针

  typedef void(* ViPointer)();

  B类:公共A

  {

  虚拟void函数1()

  {

  cout func 1() endl;

  }

  虚拟void函数2()

  {

  cout func 2() endl;

  }

  虚拟void func3()

  {

  cout func 3() endl;

  }

  };

  void printViPointer(ViPointer* f)

  {

  int I=0;

  while (f[i]!=nullptr)

  {

  printf([%d] : %p\n ,I,f[I]);

  f[I]();

  我;

  }

  }

  int main()

  {

  B b

  print ViPointer((ViPointer *)(*(int *)(b)));

  //b VS编译器虚表指针取头的地址。

  //(int*)( b))强制类型转换int*指向4个字节,因为它是32。

  //*(int*)( b)解引用获取第一个虚函数地址的地址。

  //(ViPointer*)(*(int*)( b))预类型转换

  返回0;

  }

  这证明了每一个虚函数都会把地址放在虚表中,也证明了VS的监控窗口得到了优化。

  虚拟表存在于哪里?在看这个之前,可以先看一下之前C程序的虚拟地址空间,了解一下简单的内存分配(虚拟)。简单说一下吧。

  测试之前,我们来推理一下。首先不能在栈区。要知道,堆栈区主要是一个函数堆栈框架,经常被打开和释放。一般虚拟面所指向的函数地址是不会变的,所以过一段时间再破坏虚拟表就不好了。

  相同类型的对象共享一个虚拟表。

  A级

  {

  公共:

  虚拟void函数()

  {

  }

  };

  int main()

  {

  一个a1;

  A a2

  Printf(虚拟表指针%p\n ,(*(int *)a1));

  Printf(虚拟表指针%p\n ,(*(int *)a2));

  返回0;

  }

  从这里可以看出,不可能在堆上。什么时候在堆上打开销毁?你知道一个类型会实例化多少个对象吗?销毁时是否引用和计算是个问题。

  我不能排除剩下的两个,这取决于我们的打印结果。

  #包括iostream

  #包含stdio.h

  A级

  {

  公共:

  虚拟void函数()

  {

  }

  };

  int g _ val1=1//已经初始化

  int g _ val2//未初始化

  int main()

  {

  //堆栈区域

  int a=10

  //堆区域

  int * p1=new int

  //字符常量区域

  const char * ptr= abdve

  Printf(代码区%p\n ,main);

  Printf(字符常量区域%p\n ,ptr);

  Printf(初始化的全局变量区域%p\n ,g _ val 1);

  Printf(全局变量区域% p \ n未初始化,g _ val 2);

  Printf (heap %p\n ,P1);

  Printf (stack area %p\n ,a);

  //打印虚函数表指针。

  一个aa;

  Printf(虚拟表指针%p\n ,(*(int *)aa));

  删除P1;

  返回0;

  }

  从这里可以看出,虚表很可能是一个常数区,不同的编译器可能不一样,但差别很大。

  切片会复制虚拟表吗?还有一个问题,如果我们把子类对象切片到父类,里面的虚表会被复制吗?不会,你想,如果他复制了,就是父类对象调用虚函数的时候了,这样就不会混淆了。这就是为什么只有父类的引用或指针才能构成多态。

  A级

  {

  公共:

  虚拟void函数1()

  {

  }

  };

  B类:公共A

  {

  虚拟void函数1()

  {

  }

  虚拟void函数2()

  {

  }

  };

  int main()

  {

  B b

  a a=b;

  返回0;

  }

  编译时分辨率运行时分辨率所谓的编译时分辨率决定了在编译时调用哪个函数。与对象无关,只与类型有关。运行时解析是多态的原理。它首先去虚拟表查找是否有与被调用函数匹配的,如果找到就使用它。如果没有找到,它将报告一个错误。也就是说,编译器一开始并不知道自己在调用哪个函数。

  类别人员{

  公共:

  virtual void buy ticket(){ cout buy tickets-full price endl;}

  void Buy(){ cout Person:Buy() endl;}

  };

  学生:公众人物{

  公共:

  Virtual void BuyTicket() {cout 买票-半价 endl}

  void Buy(){ cout Student:Buy() endl;}

  };

  无效函数1(人员* p)

  {

  //与对象相关,指向谁调用谁——运行时决定函数地址。

  p-buy ticket();

  //和类型有关,P类型是谁调用谁的虚函数——在编译时确定函数地址。

  p-Buy();

  }

  int main()

  {

  人p;

  学生s;

  func 1(p);

  Func1

  返回0;

  }

  在编译解析的时候,这里可以看看反汇编,这是普通函数的调用原理。

  运行时解析这里编译器回虚表找,然后一步一步来。

  有太多的问题想和大家分享。这里先不说钻石继承,简单的多继承。现在我们需要看一个代码,这里需要解释一下。

  基本类别1 {

  公共:

  virtual void func 1(){ cout base 1:func 1 endl;}

  virtual void func 2(){ cout base 1:func 2 endl;}

  私人:

  int b1

  };

  class Base2 {

  公共:

  virtual void func 1(){ cout base 2:func 1 endl;}

  virtual void func 2(){ cout base 2:func 2 endl;}

  私人:

  int b2

  };

  类别衍生:public Base1,public Base2 {

  公共:

  virtual void func 1(){ cout Derive:func 1 endl;}

  virtual void func 3(){ cout Derive:func 3 endl;}

  私人:

  int d1

  };我们知道,Derive继承了两个类,两个类都有虚表,也就是说,Derive会有两个虚表,重写的虚函数会在其中修改。这里我先画个图。

  有一个问题。现在我们需要看看func3虚函数放在哪个虚表中,还是两个虚表都放?

  int main()

  {

  推导d;

  返回0;

  }

  更何况这里监控窗口真的欺骗了我们,还需要打印功能的地址。

  typedef void(* VFPTR)();

  void PrintVTable(VFPTR vTable[])

  {

  //依次取虚表中的虚函数指针进行打印和调用。调用以查看存储了哪个函数。

  Cout“虚拟表地址”vTable endl

  for(int I=0;vTable[i]!=nullptr我)

  {

  Printf (%d th虚函数地址:0X%x,-,I,vTable[I]);

  VFP tr f=vTable[I];

  f();

  }

  cout endl

  }

  int main()

  {

  推导d;

  print vtable((VFPTR *)(*(int *)d));

  print vtable((VFPTR *)(*((int *)((char *)d sizeof(base 1)))));

  返回0;

  }

  现在我们可以确定它在Base1中,准确的说是在第一个继承的类中。

  为什么func1的地址不一样?我们解决了一个问题,现在又出现了一个问题。为什么func1的地址不一样?要知道,它们的功能是一样的。

  在这个问题之前,我们需要看看是否所有的编译器都有不同的func1地址。用g试试吧,不好意思,这里好像不一样,不过没关系。我们来分析一下,看看。

  为什么不一样?因为它们不是真实函数的地址,或者至少有一个不是,而且它又被封装了。这里我们需要看到真实的地址。

  int main()

  {

  推导d;

  print vtable((VFPTR *)(*(int *)d));

  print vtable((VFPTR *)(*((int *)((char *)d sizeof(base 1)))));

  Printf(实地址0x% x ,derivative:func 1);

  返回0;

  }

  好了,现在你会发现每个虚拟表中的地址都和真实地址不一样。这里可以得出结论,编译器为了安全,在实函数地址上做了一层封装,需要一系列的变换来调用函数,然后才能调用。

  现在你可以看看汇编语言,看看编译器如何调用真正的函数。

  在Base1虚表中寻找func1程序集,我们先来看看func1函数在Base1虚表中是如何调用的。

  第一步,找到ebp-20(里面十六进制)所在的指针,里面的内容是Base1虚表中func1存放的地址。

  第一步,调用这个地址,找到func1函数的地址。你会发现这个地址和我们打印的真实地址不一样。也许我们所谓的‘真实’也是被封装的。

  第三步是跳到这个地址,调用func1函数。看来这个jmp地址极有可能是真正的函数地址。

  在Base2虚拟表中查找func1来编译这个很麻烦。我们一步一步来,最后做个总结。

  第一步,找到ebp-20(里面是十六进制)所在的指针,里面的内容是Base2的虚拟表中func1存放的地址。

  打电话到这个地址,找到另一个func1地址,

  传输到此地址后,ecx中的值减8,结果放入ecx。原始ecx值是多少?先说一下,再跳到ecx所指的地方。

  找到了func1的真实地址,和Base1的地址一模一样。这里是jmp和call。

  总结现在我们可以做下一个总结了。Base2搜索func1花了很长时间,但最后还是和Base1一样,很有意思。

  过程中只有一个重要点,ecx=ecx-8。现在我们可以看到ecx中存在什么。我直接给出答案。ecx在Derive中存储Base2的地址,ecx-8是Base1的地址,到原点,和Base1找func1一样。

  钻石传承与钻石虚拟传承。其实我们不建议设计钻石继承和钻石虚拟继承。一方面太复杂,容易出问题。另一方面,这样的模型在访问基类成员时有一定的性能损失。所以我们不看我们钻石传承和钻石虚拟传承的虚拟表。一般不需要研究清楚,因为实践中很少用到。

  虚拟继承和多态在这里,我们将讨论虚拟继承和多态。我们一起来说说他们吧。注意,这只是理解,不是掌握。

  D必须重写A的虚函数D必须在多重继承后重写A中的虚函数。这是因为如果B和C虚拟继承A,那么A只有一个空格。至于A的这个空间里的虚表,我们不知道B和C哪个应该重写A里虚函数的地址,所以可以直接重写D,只放D的。

  A级

  {

  公共:

  虚拟void函数()

  {}

  公共:

  int _ a;

  };

  B类:虚拟公众A

  {

  公共:

  虚拟void函数()

  {}

  公共:

  int _ b;

  };

  C类:虚拟公共A

  {

  公共:

  虚拟void函数()

  {}

  公共:

  int _ c;

  };

  D类:公共B、公共C

  {

  公共:

  int _ d;

  };

  注意,这里的公共区域A中有一个虚拟表指针。

  A级

  {

  公共:

  虚拟void函数()

  {}

  公共:

  int _ a;

  };

  B类:虚拟公众A

  {

  公共:

  虚拟void函数()

  {}

  公共:

  int _ b;

  };

  C类:虚拟公共A

  {

  公共:

  虚拟void函数()

  {}

  公共:

  int _ c;

  };

  D类:公共B、公共C

  {

  公共:

  虚拟void函数()

  {}

  公共:

  int _ d;

  };

  int main()

  {

  D d

  d.b:_ a=1;

  d.c:_ a=2;

  d._ b=3;

  d._ c=4;

  d._ d=5;

  返回0;

  }

  虚拟基指针的位置是0。我们来看一个现象。我们先来看看虚基指针的内容。你会是里面的第一个还是0?这是我们之前没有解释过的。

  A级

  {

  公共:

  虚拟void函数()

  {}

  公共:

  int _ a;

  };

  B类:虚拟公众A

  {

  公共:

  虚拟void函数()

  {}

  公共:

  int _ b;

  };

  C类:虚拟公共A

  {

  公共:

  虚拟void函数()

  {}

  公共:

  int _ c;

  };

  D类:公共B、公共C

  {

  公共:

  虚拟void函数()

  {}

  公共:

  int _ d;

  };

  我们来解决一下虚基表指针指向的第一个位置为0的菱形虚继承问题。我们给b加一个虚函数,我们来看看它的内存分布。

  B类:虚拟公众A

  {

  公共:

  虚拟void函数()

  {}

  虚拟void函数1()

  {}

  公共:

  int _ b;

  };

  这里我们会发现B比C多了一个指针,很容易分辨出这是B中的虚表指针,我们先来看第二个虚基表,也是偏移量,是-4。这里从当前位置向上查找4个字节,找到了虚拟表指针。让我们来看看这个虚拟表。

  我们还发现里面好像只有一个虚函数指针,但是我们的B继承了A,所以这里就不放在单独的区域了,只放在公共区域。我们来验证一下。我们的想法是对的。

  typedef void(* VFPTR)();

  void PrintVTable(VFPTR vTable[])

  {

  Cout“虚拟表地址”vTable endl

  for(int I=0;vTable[i]!=nullptr我)

  {

  Printf (%d th虚函数地址:0X%x,-,I,vTable[I]);

  VFP tr f=vTable[I];

  f();

  }

  cout endl

  }

  int main()

  {

  D d

  print vtable((VFPTR *)(*(int *)d));

  返回0;

  }

  现在我们有一些问题,这里我们需要完成分析。

  虚函数可以是内联函数吗?这里有一个结论。如果正常调用,内联函数还是展开的。如果它被称为多态,它就不再是内联函数。它的地址应该放在虚拟表中,内联函数没有地址。

  A级

  {

  公共:

  答()

  {

  _ a=1;

  }

  虚拟内嵌void f1()

  {

  cout A:f1() endl;

  }

  虚拟void F2();

  私人:

  int _ a;

  };

  B类:公共A

  {

  公共:

  虚拟空隙f1()

  {

  cout B:f1() endl;

  }

  虚拟void F2();

  };

  void A:f2()

  {

  cout A:F2() endl;

  }

  void B:f2()

  {

  cout B:F2() endl;

  }

  //多态调用

  无效功能1(A* ptr)

  {

  ptr-f1();

  ptr-F2();

  }

  //普通呼叫

  无效功能2(指针)

  {

  ptr . f1();

  ptr . F2();

  }

  int main()

  {

  一个aa;

  B bb

  func 1(aa);

  func 1(bb);

  func 2(aa);

  func 2(bb);

  返回0;

  }

  静态成员函数可以看起来是虚函数吗?抱歉,静态成员函数不能是虚函数。你应该知道静态成员函数是不能重写的。要知道静态函数是没有this指针的,这也是虚函数表不能存储的原因。

  A级

  {

  公共:

  静态虚拟void函数()

  {}

  };

  构造函数可以是虚函数吗?不,先说虚拟表初始化的时候,在构造函数的初始化列表里,先有鸡还是先有蛋。

  A级

  {

  公共:

  答()

  {

  }

  虚拟void函数()

  {}

  };

  int main()

  {

  A a

  返回0;

  }

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • office2010激活密钥大全 怎么永久激活office2010
  • project2010产品密钥免费_project2010激活密钥永久激活码
  • c语言调用退出函数 c语言退出整个程序怎么写
  • c语言中怎么给函数初始化 c语言的初始化语句
  • c语言编写函数计算平均值 c语言求平均函数
  • chatgpt是什么?为什么这么火?
  • ChatGPT为什么注册不了?OpenAI ChatGPT的账号哪里可以注册?
  • OpenAI ChatGPT怎么注册账号?ChatGPT账号注册教程
  • chatgpt什么意思,什么是ChatGPT ?
  • CAD中怎么复制图形标注尺寸不变,CAD中怎么复制图形线性不变
  • cad中怎么创建并使用脚本文件,cad怎么运行脚本
  • cad中快速计算器的功能,cad怎么快速计算
  • cad中快速修改单位的方法有哪些,cad中快速修改单位的方法是
  • cad中心点画椭圆怎么做,cad轴测图怎么画椭圆
  • CAD中常用的快捷键,cad各种快捷键的用法
  • 留言与评论(共有 条评论)
       
    验证码: