关于C++虚函数与静态、动态绑定的问题

覆盖:如果派生类中的方法,和基类继承来的某个方法,返回值、函数名、参数列表都相同,而且基类的方法是virtual虚函数,那么派生类的这个方法,自动处理成虚函数,它们之间成为覆盖关系;也就是说派生类会在自己虚函数表中将从基类继承来的虚函数进行替换,替换成派生类自己的。

静态绑定:编译时期的多态,通过函数的重载以及模板来实现,也就是说调用函数的地址在编译时期我们就可以确定,在汇编代码层次,呈现的就是 call 函数名;

动态绑定:运行时期的多态,通过派生类重写基类的虚函数来实现。在汇编代码层次,呈现的就是 call 寄存器,寄存器的值只有运行起来我们才可以确定。

不存在虚函数

#include <iostream>
#include <typeinfo>
class Base
{
public:
  Base(int data = 10): ma(data) {}
  ~Base() {};

  void show() {
    std::cout << "Base::show()" << std::endl;
  }

  void show(int data) {
    std::cout << "Base::show()" << data << std::endl;
  }

protected:
  int ma;

};

class Derive :public Base
{
public:
  Derive(int data) :Base(data), mb(data) {}
  ~Derive() {}
  void show() {
    std::cout << "Derive::show()" << std::endl;
  }

private:
  int mb;
};

int main() {

  Derive d(50);
  Base *pb = &d;
  pb->show();//静态(编译时期)绑定(函数调用) Base::show (06F12E4h)
  pb->show(10);//Base::show (06F12BCh)

  std::cout << "Base size:" << sizeof(Base) << std::endl;//4
  std::cout << "Derive size:" << sizeof(Derive) << std::endl;//8

  std::cout << typeid(pb).name() << std::endl;//class Base *
  std::cout << typeid(*pb).name() << std::endl;//class Base 

  return 0;

 }

打断点,F5进入调试,点击反汇编

可以看到调用的都是基类的show(),在编译阶段已经生成指令调用Base下的show;

可以看到结果:
因为pb是Base类型的指针,所以调用的都是Base类的成员方法;
基类Base只有一个数据成员ma,所以大小只有4字节;
派生类Derive继承了ma,其次还有自己的mb,所以有8字节;
pb的类型是一个class Base *;
*pb的类型是一个class Base。
为了更好地理解上述过程,我们简单画图如下:

为什么Base *类型的指针,Derive类型的对象,调用方法的时候是Base而不是Derive呢?
原因如上图:
Derive类继承了Base类,导致了派生类的大小要比基类大,而pb的类型是基类的指针,所以通过pb调用方法时只能访问到Derive中从Base继承而来的方法,访问不到自己重写的方法(指针的类型限制了指针解引用的能力)

基类定义虚函数

#include <iostream>
#include <typeinfo>
class Base
{
public:
  Base(int data = 10): ma(data) {}
  ~Base() {};

  //虚函数
  virtual void show() {
    std::cout << "Base::show()" << std::endl;
  }

  void show(int data) {
    std::cout << "Base::show()" << data << std::endl;
  }

protected:
  int ma;

};

class Derive :public Base
{
public:
  Derive(int data) :Base(data), mb(data) {}
  ~Derive() {}
  void show() {
    std::cout << "Derive::show()" << std::endl;
  }

private:
  int mb;
};

int main() {

  Derive d(50);
  Base *pb = &d;

  /*
  pb->show();
  pb 指针是base类型,如果发现Base中的show是虚函数,就进行动态绑定
mov         ecx,dword ptr [pb]
00292B01 8B 45 D4             mov         eax,dword ptr [pb]   //将pb指向的内存前4个字节放入ecx寄存器,pb指向derive对象,前四个字节即vfptr,将虚函数表地址加载到eax
00292B04 8B 10                mov         edx,dword ptr [eax]  //将eax 的前四个字节 即Derive::show 加载到edx中
00292B06 8B F4                mov         esi,esp
00292B08 8B 4D D4             mov         ecx,dword ptr [pb]
00292B0B 8B 02                mov         eax,dword ptr [edx]
00292B0D FF D0                call        eax   //虚函数的地址
00292B0F 3B F4                cmp         esi,esp
00292B11 E8 9C E7 FF FF       call        __RTC_CheckEsp (02912B2h)
我们可以看到这一次,汇编码call的就不是确切的函数地址了,而是寄存器eax;
那么就很好理解了:
eax寄存器里存放的是什么内容,编译阶段根本无从知晓,只能在运行的时候确定;
故,动态绑定。
  pb->show(10);  如果发现show是普通函数,就进行静态绑定 call Base::show

  */
  pb->show();//
  pb->show(10);//

  std::cout << "Base size:" << sizeof(Base) << std::endl;//8
  std::cout << "Derive size:" << sizeof(Derive) << std::endl;//12

  std::cout << typeid(pb).name() << std::endl;//class Base *
  /*
  pb的类型:Base类型,查看Base中有没有虚函数
  (1)Base中没有虚函数*pb识别的就是编译时期的类型 *pb 就是Base类型
  (2) Base中有虚函数,*pb识别的就是运行时期的类型 RTTI类型:Derive
  */
  std::cout << typeid(*pb).name() << std::endl;//class Derive 

  return 0;

 }

在我们添加了virtual关键字后,对应的函数就变成了虚函数;
那么,一个类添加了虚函数,对这个类有什么影响呢?

  • 首先,如果类里面定义了虚函数,那么编译阶段,编译器给这个类类型产生一个唯一的vftable虚函数表,虚函数表中主要存储的内容是:RTTI(Run-time Type Information)指针和虚函数的地址,当程序运行时,每一张虚函数表都会加载到内存的.rodata区;
  • 一个类里面定义了虚函数,那么这个类定义的对象,在运行时,内存中会多存储一个vfptr虚函数指针,指向了对应类型的虚函数表vftable;
  • 一个类型定义的n个对象,他们的vfptr指向的都是同一张虚函数表;
  • 一个类里面虚函数的个数,不影响对象内存的大小(vfptr),影响的是虚函数表的大小。
  • 如果派生类中的方法和从基类继承来的某个方法中返回值、函数名以及参数列表都相同,且基类的方法是virtual,那么派生类的这个方法,自动处理成虚函数

图示如下:(以Base为例)

虚函数表
1、RTTI,存放的是类型信息,也就是(Base或者Derive)
2、偏移地址:虚函数指针相对于对象内存空间的偏移,一般vfptr都在0偏移位置
3、下面的函数时虚函数入口地址

在Derive类中,由于重写了show(),因此在Derive的虚函数表中,是使用子类的show()方法代替了Base类的show()

VS的工具来查看虚函数表的有关信息

1 找到

2 在打开的窗口中切换到当前工程所在目录:

C:\Program Files (x86)\Microsoft Visual Studio\2017\Community>cd C:\Users\Admin\source\repos\C++test\

3 输入命令:cl XXX.cpp /d1reportSingleClassLayoutXX(第一个XXX表示源文件的名字,第二个代表你想查看的类类型,我这里就是Derive)

以看到class Derived的对象的内存布局,在派生类对象的开始包含了基类Base的对象,其中有一个虚表指针,指向的就是下面的Derived::$vftable@ (virtual function table),表中包含了Derived类中所有的虚函数

多重继承、多继承 的虚函数表 1 内存分布

假设有一个基类ClassA,一个继承了该基类的派生类ClassB,并且基类中有虚函数,派生类实现了基类的虚函数。
我们在代码中运用多态这个特性时,通常以两种方式起手:
(1) ClassA *a = new ClassB();
(2) ClassB b; ClassA *a = &b;
以上两种方式都是用基类指针去指向一个派生类实例,区别在于第1个用了new关键字而分配在堆上,第2个分配在栈上

请看上图,不同两种方式起手仅仅影响了派生类对象实例存在的位置。
以左图为例,ClassA *a是一个栈上的指针。
该指针指向一个在堆上实例化的子类对象。基类如果存在虚函数,那么在子类对象中,除了成员函数与成员变量外,编译器会自动生成一个指向**该类的虚函数表(这里是类ClassB)**的指针,叫作虚函数表指针。通过虚函数表指针,父类指针即可调用该虚函数表中所有的虚函数。

2 类的虚函数表与类实例的虚函数指针

首先不考虑继承的情况。如果一个类中有虚函数,那么该类就有一个虚函数表。
这个虚函数表是属于类的,所有该类的实例化对象中都会有一个虚函数表指针去指向该类的虚函数表。
从第一部分的图中我们也能看到,一个类的实例要么在堆上,要么在栈上。也就是说一个类可以有很多很多个实例。但是!一个类只能有一个虚函数表。在编译时,一个类的虚函数表就确定了,这也是为什么它放在了只读数据段中。

3 多态代码及多重继承情况

在第二部分中,我们讨论了在没有继承的情况下,虚函数表的逻辑结构。
那么在有继承情况下,只要基类有虚函数,子类不论实现或没实现,都有虚函数表。

#include <iostream>

using namespace std;

class ClassA
{
public:
  ClassA() { cout << "ClassA::ClassA()" << endl; }
  virtual ~ClassA() { cout << "ClassA::~ClassA()" << endl; }

  void func1() { cout << "ClassA::func1()" << endl; }
  void func2() { cout << "ClassA::func2()" << endl; }

  virtual void vfunc1() { cout << "ClassA::vfunc1()" << endl; }
  virtual void vfunc2() { cout << "ClassA::vfunc2()" << endl; }
private:
  int aData;
};

class ClassB : public ClassA
{
public:
  ClassB() { cout << "ClassB::ClassB()" << endl; }
  virtual ~ClassB() { cout << "ClassB::~ClassB()" << endl; }

  void func1() { cout << "ClassB::func1()" << endl; }
  virtual void vfunc1() { cout << "ClassB::vfunc1()" << endl; }
private:
  int bData;
};

class ClassC : public ClassB
{
public:
  ClassC() { cout << "ClassC::ClassC()" << endl; }
  virtual ~ClassC() { cout << "ClassC::~ClassC()" << endl; }

  void func2() { cout << "ClassC::func2()" << endl; }
  virtual void vfunc2() { cout << "ClassC::vfunc2()" << endl; }
private:
  int cData;
};

int main()
{
  ClassC c;

  return 0;
}

请看上面代码
(1) ClassA是基类, 有普通函数: func1() func2() 。虚函数: vfunc1() vfunc2() ~ClassA()
(2) ClassB继承ClassA, 有普通函数: func1()。虚函数: vfunc1() ~ClassB()
(3) ClassC继承ClassB, 有普通函数: func2()。虚函数: vfunc2() ~ClassB()
基类的虚函数表和子类的虚函数表不是同一个表。下图是基类实例与多态情形下,数据逻辑结构。注意,虚函数表是在编译时确定的,属于类而不属于某个具体的实例。虚函数在代码段,仅有一份
ClassB继承与ClassA,其虚函数表是在ClassA虚函数表的基础上有所改动的,变化的仅仅是在子类中重写的虚函数。如果子类没有重写任何父类虚函数,那么子类的虚函数表和父类的虚函数表在内容上是一致的

ClassA *a = new ClassB();
a->func1();                    // "ClassA::func1()"   隐藏了ClassB的func1()
a->func2();                    // "ClassA::func2()"
a->vfunc1();                   // "ClassB::vfunc1()"  重写了ClassA的vfunc1()
a->vfunc2();                   // "ClassA::vfunc2()"

这个结果不难想象,看上图,ClassA类型的指针a能操作的范围只能是黑框中的范围,之所以实现了多态完全是因为子类的虚函数表指针与虚函数表的内容与基类不同
这个结果已经说明了C++的隐藏、重写(覆盖)特性。

同理,也就不难推导出ClassC的逻辑结构图了
类的继承情况是: ClassC继承ClassB,ClassB继承ClassA
这是一个多次单继承的情况。(多重继承)

4、多继承下的虚函数表 (同时继承多个基类)

多继承是指一个类同时继承了多个基类,假设这些基类都有虚函数,也就是说每个基类都有虚函数表,那么该子类的逻辑结果和虚函数表是什么样子呢?

#include <iostream>

using namespace std;

class ClassA1
{
public:
  ClassA1() { cout << "ClassA1::ClassA1()" << endl; }
  virtual ~ClassA1() { cout << "ClassA1::~ClassA1()" << endl; }

  void func1() { cout << "ClassA1::func1()" << endl; }

  virtual void vfunc1() { cout << "ClassA1::vfunc1()" << endl; }
  virtual void vfunc2() { cout << "ClassA1::vfunc2()" << endl; }
private:
  int a1Data;
};

class ClassA2
{
public:
  ClassA2() { cout << "ClassA2::ClassA2()" << endl; }
  virtual ~ClassA2() { cout << "ClassA2::~ClassA2()" << endl; }

  void func1() { cout << "ClassA2::func1()" << endl; }

  virtual void vfunc1() { cout << "ClassA2::vfunc1()" << endl; }
  virtual void vfunc2() { cout << "ClassA2::vfunc2()" << endl; }
  virtual void vfunc4() { cout << "ClassA2::vfunc4()" << endl; }
private:
  int a2Data;
};

class ClassC : public ClassA1, public ClassA2
{
public:
  ClassC() { cout << "ClassC::ClassC()" << endl; }
  virtual ~ClassC() { cout << "ClassC::~ClassC()" << endl; }

  void func1() { cout << "ClassC::func1()" << endl; }

  virtual void vfunc1() { cout << "ClassC::vfunc1()" << endl; }
  virtual void vfunc2() { cout << "ClassC::vfunc2()" << endl; }
  virtual void vfunc3() { cout << "ClassC::vfunc3()" << endl; }
};

int main()
{
  ClassC c;

  return 0;
}

ClassA1是第一个基类,拥有普通函数func1(),虚函数vfunc1() vfunc2()。
ClassA2是第二个基类,拥有普通函数func1(),虚函数vfunc1() vfunc2(),vfunc4()。
ClassC依次继承ClassA1、ClassA2。普通函数func1(),虚函数vfunc1() vfunc2() vfunc3()。

在多继承情况下,有多少个基类就有多少个虚函数表指针,前提是基类要有虚函数才算上这个基类。
如图,虚函数表指针01指向的虚函数表是以ClassA1的虚函数表为基础的,子类的ClassC::vfunc1(),和vfunc2()的函数指针覆盖了虚函数表01中的虚函数指针01的位置、02位置。当子类有多出来的虚函数时,添加在第一个虚函数表中。注意:
1.子类虚函数会覆盖每一个父类的每一个同名虚函数。
2.父类中没有的虚函数而子类有,填入第一个虚函数表中,且用父类指针是不能调用。
3.父类中有的虚函数而子类没有,则不覆盖。仅子类和该父类指针能调用

虚基类和多重继承

什么是多重继承

多重继承,很好理解,一个派生类如果只继承一个基类,称作单继承;
一个派生类如果继承了多个基类,称作多继承。
如图所示:

多重继承的优点
这个很好理解:
多重继承可以做更多的代码复用!
派生类通过多重继承,可以得到多个基类的数据和方法,更大程度的实现了代码复用。

关于菱形继承的问题
凡事有利也有弊,对于多继承而言,也有自己的缺点。
我们先通过了解菱形继承来探究多重继承的缺点:
菱形继承是多继承的一种情况,继承方式如图所示:

从图中我们可以看到:
类B类C类A单继承而来;
类D类B类C多继承而来。
那么这样继承会产生什么问题呢?
我们来看代码:

#include <iostream>

using namespace std;
class A
{
public:
  A(int data) :ma(data) { cout << "A()" << endl; }
  ~A() { cout << "~A()" << endl; }
protected:
  int ma;
};
class B :public A
{
public:
  B(int data) :A(data), mb(data) { cout << "B()" << endl; }
  ~B() { cout << "~B()" << endl; }
protected:
  int mb;
};
class C :public A
{
public:
  C(int data) :A(data), mc(data) { cout << "C()" << endl; }
  ~C() { cout << "~C()" << endl; }
protected:
  int mc;
};
class D :public B, public C
{
public:
  D(int data) : B(data), C(data), md(data) { cout << "D()" << endl; }
  ~D() { cout << "~D()" << endl; }
protected:
  int md;
};
int main()
{
  D d(10);

  return 0;
}

通过运行结果,我们发现了问题:
对于基类A而言,构造了两次,析构了两次!
并且,通过分析各个派生类的内存布局我们可以看到:

对于派生类D来说,间接继承的基类A中的数据成员ma重复了!
这对资源来说是一种浪费与消耗。
(如果多继承的数量增加,那么派生类中重复的数据也会增加!)

查看D类的内存布局:

其他多重继承的情况

除了菱形继承外,还有其他多重继承的情况,也会出现相同的问题

比如说图中呈现的:半圆形继承。

如何解决多重继承的问题

通过分析我们知道了,多重继承的主要问题是,通过多重继承,有可能得到重复的基类数据,并且可能重复的构造和析构同一个基类对象。
那么如何能够避免重复现象的产生呢?
答案就是:=》虚基类。

什么是虚基类
要理解虚基类,我们首先需要认识virtual关键字的使用场景:

修饰成员方法时:产生虚函数;
修饰继承方式时:产生虚基类。
对于被虚继承的类,称作虚基类。
比如说:

class A
{
	XXXXXX;
};
class B : virtual public A
{
	XXXXXX;
};

对于这个示例而言,B虚继承了A,所以把A称作虚基类。

虚基类如何解决问题

那么虚基类如何解决上述多重继承产生的重复问题呢?
我们来看代码:

#include <iostream>

using namespace std;
class A
{
public:
  A(int data) :ma(data) { cout << "A()" << endl; }
  ~A() { cout << "~A()" << endl; }
protected:
  int ma;
};
class B :virtual public A
{
public:
  B(int data) :A(data), mb(data) { cout << "B()" << endl; }
  ~B() { cout << "~B()" << endl; }
protected:
  int mb;
};
class C :virtual public A
{
public:
  C(int data) :A(data), mc(data) { cout << "C()" << endl; }
  ~C() { cout << "~C()" << endl; }
protected:
  int mc;
};
class D :public B, public C
{
public:
  D(int data) : B(data), C(data), md(data) { cout << "D()" << endl; }
  ~D() { cout << "~D()" << endl; }
protected:
  int md;
};
 

提示说:"A::A" : 没有合适的默认构造函数可用
为什么会这样呢?
我们可以这么理解:

刚开始BC单继承A的时候,实例化对象时,会首先调用基类的构造函数,也就是A的构造函数,到了D,由于多继承了BC,所以在实例化D的对象时,会首先调用BC的构造函数,然后调用自己(D)的。

但是这样会出现A重复构造的问题,所以,采用虚继承,把有关重复的基类A改为虚基类,这样的话,对于A构造的任务就落到了最终派生类D的头上,但是我们的代码中,对于D的构造函数:D(int data) : B(data), C(data), md(data) { cout << "D()" << endl; }并没有对A进行构造。
所以会报错。
那么我们就给D的构造函数,调用A的构造函数:
D(int data) :A(data), B(data), C(data), md(data) { cout << "D()" << endl; }
这一次再运行

我们会发现,问题解决了。

查看虚基类的内存布局

我们可以看到当前B的内存空间:

当前B的内存空间里,前四个字节是vbptr(这个就代表里虚基类指针:virtual base ptr);
vfptr(虚函数指针)指向了vftable(虚函数表)一样,
vbptr(虚基类指针)指向了vbtable(虚基类表)。

vbtable(虚基类表)的布局也如图所示,
首先是偏移量0:表示了虚基类指针再内存布局中的偏移量;
接着是偏移量8:表示从虚基类中继承而来的数据成员在内存中的偏移量。

对比普通继承下的内存布局

我们可以对比没有虚继承下的B的内存布局来理解:

我们把他们放在一起对比可以看到:

继承虚基类的类(BC)会把自己从虚基类继承而来的数据ma放在自己内存的最末尾(偏移量最大),并在原来ma的位置填充一个vbptr(虚基类指针),这个指针指向了vbtable(虚基类表)。
理解了B,我们可以看看更为复杂的D

可以看到,将ma移动到了末尾处,并在含有ma的地方,都用vbptr进行填充。
这样一来,就只有一个ma了!解决了多重继承的重复问题。

到此这篇关于关于C++虚函数与静态、动态绑定的问题的文章就介绍到这了,更多相关C++虚函数与静态、动态绑定内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解C++虚函数的工作原理

    静态绑定与动态绑定 讨论静态绑定与动态绑定,首先需要理解的是绑定,何为绑定?函数调用与函数本身的关联,以及成员访问与变量内存地址间的关系,称为绑定. 理解了绑定后再理解静态与动态. 静态绑定:指在程序编译过程中,把函数调用与响应调用所需的代码结合的过程,称为静态绑定.发生在编译期. 动态绑定:指在执行期间判断所引用对象的实际类型,根据实际的类型调用其相应的方法.程序运行过程中,把函数调用与响应调用所需的代码相结合的过程称为动态绑定.发生于运行期. C++中动态绑定 在C++中动态绑定是通过虚函数

  • 浅谈C++ 虚函数

    缘起 在上一篇文章中,测试代码2 中的 pBaseA->AA(); 输出的内容很"奇怪".其实,完全在情理之中.本文将简单探究一下 c++ 中的虚函数实现机制.本文主要基于 vs2013 生成的 32 位代码进行研究,相信其它编译器(比如,gcc)的实现大同小异. 先从对象大小开始 假设我们有如下代码,假设 int 占 4 字节,指针占 4 字节. #include "stdafx.h" #include "stdlib.h" #inclu

  • 虚函数表-C++多态的实现原理解析

    参考:http://c.biancheng.net/view/267.html 1.说明 我们都知道多态指的是父类的指针在运行中指向子类,那么它的实现原理是什么呢?答案是虚函数表 在 关于virtual 一文中,我们详细了解了C++多态的使用方式,我们知道没有 virtual 关键子就没法使用多态 2.虚函数表 我们看一下下面的代码 class A { public: int i; virtual void func() { cout << "A func" <<

  • C++ 虚函数和纯虚函数的区别分析

    首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数. 简介 假设我们有下面的类层次: class A { public: virtual void foo() { cout<<"A::foo() is called"<<endl; } }; cl

  • 详细分析C++ 多态和虚函数

    多态按字面的意思就是多种形态.当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态. C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数. 下面的实例中,基类 Shape 被派生为两个类,如下所示: #include <iostream> using namespace std; class Shape { protected: int width, height; public: Shape( int a=0, int b=0) { width = a;

  • 详解C++纯虚函数与抽象类

    1.虚函数 1.1虚函数简介 虚函数可以毫不夸张的说是C++最重要的特性之一,我们先来看一看虚函数的概念. 在基类的定义中,定义虚函数的一般形式为: virtual 函数返回值类型 虚函数名(形参表) { 函数体 } 为什么说虚函数是C++最重要的特性之一呢,因为虚函数承载着C++中动态联编的作用,也即多态,可以让程序在运行时选择合适的成员函数.虚函数必须是类的非静态成员函数(且非构造函数),其访问权限是public.那么:  (1)为什么类的静态成员函数不能为虚函数?  如果定义为虚函数,那么

  • 深入理解C++的动态绑定与静态绑定的应用详解

    为了支持c++的多态性,才用了动态绑定和静态绑定.理解他们的区别有助于更好的理解多态性,以及在编程的过程中避免犯错误.需要理解四个名词:1.对象的静态类型:对象在声明时采用的类型.是在编译期确定的.2.对象的动态类型:目前所指对象的类型.是在运行期决定的.对象的动态类型可以更改,但是静态类型无法更改.关于对象的静态类型和动态类型,看一个示例: 复制代码 代码如下: class B{}class C : public B{}class D : public B{}D* pD = new D();/

  • 关于C++虚函数与静态、动态绑定的问题

    覆盖:如果派生类中的方法,和基类继承来的某个方法,返回值.函数名.参数列表都相同,而且基类的方法是virtual虚函数,那么派生类的这个方法,自动处理成虚函数,它们之间成为覆盖关系:也就是说派生类会在自己虚函数表中将从基类继承来的虚函数进行替换,替换成派生类自己的. 静态绑定:编译时期的多态,通过函数的重载以及模板来实现,也就是说调用函数的地址在编译时期我们就可以确定,在汇编代码层次,呈现的就是 call 函数名: 动态绑定:运行时期的多态,通过派生类重写基类的虚函数来实现.在汇编代码层次,呈现

  • C++ 虚函数与纯虚函数的使用与区别

    目录 什么是虚函数: 虚函数的注意事项: 纯虚函数 纯虚函数的注意事项: 虚函数与纯虚函数区别 什么是虚函数: 虚函数 是在基类中使用关键字 virtual 声明的函数,在C++ 语言中虚函数可以继承,当一个成员函数被声明为虚函数之后,其派生类中的同名函数都自动生成为虚函数, 虚函数主要体验C++的多态方面,(多态是参数个数和类型相同而实现功能不同的函数) 为了更好的里面虚函数请看下面的demo #include <iostream> #include <string> using

  • C++之普通成员函数、虚函数以及纯虚函数的区别与用法要点

    普通成员函数是静态编译的,没有运行时多态,只会根据指针或引用的"字面值"类对象,调用自己的普通函数:虚函数为了重载和多态的需要,在基类中定义的,即便定义为空:纯虚函数是在基类中声明的虚函数,它可以再基类中有定义,且派生类必须定义自己的实现方法. 假设我们有三个类Person.Teacher.Student它们之间的关系如下: 类的关系图 普通成员函数 [Demo1] 根据这个类图,我们有下面的代码实现 #ifndef __OBJEDT_H__ #define __OBJEDT_H__

  • C++中虚函数与纯虚函数的用法

    本文较为深入的分析了C++中虚函数与纯虚函数的用法,对于学习和掌握面向对象程序设计来说是至关重要的.具体内容如下: 首先,面向对象程序设计(object-oriented programming)的核心思想是数据抽象.继承.动态绑定.通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不完全相同的新类,使用动态绑定,可以在一定程度上忽略相似类的区别,而以统一的方式使用它们的对象. 虚函数的作用是实现多态性(Polymorphism),多态性是将接口与实现进行分离,采用

  • C++虚函数及虚函数表简析

    C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有"多种形态",这是一种泛型技术.所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法.比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议. 关于虚函数的使用方法,我在这里不做过多的阐述.大家可以看看相关的C++的书籍.在这篇文章中,我只想从虚函数的实现机制上面为大家 一个

  • C++ 虚函数及虚函数表详解

    多态"的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定. #include <iostream> using namespace std; class A { public: int i; virtual void func() {} virtual void func2() {} }; class B : public A { int j; void func() {} }; int main() { cout <<

  • C++ 类中有虚函数(虚函数表)时 内存分布详解

    虚函数表 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的.简称为V-Table.在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承.覆盖的问题,保证其容真实反应实际的函数.这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数. 这里我们着重看一下这张虚函数表.C++的编译器应该是

  • 浅谈C++中虚函数实现原理揭秘

    编译器到底做了什么实现的虚函数的晚绑定呢?我们来探个究竟. 编译器对每个包含虚函数的类创建一个表(称为V TA B L E).在V TA B L E中,编译器放置特定类的虚函数地址.在每个带有虚函数的类 中,编译器秘密地置一指针,称为v p o i n t e r(缩写为V P T R),指向这个对象的V TA B L E.通过基类指针做虚函数调 用时(也就是做多态调用时),编译器静态地插入取得这个V P T R,并在V TA B L E表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生

  • 深入解析C++编程中的纯虚函数和抽象类

    C++纯虚函数详解 有时在基类中将某一成员函数定为虚函数,并不是基类本身的要求,而是考虑到派生类的需要,在基类中预留了一个函数名,具体功能留给派生类根据需要去定义. 纯虚函数是在声明虚函数时被"初始化"为0的函数.声明纯虚函数的一般形式是 virtual 函数类型 函数名 (参数表列) = 0; 关于纯虚函数需要注意的几点: 纯虚函数没有函数体: 最后面的"=0"并不表示函数返回值为0,它只起形式上的作用,告诉编译系统"这是纯虚函数"; 这是一个

随机推荐