• 【C++】C++中的虚函数与纯虚函数


    C++中的虚函数

      先来看一下实际的场景,就很容易明白为什么要引入虚函数的概念。假设我们有一个基类Base,Base中有一个方法eat;有一个派生类Derived从基类继承来,并且覆盖(Override)了基类的eat;继承表明ISA(“是一个”)的关系,现在我们有一个基类的指针(引用)绑定到派生类对象(因为派生类对象是基类的一个特例,我们当然可以用基类指针指向派生类对象),当我们调用pBase->eat()的时候,我们希望调用的是Derived类的eat,而实际上调用的是Base类的eat,测试代码如下:

     1 #include <iostream>
     2 #include <string>
     3 #include <vector>
     4 using namespace std;
     5 
     6 class Base
     7 {
     8 public:
     9     Base(){}
    10 
    11     void eat()
    12     {
    13         cout << "I am base eat()" << endl;
    14     }
    15 private:
    16 
    17 };
    18 
    19 class Derived:public Base
    20 {
    21 public:
    22     Derived(){}
    23 
    24     void eat()
    25     {
    26         cout << "I am derived eat()" << endl;
    27     }
    28 };
    29 
    30 int main()
    31 {
    32     Base* pBase;
    33     Base base;
    34     Derived* pDerived;
    35     Derived derived;
    36 
    37     // 如果基类声明为普通函数,基类指针不管指向基类对象还是派生类对象,基类指针都调用基类函数
    38     pBase = &derived;
    39     pBase->eat();
    40     pBase = &base;
    41     pBase->eat();
    42 
    43 
    44     return 0;
    45 }

      运行结果如下:

      我们将基类指针分别绑定到派生类对象和基类对象,运行结果显示我们始终调用的是基类的函数,这个时候调用哪个函数是由指针的静态类型决定的,因为pBase为Base的指针,所以不管指向基类对象还是派生类对象,都会调用由指针静态类型所决定的函数

          为了能够实现,当基类指针指向派生类对象的时候,自动调用派生类对象自己实现的函数,C++引入了虚函数的概念。可以声明基类函数为虚函数,派生类对象覆盖了整个虚函数之后,根据派生类对象的类别自动调用派生类自己实现的函数。还是上面的例子,这次我们将基类的eat函数声明为虚函数(virtual)

     1 #include <iostream>
     2 #include <string>
     3 #include <vector>
     4 using namespace std;
     5 
     6 class Base
     7 {
     8 public:
     9     Base(){}
    10 
    11     // 基类声明为虚函数
    12     virtual void eat()
    13     {
    14         cout << "I am base eat()" << endl;
    15     }
    16 private:
    17 
    18 };
    19 
    20 class Derived:public Base
    21 {
    22 public:
    23     Derived(){}
    24 
    25     void eat()
    26     {
    27         cout << "I am derived eat()" << endl;
    28     }
    29 };
    30 
    31 int main()
    32 {
    33     Base* pBase;
    34     Base base;
    35     Derived* pDerived;
    36     Derived derived;
    37 
    38     // 基类指针指向派生类时,调用派生类的eat函数;指向基类时调用基类的eat函数
    39     pBase = &derived;
    40     pBase->eat();
    41     pBase = &base;
    42     pBase->eat();
    43 
    44 
    45     return 0;
    46 }

        运行结果如下:

       从运行结果可以看到,基类指针在绑定基类对象时调用了基类的eat函数,在绑定派生类对象时调用了派生类的eat函数,具体调用哪个函数是运行时决定的。

          小结:如果没有虚函数,无论基类指针指向的实际对象是什么,都会调用基类定义的函数,无法实现多态行为。为了实现多态行为,C++引入了虚函数。具体的方式是在基类(需要实现多态)的成员函数声明之前加上virtual关键字,派生类对象覆盖override该虚函数,然后将基类的指针(或者引用)的指针绑定到派生类对象或基类对象上,编译器在运行时确定指针所指向的具体对象,并自动调用具体对象的成员函数。

    纯虚函数

        前面讨论了我们C++通过引入虚函数来实现多态的特性。然而有时会出现这样的场景,基类的某些虚函数对于基类本身是没有意义的,它只是为了让派生类去继承这个函数并override自己的实现。比如:我们有一个平面图形Figure类,所有的平面图像都有面积Area函数来计算平面图形的面积,然而直接定义一个Figure对象却没有指明具体的类别,并且去调用Area函数是没有意义的。我们并不希望用户这样做,但是在仅仅引入虚函数的情况下,我们却没有办法避免用户这么做。代码实例如下:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 class Figure
     5 {
     6 public:
     7     Figure(){}
     8     virtual double Area()
     9     {
    10         return 0.0;
    11     }
    12 };
    13 
    14 class Rect:public Figure
    15 {
    16 public:
    17     Rect(double w = 10.0, double h = 10.0):Figure(), width_(w), height_(h){}
    18     double Area()
    19     {
    20         return width_ * height_;
    21     }
    22 private:
    23     double width_;
    24     double height_;
    25 };
    26 
    27 class Circle:public Figure
    28 {
    29 public:
    30     Circle(double r = 10.0):Figure(), radius_(r){}
    31     double Area()
    32     {
    33         return 3.14 * radius_ * radius_;
    34     }
    35 private:
    36     double radius_;
    37 };
    38 
    39 int main()
    40 {
    41     // 这样的定义和调用并没有任何意义
    42     // 我们并不清楚用户这样做要达到什么的行为,我们宁愿用户根本不能定义Figure类
    43     Figure figure;
    44     figure.Area();
    45     
    46     return 0;
    47 }

         为了避免用户定义Figure类并调用Area函数,C++引入了纯虚函数概念, 通过将Area函数声明为纯虚函数(pure virtual function),来避免创建Figure类对象,纯虚函数的声明是在虚函数后面加上=0,代码如下:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 class Figure
     5 {
     6 public:
     7     Figure(){}
     8     
     9     // 声明Area函数为纯虚函数
    10     virtual double Area() = 0;
    11 };
    12 
    13 class Rect:public Figure
    14 {
    15 public:
    16     Rect(double w = 10.0, double h = 10.0):Figure(), width_(w), height_(h){}
    17     double Area()
    18     {
    19         cout << "Rect Area: " << width_ * height_ << endl;
    20         return width_ * height_;
    21     }
    22 private:
    23     double width_;
    24     double height_;
    25 };
    26 
    27 class Circle:public Figure
    28 {
    29 public:
    30     Circle(double r = 10.0):Figure(), radius_(r){}
    31     double Area()
    32     {
    33         cout << "Circle Area: " << 3.14 * radius_ * radius_ << endl;
    34         return 3.14 * radius_ * radius_;
    35     }
    36 private:
    37     double radius_;
    38 };
    39 
    40 int main()
    41 {
    42     // 创建包含纯虚函数的类对象会出现编译错误
    43     // Figure figure;
    44     // figure.Area();
    45 
    46     // 再来体会一下多态特性
    47     Figure* pFigure;
    48     Rect rect;
    49     Circle circle;
    50     pFigure = &rect;
    51     pFigure->Area();
    52     pFigure = &circle;
    53     pFigure->Area();
    54     
    55     return 0;
    56 }

      运行结果如下:

         

          如果定义Figure类对象figure会出现编译错误,编译显示不能定义抽象类实例对象。C++将包含一个或者多个虚函数的类称为抽象类,用户不能定义抽象类的具体对象,派生类必须override其继承的每一个纯虚函数,否则派生类也为抽象类,不能直接使用。注意纯虚函数通常情况下只有声明,并没有定义,留作派生类override。但是纯虚函数有定义也是可以的,表明基类希望派生类继承某些共性行为。

    C++虚函数与纯虚函数总结如下:

    【1】C++通过引入虚函数来实现多态行为。虚函数的声明方式如下:  virtual returnType FunctionName(Parameters List) ,派生类可以override基类的虚函数实现自己的行为,当基类指针或引用绑定到派生类对象时,在运行时确定指针指向的对象具体类型,并自动调用相应类型的函数实现多态行为。

    【2】析构函数应该是虚函数,这是因为假设虚构函数不是虚函数,如果基类指针指向派生类对象,如果没有虚函数提供多态机制,那么基类指针只是调用基类对应的析构函数,并没有析构派生类对象的派生类部分,因而出现资源并没有完全释放的情况;反之,如果基类的虚构函数是虚函数,则基类指针多态的调用派生类的析构函数,最后调用基类的析构函数,从而完成完全的析构过程。

    【2】C++引入纯虚函数来规范派生类的行为,实际上等同于告诉派生类 “你必须提供这些纯虚函数的具体实现,我并不关心你具体是怎么实现的”。纯虚函数的声明方式如下: virtual returnType FunctionName(Parameter List) = 0; 。

    【3】定义了一个以上纯虚函数的类称为抽象类,抽象类不能定义具体的实例对象,但是可以定义抽象类的指针或引用。抽象类的派生类必须override其继承的所有纯虚函数,否则派生类也成为抽象类,不能直接使用。在具体的工程实践过程中,抽象类一般指生命纯虚函数,但是并不定义纯虚函数,而是由其派生类来override这些纯虚函数,这样做到了派生类只是继承了抽象类的接口,但是并不继承抽象类的实现(因为根本就没有实现)。

     参考文献

    [1] Wiki:Virtual function

    [2] Hackbuteer1的专栏:虚函数与纯虚函数的区别

    [3] 类别(class)继承的一些特性

  • 相关阅读:
    转载-----nodejs内存定位
    node内存泄露排查转载
    git使用规范
    git的使用方法
    Sublime Text 3最好的功能、插件和设置
    Appium-Python-Windows环境搭建笔记
    MPI Note
    先装VS2008之后,又装了2013,然后启动VS2008提示“Tools Version”有问题?
    SQLite 编译错误
    WPF异常捕获三种处理 UI线程, 全局异常,Task异常
  • 原文地址:https://www.cnblogs.com/python27/p/3995091.html
Copyright © 2020-2023  润新知