• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号
您的位置:首页 > 程序设计 >C语言 > C++中的多态与虚函数的内部实现方法

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

作者: 字体:[增加 减小] 来源:互联网 时间:2017-05-28

通过本文主要向大家介绍了c++多态性与虚函数,c++封装继承多态,c++多态,c++多态性,c++继承与多态等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com

1、什么是多态

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

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

多态分为两种:

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

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

2、几个相关概念

(1)覆盖、重写(override)

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

(2)重载(overload)

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

(3)隐藏(hide)

分为两种:

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

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

3、几个简单的例子

/****************************************************************************************************** 
* File:PolymorphismTest 
* Introduction:测试多态的一些特性。 
* Author:CoderCong
* Date:20141114 
* LastModifiedDate:20160113 
*******************************************************************************************************/ 
#include "stdafx.h" 
#include <iostream> 
using namespace std; 
class A 
{ 
public: 
  void foo() 
  { 
    printf("1\n"); 
  } 
  virtual void fun() 
  { 
    printf("2\n"); 
  } 
}; 
class B : public A 
{ 
public: 
  void foo() //由于基类的foo函数并不是虚函数,所以是隐藏,而不是重写 
  { 
    printf("3\n"); 
  } 
  void fun() //重写 
  { 
    printf("4\n"); 
  } 
}; 
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; 
}
</div>

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

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

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

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\n", &objC, objC); 
  printf("%d %d\n", pA, *pA); 
  printf("%d %d\n", pB, *pB); 
  printf("%d %d\n", pC, *pC); 
 
  return 0; 
}
</div>

运行结果:

5241376 1563032

5241376 1563032

5241380 1563256

5241376 1563032

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

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

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

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

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

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

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

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; 
}
</div>

运行结果:

FunA2
FunA1C
FunA2</div> 是不是有点晕?没关系。我一点一点解释:pA对应一个A的对象,我们可以画出这样的一个表:</div> </div> 这就是对象*pA的虚表,两个虚函数以声明顺序排列。pA指向对象*pA,则*(int*)pA指向此虚拟表,则(Fun)*((int*)(*(int*)pA))指向FunA,同理,(Fun)*((int*)(*(int*)pA) + 1)指向FunA2。所以,出现了前两个结果。</div> 根据后两个结果, 我们可以推测*pC的虚表如下图所示:</div> </div> 也就是说,由于C中的FunA重写(override)了A中的FunA,虚拟表中虚拟函数的地址也被重写了。</div> 就是这样,这就是多态实现的内部机制。</div> 我们再回到最初的问题:为什么*pB出了问题。</div> 根据上边的结论,我们大胆地进行猜测:由于C是由A、B派生而来,所以objC有两个虚拟表,而由于表的顺序,pA、pC都指向了对应于A的虚拟表,而pB则指向了对应于B的虚拟表。做个实验来验证我们的猜想是否正确:</div> 我们不改变A、B、C类,将问题中的main改一下:</div>
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; 
}
</div>

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

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

以上就是小编为大家带

分享到:QQ空间新浪微博腾讯微博微信百度贴吧QQ好友复制网址打印

您可能想查找下面的文章:

  • C++ 通过指针实现多态实例详解
  • C++中的多态与虚函数的内部实现方法
  • C++多态的实现机制深入理解
  • 从汇编看c++中的多态详解
  • C++多态的实现及原理详细解析
  • 深入解析C++中的虚函数与多态
  • 浅析C++中的虚函数
  • 深入理解C++的多态性
  • 从汇编看c++中多态的应用

相关文章

  • 2017-05-28C 语言基础教程(我的C之旅开始了)[十]
  • 2017-05-28详解C++中的内联函数和函数重载
  • 2017-05-28详细对比C语言中的chmod()函数和fchmod()函数
  • 2017-05-28C++ 先对数组排序,在进行折半查找
  • 2017-05-28C++利用stringstream进行数据类型转换实例
  • 2017-05-28模拟实现C语言中的内存管理
  • 2017-05-28C语言+win32api写窗体应用程序
  • 2017-05-28C++中的对象数组详细解析
  • 2017-05-28C++递归删除一个目录实例
  • 2017-05-28C++程序设计-五子棋

文章分类

  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号

最近更新的内容

    • c语言线程终止练习示例
    • C语言中的sizeof操作符用法及和strlen的区别
    • 快速学习C语言中for循环语句的基本使用方法
    • 在C++中反射调用.NET的方法(二)
    • C中实现矩阵乘法的一种高效的方法
    • 引用参数和传值参数的区别深入解析
    • VC中SendMessage和PostMessage的区别
    • 关于C++静态成员函数访问非静态成员变量的问题
    • 从汇编看c++中默认构造函数的使用分析
    • C++回溯法实例分析

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有