基类的指针(Pointers to base class)

继承的好处之一是一个指向子类(derived class)的指针与一个指向基类(base class)的指针是type-compatible的。 本节就是重点介绍如何利用C++的这一重要特性。例如,我们将结合C++的这个功能,重写前面小节中关于长方形rectangle 和三角形 triangle 的程序:

// pointers to base class

#include <iostream.h>
class CPolygon {
protected:int width, height;
public:void set_values (int a, int b) {width=a; height=b;}
};
class CRectangle: public CPolygon {
public:int area (void) {return (width * height);}
};
class CTriangle: public CPolygon {
public:int area (void) {return (width * height / 2);}
};
int main () {CRectangle rect;CTriangle trgl;CPolygon * ppoly1 = &rect;CPolygon * ppoly2 = &trgl;ppoly1->set_values (4,5);ppoly2->set_values (4,5);cout << rect.area() << endl;cout << trgl.area() << endl;return 0;
}
20
10

在主函数 main 中定义了两个指向class CPolygon的对象的指针,即 *ppoly1 和 *ppoly2。 它们被赋值为rect 和 trgl的地址,因为rect 和 trgl是CPolygon 的子类的对象,因此这种赋值是有效的。

使用*ppoly1 和 *ppoly2 取代rect 和trgl 的唯一限制是*ppoly1 和 *ppoly2 是CPolygon* 类型的,因此我们只能够引用CRectangle 和 CTriangle 从基类CPolygon中继承的成员。正是由于这个原因,我们不能够使用*ppoly1 和 *ppoly2 来调用成员函数 area(),而只能使用rect 和 trgl来调用这个函数。

要想使CPolygon 的指针承认area()为合法成员函数,必须在基类中声明它,而不能只在子类进行声明(见下一小节)。

虚拟成员(Virtual members)

如果想在基类中定义一个成员留待子类中进行细化,我们必须在它前面加关键字virtual ,以便可以使用指针对指向相应的对象进行操作。

请看一下例子:

// virtual members

#include <iostream.h>
class CPolygon {protected:int width, height;public:void set_values (int a, int b)  {width=a;height=b;}virtual int area (void) { return (0); }
};
class CRectangle: public CPolygon {public:int area (void) { return (width * height); }
};
class CTriangle: public CPolygon {public:int area (void) {return (width * height / 2);}
};
int main () {CRectangle rect;CTriangle trgl;CPolygon poly;CPolygon * ppoly1 = &rect;CPolygon * ppoly2 = &trgl;CPolygon * ppoly3 = &poly;ppoly1->set_values (4,5);ppoly2->set_values (4,5);ppoly3->set_values (4,5);cout << ppoly1->area() << endl;cout << ppoly2->area() << endl;cout << ppoly3->area() << endl;return 0;
}
20
10
0

现在这三个类(CPolygon, CRectangle 和 CTriangle) 都有同样的成员:width, height, set_values() 和 area()。

area() 被定义为virtual 是因为它后来在子类中被细化了。你可以做一个试验,如果在代码种去掉这个关键字(virtual),然后再执行这个程序,三个多边形的面积计算结果都将是 0 而不是20,10,0。这是因为没有了关键字virtual ,程序执行不再根据实际对象的不用而调用相应area() 函数(即分别为CRectangle::area(), CTriangle::area() 和 CPolygon::area()),取而代之,程序将全部调用CPolygon::area(),因为这些调用是通过CPolygon类型的指针进行的。

因此,关键字virtual 的作用就是在当使用基类的指针的时候,使子类中与基类同名的成员在适当的时候被调用,如前面例子中所示。

注意,虽然本身被定义为虚拟类型,我们还是可以声明一个CPolygon 类型的对象并调用它的area() 函数,它将返回0 ,如前面例子结果所示。

抽象基类(Abstract base classes)

基本的抽象类与我们前面例子中的类CPolygon 非常相似,唯一的区别是在我们前面的例子中,我们已经为类CPolygon的对象(例如对象poly)定义了一个有效地area()函数,而在一个抽象类(abstract base class)中,我们可以对它不定义,而简单得在函数声明后面写 =0 (等于0)。

类CPolygon 可以写成这样:

// abstract class CPolygon

class CPolygon {protected:int width, height;public:void set_values (int a, int b) {width=a;height=b;}virtual int area (void) =0;
};

注意我们是如何在virtual int area (void)加 =0 来代替函数的具体实现的。这种函数被称为纯虚拟函数(pure virtual function),而所有包含纯虚拟函数的类被称为抽象基类(abstract base classes)。

抽象基类的最大不同是它不能够有实例(对象),但我们可以定义指向它的指针。因此,像这样的声明:

CPolygon poly;

对于前面定义的抽象基类是不合法的。

然而,指针:

CPolygon * ppoly1;
CPolygon * ppoly2

是完全合法的。这是因为该类包含的纯虚拟函数(pure virtual function) 是没有被实现的,而又不可能生成一个不包含它的所有成员定义的对象。然而,因为这个函数在其子类中被完整的定义了,所以生成一个指向其子类的对象的指针是完全合法的。

下面是完整的例子:

// virtual members

#include <iostream.h>
class CPolygon {protected:int width, height;public:void set_values (int a, int b) {width=a;height=b;}virtual int area (void) =0;
};
class CRectangle: public CPolygon {public:int area (void) { return (width * height); }
};
class CTriangle: public CPolygon {public:int area (void) {return (width * height / 2);}
};
int main () {CRectangle rect;CTriangle trgl;CPolygon * ppoly1 = &rect;CPolygon * ppoly2 = &trgl;ppoly1->set_values (4,5);ppoly2->set_values (4,5);cout << ppoly1->area() << endl;cout << ppoly2->area() << endl;return 0;
}
20
10

再看一遍这段程序,你会发现我们可以用同一种类型的指针(CPolygon*)指向不同类的对象,至一点非常有用。 想象一下,现在我们可以写一个CPolygon 的成员函数,使得它可以将函数area()的结果打印到屏幕上,而不必考虑具体是为哪一个子类。

// virtual members

#include <iostream.h>
class CPolygon {protected:int width, height;public:void set_values (int a, int b) {width=a;height=b;}virtual int area (void) =0;void printarea (void) {cout << this->area() << endl;}
};
class CRectangle: public CPolygon {public:int area (void) { return (width * height); }
};
class CTriangle: public CPolygon {public:int area (void) {return (width * height / 2);}
};
int main () {CRectangle rect;CTriangle trgl;CPolygon * ppoly1 = &rect;CPolygon * ppoly2 = &trgl;ppoly1->set_values (4,5);ppoly2->set_values (4,5);ppoly1->printarea();ppoly2->printarea();return 0;
}
20
10

记住,this 代表代码正在被执行的这一个对象的指针。

抽象类和虚拟成员赋予了C++ 多态(polymorphic)的特征,使得面向对象的编程object-oriented programming成为一个有用的工具。这里只是展示了这些功能最简单的用途。想象一下如果在对象数组或动态分配的对象上使用这些功能,将会节省多少麻烦

C++ 面向对象(四)—— 多态 (Polymorphism)相关推荐

  1. Java 面向对象:多态的理解

    Java 面向对象:多态的理解 一.多态的定义 一个对象的实际类型是确定的,但可以指向引用对象的类型可以有很多(父类,有关系的类) 操作符的多态 +可以作为算数运算,也可以作为字符串连接 类的多态 父 ...

  2. 12 Java面向对象之多态

    JavaSE 基础之十二 12 Java面向对象之多态 ① 多态的概念及分类 多态的概念:对象的多种表现形式和能力 多态的分类 1. 静态多态:在编译期间,程序就能决定调用哪个方法.方法的重载就表现出 ...

  3. Go语言的多态(Polymorphism)

    Go的多态(Polymorphism) 是怎么实现的? 这几天查资料下面的代码写的很容易看懂. 看代码吧.不多解释了. package mainimport "fmt"type A ...

  4. swift面向对象之多态与继承

    swift面向对象之多态与继承 1.多态 运行时多态 运行时多态的要点 1.要有继承关系的存在 2.子类要重写父类的方法 3.父类类型的引用指向子类实例 2.继承 1.继承的注意 继承可以用于类而不能 ...

  5. Golang笔记-面向对象编程-多态/类型断言

    面向对象编程-多态 基本介绍 变量(实例)具有多种形态.面向对象的第三大特征,在 Go 语言,多态特征是通过接口实现的.可 以按照统一的接口来调用不同的实现.这时接口变量就呈现不同的形态. 快速入门 ...

  6. python面向对象三大特性_Python面向对象之多态原理与用法案例分析

    本文实例讲述了Python面向对象之多态原理与用法.分享给大家供大家参考,具体如下: 目标 多态 面向对象三大特性 封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中 定义类的准则 继承 ...

  7. 学java教程之面向对象(四)

    学编程吧学java教程之面向对象(四)发布了,欢迎通过xuebiancheng8.com来访问 本次课来分析java面向对象之构造方法.什么是构造方法呢,构造方法听名字顾名思义,构造的时候执行的方法就 ...

  8. python中的多态用法_Python面向对象之多态原理与用法案例分析

    本文实例讲述了Python面向对象之多态原理与用法.分享给大家供大家参考,具体如下: 目标 多态 面向对象三大特性 封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中 定义类的准则 继承 ...

  9. Java基础篇--继承(inherit),多态(Polymorphism)

    Java基础篇--继承(inherit),多态(Polymorphism) 1. 继承概述 1.1 什么是继承 1.2 为什么要使用继承 1.3 继承的特点 1.4 继承的优点 2. 组合设计模式 2 ...

  10. Python面向对象编程---多态

    Python面向对象编程-多态 定义: 是一种使用对象的方式,子类重写父类的方法(非必须),调用不同子类对象的相同父类方法,可以产生不同的执行结果,简言之就是:传入不同的对象,产生不同的结果. 好处: ...

最新文章

  1. Linux 问题解决 :/lib/systemd/systemd-journald 占用内存过高
  2. 如何在Kubernetes集群动态使用 NAS 持久卷
  3. 《CIO新思维III-变革时代的企业IT战略与实务》即将出版,战略观点征集活动中...
  4. SAP Cloud SDK for JavaScript 的搭建和使用方法介绍
  5. c语言while运行出现错误,【图片】为什么我的while(1)不执行啊?【c语言吧】_百度贴吧...
  6. mysql innodb status_查看innodb的运行状态
  7. 哈啰出行赴美递交招股书:2020年营收60亿元 顺风车成新增长极
  8. java如何调用脚本_Java如何调用脚本的特定功能?
  9. 63.magento 后台重置密码
  10. slf4j打印未捕获异常信息_谁再悄咪咪的吃掉异常,我上去就是一 JIO
  11. 中国GBA模拟器先驱李可文不幸去世
  12. uniapp适配pc_uniapp+Html5端实现PC端适配
  13. 如何实现局域网时间同步
  14. 一条简单的sql语句导致的系统问题(r4笔记第51天)
  15. Spring Cloud Alibaba入门实践(五)-远程调用Feign
  16. Ubuntu18.04系统硬盘分区方法
  17. 【用户投稿】优麒麟社区懒人版本(含软件全家桶)一键安装
  18. Kubespray安装kubernetes
  19. iOS开发——Siri语音识别
  20. Thinkpad T430 关机蓝屏后重启--问题分析及解决(转载)

热门文章

  1. zookeeper windows 下安装
  2. linux批量远程多服务器FTP并下载文件的脚本
  3. php判断 二维数组中 是否 存在某个一维数组
  4. LInux 字符设备驱动程序
  5. Discuz论坛架设从零起步之四
  6. plotly python_使用Plotly for Python时的基本思路
  7. 数据特征分析-统计分析
  8. APP应用 HTTP/1.0中keep-alive
  9. 你不知道的JavaScript-0
  10. Linux多命令协作:管道及重定向