一、多态

多态性是面向对象程序设计的重要特征之一。
多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为。
多态的实现:

函数重载

运算符重载

模板

虚函数

(1)、静态绑定与动态绑定

静态绑定

绑定过程出现在编译阶段,在编译期就已确定要调用的函数。

动态绑定

绑定过程工作在程序运行时执行,在程序运行时才确定将要调用的函数。

二、虚函数

虚函数的概念:在基类中冠以关键字 virtual 的成员函数
虚函数的定义:

virtual 函数类型 函数名称(参数列表);

如果一个函数在基类中被声明为虚函数,则他在所有派生类中都是虚函数

只有通过基类指针或引用调用虚函数才能引发动态绑定
虚函数不能声明为静态

(1)、虚函数表指针

虚函数的动态绑定是通过虚函数表来实现的。(虚函数表存放虚函数的函数指针)
包含虚函数的类头4个字节存放指向虚函数表的指针

注意:若不是虚函数,一般的函数不会出现在虚函数表,因为不用通过虚函数表指针间接去访问。

 C++ Code 

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

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

 
#include <iostream>


using 
namespace std;

class Base

{


public:

    
virtual 
void Fun1()

    {

        cout << 
"Base::Fun1 ..." << endl;

    }

virtual 
void Fun2()

    {

        cout << 
"Base::Fun2 ..." << endl;

    }

void Fun3() 
//被Derived继承后被隐藏
    {

        cout << 
"Base::Fun3 ..." << endl;

    }

};

class Derived : 
public Base

{


public:

    
/*virtual */

    
void Fun1()

    {

        cout << 
"Derived::Fun1 ..." << endl;

    }

/*virtual */
void Fun2()

    {

        cout << 
"Derived::Fun2 ..." << endl;

    }

void Fun3()

    {

        cout << 
"Derived::Fun3 ..." << endl;

    }

};

int main(
void)

{

    Base *p;

    Derived d;

p = &d;

    p->Fun1();      
// Fun1是虚函数,基类指针指向派生类对象,调用的是派生类对象的虚函数(间接)
    p->Fun2();

    p->Fun3();      
// Fun3非虚函数,根据p指针实际类型来调用相应类的成员函数(直接)

Base &bs = d;

    bs.Fun1();

    bs.Fun2();

    bs.Fun3();

d.Fun1();

    d.Fun2();

    d.Fun3();

return 
;

}

sizeof(Base); 和 sizeof(Derived); 都是4个字节,其实就是虚表指针,据此可以画出对象的模型:

Derived类继承了Base类的虚函数Fun1,Fun2, 但又重新实现了,即覆盖了。程序中通过基类的指针或引用可以通过vptr间接访问到Derived::Fun1, Derived:::Fun2,但因为Fun3不是虚函数(基类的Fun3 被继承后被隐藏),故p->Fun3(); 和bs.Fun3(); 根据指针或引用的实际类型去访问,即访问到被Derived继承下来的基类Fun3。函数的覆盖与隐藏可以参考这里

三、虚析构函数

何时需要虚析构函数?
当你可能通过基类指针删除派生类对象时
如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),并且被析构的派生类对象是有重要的析构函数需要执行,就需要让基类的析构函数作为虚函数。

 C++ Code 

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

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

 
#include <iostream>


using 
namespace std;

class Base

{


public:

    
virtual 
void Fun1()

    {

        cout << 
"Base::Fun1 ..." << endl;

    }

virtual 
void Fun2()

    {

        cout << 
"Base::Fun2 ..." << endl;

    }

void Fun3()

    {

        cout << 
"Base::Fun3 ..." << endl;

    }

Base()

    {

        cout << 
"Base ..." << endl;

    }

    
// 如果一个类要做为多态基类,要将析构函数定义成虚函数
    
virtual ~Base()

    {

        cout << 
"~Base ..." << endl;

    }

};

class Derived : 
public Base

{


public:

    
/*virtual */

    
void Fun1()

    {

        cout << 
"Derived::Fun1 ..." << endl;

    }

/*virtual */
void Fun2()

    {

        cout << 
"Derived::Fun2 ..." << endl;

    }

void Fun3()

    {

        cout << 
"Derived::Fun3 ..." << endl;

    }

    Derived()

    {

        cout << 
"Derived ..." << endl;

    }

    
/*  virtual*/ ~Derived() 
//即使没有virtual修饰,也是虚函数
    {

        cout << 
"~Derived ..." << endl;

    }

};

int main(
void)

{

    Base *p;

    p = 
new Derived;

p->Fun1();

    
delete p; 
//通过基类指针删除派生类对象

return 
;

}

即通过delete 基类指针删除了派生类对象(执行派生类析构函数)。

四、object slicing与虚函数

首先看下图的继承体系:

 C++ Code 

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

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

 
#include <iostream>


using 
namespace std;

class CObject

{


public:

    
virtual 
void Serialize()

    {

        cout << 
"CObject::Serialize ..." << endl;

    }

};

class CDocument : 
public CObject

{


public:

    
int data1_;

    
void func()

    {

        cout << 
"CDocument::func ..." << endl;

        Serialize();

    }

    
virtual 
void Serialize()

    {

        cout << 
"CDocument::Serialize ..." << endl;

    }

    CDocument()

    {

        cout << 
"CDocument()" << endl;

    }

    ~CDocument()

    {

        cout << 
"~CDocument()" << endl;

    }

    CDocument(
const CDocument &other)

    {

        data1_ = other.data1_;

        cout << 
"CDocument(const CDocument& other)" << endl;

    }

};

class CMyDoc : 
public CDocument

{


public:

    
int data2_;

    
virtual 
void Serialize()

    {

        cout << 
"CMyDoc::Serialize ..." << endl;

    }

};

int main(
void)

{

    CMyDoc mydoc;

    CMyDoc *pmydoc = 
new CMyDoc;

cout << 
"#1 testing" << endl;

    mydoc.func();

cout << 
"#2 testing" << endl;

    ((CDocument *)(&mydoc))->func();

cout << 
"#3 testing" << endl;

    pmydoc->func();

cout << 
"#4 testing" << endl;

    ((CDocument)mydoc).func();      
//mydoc对象强制转换为CDocument对象,向上转型
    
//完完全全将派生类对象转化为了基类对象
    
//包括虚函数表也变成基类的虚表
    
delete pmydoc;

return 
;

}

由于Serialize是虚函数,故前3个testing输出都是CMyDoc::Serialize ...但第4个testing中发生了Object Slicing,即对象切割,将CMyDoc对象转换成基类CDocument对象时,调用了CDocument类的拷贝构造函数,CMyDoc类的额外成员如data2_消失,成为完全一个CDocument对象,包括虚表也变成基类的虚表,故输出的是CDocument::Serialize ...

此外还可以看到,调用了两次CDocument构造函数和一次CDocument 拷贝构造函数,CDocument析构函数被调用3次。

参考:

C++ primer 第四版
Effective C++ 3rd
C++编程规范

从零开始学C++之虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数的更多相关文章

  1. c++ 虚函数多态、纯虚函数、虚函数表指针、虚基类表指针详解

    静态多态.动态多态 静态多态:程序在编译阶段就可以确定调用哪个函数.这种情况叫做静态多态.比如重载,编译器根据传递给函数的参数和函数名决定具体要使用哪一个函数.动态多态:在运行期间才可以确定最终调用的 ...

  2. C++学习 - 虚表,虚函数,虚函数表指针学习笔记

    http://blog.csdn.net/alps1992/article/details/45052403 虚函数 虚函数就是用virtual来修饰的函数.虚函数是实现C++多态的基础. 虚表 每个 ...

  3. c++基础之虚函数表指针和虚函数表创建时机

    虚函数表指针 虚函数表指针随对象走,它发生在对象运行期,当对象创建的时候,虚函数表表指针位于该对象所在内存的最前面. 使用虚函数时,虚函数表指针指向虚函数表中的函数地址即可实现多态. 虚函数表 虚函数 ...

  4. C++子类虚函数表指针

    最近看剑指offer,记录一下 #include <iostream> #include <string> #include <cctype> #include&l ...

  5. swift class的虚函数表、扩展、@objc修饰、虚函数的派发方式研究

    swift class的虚函数表.扩展.@objc修饰的研究 工具: swiftc -emit-sil BaseClass.swift | xcrun swift-demangle > Clas ...

  6. 从零开始学C++之虚继承和虚函数对C++对象内存模型造成的影响

    首先重新回顾一下关于类/对象大小的计算原则: 类大小计算遵循结构体对齐原则 第一个数据成员放在offset为0的位置 其它成员对齐至min(sizeof(member),#pragma pack(n) ...

  7. C++虚函数实现多态原理(转载)

    一.前言 C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有"多种形态 ...

  8. C++虚函数与多态

    C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写.(这里我觉得要补充,重写的话可以有两种,直接重写成员函数和重写虚函 ...

  9. C++ 多态、虚函数机制以及虚函数表

    1.非virtual函数,调用规则取决于对象的显式类型.例如 A* a  = new B(); a->display(); 调用的就是A类中定义的display().和对象本体是B无关系. 2. ...

随机推荐

  1. Informatica9.6.1在Linux Red Hat 5.8上安装遇到的有关问题整理_3

    3.Repository Service启动后的页面编码问题 1)错误信息: 2)原因分析及解决步骤 原因分析: informatica产品安装背后AdminConsole的Code page默认为U ...

  2. RESTful HTTP的实践

    REST是一种风格,而不是标准.因为既没有REST RFC,也没有REST协议规范或者类似的规定.REST架构是Roy Fielding(他也是HTTP和URI规范的主要作者之一)在一篇论文中描述的. ...

  3. 打造万能的ListView GridView 适配器

    转载:http://blog.csdn.net/lmj623565791/article/details/38902805/ 通用的ViewHolder 首先分析下ViewHolder的作用,通过co ...

  4. selenium python (四)键盘事件

    #!/usr/bin/python# -*- coding: utf-8 -*-__author__ = 'zuoanvip' #在实际测试过程中,有时候我们需要使用tab键将焦点转移到下一个需要操作 ...

  5. <测试用例设计>用户及权限管理功能常规测试方法

    1)  赋予一个人员相应的权限后,在界面上看此人员是否具有此权限,并以此人员身份登陆,验证权限设置是否正确(能否超出所给予的权限): 2)  删除或修改已经登陆系统并正在进行操作的人员的权限,程序能否 ...

  6. 页面性能测试&提升方式

    性能测试包括:web系统页面测试.web系统后台测试 2种方式来提升你的web 应用程序的速度: ● 减少请求和响应的往返次数 ● 减少请求和响应的往返字节大小. 详细的看此文http://www.5 ...

  7. js代码大全

    超级实用且不花哨的js代码大全 事件源对象event.srcElement.tagNameevent.srcElement.type 捕获释放event.srcElement.setCapture() ...

  8. SQLite数据库和JPA简单介绍

    SQLite数据库和JPA简单介绍 一.SQLite简单使用 SQLite是遵循ACID的关系数据库管理系统,它的处理速度很快,它的设计目标是嵌入式的,只需要几百K的内存就可以了. 1.下载SQLit ...

  9. iOS App中数据加载的6种方式

    我们看到的APP,往往有着华丽的启动界面,然后就是漫长的数据加载等待,甚至在无网络的时候,整个处于不可用状态.那么我们怎么处理好界面交互中的加载设计,保证体验无缝衔接,保证用户没有漫长的等待感,而可以 ...

  10. sql-labs 分享

    前段时间在网上发现了一个阿三同学托管在github上的sql注入入门科普项目,感觉挺不错,在此分享一下.虽然现在有很多工具比如sqlmap可以实现自动化的sql注入,但是个人感觉如果只知其然而不知其所 ...