C++ Primer 学习笔记_38_面向对象编程(5)--虚函数与多态(二):纯虚函数、抽象类、虚析构函数


一、纯虚函数

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

基类的指针指向派生类的对象,调用的是派生类的虚函数

这就使得我们可以以一致的观点来看待不同的派生类对象

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

(2)接口要定义为虚函数


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

(1)如形状类Shape,形状不知道形状类该如何绘制

Shape

    Draw();

Circle

    Draw();

Square

    Draw();


3、解决方法

    因此,将这些接口定义为纯虚函数,拥有纯虚函数的类称为抽象类,这些抽象类不能够实例化(也就是不能定义对象)


4、在基类中不能给出有意义的虚函数定义,这时可以把它声明成纯虚函数,把它的定义留给派生类来做


5、定义纯虚函数:

class 类名{

    virtual 返回值类型 函数名(参数表) = 0;

};


6、纯虚函数不需要实现




二、抽象类

1、定义:

    凡是带有纯虚函数的类称为抽象类。

    只定义了protected型构造函数的类也是抽象类,没有提供public构造函数。如下,Base类也是一个抽象类。

class Base
{
private:
    int x;
protected:
    Base(int p = 0)
    {
        x = p;
    }
};

2、作用

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

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

(3) 示例

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

class Shape
{
public:
    virtual void Draw() = 0;  //基类不实现
};

class Circle : public Shape
{
public:
    void Draw()  //由派生类实现纯虚函数
    {
        cout << "Circle::Draw() ..." << endl;
    }
};

class Square : public Shape
{
public:
    void Draw()
    {
        cout << "Square::Draw() ..." << endl;
    }
};

void DrawAllShapes(const vector<Shape *> &v)
{
    vector<Shape*>::const_iterator it;
    for (it = v.begin(); it != v.end(); ++it)
    {
        (*it)->Draw();
    }
}

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);
    DrawAllShapes(v);

    return 0;
}

运行结果:

Circle::Draw() ...

Square::Draw() ...

如果基类实现了,则都只会调用基类的Draw()函数。


3、注意

(1)抽象类只能作为基类来使用

(2)不能声明抽象类的对象

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

(4)示例

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

class Shape
{
public:
    virtual void Draw() = 0;  //基类不实现
    virtual ~Shape()
    {
    }
};

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

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);
    }
}


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);
    DrawAllShapes(v);
    DeleteAllShapes(v);

    return 0;
}

运行结果:

Circle::Draw() ...

Square::Draw() ...

~Circle...

~Square ...

如果析构函数不是虚函数,DeleteAllShapes函数不会调用派生类的析构函数,存在内存泄露。


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

【例】

假设A为抽象类,下列声明()是正确的?

A、A fun(int);           B、A* p           C、int fun(A)          D、A obj

解答:B。抽象类不能定义对象,但是可以作为指针或者引用类型使用。若在A和C选项中的A后面加上&或*就是对的。


【例】

抽象类为什么不能实例化?

解答:抽象类中的纯虚函数没有具体的实现,所以没办法实例化。



三、多态优点

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

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

(2)具体的操作交给具体的对象去做


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

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

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

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

(4)示例
#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 = 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;
}
运行结果:

Circle::Draw() ...
Square::Draw() ...
Rectangle::Draw() ...
~Circle ...
~Shape...
~Square ...
~Shape...
~Rectangle ...
~Shape...

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



四、虚析构函数

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

(1)delete 基类指针;

(2)程序会根据基类指针指向的对象的类型确定要调用的析构函数

(3)基类的析构函数为虚函数,所有派生类的析构函数都是虚函数


2、构造函数不能是虚函数

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

(1)对于一个没有任何接口的类,如果想要将它定义成抽象类,只能将虚析构函数声明为纯虚的
(2)示例
#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;
}
如果纯虚析构函数没有给出实现,Derived d;会出错


参考:

C++ primer 第四版

C++ primer 第五版

你可能感兴趣的:(C++,C++,多态,面向对象编程,Primer)