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

一、纯虚函数

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

需要在基类中定义共同的接口 接口要定义为虚函数

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

如形状类Shape

解决方法

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

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

二、抽象类

作用

抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。 对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。

注意

抽象类只能作为基类来使用。 不能声明抽象类的对象。 构造函数不能是虚函数,析构函数可以是虚函数

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

#include <iostream>
#include <vector>
#include <string>
using namespace std;

class Shape
{
public:
    virtual void Draw() = 0;
    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 = 0;
        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 0;
}

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

三、多态优点

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

控制模块能专注于一般性问题的处理 具体的操作交给具体的对象去做

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

可以把控制模块与被操作的对象分开 可以添加已定义类的新对象,并能管理该对象 可以添加新类(已有类的派生类)的新对象,并能管理该对象

四、虚析构函数

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

delete 基类指针; 程序会根据基类指针指向的对象的类型确定要调用的析构函数 基类的析构函数为虚函数,所有派生类的析构函数都是虚函数

构造函数不得是虚函数

如果要操作具有继承关系的类的动态对象,最好使用虚析构函数。特别是在派生类析构函数需要完成一些有意义的操作,比如释放内存 析构函数还可以是纯虚的。

#include <iostream>
using namespace std;


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

    }
};

class Derived : public Base
{

};

int main(void)
{
    //  Base b; //error, 抽象类
    Derived d;
    return 0;
}

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

参考:

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏于晓飞的专栏

Java 泛型进阶

在 List<String> 中添加 Integer 将不会通过编译,但是List<Sring>与List<Integer>在运行时的确是同一种类型。

1173
来自专栏维C果糖

编程思想 之「操作符」

在 Java 编程的过程中,我们对数据的处理,都是通过操作符来实现的。例如,用于赋值的赋值操作符、用于运算的运算操作符、用于比较的比较操作符,还包括逻辑操作符、...

4056
来自专栏大内老A

ASP.NET Core的路由[2]:路由系统的核心对象——Router

ASP.NET Core应用中的路由机制实现在RouterMiddleware中间件中,它的目的在于通过路由解析为请求找到一个匹配的处理器,同时将请求携带的数据...

560
来自专栏java架构师

Unit断言学习

[TestMethod]—用于把一个方法标记为一个测试方法。当你运行你的测试时,仅标记有这个属性的方法才能够运行。 [TestClass]—用于把一个类标记为...

26211
来自专栏大内老A

ASP.NET Core的路由[2]:路由系统的核心对象——Router

ASP.NET Core应用中的路由机制实现在RouterMiddleware中间件中,它的目的在于通过路由解析为请求找到一个匹配的处理器,同时将请求携带的数据...

3903
来自专栏DOTNET

ASP.NET MVC编程——控制器

每一个请求都会经过控制器处理,控制器中的每个方法被称为控制器操作,它处理具体的请求。 1操作输入参数 控制器的操作的输入参数可以是内置类型也可以是自定义类型。 ...

2529
来自专栏一个会写诗的程序员的博客

Groovy&Java动态编译执行

工作中,遇到部分业务经常动态变化,或者在不发布系统的前提下,对业务规则进行调整。那么可以将这部分业务逻辑改写成Groovy脚本来执行,那么就可以在业务运行过程中...

872
来自专栏漫漫全栈路

C#历代版本新特性——面试题常用

掌握一门语言,当然要掌握它的特性,而随着C#历代版本的迭代更替,C#语言也日趋完善,在C#2.0~C#4.0版本所带来的新的语法特性格外重要。C#的新特性,其...

3909
来自专栏逸鹏说道

C# 温故而知新: 线程篇(二) 下

首先介绍下Classic Async Pattern: 其实Classic Async Pattern指的就是我们常见的BeginXXX和EndXXX IAsy...

2717
来自专栏恰同学骚年

.NET基础拾遗(3)字符串、集合和流

  众所周知,在.NET中String是引用类型,具有不可变性,当一个String对象被修改、插入、连接、截断时,新的String对象就将被分配,这会直接影响到...

641

扫码关注云+社区