多态(C++)下

简介: 多态(C++)

多态的原理


虚函数表


class Base
{
public:
  virtual void Test()
  {
  cout << "Test()" << endl;
  }
private:
  int _a = 0;
};
int main()
{
  Base b;
  cout << sizeof(b) << endl;
  return 0;
}


按照以往的方式计算 Base类的大小:类中成员变量只有 int类型大小四个字节,成员函数 Test不占空间,所以类的大小应该是四个字节


运行结果如下


4d34aae9215b86db4b48564b01800f89_cdcf4b2888da4c3db72d92a5bf873038.png


运行结果和预算的结果不一样,这是怎么回事呢?难道是成员函数也要计算大小吗?接下来,通过监视一探究竟


71d7f8412caa257822f053b7fbd79b64_b42e5ac296514af998f3ed27f412510f.png


原来对象类中除了成员变量之外,还有_vfptr,也就是接下来要学习的虚函数表指针,想要学习指针,就先来了解虚函数表


虚函数表:用来存放虚函数,就如上面[0]中存放的就是Test的地址;本质就是函数指针数组


27b5491fb8421a53a7a745771d682749_7e7fceaf4d394ad28443f65b8c5a56e2.png


class Car
{
public:
  virtual void Test1()
  {
  cout << "Car:Test1()" << endl;
  }
  virtual void Test2()
  {
  cout << "Car:Test2()" << endl;
  }
private:
  int _a = 0;
};
class NIO : public Car
{
public:
  virtual void Test1()
  {
  cout << "NIO:Test1()" << endl;
  }
private:
  int _b = 0;
};
int main()
{
  Car c;
  NIO et7;
  return 0;
}


a7553d0945b6d301fd371277bd746c7b_beacfe8c15d3479e843c7c1ea7b97049.png


完成重写的虚函数,表函数表对应位置覆盖成重写的虚函数


多态的原理


int main()
{
  Car c;
  NIO et7;
    //多态调用
  Car* ptr = &c;
  ptr->Test1();
  ptr = &et7;
  ptr->Test1();
    //普通调用
  ptr = &c;
  ptr->Test2();
  ptr = &et7;
  ptr->Test2();
  return 0;
}


运行结果如下


a0c369372085a5e51687370354a88dff_e1413c1ea1ef4b6d8e7ef4419694be8e.png


普通调用由调用对象类型决定:Test2()函数不是虚函数,调用类型是Car;多态调用由指针/引用指向的对象决定:Test1()函数是虚函数,两次调用指向的对象都不同


再通过汇编进行观察


97c3e7b5ea6330746528943e337b3bc6_05dc8fba0ce1493cbf792dc543446d44.png


普通调用在编译时就确定好的(静态);多态调用在编译时是不确定的(动态)运行之后在,根据调用对象指向的类型,在表函数表中找到对应的函数进行调用


20a090f759cb17ffc81f0083d319f0c4_9d3063f2b603444fb651893117ab2eb0.png


动态绑定与静态绑定


静态绑定,在程序编译期间就已经确定程序的行为,也称静态多态

动态绑定,在程序运行期间,根据具体的类型确定程序具体的行为,调用具体的函数,也称动态多态


单继承和多继承关系中的虚函数表


单继承中的虚函数表


观察下列代码


class Car
{
public:
  virtual void test1()
  {
  cout << "Car test1()" << endl;
  }
  virtual void test2()
  {
  cout << "Car test2()" << endl;
  }
private:
  int _a;
};
class NIO:public Car
{
public:
  virtual void test1()
  {
  cout << "NIO test1()" << endl;
  }
  virtual void test3()
  {
  cout << "NIO test3()" << endl;
  }
private:
  int _b;
};
int main()
{
  Car c;
  NIO et7;
  return 0;
}


834378530dbb8baaab80cfd634005d8f_68e3d50faf6246bf971283b5b6d125d1.png


通过监视窗口能够发现:在派生类对象et7中重写了test1,继承了test2,但是本身的test3却没有,在内存窗口看到一个未知的地址,可以猜测是test3函数,现在就是想办法将其打印出来进行验证


上面了解到虚函数表本质是函数指针数组,接下来通过函数指针数组将虚函数表打印出来


typedef void(*_vfptr)();
void Print_vfptr(_vfptr vft[])
{
  for (int i = 0; vft[i] != nullptr; i++)
  {
  printf("[%d]:%p->", i, vft[i]);
  vft[i]();
  }
  cout << endl;
}

通过将对象进行取址再强转 int*获取前四个字节,在类型转换 _vfptr*传递给函数进行打印


Print_vfptr((_vfptr*)*(int*)&c);
  Print_vfptr((_vfptr*)*(int*)&et7);


完整代码如下


class Car
{
public:
  virtual void test1()
  {
  cout << "Car test1()" << endl;
  }
  virtual void test2()
  {
  cout << "Car test2()" << endl;
  }
private:
  int _a;
};
class NIO:public Car
{
public:
  virtual void test1()
  {
  cout << "NIO test1()" << endl;
  }
  virtual void test3()
  {
  cout << "NIO test3()" << endl;
  }
private:
  int _b;
};
typedef void(*_vfptr)();
void Print_vfptr(_vfptr vft[])
{
  for (int i = 0; vft[i] != nullptr; i++)
  {
  printf("[%d]:%p->", i, vft[i]);
  vft[i]();
  }
  cout << endl;
}
int main()
{
  Car c;
  Print_vfptr((_vfptr*)*(int*)&c);
  NIO et7;
  Print_vfptr((_vfptr*)*(int*)&et7);
  return 0;
}


12d6953a911ce3a81abdd7dc87ee4cfa_75727010afe84054b441ddf1f78dd884.png


打印结果和预期一致


436b3350f4ba29109e03f1ba1347c8de_a952c9421a82439a9cc3a34c46528815.png


多继承中的虚函数表


class NIO
{
public:
  virtual void test1()
  {
  cout << "NIO test1()" << endl;
  }
  virtual void test2()
  {
  cout << "NIO test2()" << endl;
  }
private:
  int _a;
};
class XPENG
{
public:
  virtual void test1()
  {
  cout << "XPENG test1()" << endl;
  }
  virtual void test2()
  {
  cout << "XPENG test2()" << endl;
  }
private:
  int _b;
};
class NEA :public NIO, public XPENG
{
public:
  virtual void test1()
  {
  cout << "NEA test1()" << endl;
  }
  virtual void test3()
  {
  cout << "NEA test3()" << endl;
  }
private:
  int _c;
};
int main()
{
  NIO et7;
  XPENG p7;
  NEA car;
  return 0;
}


b63c644aa25c31fb2400d170619ddf16_cb146da990d947ca8079d58442eb0e7b.png


通过监视会发现与上面类似的疑问,派生类 NEA对象本身的虚函数存放在第一个基类 NIO中还是第二个基类 XPENG中呢?


接下来再次通过函数指针数组进行验证


typedef void(*_vfptr)();
void Print_vfptr(_vfptr vft[])
{
  for (int i = 0; vft[i] != nullptr; i++)
  {
  printf("[%d]:%p->", i, vft[i]);
  vft[i]();
  }
  cout << endl;
}
class NIO
{
public:
  virtual void test1()
  {
  cout << "NIO test1()" << endl;
  }
  virtual void test2()
  {
  cout << "NIO test2()" << endl;
  }
private:
  int _a;
};
class XPENG
{
public:
  virtual void test1()
  {
  cout << "XPENG test1()" << endl;
  }
  virtual void test2()
  {
  cout << "XPENG test2()" << endl;
  }
private:
  int _b;
};
class NEA :public NIO, public XPENG
{
public:
  virtual void test1()
  {
  cout << "NEA test1()" << endl;
  }
  virtual void test3()
  {
  cout << "NEA test3()" << endl;
  }
private:
  int _c;
};
int main()
{
  NIO et7;
  Print_vfptr((_vfptr*)*(int*)&et7);
  XPENG p7;
  Print_vfptr((_vfptr*)*(int*)&p7);
  NEA car;
  //NIO虚函数表
  Print_vfptr((_vfptr*)*(int*)&car);
  XPENG* ptr = &p7;
  //XPENG虚函数表
  Print_vfptr((_vfptr*)*(int*)ptr);
  return 0;
}

b662e4d05615c61e8ca484c7be427f54_f67f3f339bbe44f6841ede95f90cad48.png


打印结果显示:派生类本身的虚函数是存放在第一个基类的虚函数表中的

8ee14e30e1db95be82ea6c6fb9a7c5a3_51d1174ffc904b77b1a1d4cb6ec05dbc.png


目录
相关文章
|
编译器 C语言 C++
49 C++ - 多态
49 C++ - 多态
42 0
|
7月前
|
Java 关系型数据库
多态
多态
46 0
|
4月前
|
存储 编译器 C++
|
7月前
|
存储 编译器 C++
|
7月前
|
编译器 C++
多态的讲解
多态的讲解
52 2
多态你真的了解吗?
多态你真的了解吗?
75 0
什么是多态。
什么是多态。
89 0
|
7月前
|
存储 编译器 C++
|
7月前
|
存储 编译器 C++
C++【多态】
C++【多态】
72 0
|
存储 设计模式 编译器
多态【C++】
多态【C++】
69 0

热门文章

最新文章