一、纯虚函数

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

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

接口要定义为虚函数

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

如形状类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. webpack的学习

    什么是webpack? 他有什么优点? 首先对于很多刚接触webpack人来说,肯定会问webpack是什么?它有什么优点?我们为什么要使用它?带着这些问题,我们来总结下如下: Webpack是前端一 ...

  2. 使用Spring 3的@value简化配置文件的读取

    Spring 3支持@value注解的方式获取properties文件中的配置值,大简化了读取配置文件的代码. 1.在applicationContext.xml文件中配置properties文件 & ...

  3. getHibernateTemplate() 一直报NullPointerException 错误

    原来是调用方法有误正确调用方法: ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContex ...

  4. POJ 1844 Sum

    题意:给一个整数n,求当n由1到k的连续整数加或减组成时的最小的k. 解法:一开始觉得dp……后来觉得复杂度太大了……GG……百度了一下是个数学题orz. 如果n全部由加法组成,那么k可以组成k(k+ ...

  5. SqlDataReader对象的NextResult方法读取存储过程多个结果集

    如上图,我这里有返回4个记录集,分别表示 发送的总数,成功数,失败数,以及所有的记录集,那么我怎么去获取呢? 如果我第二个记录集 第三个记录集 都是多行的话,怎么办?可以参考下面这张图

  6. django-grappelli 安装配置

    在python34/scripts文件夹下pip3 install django-grappelli pip安装一般会装在python34的tools或lib之类的文件夹下,一定要找到那个文件夹,gr ...

  7. Mac OS 10.8 中的 OpenCV 开发环境设置

    一.编译OpenCV 要在Mac OS上使用OpenCV,需要自己编译源代码.操作过程如下: 1)从http://www.cmake.org下载cmake 2.8安装包. 2)安装cmake 2.8. ...

  8. effective c++:对象的赋值运算

    operator 中处理”自我赋值“ operator=操作符缺省情况下返回引用——TYPE& TYPE::operator=(const TYPE&),原因很简单,operator= ...

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

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

  10. nodejs cmd

    引入系统模块 var exec = require('child_process').exec; cmd.js var exec = require('child_process').exec; va ...