细谈多态

C++的三大特性:封装、继承、多态。

类的多态特性是支持面向对象的语言最主要的特性,有过非面向对象语言开发经历的人,通常对这一章节的内容会觉得不习惯,因为很多人错误的认为,支持类的封装的语言就是支持面向对象的,其实不然,Visual BASIC 6.0 是典型的非面向对象的开发语言,但是它的确是支持类,支持类并不能说明就是支持面向对象,能够解决多态问题的语言,才是真正支持面向对象的开发的语言,所以务必提醒有过其它非面向对象语言基础的读者注意!

多态的这个概念稍微有点模糊,如果想在一开始就想用清晰用语言描述它,让读者能够明白,似乎不太现实,所以我们先看如下代码:

#include <iostream>
using namespace std;   class Vehicle
{
public:   Vehicle(float speed,int total) { Vehicle::speed=speed; Vehicle::total=total; } void ShowMember() { cout<<speed<<"|"<<total<<endl; }
protected:   float speed; int total;
};
class Car:public Vehicle
{
public:   Car(int aird,float speed,int total):Vehicle(speed,total)   {   Car::aird=aird;   } void ShowMember() { cout<<speed<<"|"<<total<<"|"<<aird<<endl; }
protected:   int aird;
};   void main()
{   Vehicle a(120,4); a.ShowMember(); Car b(180,110,4); b.ShowMember(); cin.get();
}

在c++中是允许派生类重载基类成员函数的,对于类的重载来说,明确的,不同类的对象,调用其类的成员函数的时候,系统是知道如何找到其类的同名成员,上面代码中的a.ShowMember();,即调用的是Vehicle::ShowMember(),b.ShowMember();,即调用的是Car::ShowMemeber();。

但是在实际工作中,很可能会碰到对象所属类不清的情况,下面我们来看一下派生类成员作为函数参数传递的例子,代码如下:

#include <iostream>
using namespace std;   class Vehicle
{
public:   Vehicle(float speed,int total) { Vehicle::speed=speed; Vehicle::total=total; } void ShowMember() { cout<<speed<<"|"<<total<<endl; }
protected:   float speed; int total;
};
class Car:public Vehicle
{
public:   Car(int aird,float speed,int total):Vehicle(speed,total)   {   Car::aird=aird;   } void ShowMember() { cout<<speed<<"|"<<total<<"|"<<aird<<endl; }
protected:   int aird;
};   void test(Vehicle &temp)
{ temp.ShowMember();
} int main()
{ Vehicle a(120,4); Car b(180,110,4); test(a); test(b); // cin.get(); return 0;
}

例子中,对象a与b分别是基类和派生类的对象,而函数test的形参却只是Vehicle类的引用,按照类继承的特点,系统把Car类对象看做是一个Vehicle类对象,因为Car类的覆盖范围包含Vehicle类,所以test函数的定义并没有错误,我们想利用test函数达到的目的是,传递不同类对象的引用,分别调用不同类的,重载了的,ShowMember成员函数,但是程序的运行结果却出乎人们的意料,系统分不清楚传递过来的基类对象还是派生类对象,无论是基类对象还是派生类对象调用的都是基类的ShowMember成员函数。
为了要解决上述不能正确分辨对象类型的问题,c++提供了一种叫做多态性(polymorphism)的技术来解决问题,对于例程序1,这种能够在编译时就能够确定哪个重载的成员函数被调用的情况被称做先期联编(early binding),而在系统能够在运行时,能够根据其类型确定调用哪个重载的成员函数的能力,称为多态性,或叫滞后联编(late binding),下面我们要看的例程3,就是滞后联编,滞后联编正是解决多态问题的方法。

#include <iostream>
using namespace std;   class Vehicle
{
public:   Vehicle(float speed,int total) { Vehicle::speed = speed; Vehicle::total = total; } virtual void ShowMember()//虚函数  { cout<<speed<<"|"<<total<<endl; }
protected:   float speed; int total;
};
class Car:public Vehicle
{
public:   Car(int aird,float speed,int total):Vehicle(speed,total)   {   Car::aird = aird;   } virtual void ShowMember()//虚函数,在派生类中,由于继承的关系,这里的virtual也可以不加  { cout<<speed<<"|"<<total<<"|"<<aird<<endl; }
public:   int aird;
}; void test(Vehicle &temp)
{ temp.ShowMember();
} int main()
{   Vehicle a(120,4); Car b(180,110,4); test(a); test(b);
}

运行结果为

120|4
110|4|180

多态特性的工作依赖虚函数的定义,在需要解决多态问题的重载成员函数前,加上virtual关键字,那么该成员函数就变成了虚函数,从上例代码运行的结果看,系统成功的分辨出了对象的真实类型,成功的调用了各自的重载成员函数。

多态特性让程序员省去了细节的考虑,提高了开发效率,使代码大大的简化,当然虚函数的定义也是有缺陷的,因为多态特性增加了一些数据存储和执行指令的开销,所以能不用多态最好不用。

虚函数的定义要遵循以下重要规则:

  • 如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不同,或者是返回类型不同,那么即使加上了virtual关键字,也是不会进行滞后联编的。

  • 只有类的成员函数才能说明为虚函数,因为虚函数仅适合用与有继承关系的类对象,所以普通函数不能说明为虚函数。

  • 静态成员函数不能是虚函数,因为静态成员函数的特点是不受限制于某个对象。

  • 内联(inline)函数不能是虚函数,因为内联函数不能在运行中动态确定位置。即使虚函数在类的内部定义,但是在编译的时候系统仍然将它看做是非内联的。******

  • 构造函数不能是虚函数,因为构造的时候,对象还是一片未定型的空间,只有构造完成后,对象才是具体类的实例。

  • 析构函数可以是虚函数****,**而且通常声名为虚函数。

说明一下,虽然我们说使用虚函数会降低效率,但是在处理器速度越来越快的今天,将一个类中的所有成员函数都定义成为virtual总是有好处的,它除了会增加一些额外的开销是没有其它坏处的,对于保证类的封装特性是有好处的。

对于上面虚函数使用的重要规则6,我们有必要用实例说明一下,为什么具备多态特性的类的析构函数,有必要声明为****virtual

#include <stdfix.h>
#include <iostream>
using namespace std;class Vehicle
{public:Vehicle(float speed, int total){Vehicle::speed = speed;Vehicle::total = total;}virtual void ShowMember(){cout << speed << "|" << total << endl;}virtual ~Vehicle(){cout << "载入Vehicle基类析构函数" << endl;}protected:float speed;int total;
};class Car : public Vehicle
{public:Car(int aird, float speed, int total) : Vehicle(speed, total){Car::aird = aird;}virtual void ShowMember(){cout << speed << "|" << total << "|" << aird << endl;}virtual ~Car(){cout << "载入Car派生类析构函数" << endl;}protected:int aird;
};void test(Vehicle &temp)
{temp.ShowMember();
}void DelPN(Vehicle *temp)
{delete temp;
}int main()
{Car *a = new Car(100, 1, 1);a->ShowMember();DelPN(a);return 0;
}

输出结果为

1|1|100
载入Car派生类析构函数
载入Vehicle基类析构函数

从上例代码的运行结果来看,当调用DelPN(a);后,在析构的时候,系统成功的确定了先调用Car类的析构函数,而如果将析构函数的virtual修饰去掉,再观察结果,会发现析构的时候,始终只调用了基类的析构函数,由此我们发现,多态的特性的virtual修饰,不单单对基类和派生类的普通成员函数有必要,而且对于基类和派生类的析构函数同样重要。

c++重启——细谈多态相关推荐

  1. windows server 2003 DNS 细谈系列之(二)记录类型、数据库

    windows server 2003 DNS 细谈系列之(二)记录类型.数据库<?xml:namespace prefix = o ns = "urn:schemas-microso ...

  2. QT乱码总结4.细谈本地编码

    QT乱码总结0.Qt乱码产生因素 https://blog.csdn.net/liujiayu2/article/details/103167953 QT乱码总结1.Unicode 和 UTF-8 h ...

  3. 再谈多态——向上映射及VMT/DMT(转)

    在<浅谈多态--概念描述>一文中,提到多态的本质就是"将子类类型的指针赋值给父类类型的指 针".那么,为什麽这种赋值是允许的,或者说是安全的呢?反过来行不行?虚函数的动 ...

  4. Java程序员从笨鸟到菜鸟之(五十一)细谈Hibernate(二)开发第一个hibernate基本详解...

    在上篇博客中,我们介绍了<hibernate基本概念和体系结构>,也对hibernate框架有了一个初步的了解,本文我将向大家简单介绍Hibernate的核心API调用库,并讲解一下它的基 ...

  5. 细谈getRequestDispatcher()与sendRedirect()的区别

    问题?细谈getRequestDispatcher()与sendRedirect()的区别 首先我们要知道: (1)request.getRequestDispatcher()是请求转发,前后页面共享 ...

  6. 三个角度细谈:如何发挥朋友圈广告的威力

    加入腾讯理财通一年以来,我已经亲自操刀投过9个朋友圈广告,单次广告多则2000万,少则300万均有涉猎.此文,是我对于过去一年投放的总结与沉淀,回顾过去投放历史,沉淀做好朋友圈广告的方法.从广告主的角 ...

  7. 再谈多态——向上映射及VMT/DMT

    再谈多态--向上映射及VMT/DMT 作者:Nicrosoft(nicrosoft@sunistudio.com) 2001.10.9 个人主页:http://www.sunistudio.com/n ...

  8. 细谈JVM垃圾回收与部分底层实现

    JVM系列文章目录 初识JVM 深入理解JVM内存区域 玩转JVM对象和引用 JVM分代回收机制和垃圾回收算法 细谈JVM垃圾回收与部分底层实现 Class文件结构及深入字节码指令 玩转类加载和类加载 ...

  9. 细谈Type-C、PD原理(上/下)

    一.Type-C简介以及历史 自1998年以来,USB发布至今,USB已经走过20个年头有余了.在这20年间,USB-IF组织发布N种接口状态,包括A口.B口.MINI-A.MINI-B.Micro- ...

最新文章

  1. python爬取boss直聘招聘信息_Python笔记-爬取Boss直聘的招聘信息
  2. CSS基础篇--强制性换行word-break与word-wrap的使用
  3. Pytorch基础(十)——优化器(SGD,Adagrad,RMSprop,Adam,LBFGS等)
  4. Linq To Sql进阶系列 -目录导航
  5. DataGridView插入图片
  6. java工程窗口程序_java工程开发之图形化界面之(第二课)
  7. git 解决ahead behind分叉以及删除远端commit
  8. 一文总结数据科学家常用的Python库(上)
  9. 关闭计算机端口的命令行,关闭端口命令,小编教你如何关闭电脑80端口
  10. fiddler手机模拟器抓包_fiddler抓取手机模拟器数据
  11. 解决QQ邮箱接收不到Stream邮件问题
  12. windows server2012计算机管理“系统工具”里面没有“本地用户和组”怎么办?
  13. 每日写题分享--包含min函数的栈/双栈实现
  14. java取石子_取石子游戏 - Snowdream - BlogJava
  15. Ant Design的入门使用教程
  16. [乡土民间故事_徐苟三传奇]第廿九回_蠢财主落水知上当
  17. You Can’t Future-Proof Solutions
  18. 我心中的计算机作文500,我眼中的作文500字
  19. 被问麻了,Spring 如何处理循环依赖?
  20. 极光行动_流量分析_漏洞复现

热门文章

  1. 轻代码研发平台-开发如此简单
  2. 逻辑分页和排序 的思路
  3. Java精炼语言语法描述
  4. 【大数据】HBase入门学习
  5. 冰冰学习笔记:vim工具的基本操作
  6. 你知道智能机柜与普通机柜有哪些区别吗
  7. zabbix监控配置-邮箱警告<五>
  8. 通过14个示例彻底掌握 linux ls 命令的使用
  9. HTML CSS 鼠标样式效果
  10. Java Web基本编程