一、纯虚函数

虚函数是实现多态性的前提

需要在基类中定义共同的接口

接口要定义为虚函数

如果基类的接口没办法实现怎么办?

如形状类Shape

解决方法

将这些接口定义为纯虚函数

在基类中不能给出有意义的虚函数定义,这时可以把它声明成纯虚函数,把它的定义留给派生类来做
定义纯虚函数:
class 类名{
        virtual 返回值类型 函数名(参数表) = 0;
    };
纯虚函数不需要实现

二、抽象类

作用

抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。

对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。

注意

抽象类只能作为基类来使用。

不能声明抽象类的对象。

构造函数不能是虚函数,析构函数可以是虚函数

1、抽象类不能用于直接创建对象实例,可以声明抽象类的指针和引用
2、可使用指向抽象类的指针支持运行时多态性
3、派生类中必须实现基类中的纯虚函数,否则它仍将被看作一个抽象类

 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

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

 
#include <iostream>


#include <vector>


#include <string>


using 
namespace std;

class Shape

{


public:

    
virtual 
void Draw() = 
;

    
virtual ~Shape()

    {

        cout << 
"~Shape..." << endl;

    }

};

class Circle : 
public Shape

{


public:

    
void Draw()

    {

        cout << 
"Circle::Draw() ..." << endl;

    }

    ~Circle()

    {

        cout << 
"~Circle ..." << endl;

    }

};

class Square : 
public Shape

{


public:

    
void Draw()

    {

        cout << 
"Square::Draw() ..." << endl;

    }

    ~Square()

    {

        cout << 
"~Square ..." << endl;

    }

};

class Rectangle : 
public Shape

{


public:

    
void Draw()

    {

        cout << 
"Rectangle::Draw() ..." << endl;

    }

    ~Rectangle()

    {

        cout << 
"~Rectangle ..." << endl;

    }

};

void DrawAllShapes(
const vector<Shape *> &v)

{

    vector<Shape *>::const_iterator it;

    
for (it = v.begin(); it != v.end(); ++it)

    {

        (*it)->Draw();

    }

}

void DeleteAllShapes(
const vector<Shape *> &v)

{

    vector<Shape *>::const_iterator it;

    
for (it = v.begin(); it != v.end(); ++it)

    {

        
delete(*it);

    }

}

// 简单工厂模式

class ShapeFactory

{


public:

    
static Shape *CreateShape(
const string &name)

    {

        Shape *ps = 
;

        
if (name == 
"Circle")

        {

            ps = 
new Circle;

        }

        
else 
if (name == 
"Square")

        {

            ps = 
new Square;

        }

        
else 
if (name == 
"Rectangle")

        {

            ps = 
new Rectangle;

        }

return ps;

    }

};

int main(
void)

{

    
//Shape s;      //Error,不能实例化抽象类
    vector<Shape *> v;

    
//Shape* ps;
    
//ps = new Circle;
    
//v.push_back(ps);
    
//ps = new Square;
    
//v.push_back(ps);
    
//ps = new Rectangle;
    
//v.push_back(ps);

Shape *ps;

    ps = ShapeFactory::CreateShape(
"Circle");

    v.push_back(ps);

    ps = ShapeFactory::CreateShape(
"Square");

    v.push_back(ps);

    ps = ShapeFactory::CreateShape(
"Rectangle");

    v.push_back(ps);

DrawAllShapes(v);

    DeleteAllShapes(v);

return 
;

}

Shape类是抽象类,Draw函数是纯虚函数,Circle, Square, Rectangle都重新实现了Draw,在这里把Shape的析构函数声明为虚函数,那么delete 基类指针,会调用派生类的析构函数,这样不容易造成内存泄漏。虚函数可以让我们以一致的观点看待从同一基类继承下来的派生类对象,都是通过Shape* 去调用Draw,但能够实现不同的行为。

三、多态优点

多态性有助于更好地对程序进行抽象

控制模块能专注于一般性问题的处理

具体的操作交给具体的对象去做

多态性有助于提高程序的可扩展性

可以把控制模块与被操作的对象分开

可以添加已定义类的新对象,并能管理该对象

可以添加新类(已有类的派生类)的新对象,并能管理该对象

四、虚析构函数

析构函数可以声明为虚函数

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

 
#include <iostream>


using 
namespace std;

// 对于一个没有任何接口的类,如果想要将它定义成抽象类,只能将虚析构函数声明为纯虚的
// 通常情况下在基类中纯虚函数不需要实现
// 例外是纯虚析构函数要给出实现。(给出一个空的实现即可)

class Base

{


public:

    
virtual ~Base() =

{

}

};

class Derived : 
public Base

{

};

int main(
void)

{

    
//  Base b; //error, 抽象类
    Derived d;

    
return 
;

}

// 对于一个没有任何接口的类,如果想要将它定义成抽象类,只能将虚析构函数声明为纯虚的
// 通常情况下在基类中纯虚函数不需要实现
// 例外是纯虚析构函数要给出实现。(给出一个空的实现即可)

参考:

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

从零开始学C++之虚函数与多态(二):纯虚函数、抽象类、虚析构函数的更多相关文章

  1. 从零开始学 Java - Spring 集成 ActiveMQ 配置(二)

    从上一篇开始说起 上一篇从零开始学 Java - Spring 集成 ActiveMQ 配置(一)文章中讲了我关于消息队列的思考过程,现在这一篇会讲到 ActivMQ 与 Spring 框架的整合配置 ...

  2. 从零开始学C++之对象的使用(二):四种对象生存期和作用域、static 用法总结

    一.四种对象生存期和作用域 栈对象 隐含调用构造函数(程序中没有显式调用) 堆对象 隐含调用构造函数(程序中没有显式调用),要显式释放 全局对象.静态全局对象 全局对象的构造先于main函数 已初始化 ...

  3. ptyhon 编程基础之函数篇(二)-----返回函数,自定义排序函数,闭包,匿名函数

    一.自定义排序函数 在Python中可以使用内置函数sorted(list)进行排序: 结果如下图所示: 但sorted也是一个高阶函数,可以接受两个参数来实现自定义排序函数,第一个参数为要排序的集合 ...

  4. 从零开始学C++之IO流类库(二):文件流(fstream, ifstream, ofstream)的打开关闭、流状态

    一.文件流 ofstream,由ostream派生而来,用于写文件 ifstream,由istream派生而来, 用于读文件 fstream,由iostream派生而来,用于读写文件 二.打开文件 说 ...

  5. 从零开始学spring源码之xml解析(二):默认标签和自定义标签解析

    默认标签: 上一篇说到spring的默认标签和自定义标签,发现这里面东西还蛮多的.决定还是拆开来写.今天就来好好聊聊这两块是怎么玩的,首先我们先看看默认标签: private void parseDe ...

  6. C++虚函数与多态

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

  7. 从零开始学 Java - Spring 集成 ActiveMQ 配置(一)

    你家小区下面有没有快递柜 近两年来,我们收取快递的方式好像变了,变得我们其实并不需要见到快递小哥也能拿到自己的快递了.对,我说的就是类似快递柜.菜鸟驿站这类的代收点的出现,把我们原来快递小哥必须拿着快 ...

  8. 从零开始学 Java - Spring 集成 Memcached 缓存配置(一)

    硬盘和内存的作用是什么 硬盘的作用毫无疑问我们大家都清楚,不就是用来存储数据文件的么?如照片.视频.各种文档或等等,肯定也有你喜欢的某位岛国老师的动作片,这个时候无论我们电脑是否关机重启它们永远在那里 ...

  9. 从零开始学 Java - 我放弃了 .NET ?

    这不是一篇引起战争的文章 毫无疑问,我之前是一名在微软温暖怀抱下干了近三年的 .NET 开发者,为什么要牛(sha)X一样去搞 Java 呢?因为我喜欢 iOS 阿!哈哈,开个玩笑.其实,开始学 Ja ...

  10. C++中 线程函数为静态函数 及 类成员函数作为回调函数

    线程函数为静态函数: 线程控制函数和是不是静态函数没关系,静态函数是在构造中分配的地址空间,只有在析构时才释放也就是全局的东西,不管线程是否运行,静态函数的地址是不变的,并不在线程堆栈中static只 ...

随机推荐

  1. Android Retrofit实现原理分析

    retrofit有几个关键的地方. 1.用户自定义的接口和接口方法.(由动态代理创建对象.) 2.converter转换器.(把response转换为一个具体的对象) 3.注解的使用. 让我们跟随Ap ...

  2. xxx

       <div style="position:absolute;left:0px;top:50px;width:1300px;height:550px;"><d ...

  3. C# 阳历转农历

    你妹的sb 原文 C#(ASP.NET)公历转农历的简单方法 Dot Net 平台,对全球化的支持做的非常好,不得不称赞一个 通常,将公历转为农历,是个非常烦的事情,需要整理闰年.闰月等的对照表. 在 ...

  4. Nginx工作原理和优化、漏洞

    1.  Nginx的模块与工作原理 第三方模块:HTTP Upstream Request Hash模块.Notice模块和HTTP Access Key模块. 图1-1展示了Nginx模块常规的HT ...

  5. [转]LoadRunner参数化详解

    http://www.cnblogs.com/fnng/archive/2012/06/22/2558900.html 距离上次使用loadrunner 已经有一年多的时间了.初做测试时在项目中用过, ...

  6. Oracle VirtualBox 模拟Android系统 素材

    Android to x86 下载地址: http://www.android-x86.org/download VirtualBox 下载地址: https://www.virtualbox.org ...

  7. IA32系统级架构总览(一) 实模式和保护模式

    应用程序的编写大部分的时候是不必关心系统级架构的,最多学习一下平台所给的API即可,也就是我们通常说的黑箱子.但是在学习操作系统的时候,系统级架构是要关心的. 系统级架构很难学习,其中一个很大的原因是 ...

  8. Wiki动画回顾系列序&&目录

    嘛,前前后后看了太多动画,我自己一直想做的事也是喜欢能做一款acg相关的应用,但一直没有好的点子,当然纠结到最后还是需要一个比较好的社区来让大家加入进来.一直有人让我给他们推番,而我也慢慢懂得“人心” ...

  9. STL源码分析读书笔记--第二章--空间配置器(allocator)

    声明:侯捷先生的STL源码剖析第二章个人感觉讲得蛮乱的,而且跟第三章有关,建议看完第三章再看第二章,网上有人上传了一篇读书笔记,觉得这个读书笔记的内容和编排还不错,我的这篇总结基本就延续了该读书笔记的 ...

  10. Vi的基本使用方法

    转载自http://linux.chinaunix.net/doc/office/2005-01-24/898.shtml vi编辑器是所有Unix及Linux系统下标准的编辑器,它的强大不逊色于任何 ...