转载博客:https://mp.weixin.qq.com/s?__biz=MzAxNzYzMTU0Ng==&mid=2651289202&idx=1&sn=431ffd1fae4823366a50b68aed2838d4&chksm=80114627b766cf31f72018ef5f1fe29591e9f6f4bd72018e7aea849342ca6f0a271fb38465ae#rd

学习C++的多态性,你必然听过虚函数的概念,你必然知道有关她的种种语法,但你未必了解她为什么要那样做,未必了解她种种行为背后的所思所想。深知你不想在流于表面语法上的蜻蜓点水似是而非,今天我们就一起来揭开挡在你和虚函数(女神)之间的这一层窗户纸。

首先,我们要搞清楚女神的所作所为,即语法规范。然后再去探究她背后的逻辑道理。她的语法说来也不复杂,概括起来就这么几条:

  1. 在类成员方法的声明(不是定义)语句前面加个单词:virtual,她就会摇身一变成为虚函数。

  2. 在虚函数的声明语句末尾中加个 =0 ,她就会摇身一变成为纯虚函数。

  3. 子类可以重新定义基类的虚函数,我们把这个行为称之为复写override)。

  4. 不管是虚函数还是纯虚函数,基类都可以为提供他们的实现implementation),如果有的话子类可以调用基类的这些实现。

  5. 子类可自主选择是否要提供一份属于自己的个性化虚函数实现。

  6. 子类必须提供一份属于自己的个性化纯虚函数实现。

语法都列出来了,背后的逻辑含义是什么呢?我们用一个生动的例子来说明,虚函数是如何实现多态性的。

假设我们要设计关于飞行器的类,并且提供类似加油、飞行的实现代码,考虑具体情况,飞行器多种多样,有民航客机、歼击机、轰炸机、直升机、热气球、火箭甚至窜天猴、孔明灯、纸飞机!

假设我们有一位牛得一比的飞行员,他能给各式各样的飞行器加充不同的燃料,也能驾驶各式各样的飞行器。下面我们来看看这些类可以怎么设计。

首先,飞行器。由于我们假设所有的飞行器都有两种行为:加油飞行。因此我们可以将这两种行为抽象到一个基类中,并由它来派生具体的某款飞行器。

这是一个描述飞行器的基类,提供了两个基本的功能:加油和飞行

class aircraft

{

    void refuel(); // 加燃油,普通虚函数

    void fly()=0;  // 飞行,纯虚函数

};

 

这是一个普通虚函数,意味着基类希望子类提供自己的个性化实现代码,但基类同时也提供一个缺省的虚函数实现版本,在子类不复写该虚函数的情况下作为备选方案

void aircraft::refuel()

{

// 加充通用型燃油

}

 

这是一个纯虚函数,意味着基类强制子类必须提供自己的个性化版本,否则编译将失败。但让人惊奇的是,C++仍然保留了基类提供该纯虚函数代码实现的权利,这也许是给千变万化的实际情况留下后路

void aircraft::fly()

{

// 一种不应该被使用的缺省飞行方案

}

有了基类aircraft,我们就可以潇洒地派生出各式各样的飞行器了,比如轰炸机直升机

轰炸机类定义,复写了加油和飞行

class bomber : public aircraft

{

    void refuel(){} // 加充轰炸机的特殊燃油!

    void fly(){} // 轰炸机实弹飞行!

};

直升机类定义,复写了飞行代码,但没有复写加油

class copter: public aircraft

{

    void fly(){} // 直升机盘旋!

};

以上代码可以看到,直升机类(copter)没有自己的加油方式,直接使用了基类提供的缺省加油的方式。此时我们来定义一个能驾驭多机型的王牌飞行员类:

一个能王牌飞行员

class pilot

{

    void refuelPlane(aircraft *p);

    void dirvePlane(aircraft *p);

};

给我什么飞机我就加什么油

void pilot::refuelPlane(aircraft *p)

{

    p->refuel();

}

给我什么飞机我就怎么飞

void pilot::dirvePlane(aircraft *p)

{

    p->fly();

}

很明显,我们接下来要给这位很浪的飞行员表演一下操纵各种飞行器的机会,我们来定义各种飞机然后丢给他去处理

定义两架飞机,一架轰6K,一架武直10

aircraft *H6K = new bomber;

aircraft *WZ10 = new copter;

来一个王牌飞行员,给H6K加油(加的是轰炸机特殊燃油),并且按照H6K的特点飞行

pilot Jack;

Jack.refuelPlane(H6K);  // 加充轰炸机燃油

Jack.flyPlane(H6K); // 轰炸机实弹飞行

给WZ10加油(加的是基类提供的通用燃油),按照WZ10的特点飞行

Jack.refuelPlane(WZ10); // 加充通用型燃油

Jack.flyPlane(WZ10); // 直升机盘旋

上述代码体现了最经典的所谓多态的场景,给Jack不同的飞机,就能表现不同的结果。虚函数和纯虚函数都能做到这一点,区别是,子类如果不提供虚函数的实现,那就会自动调用基类的缺省方案。而子类如果不提供纯虚函数的实现,则编译将会失败。基类提供的纯虚函数实现版本,无法通过指向子类对象的基类类型指针或引用来调用,因此不能作为子类相应虚函数的备选方案。下面给出总结。

第一,当基类的某个成员方法,在大多数情形下都应该由子类提供个性化实现,但基类也可以提供一个备选方案的时候,请将其设计为虚函数。例如飞行器的加油动作,每种不同的飞行器原则上都应该有自己的个性化的加充然后的方式,但也不免可以有一种通用的然后和加充方式。

第二,当基类的某个成员方法,必须由子类提供个性化实现的时候,请将其设计为纯虚函数。例如飞行器的飞行动作,逻辑上每种飞行器都必须提供为其特殊设计的个性化飞行行为,而不应该有任何一种“通用的飞行方式”。

第三,使用一个基类类型的指针或者引用,来指向子类对象,进而调用经由子类复写了的个性化的虚函数,这是C++实现多态性的一个最经典的场景

第四,基类提供的纯虚函数的实现版本,并非为了多态性考虑,因为指向子类对象的基类指针和引用无法调用该版本。纯虚函数在基类中的实现跟多态性无关,它只是提供了一种语法上的便利,在变化多端的应用场景中留有后路

第五,虚函数和普通的函数实际上是存储在不同的区域的,虚函数所在的区域是可被覆盖(也称复写override)的,每当子类定义相同名称的虚函数时就将原来基类的版本给覆盖了,另一侧面也说明了为什么基类中声明的虚函数在后代类中不需要另加声明一律自动为虚函数,因为它所存储的位置不会发生改变。而普通函数的存储区域不会覆盖,每个类都有自己独立的区域互不相干。

最后附一幅草图以供参考

c++ 虚函数,纯虚函数的本质区别的更多相关文章

  1. 虚函数&纯虚函数&抽象类&虚继承

    C++ 虚函数&纯虚函数&抽象类&接口&虚基类   1. 多态 在面向对象语言中,接口的多种不同实现方式即为多态.多态是指,用父类的指针指向子类的实例(对象),然后通过 ...

  2. 【转】C++ 虚函数&纯虚函数&抽象类&接口&虚基类

    1. 动态多态 在面向对象语言中,接口的多种不同实现方式即为多态.多态是指,用父类的指针指向子类的实例(对象),然后通过父类的指针调用实际子类的成员函数. 多态性就是允许将子类类型的指针赋值给父类类型 ...

  3. C++ 虚函数&纯虚函数&抽象类&接口&虚基类(转)

    http://www.cnblogs.com/fly1988happy/archive/2012/09/25/2701237.html 1. 多态 在面向对象语言中,接口的多种不同实现方式即为多态.多 ...

  4. C++学习基础十二——纯虚函数与抽象类

    一.C++中纯虚函数与抽象类: 1.含有一个或多个纯虚函数的类成为抽象类,注意此处是纯虚函数,而不是虚函数. 2.如果一个子类继承抽象类,则必须实现父类中的纯虚函数,否则该类也为抽象类. 3.如果一个 ...

  5. C++学习笔记(十二):类继承、虚函数、纯虚函数、抽象类和嵌套类

    类继承 在C++类继承中,一个派生类可以从一个基类派生,也可以从多个基类派生. 从一个基类派生的继承称为单继承:从多个基类派生的继承称为多继承. //单继承的定义 class B:public A { ...

  6. c++,纯虚函数与抽象类

    1.纯虚函数的定义: (1)虚函数被“初始化”为0的函数.声明纯虚函数的一般形式是virtual 函数类型 函数名(参数表列) =0;(2)纯虚函数没有函数体:(3)最后面的“=0”并不表示函数返回值 ...

  7. C++多态、虚函数、纯虚函数、抽象类、虚基类

    一.C++多态 C++的多态包括静态多态和动态多态.静态多态包括函数重载和泛型编程,动态多态包括虚函数.静态多态是指在编译期间就可以确定,动态多态是指在程序运行时才能确定. 二.虚函数 1.虚函数为类 ...

  8. C++基础知识 基类指针、虚函数、多态性、纯虚函数、虚析构

    一.基类指针.派生类指针 父类指针可以new一个子类对象 二.虚函数 有没有一个解决方法,使我们只定义一个对象指针,就可以调用父类,以及各个子类的同名函数? 有解决方案,这个对象指针必须是一个父类类型 ...

  9. C++多态、虚函数、纯虚函数、抽象类

    多态 同一函数调用形式(调用形式形同)可以实现不同的操作(执行路径不同),就叫多态. 两种多态: (1)静态多态:分为函数重载和运算符重载,编译时系统就能决定调用哪个函数. (2)动态多态(简称多态) ...

  10. C++ 虚函数、纯虚函数、虚继承

    1)C++利用虚函数来实现多态. 程序执行时的多态性通过虚函数体现,实现运行时多态性的机制称爲动态绑定:与编译时的多态性(通过函数重载.运算符重载体现,称爲静态绑定)相对应. 在成员函数的声明前加上v ...

随机推荐

  1. UVA1395 Slim Span(kruskal)

    题目:Slim Span UVA 1395 题意:给出一副无向有权图,求生成树中最小的苗条度(最大权值减最小权值),如果不能生成树,就输出-1: 思路:将所有的边按权值有小到大排序,然后枚举每一条边, ...

  2. HDU 4027(线段树)

    HDU4027 题意:操作指令为0时,对区间[x,y]之间的数字进行开平方:指令为1的时候,对区间[x,y]之间的数字求和并输出: 思路:线段树处理就OK了,但是64位内的数最多开8次平方就为1了(开 ...

  3. odoo widget 标签介绍

    widget="statusbar" 头部状态条标签 widget="email" 电子邮件地址标签 widget="selection" ...

  4. Flask - 内置Session

    目录 Flask - 内置Session 基本用法 给视图添加装饰器验证 Flask - 内置Session Flask中的Session非常的奇怪,他会将你的SessionID存放在客户端的Cook ...

  5. 关于单片机编程里面调用sprintf死机的解决方法及原因分析

    好久之前的做的笔记,这里贴出. char String[100];//直接用数组代替指针即可解决 下面代代码下载至单片机中,发现会出现单片机死机问题 #include "stdio.h&qu ...

  6. 清北学堂模拟赛d1t4 一道图论好题(graph)

    题目描述 LYK有一张无向图G={V,E},这张无向图有n个点m条边组成.并且这是一张带权图,不仅有边权还有点权. LYK给出了一个子图的定义,一张图G’={V’,E’}被称作G的子图,当且仅当 ·G ...

  7. HyperLedger项目以及社区

    本文不涉及任何技术开发的内容,仅供你跟同学.同事吹牛B之用.就像很多牛人总爱讲历史典故一样. 一.诞生与现状 HyperLedger 诞生于2015年12月17日,HyperLedger 追寻Apac ...

  8. 【ACM】hdu_1276_士兵队列训练问题_201308131032

    士兵队列训练问题Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Sub ...

  9. iphone照片查看器

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/stri ...

  10. Linux C程序存储空间的逻辑布局

    原文:http://blog.chinaunix.net/uid-20692625-id-3057053.html ------------------------------------------ ...