C++中的多态与虚函数的内部实现方法

2025-05-27 0 95

1、什么是多态

多态性可以简单概括为“一个接口,多种行为”。

也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。这是一种泛型技术,即用相同的代码实现不同的动作。这体现了面向对象编程的优越性。

多态分为两种:

(1)编译时多态:主要通过函数的重载和模板来实现。

(2)运行时多态:主要通过虚函数来实现。

2、几个相关概念

(1)覆盖、重写(override)

override指基类的某个成员函数为虚函数,派生类又定义一成员函数,除函数体的其余部分都与基类的成员函数相同。注意,如果只是函数名相同,形参或返回类型不同的话,就不能称为override,而是hide。

(2)重载(overload)

指同一个作用域出生多个函数名相同,但是形参不同的函数。编译器在编译的时候,通过实参的个数和类型,选择最终调用的函数。

(3)隐藏(hide)

分为两种:

1)局部变量或者函数隐藏了全局变量或者函数
2)派生类拥有和基类同名的成员函数或成员变量。

产生的结果:使全局或基类的变量、函数不可见。

3、几个简单的例子

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46
/******************************************************************************************************

* File:PolymorphismTest

* Introduction:测试多态的一些特性。

* Author:CoderCong

* Date:20141114

* LastModifiedDate:20160113

*******************************************************************************************************/

#include "stdafx.h"

#include <iostream>

using namespace std;

class A

{

public:

void foo()

{

printf("1 ");

}

virtual void fun()

{

printf("2 ");

}

};

class B : public A

{

public:

void foo() //由于基类的foo函数并不是虚函数,所以是隐藏,而不是重写

{

printf("3 ");

}

void fun() //重写

{

printf("4 ");

}

};

int main(void)

{

A a;

B b;

A *p = &a;

p->foo(); //输出1。

p->fun(); //输出2。

p = &b;

p->foo(); //输出1。因为p是基类指针,p->foo指向一个具有固定偏移量的函数。也就是基类函数

p->fun(); //输出4。多态。虽然p是基类指针,但实际上指向的是一个子类对象。p->fun指向的是一个虚函数。按照动态类型,调用子类函数

return 0;

}

4、运行时多态以及虚函数的内部实现

看了上边几个简单的例子,我恍然大悟,原来这就是多态,这么简单,明白啦!

好,那我们再看一个例子:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42
class A

{

public:

virtual void FunA()

  {

    cout << "FunA1" << endl;

  };

  virtual void FunAA()

  {

    cout << "FunA2" << endl;

  }

};

class B

{

public:

virtual void FunB()

  {

    cout << "FunB" << endl;

  }

};

class C :public A, public B

{

public:

  virtual void FunA()

  {

    cout << "FunA1C" << endl;

  };

};

int _tmain(int argc, _TCHAR* argv[])

{

  C objC;

  A *pA = &objC;

  B *pB = &objC;

  C *pC = &objC;

  printf("%d %d ", &objC, objC);

  printf("%d %d ", pA, *pA);

  printf("%d %d ", pB, *pB);

  printf("%d %d ", pC, *pC);

  return 0;

}

运行结果:

5241376 1563032

5241376 1563032

5241380 1563256

5241376 1563032

细心的同志一定发现了pB出了问题,为什么明明都是指向objC的指针,pB跟别人的值都不一样呢?

是不是编译器出了问题呢?

当然不是!我们先讲结论:

(1)每一个含有虚函数的类,都会生成虚表(virtual table)。这个表,记录了对象的动态类型,决定了执行此对象的虚成员函数的时候,真正执行的那一个成员函数。

(2)对于有多个基类的类对象,会有多个虚表,每一个基类对应一个虚表,同时,虚表的顺序和继承时的顺序相同。

(3)在每一个类对象所占用的内存中,虚指针位于最前边,每个虚指针指向对应的虚表。

先从简单的单个基类说起:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37
class A

{

public:

  virtual void FunA()

  {

    cout << "FunA1" << endl;

  }

  virtual void FunA2()

  {

    cout << "FunA2" << endl;

  }

};

class C :public A

{

  virtual void FunA()

  {

    cout << "FunA1C" << endl;

  }

};

int _tmain(int argc, _TCHAR* argv[])

{

  A *pA = new A;

  C *pC = new C;

  typedef void (*Fun)(void);

  Fun fun= (Fun)*((int*)(*(int*)pA));

  fun();//pA指向的第一个函数

  fun = (Fun)*((int*)(*(int*)pA) +1);

  fun();//pA指向的第二个函数

  

  fun = (Fun)*((int*)(*(int*)pC));

  fun();//pC指向的第一个函数

  fun = (Fun)*((int*)(*(int*)pC) + 1);

  fun();//pC指向的第二个函数

  return 0;

}

运行结果:

FunA1
FunA2
FunA1C
FunA2

是不是有点晕?没关系。我一点一点解释:pA对应一个A的对象,我们可以画出这样的一个表:

      C++中的多态与虚函数的内部实现方法

这就是对象*pA的虚表,两个虚函数以声明顺序排列。pA指向对象*pA,则*(int*)pA指向此虚拟表,则(Fun)*((int*)(*(int*)pA))指向FunA,同理,(Fun)*((int*)(*(int*)pA) + 1)指向FunA2。所以,出现了前两个结果。

根据后两个结果, 我们可以推测*pC的虚表如下图所示:

      C++中的多态与虚函数的内部实现方法

也就是说,由于C中的FunA重写(override)了A中的FunA,虚拟表中虚拟函数的地址也被重写了。

就是这样,这就是多态实现的内部机制。

我们再回到最初的问题:为什么*pB出了问题。

根据上边的结论,我们大胆地进行猜测:由于C是由A、B派生而来,所以objC有两个虚拟表,而由于表的顺序,pA、pC都指向了对应于A的虚拟表,而pB则指向了对应于B的虚拟表。做个实验来验证我们的猜想是否正确:

我们不改变A、B、C类,将问题中的main改一下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19
int _tmain(int argc, _TCHAR* argv[])

{

  C objC;

  A *pA = &objA;

  B *pB = &objC;

  C *pC = &objC;

  

  typedef void (*Fun)(void);

  Fun fun = (Fun)*((int*)(*(int*)pC));

  fun();//第一个表第一个函数

  fun = (Fun)*((int*)(*(int*)pC)+1);

  fun();//第一个表第二个函数

  fun = (Fun)*((int*)(*((int*)pC+1)));

  fun();<span style="white-space:pre"> </span>//第二个表第一个函数

  fun = (Fun)*((int*)(*(int*)pB));

  fun();//pB指向的表的第一个函数

  return 0;

}

哈哈,和我们的猜测完全一致:

FunA1C
FunA2
FunB
FunB

我们可以画出这样的虚函数图:

        C++中的多态与虚函数的内部实现方法

暂且这样理解,编译器执行B *pB = &objC时不是仅仅是赋值,而是做了相应的优化,将pB指向了第二张虚表。

说了这么多,我是只是简单地解释了虚函数的实现原理,可究竟对象的内部的内存布局是怎样的?类数据成员与多个虚表的具体内存布局又是怎样的?编译器是如何在赋值的时候作了优化的呢?我在以后的时间里会讲一下。

以上就是小编为大家带来的C++中的多态虚函数的内部实现方法全部内容了,希望大家多多支持快网idc~

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 C++中的多态与虚函数的内部实现方法 https://www.kuaiidc.com/74533.html

相关文章

发表评论
暂无评论