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的析构函数\n";
    }
};
class D:public B{
  public:
    ~D()
    {
     cout<<"调用派生类D的析构函数\n";
    }
}; 
int main()
{
 D obj;
 return 0;
}
*/
/*
运行结果是: 
调用派生类D的析构函数
调用基类B的析构函数

显然本程序的运行结果是符和预想的。但是,如果在主函数中用new运算符建立一个无名对象
和定义了一个基类的对象指针,并将无名的对象的地址赋给这个对象指针。当用delete运算符
撤销无名对象时,系统只执行基类的析构函数,而不执行派生类的析构函数。
例如下面的例子: 
*/

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

#include<iostream>
using namespace std;
class B{
  public:
    ~B()
    {
     cout<<"调用基类B的析构函数\n";
    }
};
class D:public B{
  public:
    ~D()
    {
     cout<<"调用派生类D的析构函数\n";
    }
}; 
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的析构函数\n";
    }
};
class D:public B{
  public:
    ~D()
    {
     cout<<"调用派生类D的析构函数\n";
    }
}; 
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--\n";
}
void Base::func2()
{
 cout<<"--Base func2--\n";
}
void Base::func3()
{
 cout<<"--Base func3--\n";
}
void Base::func4()
{
 cout<<"--Base func4--\n";
}
void Derived::func1()
{
 cout<<"--Derived func1--\n";
}
void Derived::func2(int i)
{
 cout<<"--Derived func2--\n";
}
void Derived::func4()
{
 cout<<"--Derived func4--\n";
}
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--\n";
   }
}; 
class Base2{
  public: 
   void fun()                  //定义fun是普通的成员函数 
   {
    cout<<"--Base2--\n";
   }
};
class Derived:public Base1,public Base2{
  public:
   void fun()
   {
    cout<<"--Derived--\n";
   }
};
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 
*/

 

posted @ 2015-10-26 22:16  XYQ全哥  阅读(1995)  评论(0编辑  收藏  举报