• C++:虚函数的详解


    5.4.2 虚函数详解


    1.虚函数的定义
    虚函数就是在基类中被关键字virtual说明,并在派生类重新定义的函数。虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。

    虚函数的定义是在基类中进行的,它是在基类中需要定义为虚函数的成员函数的声明中冠以关键字virtual。定义虚函数的格式如下:

    virtual 函数类型 函数名(形参表)
    {
         函数体
    }
    在基类中的某个成员函数声明为虚函数后,此虚函数就可以在一个或多个派生类中被重新定义。在派生类中重新定义时,其函数类型、函数名、参数个数、参数类型的顺序,都必须与基类中的原型完全相同。 

    //例 5.21 虚函数的使用

    #include<iostream>
    using namespace std;
    class B0{
     public:
       virtual void print(char *p)      //定义基类B0中的虚函数 
       {
         cout<<p<<"print()"<<endl;
       }
    };
    class B1:public B0{
      public:
    
        virtual void print(char *p)     //定义基类B0的公有派生类B1中的虚函数
        {
         cout<<p<<"print()"<<endl;
        }
    };
    class B2:public B1{
      public:
        virtual void print(char *p)     //定义基类B1的公有派生类B2中的虚函数
        {
         cout<<p<<"print()"<<endl;
        }
    };
    int main()
    {
     B0 ob0,*op;                  //定义基类对象ob0和对象指针op 
     op=&ob0;op->print("B0::");   //调用基类的B0的print()
     B1 ob1;                      //定义派生类B1的对象ob1 
     op=&ob1;op->print("B1::");   //调用派生类B1的print() 
     B2 ob2;                      //定义派生类B2的对象ob2
     op=&ob2;op->print("B2::");   //调用派生类B2的print()
     return 0;
    } 
     /*
       在程序中,语句op->print();
       出现了3次,由于op指向的对象不同,每次出现都执行了相应对象的虚函数print
       
       程序运行结果:
         B0::print()
         B1::print()
         B2::print()

    说明:
    (1)若在基类中,只是声明虚函数原型(需要加上virtual),而在类外定义虚函数时,则不必再加上virtual。

    (2)在派生类中重新定义时,其函数类型、函数名、参数个数、参数类型的顺序,都必须与基类中的原型完全相同。

    (3)C++规定,当一个成员函数被定义为虚函数后,其派生类中符合重新定义虚函数要求的同名函数都自动称为虚函数。因此,在派生类中重新定义该虚函数时,关键字virtual可写可不写。但是为了程序更加清晰,最好在每一层派生类中定义函数时都加上关键字virtual。

    (4)如果在派生类中没有对基类的虚函数重新定义,则公有派生类继承其直接基类的虚函数。
    一个虚函数无论被公有继承多少次。它仍然保持其虚函数的特性。
    例如:
    class B0{
          ...
          public:
                  virtual void show(); //在基类中定义show为虚函数
    };
    class B1:public B0{
          ...
    };
    若在公有派生类B1中没有重新定义虚函数show,则函数在派生类中被继承,仍然是虚函数。
    (5)虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,因为虚函数的调用要靠特定的对象来决定该激活哪个函数。

    (6)虽然使用对象名和点运算符的方式也可以调用虚函数,但是这种调用是在编译时进行的,是静态联编,它没有利用虚函数的特性。只有通过指针访问虚函数时才能获得运行时的多态性。

     2. 虚析构函数

    在C++中,不能声明虚构造函数,但是可以声明虚析构函数。
    //例5.23 虚析构函数的引例

    #include<iostream>
    using namespace std;
    class B{
      public:
        ~B()
        {
         cout<<"调用基类B的析构函数
    ";
        }
    };
    class D:public B{
      public:
        ~D()
        {
         cout<<"调用派生类D的析构函数
    ";
        }
    }; 
    int main()
    {
     D obj;
     return 0;
    }
    */
    /*
    运行结果是: 
    调用派生类D的析构函数
    调用基类B的析构函数
    
    显然本程序的运行结果是符和预想的。但是,如果在主函数中用new运算符建立一个无名对象
    和定义了一个基类的对象指针,并将无名的对象的地址赋给这个对象指针。当用delete运算符
    撤销无名对象时,系统只执行基类的析构函数,而不执行派生类的析构函数。
    例如下面的例子: 
    */

    //例5.24 虚析构函数的引例2

    #include<iostream>
    using namespace std;
    class B{
      public:
        ~B()
        {
         cout<<"调用基类B的析构函数
    ";
        }
    };
    class D:public B{
      public:
        ~D()
        {
         cout<<"调用派生类D的析构函数
    ";
        }
    }; 
    int main()
    {
     B *p;          //定义指向基类B的指针变量p 
     p = new D;     //用new运算符为派生类的无名对象动态的分配了一个存储空间,并将地址赋给对象指针p 
     delete p;      //用delete撤销无名对象时,释放动态存储空间 
     return 0;
    } 
    /* 程序运行结果: 调用基类B的析构函数 程序结果表示,本程序只执行了基类B的析构函数,而没有执行派生类D的析构函数。 原因是:当撤销指针p所指的派生类的无名对象,而调用析构函数时,采用了静态联编方式, 只调用了基类B的析构函数。 那么如何在撤销指针p所指的派生类的无名对象,既调用基类B的析构函数,也调用派生类D的 析构函数呢? 方法是:可以将基类的析构函数声明为虚析构函数,采用了多态性的动态联编方式。 虚析构函数没有类型,也没有参数,和普通虚函数相比,虚析构函数比较简单。 其声明格式:
    virtual ~类名() */

    //例5.25 虚析构函数的使用

    #include<iostream>
    using namespace std;
    class B{
      public:
        virtual ~B()
        {
         cout<<"调用基类B的析构函数
    ";
        }
    };
    class D:public B{
      public:
        ~D()
        {
         cout<<"调用派生类D的析构函数
    ";
        }
    }; 
    int main()
    {
     B *p;          //定义指向基类B的指针变量p 
     p = new D;     //用new运算符为派生类的无名对象动态的分配了一个存储空间,并将地址赋给对象指针p 
     delete p;      //用delete撤销无名对象时,释放动态存储空间 
     return 0;
    }
    /*
    程序运行结果是:
    调用派生类D的析构函数
    调用基类B的析构函数
    
       说明:虽然派生类的析构函数与基类的析构函数名字不相同,但是如果将基类的析构函数
             定义为虚函数,则由该基类所派生的所有派生类的析构函数也都自动成为虚函数。 
    */ 

    3.虚函数与重载函数的关系
    在一个派生类中重新定义基类的虚函数是函数重载的另一种形式,但它不同于一般函数重载。
    当普通的函数重载时,其函数的参数或参数类型有所不同,函数的返回类型也可以不同。但是当重载一个虚函数时,也就是说在派生类中重新定义虚函数时,要求函数名、返回类型、参数个数、参数的类型和顺序与基类的虚函数原型完全相同。如果仅仅返回类型不同,其余均相同,系统会给出错误信息;若仅仅函数名相同,而参数的个数、类型或顺序不同,系统将它作为普通的函数重载,这时虚函数的特性将丢失。 

    //例5.26 虚函数与重载函数的关系

    #include<iostream>
    using namespace std;
    class Base{
     public:
       virtual void func1();
       virtual void func2();
       virtual void func3();
       void func4();   
    };
    class Derived:public Base{
     public:
       virtual void func1();     //func1是虚函数,这里可以不写virtual 
       void func2(int i);        //与基类中的func2作为普通函数的重载,虚特性消失 
       //char func3();           //错误,因为与基类的func3返回类型不同,应删去 
       void func4();             //与基类中的func4是普通函数的重载,不是虚函数 
    };
    void Base::func1()
    {
     cout<<"--Base func1--
    ";
    }
    void Base::func2()
    {
     cout<<"--Base func2--
    ";
    }
    void Base::func3()
    {
     cout<<"--Base func3--
    ";
    }
    void Base::func4()
    {
     cout<<"--Base func4--
    ";
    }
    void Derived::func1()
    {
     cout<<"--Derived func1--
    ";
    }
    void Derived::func2(int i)
    {
     cout<<"--Derived func2--
    ";
    }
    void Derived::func4()
    {
     cout<<"--Derived func4--
    ";
    }
    int main()
    {
     Base b,*pt;    //定义基类的对象b和对象指针pt 
     Derived d;     //定义派生类的对象d 
     pt = &d;       //基类的指针pt象派生类的对象d 
     pt->func1();   //调用的是派生类的fun1,结果是--Derived func1-- (虚函数的特性)
     pt->func2();   //调用的是基类的fun2,结果是--Base func2--(参数表中多了一个参数,变成普通重载函数,丢失虚函数的特性) 
     pt->func4();   //调用的是基类的fun4,结果是--Base func4--(基类和派生类中均没有virtual关键字,普通成员函数的重载) 
     return 0;
    }
    /*
    程序运行结果是:
    --Derived func1--
    --Base func2--
    --Base func4-- 
    */

    4. 多重继承与虚函数 

    //例5.27 多重继承与虚函数的例子

    #include<iostream>
    using namespace std;
    class Base1{
      public:
       virtual void fun()           //定义fun是虚函数 
       {
        cout<<"--Base1--
    ";
       }
    }; 
    class Base2{
      public: 
       void fun()                  //定义fun是普通的成员函数 
       {
        cout<<"--Base2--
    ";
       }
    };
    class Derived:public Base1,public Base2{
      public:
       void fun()
       {
        cout<<"--Derived--
    ";
       }
    };
    int main()
    {
     Base1 *ptr1;
     Base2 *ptr2;
     Derived d;
     ptr1 = &d;  //基类的指针指向派生类的对象 
     ptr1->fun();/*(*ptr1).fun();*/  //调用派生类Derived的fun方法,因为它是由Base1派生来的,为虚函数,有虚特性 
     ptr2->fun();/*(*ptr2).fun();*/  //调用基类Base2的fun方法,派生类的fun方法是由Base2派生类来的,为普通成员重载函数 
     
    
     Derived obj;//基类的引用指向派生类的对象
     Base1 &p1 = obj;
     Base2 &p2 = obj;
     p1.fun();//调用派生类Derived的fun方法,因为它是由Base1派生来的,为虚函数,有虚特性 
     p2.fun();//调用基类Base2的fun方法,派生类的fun方法是由Base2派生类来的,为普通成员重载函数,无虚特性 
     
     return 0;
    }

    5.虚函数的综合应用 

    //例5.28 应用C++的多态性,计算三角形、矩形和圆的面积。

    #include<iostream>
    #define PI 3.1416
    using namespace std;
    class Shape{                    //定义一个公共的基类 
       public:
       // Shape(){}
        Shape(double a=0.0,double b=0.0)  //带默认的构造函数 
        {
         x = a;
         y = b;
        }
        virtual void area()
        {
         cout<<"在基类中定义的虚基类";
         cout<<"为派生类提供一个公共的接口,";
         cout<<"以便派生类根据需要重新定义虚函数"<<endl;
        }
       protected:
        double x;
        double y;    
    };
    class Triangle:public Shape{     //定义一个三角形的派生类 
      public:
         Triangle(double a,double b):Shape(a,b){}
         void area()
         {
           cout<<"三角形的高是:"<<x<<","<<"底是:"<<y<<endl;    
           cout<<"三角形面积:"<<0.5*x*y<<endl;
         } 
    };
    class Square:public Shape{     //定义一个矩形的派生类 
      public:
         Square(double a,double b):Shape(a,b){}
         void area()
         {
           cout<<"矩形的长是:"<<x<<","<<"宽是:"<<y<<endl;        
           cout<<"矩形面积:"<<x*y<<endl;
         } 
    }; 
    class Circle:public Shape{     //定义一个圆的派生类 
      public:
         Circle(double a):Shape(a,a){}
         void area()
         {
           cout<<"圆的半径是:"<<x/2<<endl;    
           cout<<"圆面积:"<<PI*x*x<<endl;
         } 
    }; 
    int main()
    {
     Shape *p,obj;                     //定义基类的对象指针p和对象obj 
     p=&obj;                           //基类的对象指针p指向基类的对象obj 
     p->area();                        //调用基类的area方法 
     Triangle t(10.0,6.0);             //定义三角形的对象t 
     Square s(10.0,6.0);               //定义矩形的对象s
     Circle c(10.0);                   //定义圆形的对象t
     p=&t;
     p->area();                        //计算三角形的面积 
     p=&s;
     p->area();                        //计算矩形的面积
     p=&c;
     p->area();                        //计算圆形的面积
     return 0;
    } 
    /*
     程序运行结果:
     在基类中定义的虚基类为派生类提供一个公共的接口,以便派生类根据需要重新定义虚函数
    三角形的高是:10,底是:6
    三角形面积:30
    矩形的长是:10,宽是:6
    矩形面积:60
    圆的半径是:
    圆面积:314.16 
    */
  • 相关阅读:
    BZOJ4076 : [Wf2014]Maze Reduction
    XVII Open Cup named after E.V. Pankratiev. Eastern GP, Division 1
    BZOJ4617 : [Wf2016]Spin Doctor
    BZOJ4613 : [Wf2016]Longest Rivers
    BZOJ2587 : [Ceoi2011]Team
    BZOJ4422 : [Cerc2015]Cow Confinement
    BZOJ4437 : [Cerc2015]Looping Labyrinth
    BZOJ4432 : [Cerc2015]Greenhouse Growth
    BZOJ2670 : Almost
    寻找“最好”(4)——不等约束和KKT条件
  • 原文地址:https://www.cnblogs.com/XYQ-208910/p/4912639.html
Copyright © 2020-2023  润新知