转自: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\n";}
}; class car : public vehicle{
  int passenger_load;
public:
  void message(void) {cout << "Car message\n";}
}; 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\n";}
}; 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;

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

【转】C++的继承与多态:为什么需要虚函数的更多相关文章

  1. Java继承和多态实例

    我们知道面向对象的三大特性是封装.继承和多态.然而我们有时候总是搞不清楚这些概念.下面对这些概念进行整理, 为以后面向抽象的编程打下坚实的基础. 封装的概念还是很容易理解的.如果你会定义类,那么相信你 ...

  2. C#基础总结之八面向对象知识点总结-继承与多态-接口

    .方法深入讲解(返回值,形参与实参) 方法 public int getName(int i,int j) { int sum = i + j; return sum; } .利用泛型存储对象数据 . ...

  3. PHP面向对象三大特点学习(充分理解抽象、封装、继承、多态)

    PHP面向对象三大特点学习 学习目标:充分理解抽象.封装.继承.多态   面象对向的三大特点:封装性.继承性.多态性 首先简单理解一下抽象:我们在前面定义一个类的时候,实际上就是把一类事物共有的属性和 ...

  4. Java学习之旅基础知识篇:面向对象之封装、继承及多态

    Java是一种面向对象设计的高级语言,支持继承.封装和多态三大基本特征,首先我们从面向对象两大概念:类和对象(也称为实例)谈起.来看看最基本的类定义语法: /*命名规则: *类名(首字母大写,多个单词 ...

  5. */美女镇楼/*>>>---PHP中的OOP-->面对过程与面对对象基础概念与内容--(封装、继承、多态)

      前  言  OOP  学习了好久的PHP,今天来总结一下PHP中的重要成员OOP 1  面向过程&面向对象 1.专注于解决一个问题的过程.面向过程的最大特点,是由一个一个的函数去解决处理这 ...

  6. Java基础知识回顾之三 ----- 封装、继承和多态

    前言 在上一篇中回顾了java的修饰符和String类,这篇就来回顾下Java的三大特性:封装.继承.多态. 封装 什么是封装 在面向对象程式设计方法中,封装是指一种将抽象性函式接口的实现细节部份包装 ...

  7. Java面向对象概述及三大特征(封装,继承和多态)

    一.面向对象思想 Java是面向对象的高级语言,对于Java语言来说,万事万物皆对象! 它的基本思想是使用类,对象,继承,封装,消息等基本概念进行程序设计.面向对象程序的最小单元是类,类代表了客观世界 ...

  8. Java学习笔记(三)——封装、继承、多态

    一.封装 概念: 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问. 实现步骤: 修改属性的可见性——设为private. 创建getter/se ...

  9. python面向对象(封装、继承、多态)+ 面向对象小栗子

    大家好,下面我说一下我对面向对象的理解,不会讲的很详细,因为有很多人的博客都把他写的很详细了,所以,我尽可能简单的通过一些代码让初学者可以理解面向对象及他的三个要素. 摘要:1.首先介绍一下面向对象 ...

随机推荐

  1. HTML5给我们带来了什么?

    HTML5初探 传说中的HTML标准已经超过10年没有更新了,如今HTML5席卷全球,那么到底什么是HTML5呢?都在讲HTML5是web的新一代标准,它有着很多之前浏览器没有的新特性,可以说HTML ...

  2. 【 NOIP2015 DAY1 T2 信息传递】带权并查集

    题目描述 有n个同学(编号为1到n)正在玩一个信息传递的游戏.在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti同学. 游戏开始时,每人都只知道自己的生日.之后每一 ...

  3. ANDROID_MARS学习笔记_S01原始版_014_WIFI

    一.代码1.xml(1)main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayo ...

  4. 打死也不换系统?笑谈过气的Windows XP

    http://tech.qq.com/a/20131012/007336.htm 按照IT领域的“安迪-比尔定律”:软件和游戏不断生成过户需求,硬件则通过技术创新来消化这些需求,这个过程会刺激用户在电 ...

  5. 49. 面向对象的LotusScript(十五)之Log4Dom下

    Log4Dom是模仿Log4J的思想建立的.Log4J能够向多种记录媒介以统一的格式写入各种级别的日志信息(包括错误.调试和信息等),还可以籍配置文件在运行时方便地修改记入日志的级别.Log4Dom提 ...

  6. 存储过程系列之调试存储过程 SQL Server 2005

    在数据库中直接调试  在数据库中直接调试是调试SQL Server 2005的存储过程的最简单的方法. 在Visual Stuido的IDE中你可以选择单步执行存储过程,然后就可以一条语句一条语句地单 ...

  7. Oracle备忘录

    习惯是一个数据库中有多个用户,但是一个用户对应一个系统 数据库管理员主要职责: 每个Oracle数据库应该至少有一个数据库管理员(dba),对于一个小的数据库,一个dba就够了,但是对于一个大的数据库 ...

  8. WebChart网页局域网聊天系列(一):ActiveX插件编写

    第一步:创建ActiveX控件类库,在解决方案中右击添加Window窗体控件库 在该类库属性中,设置 使程序集COM可见,同时设置为COM互操作注册 另外在自动生成的文件中AssemblyInfo.c ...

  9. LightOJ 1356 Prime Independence 二分图最大独立集,HK算法

    这个题唯一需要说的就是普通的匈牙利算法是O(nm)的,过不了 然后HK算法可以O(n^0.5m),这个算法可以每次找很多同样长度的最短增广路 分析见:http://www.hardbird.net/l ...

  10. POJ 2411 Mondriaan's Dream

    思路:状态压缩dp,如果在(i,j)位置横着放砖块,那么(i,j)和(i+1.j)都是1,如果竖着放砖块,那么(i,j)为0,(i,j+1)为1,这样每行就可以用一个整数来存放状态,设dp[i][j] ...