zoukankan      html  css  js  c++  java
  • 【转】C++的继承与多态:为什么需要虚函数

    转自:http://www.educity.cn/zk/gjyy/201306271108011682.htm

    多态性是面向对象设计语言的基本特征。仅仅是将数据和函数捆绑在一起,进行类的封装,使用一些简单的继承,还不能算是真正应用了面向对象的设计思想。多态性是面向对象的精髓,也是难点。在C++中,多态性是通过虚函数来实现的。

    1. 为什么需要虚函数

      为了说明虚函数的作用,我们先看一个程序实例:

    #include <iostream.h>
    
    class vehicle{
      int wheels;
      float weight;
    public:
      void message(void) {cout << "Vehicle message
    ";}
    };
    
    class car : public vehicle{
      int passenger_load;
    public:
      void message(void) {cout << "Car message
    ";}
    };
    
    class truck : public vehicle{
      int passenger_load;
      float payload;
    public:
      int passengers(void) {return passenger_load;}
    };
    
    class boat : public vehicle{
      int passenger_load;
    public:
      int passengers(void) {return passenger_load;}
      void message(void) {cout << "Boat message
    ";}
    };
    
    int main(){
      vehicle *unicycle;
      car *sedan;
      truck semi;
      boat sailboat;
    
      unicycle = new vehicle;
      unicycle-> message(); //输出Vehicle message
      delete unicycle;
    
      unicycle = new car;
      unicycle -> message(); //输出Vehicle message
    
      sedan = (car *) unicycle;
      sedan -> message(); //输出Car message
      delete sedan;
    
      semi.message(); //输出Vehicle message
      sailboat.message(); //输出Boat message
    }
    

    该程序的运行结果,我们已经标注在程序之中。因为指针的类型决定调用那一个成员函数,所以,一个vehicle*调用vehicle成员函数,即使它指向派生类的对象。同样,一个car *也调用car 的成员函数。我们把这称为早期联编或静态联编,因为指针要调用那一个函数是在编译时就确定的

    那么,当vehicle*指向派生类对象时,我们能不能通过该指针来调用派生类的成员函数呢?在C++中,我们是可以作到的,这要用到C++的多态特性。 也就是说,基类指针是调用基类的成员函数,还是调用派生类的成员函数,不是由指针的类型决定的,而是由指针指向的对象的类型决定的。

    2. 什么是多态

    多态也称为动态联编或迟后联编,因为到底调用哪一个函数,在编译时不能确定,而要推迟到运行时确定。也就是说,要等到程序运行时,确定了指针所指向的对象的类型时,才能够确定。在C++中,动态联编是通过虚函数来实现的。

    我们知道,函数调用是通过相应的函数名来实现的。对于源程序进行编译后,存放在内存中的可执行程序,函数实际上是一段机器代码,它是通过首地址进行标识和调用的。例如,假定定义一个函数:  

    void func(){
        //…
    };
    

    我们可以用下面的语句调用这个函数:

    func(); //调用func函数
    

     这是在源程序中调用函数的方法,它是用函数名操作的。下面我们看看在可执行程序中函数调用是怎么操作的,我们用汇编语言来说明,因为汇编语言和机器语言(计算机可以直接执行的语言)是一一对应的。

      在可执行程序中,函数调用使用下面的方法:

    call [xxxxx]
    xxxxx代表存放函数代码内存空间的首地址。
    

     call是汇编语句中的一条指令,意思是调用一个函数。实际操作过程是:保存当前地址、保护现场,跳转到xxxxx地址执行。正是基于这个原因,在C/C++中的函数名是一个指针,该指针指向该函数段代码在内存中的首地址。如何将源程序中的函数调用和函数体(也就是在内存中该函数的机器代码)联系起来呢?这件工作是由编译器和连接程序来完成的。

     在C/C++语言中,函数调用在程序运行之前就已经和函数体(函数的首地址)联系起来。编译器把函数体翻译成机器代码,并记录了函数的首地址。 在对函数调用的源程序段进行编译的时候,编译器知道这个函数名的首地址在那里(它可以从生成的标识符表中查到这个函数名对应的首地址),然后将这个首地址替换函数名,一并翻译成机器码。这种编译方法称为早期或静态联编。

     那么,当vehicle*指向派生类对象时,我们能不能通过该指针来调用派生类的成员函数呢?从这种编译方法来看,是不可能的。因为编译器只会寻找vehicle*的成员函数。如何实现这个功能:当用基类指针调用成员函数时,是调用基类的成员函数,还是调用派生类的成员函数,不由指针的类型决定,而由指针指向的对象的类型决定呢?也就是说,如果基类指针指向基类对象,就调用基类的成员函数,如果基类指针指向派生类对象,就调用派生类的成员函数。这就要用到另外一种方法,称为动态联编或迟后联编。到底调用哪一个函数,在编译时不能确定,而要推迟到运行时确定。在C++中,动态联编是通过虚函数来实现的。下面我们先介绍虚函数,然后讨论动态联编实现的原理。

    3. 为什么使用虚函数

    使用虚函数,我们可以获得良好的可扩展性。在一个设计比较好的面向对象程序中,大多数函数都是与基类的接口进行通信。因为使用基类接口时,调用基类接口的程序不需要改变就可以适应新类。如果用户想添加新功能,他就可以从基类继承并添加相应的新功能。

    4. 虚函数的特点

    虚函数的定义很简单,只要在成员函数原型前加一个关键字virtual即可。如果一个基类的成员函数定义为虚函数,那么,它在所有派生类中也保持为虚函数,即使在派生类中省略了virtual关键字。需要注意的是:要达到动态联编的效果,基类和派生类的对应函数不仅名字相同,而且返回类型、参数个数和类型也必须相同。

    基类vehicle的成员函数message被定义为虚函数,虽然其派生类中的message成员函数定义时,没有virtual关键字,但都是虚函数。如果派生类中有与基类对应的方法,并且基类指针指向派生类的对象,那么基类指针调用的方法是派生类的方法。

    如果将vechicle中的message()函数定义为虚函数,那么main函数中四个相同的语句“unicycle->message();”,它们的结果并不相同,结果依次是:vechicle message, car message, car message, vechicle message, boat message。哪一个类的message成员函数被调用,不是在编译时确定的,而是根据运行时unicycle指针指向的对象的类型确定的。由于类truck没有覆盖基类的message成员函数,系统调用基类的message成员函数。

    一个有意思的情况:如果在类car中,将虚函数void message(void)声明为private,那么,car sedan将无法调用message()函数(这是因为message()是car类的私有函数,实例不能调用),而vehicle *unicycle = &sedan,却可以调用message()函数,且调用的是car类中的message()函数。

    5. 什么是纯虚函数及纯虚函数的作用

    纯虚函数是一种特殊的虚函数。纯虚函数的定义如下:

    class vehicle{
      int wheels;
      float weight;
    public:
      virtual void message(void) = 0;  //纯虚函数
    };
    

     纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本,纯虚函数的声明格式为:

     virtual 函数类型 函数名(参数表) =0;
    

     声明为纯虚函数之后,基类中就不再给出函数的实现部分。纯虚函数的函数体由派生类给出。含有纯虚函数的类称为虚类,又叫做抽象类,不能被实例化。那这种虚类为什么会存在呢?这是因为,有些定义的基类,并不适合被实例化。比如交通工具可以是车、船等,但交通工具本身是一个抽象的概念,不对应着某一具体的事物。所以,可以以虚类为基类,然后由派生类来实现虚基类中的纯虚函数,再实例化这个派生类。

  • 相关阅读:
    check datagurad scripts using python
    nagios check_oracle plugin (add check temp tablespace)
    monitor tomcat sctips
    ATM 练习
    socket 练习 ftp
    socket 练习 sendcommand
    带你走进虚拟化世界之kvm(转载)
    luogu2732商店购物
    uva1625颜色的长度
    luogu3147 [USACO16OPEN]262144
  • 原文地址:https://www.cnblogs.com/sunada2005/p/3397423.html
Copyright © 2011-2022 走看看