多态,虚函数,虚函数表

封装,抽象,继承,多态是面向对象编程语言的特点。个人认为抽象和继承都是手段,多态才是目的,继承是多态的基础。

封装:

(1)将属性和函数封装到一个类里边,属性和函数组成一个完整的对象

(2)权限管理,增强安全性

(3)暴露有限的接口,增强了易用性,增强了安全性

抽象:

(1)抽象这个概念存在于很多场景,我们使用函数,本身就是通过函数隐藏具体的实现细节,这也算抽象

(2)c++ 中的抽象,可以使用抽象类来认识,抽象类是包含纯虚函数的类,抽象类不能创建对象

继承:

(1)技术来源于生活,生活中的对象,比如植物,动物,都存在继承

(2)继承带来的好处是子类可以复用父类的属性和函数,复用

(3)c++ 中继承是实现多态的基础

多态:

同一个父类,可以派生出多个子类,每个子类都有自己专有的属性和方法,丰富多彩

1 多态

提到多态我们一般会想到这样一种使用场景:有一个基类,基类里边有虚函数;不同的类继承基类,然后分别在虚函数中实现自己的逻辑,覆盖基类的虚函数;派生类的指针也可以赋值给基类,基类调用虚函数的时候调用的是派生类实现的函数。

上边说的是典型的多态的场景,在有些文章或者书中,也把重载说成是一种多态。本节分别举一个虚函数多态的例子,一个重载多态的例子。

1.1 虚函数

如下代码, Phone 是一个基类,类中有一个虚函数 virtual boid CallUp()。Phone 派生出 3 个类,ApplePhone,MiPhone,OppoPhone。在 main 函数中针对 3 个派生类,分别创建了一个对象。定义了 3 个函数 CallUp1(),CallUp2(),CallUp3(),3 个函数的形参分别是 Phone 引用,Phone 指针,Phone 对象,在 main() 函数中分别调用者 3 个函数。

#include <iostream>
#include <string>

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

    virtual void CallUp() {
      std::cout << "Phone call up" << std::endl;
    }
};

class ApplePhone : public Phone {
public:
    void CallUp() {
        std::cout << "ApplePhone call up" << std::endl;
    }
};

class MiPhone : public Phone {
public:
    void CallUp() {
        std::cout << "MiPhone call up" << std::endl;
    }
};

class OppoPhone : public Phone {
public:
    void CallUp() {
        std::cout << "OppoPhone call up" << std::endl;
    }
};

void CallUp1(Phone &phone) {
  std::cout << "CallUp(Phone &phone)" << std::endl;
  phone.CallUp();
}

void CallUp2(Phone *phone) {
  std::cout << "CallUp2(Phone *phone)" << std::endl;
  phone->CallUp();
}

// 派生类可以赋值给基类
// 因为基类中的数据成员,在派生类中都有
// 派生类赋值给基类的过程,就是成员赋值的过程
void CallUp3(Phone phone) {
  std::cout << "CallUp3(Phone phone)" << std::endl;
  phone.CallUp();
}

int main() {
    ApplePhone apple;
    MiPhone mi;
    OppoPhone oppo;

    CallUp1(apple);
    CallUp1(mi);
    CallUp1(oppo);
    std::cout << std::endl;

    CallUp2(&apple);
    CallUp2(&mi);
    CallUp2(&oppo);
    std::cout << std::endl;

    CallUp3(apple);
    CallUp3(mi);
    CallUp3(oppo);
    std::cout << std::endl;

    return 0;
}

代码运行结果如下,可以看到,调用 CallUp1() 和 CallUp2() 能体现出多态的特点,分别调用的是类自己的 CallUp();CallUp3() 没有体现多态,调用的都是基类的虚函数。引用传递和指针传递都能体现多态的特点;值传递不能体现多态的特点,还是调用的基类中的函数。

1.2 重载

重载的示例代码如下,有以下几点需要注意:

(1)形参列表相同,返回值不同,不能重载

(2)形参列表的类型一样,但是形参名不一样,比如 (int a, int b) 和 (int b, int a),不构成重载

(3)对于实参是小数的,编译器默认为 double 类型的,如果函数的形参没有 double 类型,只有 float,int,那么编译器找不到 double 形参的时候会尝试转化成 float 或者 int,这样会产生二义性,编译错误。

#include <iostream>
#include <string>

class Test {
public:
  void Print(int a) {
    std::cout << "int a = " << a << std::endl;
  }

  void Print(double a) {
    std::cout << "double a = " << a << std::endl;
  }

  // g++ 中小数默认是 double 数据类型的
  // 如果调用的时候,不使用 float 做数据类型转换
  // 会和 Print(int a, int b) 产生二义性
  void Print(float a, float b) {
    std::cout << "float a = " << a << ", float b = " << b << std::endl;
  }

  void Print(char c) {
    std::cout << "int c = " << c << std::endl;
  }

  void Print(int a, char c) {
    std::cout << "int a = " << a << ", int c = " << c << std::endl;
  }

  void Print(char c, int a) {
    std::cout << "char c = " << c << ", int a = " << a << std::endl;
  }

  // int Print(int a) {
  //  std::cout << "return int, int a = " << a << std::endl;
  // }

  void Print(int a, int b) {
    std::cout << "int a = " << a << ", int b = " << b <<std::endl;
  }

  // void Print(int b, int a) {
  //  std::cout << "int b = " << b << ", int a = " << a << std::endl;
  // }
};

int main() {
  Test t;
  t.Print(10);
  t.Print(10.0);
  t.Print((float)10.0, (float)10.0);
  t.Print('a');
  t.Print(10, 'a');
  t.Print('a', 10);
  t.Print(10, 20);
  t.Print(20, 20);
  return 0;
}

1.2.1 c++ 为什么能重载,而 c 语言不能重载

c++ 中的函数符号包括了形参信息,所以不同的形参在 c++ 编译的目标文件中是不同的符号,所以, c++ 可以重载;c 语言中的函数符号不包括形参信息,如果重载的话就会产生符号冲突,所以不支持重载。

使用 objdump -tT aout 查看符号表,过滤 Print,可以看到如下符号。函数名相同,形参列表不同,生成的符号是不一样的。

使用 objdump -d -S -l a.out 查看反汇编结果,可以看到,main()书中调用了不同的符号。

如下的代码使用 gcc 是编译不过的,使用 g++ 可以编译通过。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void Print(int a) {
  printf("int a = %d\n", a);
}

void Print(int a, int b) {
  printf("int a = %d, int b = %d\n", a, b);
}

int main() {
  Print(10);
  Print(10, 10);
  return 0;
}

extern "C":

extern "C" 在 c++ 代码中使用,告诉编译器按 c 的风格来编译。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

extern "C" {
void Print(int a, int b) {
  printf("int a = %d, int b = %d\n", a, b);
}
}

void Print(char a, char b) {
  printf("char a = %d, char b = %d\n", a, b);
}

int main() {
  Print(10, 10);
  Print('a', 'b');
  return 0;
}

可以看到被 extern "C" 修饰的函数符号中不包含形参信息,是 c 的风格;没有被 extern "C" 修饰的函数符号中包含形参信息,是 c++ 的风格。

1.2.2 重载是编译多态,虚函数是运行多态

由上边的分析可以知道,函数重载,具体调用哪个函数,在编译的时候就确定了,所以可以称为编译时多态,是静态的。

虚函数直到程序运行的时候才确定调用哪个函数,所以称为运行时多态,是动态的。

2 虚函数和虚函数表

2.1 函数名即地址

在 c 和 c++ 中函数和全局变量类似,都是有地址的,函数名就表示函数的入口地址。

如下代码,声明了一个函数指针 PF,形参和返回值与 Hello 是一致的,这样就可以直接通过 pf 来对 Hello 做函数调用了。

#include <iostream>
#include <string>

void Hello(std::string name) {
  std::cout<< "hello " << name << std::endl;
}

typedef void (*PF)(std::string name);

int main() {
  std::cout<< (void *)Hello << std::endl;
  Hello("marry");

  PF pf = Hello;
  std::cout << "pf = " << (void *)pf << std::endl;
  pf("tom");
  return 0;
}

2.2 虚表

虚函数表是虚函数工作的基础,也是实现多态的核心。对于有虚函数的类来说,都会有一个虚函数表。如下图所示,有虚函数的类,这个类的每个对象的第一个 8 字节(64 位系统是 8 字节,32 位系统是 4 字节) 中保存的是虚函数表的地址。

如果一个有虚函数的类,没有其它成员变量,那么 sizeof() 计算这个类的大小是 8(64 位系统上),8 字节也就是存放虚函数表的空间。

#include <iostream>
#include <string>

class Base {
public:
  virtual void SayHello1() {
    std::cout << "hello1" << std::endl;
  }
};

int main() {
  std::cout << "sizeof(Base) = " << sizeof(Base) << std::endl;
  return 0;
}

一个类的所有对象共用一张虚函数表,所以每个对象的第一个 8 字节里边存储的数据内容是相等的,都是虚函数表的地址。虚表中保存的是函数的地址,而函数又属于代码段,代码段是共享的,所以一个类的对象共享虚表。

如下代码,有一个包含虚函数的类 Base,b1 和 b2 都是 Base 的对象,我们获取对象第一个 8 字节中的内容,发现 b1 和 b2 是相等的,这也验证了一个类的对象指向同一个虚表。

#include <iostream>
#include <string>

class Base {
public:
  virtual void SayHello1() {
    std::cout << "hello1" << std::endl;
  }

  virtual void SayHello2() {
    std::cout << "hello2" << std::endl;
  }

  virtual void SayHello3() {
    std::cout << "hello3" << std::endl;
  }
};

int main() {
  Base b1;
  Base b2;

  printf("&b1 = %p, *(long *)(&b1) = 0x%llx\n", &b1, *(long *)(&b1));
  printf("&b1 = %p, *(long *)(&b2) = 0x%llx\n", &b2, *(long *)(&b2));
  return 0;
}

运行结果如下,从打印信息来看,虚函数表的地址是 0x55f2dd2dbd58。

虚函数表中每一个表项都是一个函数的地址,在 64 位机器上,一个函数地址的长度是 8 字节,所以一个表项占  8 字节。如下代码,在类 Base 中有 3 个虚函数,另外定义了一个函数指针类型。在 main 函数中首先找到了虚函数表,然后依次取出了虚函数的地址,通过函数地址可以直接进行调用。

#include <iostream>
#include <string>

class Base {
public:
  virtual void SayHello1() {
    std::cout << "hello1" << std::endl;
  }

  virtual void SayHello2() {
    std::cout << "hello2" << std::endl;
  }

  virtual void SayHello3() {
    std::cout << "hello3" << std::endl;
  }


};

// 定义一个函数指针类型
typedef void (*PF)(void);
int main() {
  std::cout << "sizeof(Base) = " << sizeof(Base) << std::endl;

  Base b;
  // vfptr 是指向虚函数表的地址
  long *vfptr = (long *)*(long *)(&b);
  // 虚函数表的第一个 8 字节就是函数 SayHello1 的地址
  long f1_function = *(vfptr + 0);
  // 虚函数表的第二个 8 字节是函数 SayHello2 的地址
  long f2_function = *(vfptr + 1);
  // 虚函数表的第三个 8 字节是函数 SayHello3 的地址
  long f3_function = *(vfptr + 2);

  printf("function1 = %p\n", f1_function);
  printf("function2 = %p\n", f2_function);
  printf("function3 = %p\n", f3_function);

  PF pf;
  pf = (PF)(f1_function);
  pf();

  pf = (PF)(f2_function);
  pf();

  pf = (PF)(f3_function);
  pf();
  return 0;
}

运行结果如下:

2.3 单继承,没有虚函数覆盖

如下代码, Base 中有 3 个虚函数,Derived 继承了 Base 类, Derived 中有 2 个虚函数。Derived  中没有覆盖 Base 中的函数,所以 Derived 的虚表有 5 个表项,Base 中的虚表有 3 个表项。

#include <iostream>
#include <string>

class Base {
public:
  virtual void faa1() {
    std::cout << "faa1" << std::endl;
  }

  virtual void faa2() {
    std::cout << "faa2" << std::endl;
  }

  virtual void faa3() {
    std::cout << "faa3" << std::endl;
  }
};

class Derived : public Base{
public:
  virtual void fbb1() {
    std::cout << "fbb1" << std::endl;
  }

  virtual void fbb2() {
    std::cout << "fbb2" << std::endl;
  }
};

typedef void (*PF)(void);

void CallPF1() {
  // Base b; // Base 中的虚表只有 3 个函数

  // 直接对象赋值,b 中的虚表也是只用 3 个表项
  // Derived d;
  // Base b = d;
  Base b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);

  PF pf;
  pf = (PF)(f1_function);
  pf();

  pf = (PF)(f2_function);
  pf();

  pf = (PF)(f3_function);
  pf();

  pf = (PF)(f4_function);
  pf();

  pf = (PF)(f5_function);
  pf();
}

void CallPF2() {
  Derived b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);

  PF pf;
  pf = (PF)(f1_function);
  pf();

  pf = (PF)(f2_function);
  pf();

  pf = (PF)(f3_function);
  pf();

  pf = (PF)(f4_function);
  pf();

  pf = (PF)(f5_function);
  pf();
}

void CallPF3() {
  Base *b = new Derived();
  long *vfptr = (long *)*(long *)(b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);

  PF pf;
  pf = (PF)(f1_function);
  pf();

  pf = (PF)(f2_function);
  pf();

  pf = (PF)(f3_function);
  pf();

  pf = (PF)(f4_function);
  pf();

  pf = (PF)(f5_function);
  pf();
}

int main() {
  std::cout << "sizeof(Base) = " << sizeof(Base) << std::endl;
  std::cout << "sizeof(Derived) = " << sizeof(Derived) << std::endl;

  CallPF2();
  std::cout << std::endl;

  CallPF3();
  std::cout << std::endl;

  CallPF1();
  std::cout << std::endl;
  return 0;
}

运行结果如下,因为 Base 中的虚函数表的表项还是 3 个,所以调用 f4_function 和 f5_function 的时候会出现段错误。

2.4 单继承,有虚函数覆盖 --> 表项替换

在子类中,可以覆盖父类的虚函数,覆盖虚函数就是将虚函数表中的表项覆盖,改成子类中的虚函数的地址。当调用成员函数的时候,如果是虚函数,那么会查虚函数表,找到表之后再调用对应的函数。不同的子类,使用自己的虚函数地址覆盖了虚函数表的表项,那么函数调用的时候当然是调用子类中的函数。

如下代码,Base 类中有 3 个虚函数;Derived 继承了 Base,覆盖了 Base 中的 faa1 和 faa2;Derived1 继承了 Derived,覆盖了 faa1 和 faa2。

#include <iostream>
#include <string>

class Base {
public:
  virtual void faa1() {
    std::cout << "faa1" << std::endl;
  }

  virtual void faa2() {
    std::cout << "faa2" << std::endl;
  }

  virtual void faa3() {
    std::cout << "faa3" << std::endl;
  }
};

class Derived : public Base{
public:
  virtual void faa1() {
    std::cout << "derived faa1" << std::endl;
  }

  void faa2() {
    std::cout << "derived faa2" << std::endl;
  }

  virtual void fbb1() {
    std::cout << "fbb1" << std::endl;
  }

  virtual void fbb2() {
    std::cout << "fbb2" << std::endl;
  }
};

class Derived1 : public Derived {
public:
  void faa1() {
    std::cout << "Derived1 faa1" << std::endl;
  }

  void fbb1() {
    std::cout << "Derived1 fbb1" << std::endl;
  }
};

typedef void (*PF)(void);

void CallPF1() {
  // Base b; // Base 中的虚表只有 3 个函数

  // 直接对象赋值,b 中的虚表也是只用 3 个表项
  // Derived d;
  // Base b = d;
  Base b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);

  PF pf;
  pf = (PF)(f1_function);
  pf();

  pf = (PF)(f2_function);
  pf();

  pf = (PF)(f3_function);
  pf();

  pf = (PF)(f4_function);
  pf();

  pf = (PF)(f5_function);
  pf();
}

void CallPF2() {
  Derived b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);

  PF pf;
  pf = (PF)(f1_function);
  pf();

  pf = (PF)(f2_function);
  pf();

  pf = (PF)(f3_function);
  pf();

  pf = (PF)(f4_function);
  pf();

  pf = (PF)(f5_function);
  pf();
}

void CallPF3() {
  Derived1 b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);

  PF pf;
  pf = (PF)(f1_function);
  pf();

  pf = (PF)(f2_function);
  pf();

  pf = (PF)(f3_function);
  pf();

  pf = (PF)(f4_function);
  pf();

  pf = (PF)(f5_function);
  pf();
}

int main() {
  std::cout << "sizeof(Base) = " << sizeof(Base) << std::endl;
  std::cout << "sizeof(Derived) = " << sizeof(Derived) << std::endl;

  CallPF2();
  std::cout << std::endl;

  CallPF3();
  std::cout << std::endl;

  CallPF1();
  std::cout << std::endl;
  return 0;
}

运行结果如下:

2.5 多继承

如下代码,有 3 个基类,Base1,Base2 和 Base3。3 个基类中都有虚函数,也都有一个虚函数表。Derived 类继承 3 个基类,那么 Derived 中就有 3 个虚函数表。从打印的  Derived 的大小也能看出来,Base1、Base2、Base3 的大小是 8,Derived 的大小是 24。

#include <iostream>
#include <string>

class Base1 {
public:
  virtual void foo1() {
    std::cout << "Base1::foo1" << std::endl;
  }

  virtual void faa2() {
    std::cout << "Base1::faa2" << std::endl;
  }

  virtual void faa3() {
    std::cout << "Base1::faa3" << std::endl;
  }
};

class Base2 {
public:
  virtual void foo1() {
    std::cout << "Base2::foo1" << std::endl;
  }

  virtual void fbb2() {
    std::cout << "Base2::fbb2" << std::endl;
  }

  virtual void fbb3() {
    std::cout << "Base2::fbb3" << std::endl;
  }
};

class Base3 {
public:
  virtual void foo1() {
    std::cout << "Base3::foo1" << std::endl;
  }

  virtual void fcc2() {
    std::cout << "Base3::fcc2" << std::endl;
  }

  virtual void fcc3() {
    std::cout << "Base3::fcc3" << std::endl;
  }
};

class Derived : public Base1, public Base2, public Base3 {
public:
  virtual void fbb1() {
    std::cout << "Derived::fbb1" << std::endl;
  }

  void foo1() {
    std::cout << "Derived::foo1" << std::endl;
  }

  void fbb2() {
    std::cout << "Derived::fbb2" << std::endl;
  }
};

int main() {
  std::cout << "sizeof(Base1) = " << sizeof(Base1) << std::endl; // 8
  std::cout << "sizeof(Base2) = " << sizeof(Base2) << std::endl; // 8
  std::cout << "sizeof(Base3) = " << sizeof(Base3) << std::endl; // 8
  std::cout << "sizeof(Derived) = " << sizeof(Derived) << std::endl; // 24

  Derived d;
  d.foo1();
  d.fbb2();
  d.fcc2();
  return 0;
}

运行结果如下:

2.6 一个类的多个对象是不是共享一个虚函数表 ?

是,一个包含虚函数的类的多个对象共享一个虚函数表。

如下代码,Base 是基类,Derived 是派生类。两个类分别有自己的虚函数表,对于一个类的多个对象来说,多个对象共享这个类的虚函数表。

对于一个对象来说,第一个 8 字节保存的就是虚函数表的地址,代码中打印出了每个对象的虚函数表。

#include <iostream>
#include <string>

class Base {
public:
  Base() {
    std::cout << "Base()" << std::endl;
  }

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

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

class Derived : public Base {
public:
  Derived() {
    std::cout << "Derived()" << std::endl;
  }

  ~Derived() {
    std::cout << "~Derived()" << std::endl;
  }

  void Do() {
    std::cout << "Derived() Do()" << std::endl;
  }
};

typedef void (*PF)(void);

void TestBase() {
  Base b1;
  Base b2;
  Base b3;

  long *vfptr1 = (long *)*(long *)(&b1);
  long *vfptr2 = (long *)*(long *)(&b2);
  long *vfptr3 = (long *)*(long *)(&b3);

  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);

  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}

void TestDerived() {
  Derived d1;
  Derived d2;
  Derived d3;

  long *vfptr1 = (long *)*(long *)(&d1);
  long *vfptr2 = (long *)*(long *)(&d2);
  long *vfptr3 = (long *)*(long *)(&d3);

  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);

  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}

void TestDerivedBase() {
  Base *d1 = new Derived;
  Base *d2 = new Derived;
  Base *d3 = new Derived;

  long *vfptr1 = (long *)*(long *)(d1);
  long *vfptr2 = (long *)*(long *)(d2);
  long *vfptr3 = (long *)*(long *)(d3);

  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);

  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}

int main() {
  std::cout << "Base ------------------------" << std::endl;
  TestBase();
  std::cout << "Derived ---------------------" << std::endl;
  TestDerived();
  std::cout << "DerivedBase -----------------" << std::endl;
  TestDerivedBase();
  return 0;
}

打印结果如下:

(1)对于同一个类的多个对象来说,虚函数表是相同的

(2)基类和派生类的虚函数表是不同的,各自有各自的虚函数表,但是把派生类指针或者引用赋值给基类指针或者基类引用,那么基类中的虚函数表是派生类中的虚函数表

2.7 一个基类的多个派生类之间是共享一个虚表吗 ?

不是,虚函数表是以类为单位而存在的,一个基类的多个派生类分别有各自的虚函数表。

如下代码,Base 是基类,有 3 个派生类 Derived1,Derived2 和 Derived3。分别打印了 3 个派生类的虚函数表,虚函数表是不一样的。

#include <iostream>
#include <string>

class Base {
public:
  Base() {
    std::cout << "Base()" << std::endl;
  }

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

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

class Derived1 : public Base {
public:
  Derived1() {
    std::cout << "Derived1()" << std::endl;
  }

  ~Derived1() {
    std::cout << "~Derived1()" << std::endl;
  }

  void Do() {
    std::cout << "Derived1() Do()" << std::endl;
  }
};

class Derived2 : public Base {
public:
  Derived2() {
    std::cout << "Derived2()" << std::endl;
  }

  ~Derived2() {
    std::cout << "~Derived2()" << std::endl;
  }

  void Do() {
    std::cout << "Derived2() Do()" << std::endl;
  }
};

class Derived3 : public Base {
public:
  Derived3() {
    std::cout << "Derived3()" << std::endl;
  }

  ~Derived3() {
    std::cout << "~Derived3()" << std::endl;
  }

  void Do() {
    std::cout << "Derived3() Do()" << std::endl;
  }
};


typedef void (*PF)(void);

void TestBase() {
  Base b1;
  Base b2;
  Base b3;

  long *vfptr1 = (long *)*(long *)(&b1);
  long *vfptr2 = (long *)*(long *)(&b2);
  long *vfptr3 = (long *)*(long *)(&b3);

  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);

  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}

void TestDerived1() {
  Derived1 d1;
  Derived1 d2;
  Derived1 d3;

  long *vfptr1 = (long *)*(long *)(&d1);
  long *vfptr2 = (long *)*(long *)(&d2);
  long *vfptr3 = (long *)*(long *)(&d3);

  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);

  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}

void TestDerived2() {
  Derived2 d1;
  Derived2 d2;
  Derived2 d3;

  long *vfptr1 = (long *)*(long *)(&d1);
  long *vfptr2 = (long *)*(long *)(&d2);
  long *vfptr3 = (long *)*(long *)(&d3);

  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);

  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}

void TestDerived3() {
  Derived3 d1;
  Derived3 d2;
  Derived3 d3;

  long *vfptr1 = (long *)*(long *)(&d1);
  long *vfptr2 = (long *)*(long *)(&d2);
  long *vfptr3 = (long *)*(long *)(&d3);

  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);

  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}


int main() {
  std::cout << "Base ------------------------" << std::endl;
  TestBase();
  std::cout << "Derived1 ---------------------" << std::endl;
  TestDerived1();
  std::cout << "Derived2 ---------------------" << std::endl;
  TestDerived2();
  std::cout << "Derived3 ---------------------" << std::endl;
  TestDerived3();
  return 0;
}

运行结果如下:

Base ------------------------
Base()
Base()
Base()
1, virtual function table addr 0x5565ee1b8d08
2, virtual function table addr 0x5565ee1b8d08
3, virtual function table addr 0x5565ee1b8d08
Base() Do()
Base() Do()
Base() Do()
~Base()
~Base()
~Base()
Derived1 ---------------------
Base()
Derived1()
Base()
Derived1()
Base()
Derived1()
1, virtual function table addr 0x5565ee1b8cf0
2, virtual function table addr 0x5565ee1b8cf0
3, virtual function table addr 0x5565ee1b8cf0
Derived1() Do()
Derived1() Do()
Derived1() Do()
~Derived1()
~Base()
~Derived1()
~Base()
~Derived1()
~Base()
Derived2 ---------------------
Base()
Derived2()
Base()
Derived2()
Base()
Derived2()
1, virtual function table addr 0x5565ee1b8cd8
2, virtual function table addr 0x5565ee1b8cd8
3, virtual function table addr 0x5565ee1b8cd8
Derived2() Do()
Derived2() Do()
Derived2() Do()
~Derived2()
~Base()
~Derived2()
~Base()
~Derived2()
~Base()
Derived3 ---------------------
Base()
Derived3()
Base()
Derived3()
Base()
Derived3()
1, virtual function table addr 0x5565ee1b8cc0
2, virtual function table addr 0x5565ee1b8cc0
3, virtual function table addr 0x5565ee1b8cc0
Derived3() Do()
Derived3() Do()
Derived3() Do()
~Derived3()
~Base()
~Derived3()
~Base()
~Derived3()
~Base()

  • 28
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值