zoukankan      html  css  js  c++  java
  • 【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)继承的一些特性

  • 相关阅读:
    创建类以及引用一个类
    修改hosts文件
    微信第三方登录接口开发
    Android定位
    Leetcode 102. Binary Tree Level Order Traversal
    Leetcode 725. Split Linked List in Parts
    Leetcode 445. Add Two Numbers II
    Leetcode 328. Odd Even Linked List
    Leetcode 237. Delete Node in a Linked List
    Leetcode 234. Palindrome Linked List
  • 原文地址:https://www.cnblogs.com/python27/p/3995091.html
Copyright © 2011-2022 走看看