zoukankan      html  css  js  c++  java
  • 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;
    }

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

  • 相关阅读:
    Drupal 7 中文安装教程
    苹果之硬盘启动安装
    STP详解
    RedHat Install
    Linux密码更改
    win8.1开启虚拟wifi
    跳过安装密钥安装系统
    虚拟机中Linux安装Tools
    桌面虚拟化之XenDesktop7
    桌面虚拟化之部署DDC-5.6
  • 原文地址:https://www.cnblogs.com/DLzhang/p/4796201.html
Copyright © 2011-2022 走看看