C++学习之路:纯虚函数

背景:

当有些方法无法继承,或者说无意义的时候,例如shape类,那么基类的接口便无法实现。

那么这时候就需要引入纯虚函数。

几何基类:

      Shape 拥有Draw方法,三角,圆形,菱形等Draw方法各不相同。只能使用纯虚函数,

拥有纯虚函数的基类称为抽象类,抽象类无法被实例化,纯虚函数也不需要实现。

@纯虚函数的定义

image

#纯虚函数一般不需要实现。

@抽象类

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

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

Warning

  1.抽象类智能作为基类来使用。

  2.不能声明抽象的对象。

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

@为什么析构函数不能是虚函数?

   如果构造函数是虚函数,那么在构造函数调用之前,虚函数表是不能确定的,在实例化之前是找不到虚函数入口的,也就是无法动态绑定,那么该类就无法构造。

   那么析构函数呢?

   实际上抽象类的析构函数,理应是一个虚析构函数。

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Shape{
    public:
        
         ~Shape(){
             cout << "~Shape" << endl;
         }
         /*virtual */void Draw() {
         cout << "hehe" << endl;
     }
        
};

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

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


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

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


int main(int argc, const char *argv[])
{
    //Shape s;//不能实例化一个抽象类
    std::vector<Shape*> v;
    Shape* ps;
    ps = new Circle;
    v.push_back(ps);
    ps = new Square;
    v.push_back(ps);

    DrawAllShapes(v);
    return 0;
}

如果基类的Draw方法不是虚的,那么便是静态绑定,main函数中的指针编译期间就确定了该调用基类的Draw方法,我们看结果打印:

➜  cpp  ./a.out 
hehe
hehe

我们把基类的Draw方法改为虚的,那么派生类同名的Draw方法也是虚的,那么便在派生类中维护一个虚函数表,那么便可以再运行时确定调用哪个Draw方法。

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Shape{
    public:
        
         ~Shape(){
             cout << "~Shape" << endl;
         }
         virtual void Draw() =0 ;
        
};

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

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


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

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





int main(int argc, const char *argv[])
{
    //Shape s;//不能实例化一个抽象类
    std::vector<Shape*> v;
    Shape* ps;
    ps = new Circle;
    v.push_back(ps);
    ps = new Square;
    v.push_back(ps);

    DrawAllShapes(v);
    return 0;
}

结果打印:

➜  cpp  ./a.out 
Draw()...
Square()...

tip:若基类析构函数不为虚函数,那么派生类的虚构函数就也不是虚函数

同理我们可以想象一下,如果在main中用基类指针ps 持有一个派生类。

然后delete(ps);

    Shape* ps;
    ps = new Square;
    delete(ps);

像上述代码,结果只会调用基类的析构函数,而不会调用派生类的析构函数,如果派生类在堆上有资源,那么就面临内存泄露的风险。

看看结果。

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Shape{
    public:
        
         ~Shape(){
             cout << "~Shape" << endl;
         }
         virtual void Draw() =0 ;
        
};

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

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


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

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





int main(int argc, const char *argv[])
{
    //Shape s;//不能实例化一个抽象类
    std::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;
}

结果打印:

Draw()...
Square()...
~Shape
~Shape
并没有正确的调用派生类析构函数,square对象析构时就可能导致内存泄露。

然后我们将基类的析构函数设置为虚函数,再看看打印结果

➜  cpp  ./a.out 
Draw()...
Square()...
~Circle
~Shape
~Square
~Shape

如果基类析构函数为虚构函数,便会正确的析构。先调用派生类自身的析构函数,再调用基类的析构函数。

总结:

1.抽象类不能用于直接创建对象实例,但是可以申明抽象类的指针和引用。

2.可使用指向抽象类的指针支持运行时的多态。

3.派生类中必须实现基类中的纯虚函数,否则仍会成为一个抽象类。

下面列出一个抽象类常用的例子,@看注释

#include <iostream>
using namespace std;


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

    }
};

class Drived : public Base
{

};

int main(void)
{
    Drived d;
    return 0;
}

如果基类虚析构函数不实现,编译无法通过。 

原文地址:https://www.cnblogs.com/DLzhang/p/4796201.html