当前课程知识点:基于Linux的C++ >  第九讲 类与对象 >  9.12 多态(一) >  LinuxCPP0912

返回《基于Linux的C++》慕课在线视频课程列表

LinuxCPP0912在线视频

LinuxCPP0912

下一节:LinuxCPP0913

返回《基于Linux的C++》慕课在线视频列表

LinuxCPP0912课程教案、知识点、字幕

最后一节是多态 什么叫多态呢

多态其实就是一种物体的几种不同的形态

所以叫多态 听上去很高级

在我们面向对象程序设计中

多态性是非常重要的一个概念

想写好程序 不理解多态性肯定是不行的

但实际上 这个概念我们一直在用

而我们压根就没有注意到

首先 你从小学开始

你用到的那个操作就是多态的

你比如说加法 你一开始学1+1=2

后来你学1.0+2.0=3.0

也就是先是整数加法

后来你学了小数加法

后来你又学了整数和小数加法

要小数点对齐

再后来你又学了代数的加法

再后来呢 你又会学抽象代数的加法

所有的这些操作 你注意看

这个加法操作本身实际上是多态的

就一个加法符号

我们其实把它理解成多态的一个加法操作

它在整数加法上 在小数加法上

在代数加法上

它的做法可能都是不一样的

这个其实就叫多态

到我们计算机里边 那更是完全不一样了

因为整数加法指令是整数加法指令

小数加法指令是小数加法指令

那是两个不同指令

同样的一个加法操作

是会翻译成两条不同指令的

压根就不是一回事啊

所以那个加法一定是个多态的

所以你看我们一直就在用它

而我们编程真地就离不开它

也就是多态的目的就是为了让不同对象

在接受到同样消息的时候

能够进行不同的响应

我要做一个加法操作

传我整数 传我浮点数 我会做不同指令

一个类的成员函数在不同的类下边

应该做不同响应

我传给基类 这个函数应该做这件事情

我传给派生类 它应该做另外一件事情

这个就叫多态 同一个函数做不同的事

就看它是处于基类还是派生类的

多态性所对应的这个现象

就是对应于同样的成员函数的名称

它在不同的基类和派生类里面

执行的是不同的代码 不同的函数体

所以实现的时候我们使用virtual这个关键字

来声明这个成员函数

就让这个成员函数成为多态的

注意 多态性只影响函数

数据成员不存在多态这个概念

只有成员函数 因为它对应是操作

所以virtual只作用在我们的成员函数上面

我们在成员函数上面写了一个virtual

就表示这个成员函数将成为一个虚函数

维持它的多态性

这个维持 是指从这个类把这个函数

定义成虚函数之后就开始了

它的派生类里边

不管这个同名的函数有没有写virtual

它也是virtual的

也就是说 一日为virtual 永远为virtual

注意这个概念

声明的格式就是函数的原型前面加virtual

这个virtual只需要写在声明里

在函数的实现时候不需要写

我们看这个例子

我们定义了三个对象 一个叫账户

我们这个例子做什么用的呢

实际上处理的是一个银行账户

同学们知道

这个银行账户实际上是分成两大类

一类是储蓄账户

我们平时用的存钱、取钱

像活期存款、定期存款

那个就叫储蓄账户

另外是给予你做结算的目的的

主要是企业在用的那种结算的账户

和我们储蓄账户是不一样的

这是两类不同的账户

我们编程实现它的时候

就必须能够体现这一点

所以我们必须为储蓄账户和结算账户

定义不同的类 它们显然都是相关的

因为它们都是银行的账户

所以我们需要定义一个账户的基类

这个例子里边 我们就定义了这样三个类

一个是基类 抽象的账户 class Account

这里边定义了三个成员函数

只有一个私有的数据成员_balance

表示这个账户的余额

这个就是我们的账户这个类的定义

实现它 内联GetBalance

return_balance就行了

因为这个构造函数也被我们内联了

直接定义在这个类定义里边了

所以它已经被内联了

我们就把GetBalance也定义成内联的

接下来需要实现的就只有一个Print函数了

接下来是两个账户 CheckingAccount

我们的结算账户 它从Account账户公有继承

有自己的构造函数 有PrintBalance

新写的一个函数

还有一个类是储蓄账户 SavingsAccount

也是从Account账户公有继承下来

同样有一个构造函数 有一个PrintBalance函数

你注意看 不管是CheckingAccount

还是SavingsAccount

里边都有两个PrintBalance函数

一个是从基类继承下来的

一个是自己实现的

现在我们要实现这三个PrintBalance函数

对于一个标准的账户来讲

因为它是一个抽象的账户

所以实际上我们在Account这个账户上

我PrintBalance 想打印余额

那实际上是不可以打印的

所以我们cerr 直接向标准错误流里面

输出“余额不可用”信息就完了

当我们在结算账户和储蓄账户打印的时候

那我们就直接就打印它的余额 就完了

代码倒很简单

就都是cout和cerr这样的一个输出

关键我们来看我们程序的使用

我们的main函数里边

定义了一个指向结算账户的一个指针checking

然后new一个CheckingAccount

构造一个结算账户出来

当然是动态构造的

然后把它的地址赋值给checking

然后我又动态构造了一个储蓄账户出来

把它的地址赋值给Savings

Savings是指向SavingsAccount类的一个指针

现在我们定义一个

指向Account类的一个指针 account

然后我把它初始化成checking

我用一个结算账户

去初始化我们的一个抽象账户

我们在account上面调用PrintBalance

想打印我们的余额 savings赋值给account

然后在account上面

再次调用PrintBalance打印余额

然后删除checking 然后删除Savings

代码倒很简单

两个指针分别指向结算账户和储蓄账户

然后再定义指向抽象账户的一个指针

分别用结算账户和储蓄账户指针赋值过去

然后在抽象账户上面打印余额

这个程序的输出结果将会是什么呢

回忆我们前面讲的

同学们现在一定就知道

它打印出来的东西都是cerr

因为我们只能调用Account上面的

那个PrintBalance那个函数

指向基类的一个指针

不管接受的是基类的对象的地址

还是派生类对象的地址

都只能访问它的基类部分

所以访问的都是基类的对象

调用的都是基类的成员函数

你操纵的结果就是cerr

向标准错误流里边输出我们的错误信息

不能真正打印余额

因为你去银行处理这个东西的时候

你说我想看看我银行卡里边有多少钱

就是余额是多少 让她 打印一下

你管的账户叫储蓄账户还是结算账户呢

所以真实来讲 它就是一个账户

我就应该能够打印出来它的余额出来才行

这是非常重要的一个地方

也就是说 我们要的就是

如果account是一个指向基类的指针

那么当我们用它指向派生类的时候

我们应该调用派生类的那个成员函数

把它的余额打印出来

而不是调用基类的那个函数

当在非虚函数情况下

这个工作是做不了的

所以我们必须使用虚函数

我们看我们虚函数的版本

我们简单地在Account这个类的定义处

将PrintBalance这个函数写成virtual

本身这个函数是个const类型的

它并不需要改变_balance的值

所以它本身是个const

最前边要加上virtual表示它是个虚函数

如果在Account这个类上边

将PrintBalance定义成了一个虚函数

那么一切就不同了

我们在CheckingAccount和SavingsAccount下边

继续将PrintBalance这个函数定义成虚函数

因为我们要在这两个账户下边重新地实现它

我们要重新写一遍

所以就要重新把它定义成虚函数

因为我们刚才讲了

一日为虚 终日为虚嘛

既然已经在Account账户上边

将PrintBalance定义成虚函数了

所以在CheckingAccount和SavingsAccount上边

重写PrintBalance的时候

前边不写virtual 它也自动是virtual

不过这可能对你会造成一种困扰

你可能看派生类的时候

就不知道这个函数到底是虚的还是不是虚的

所以我建议同学们 只要它应该是虚的

哪怕是在派生类继续是虚的

所以前面都要加上virtual

反正加上不为错

然后我们按照刚才那个代码去调用

定义一个指向CheckingAccount账户的指针

定义一个指向Savings账户的指针

然后构造这两个对象 分别初始化给它们

然后我将checking赋值给Account

然后我在Account上面调用PrintBalance

然后把Savings再赋值为Account

然后在Account上面再调用一次PrintBalance

最后你来看我们这个程序的运行结果

你会发现当第一次打印PrintBalance的时候

它将调用CheckingAccount类的

那个PrintBalance函数

当它第二次调用PrintBalance的时候

它将打印SavingsAccount账户里面的内容

它将调用SavingsAccount那个里面的

成员函数PrintBalance

也就是说 我这样一个指向基类的指针account

将能够根据它实际指向的那个对象的类型

来调用对应的恰当的那个虚函数

这是一个基类指针

如果它指向基类 它就调用基类的PrintBalance

如果指向派生类

它就调用派生类的PrintBalance

不管那个派生类是CheckingAccount

还是SavingsAccount

总之那个派生类是哪一个

它就调用哪一个类的对应虚函数

很明确吧 这个就叫自适应

它能够自动地适应 它所指向的目标类

它自然就体现出了多态

这个就是虚函数最重要的一个地方

在讲虚函数的时候

有一点是需要强调的

如果基类中有一个虚函数

你的派生类里边不想动它

那你怎么办呢 你可以不写

因为派生类自动地能够继承

基类的全部数据对象和成员函数

所以基类的那个虚函数

自动地在你的派生类中就存在了

你不写它也有 只不过它的行为

和基类的那个函数一模一样

你写了 就意味着新写的这个函数

将会替换基类的对应的那个虚函数

怎么实现的呢 在C++代码里边

它会为每一个这样的类的一个对象

维持着一个虚拟表

我们称为虚拟表的指针

用一个虚拟表的指针指向这样一个虚表

每一个虚表里边就会记录

你这个类所实现所有的虚函数的入口地址

当它是一个基类的时候

它就把基类虚函数的入口地址写进去

如果它是一个派生类的时候

它就把派生类的虚拟函数的基地址写进去

当你构造的是一个派生类对象的时候

它写的实际上是派生类的

那个虚函数的入口地址

所以即使你是使用一个指向基类的指针

指向这个派生类的对象

当它调用那个虚函数的时候

它一查那个虚拟表

查到的依然是派生类的

那个虚函数的入口地址

而不是基类的那个虚函数的入口地址

所以我们的指向基类的那个指针

才能够调用派生类的虚函数

如果是非虚的函数 这个事情是做不了的

它是哪一个类

它就调用哪一个类的对应函数

想调用其它类函数 你必须解析它

调用派生类的函数 它看不见 调用不了

所以它是做不到的

而只有虚函数才能够做到这一点

非常非常重要的机制

基于Linux的C++课程列表:

第一讲 C/C++基本语法元素

-1.1 提纲

--LinuxCPP0101

-1.2 程序设计的基本概念

--LinuxCPP0102

-1.3 简单C/C++程序介绍

--LinuxCPP0103

-1.4 程序设计的基本流程

--LinuxCPP0104

-1.5 基本语法元素

--LinuxCPP0105

-1.6 程序设计风格

--LinuxCPP0106

-1.7 编程实践

--LinuxCPP0107

-第一讲 C/C++基本语法元素--编程实践提交入口

第二讲 程序控制结构

-2.1 提纲

--LinuxCPP0201

-2.2 结构化程序设计基础

--LinuxCPP0202

-2.3 布尔数据

--LinuxCPP0203

-2.4 分支结构

--LinuxCPP0204

-2.5 break语句

--LinuxCPP0205

-2.6 循环结构

--LinuxCPP0206

-2.7 编程实践

--LinuxCPP0207

-第二讲 程序控制结构--编程实践提交入口

第三讲 函数

-3.1 提纲

--LinuxCPP0301

-3.2 函数声明、调用与定义

--LinuxCPP0302

-3.3 函数调用栈框架

--LinuxCPP0303

-3.4 编程实践

--LinuxCPP0304

-第三讲 函数--编程实践提交入口

第四讲 算法

-4.1 提纲

--LinuxCPP0401

-4.2 算法概念与特征

--LinuxCPP0402

-4.3 算法描述

--LinuxCPP0403

-4.4 算法设计与实现

--LinuxCPP0404

-4.5 递归算法(一)

--LinuxCPP0405

-4.6 递归算法(二)

--LinuxCPP0406

-4.7 容错与计算复杂度

--LinuxCPP0407

-4.8 编程实践

--LinuxCPP0408

-第四讲 算法--编程实践提交入口

第五讲 程序组织与开发方法

-5.1 提纲

--LinuxCPP0501

-5.2 库与接口

--LinuxCPP0502

-5.3 随机数库(一)

--LinuxCPP0503

-5.4 随机数库(二)

--LinuxCPP0504

-5.5 作用域与生存期

--LinuxCPP0505

-5.6 典型软件开发流程(一)

--LinuxCPP0506

-5.7 典型软件开发流程(二)

--LinuxCPP0507

-5.8 编程实践

--LinuxCPP0508

-第五讲 程序组织与开发方法--编程实践提交入口

第六讲 复合数据类型

-6.1 提纲

--LinuxCPP0601

-6.2 字符

--LinuxCPP0602

-6.3 数组(一)

--LinuxCPP0603

-6.4 数组(二)

--LinuxCPP0604

-6.5 结构体

--LinuxCPP0605

-6.6 编程实践

--LinuxCPP0606

-第六讲 复合数据类型--编程实践提交入口

第七讲 指针与引用

-7.1 提纲

--LinuxCPP0701

-7.2 指针基本概念

--LinuxCPP0702

-7.3 指针与函数

--LinuxCPP0703

-7.4 指针与复合数据类型(一)

--LinuxCPP0704

-7.5 指针与复合数据类型(二)

--LinuxCPP0705

-7.6 字符串

--LinuxCPP0706

-7.7 动态存储管理(一)

--LinuxCPP0707

-7.8 动态存储管理(二)

--LinuxCPP0708

-7.9 引用

--LinuxCPP0709

-7.10 编程实践

--LinuxCPP0710

-第七讲 指针与引用--编程实践提交入口

第八讲 链表与程序抽象

-8.1 提纲

--LinuxCPP0801

-8.2 数据抽象(一)

--LinuxCPP0802

-8.3 数据抽象(二)

--LinuxCPP0803

-8.4 链表(一)

--LinuxCPP0804

-8.5 链表(二)

--LinuxCPP0805

-8.6 链表(三)

--LinuxCPP0806

-8.7 链表(四)

--LinuxCPP0807

-8.8 函数指针(一)

--LinuxCPP0808

-8.9 函数指针(二)

--LinuxCPP0809

-8.10 抽象链表(一)

--LinuxCPP0810

-8.11 抽象链表(二)

--LinuxCPP0811

-8.12 编程实践

--LinuxCPP0812

-第八讲 链表与程序抽象--编程实践提交入口

第九讲 类与对象

-9.1 提纲

--LinuxCPP0901

-9.2 程序抽象与面向对象

--LinuxCPP0902

-9.3 类类型

--LinuxCPP0903

-9.4 对象(一)

--LinuxCPP0904

-9.5 对象(二)

--LinuxCPP0905

-9.6 类与对象的成员(一)

--LinuxCPP0906

-9.7 类与对象的成员(二)

--LinuxCPP0907

-9.8 类与对象的成员(三)

--LinuxCPP0908

-9.9 继承(一)

--LinuxCPP0909

-9.10 继承(二)

--LinuxCPP0910

-9.11 继承(三)

--LinuxCPP0911

-9.12 多态(一)

--LinuxCPP0912

-9.13 多态(二)

--LinuxCPP0913

-9.14 编程实践

--LinuxCPP0914

-第九讲 类与对象--编程实践提交入口

第十讲 操作符重载

-10.1 提纲

--LinuxCPP1001

-10.2 四则运算符重载(一)

--LinuxCPP1002

-10.3 四则运算符重载(二)

--LinuxCPP1003

-10.4 关系与下标操作符重载

--LinuxCPP1004

-10.5 赋值操作符重载(一)

--LinuxCPP1005

-10.6 赋值操作符重载(二)

--LinuxCPP1006

-10.7 赋值操作符重载(三)

--LinuxCPP1007

-10.8 赋值操作符重载(四)

--LinuxCPP1008

-10.9 赋值操作符重载(五)

--LinuxCPP1009

-10.10 流操作符重载(一)

--LinuxCPP1010

-10.11 流操作符重载(二)

--LinuxCPP1011

-10.12 流操作符重载(三)

--LinuxCPP1012

-10.13 操作符重载总结

--LinuxCPP1013

-10.14 编程实践

--LinuxCPP1014

-第十讲 操作符重载--编程实践提交入口

第十一讲 泛型编程

-11.1 提纲

--LinuxCPP1101

-11.2 泛型编程概览

--LinuxCPP1102

-11.3 异常处理机制(一)

--LinuxCPP1103

-11.4 异常处理机制(二)

--LinuxCPP1104

-11.5 运行期型式信息(一)

--LinuxCPP1105

-11.6 运行期型式信息(二)

--LinuxCPP1106

-11.7 模板与型式参数化

--LinuxCPP1107

-11.8 题外话:术语翻译

--LinuxCPP1108

-11.9 泛型编程实践(一)

--LinuxCPP1109

-11.10 泛型编程实践(二)

--LinuxCPP1110

-11.11 泛型编程实践(三)

--LinuxCPP1111

-11.12 泛型编程实践(四)

--LinuxCPP1112

-11.13 泛型编程实践(五)

--LinuxCPP1113

-11.14 泛型编程实践(六)

--LinuxCPP1114

-11.15 泛型编程实践(七)

--LinuxCPP1115

-11.16 泛型编程实践(八)

--LinuxCPP1116

-11.17 泛型编程实践(九)

--LinuxCPP1117

-11.18 泛型编程实践(十)

--LinuxCPP1118

-11.19 编程实践

--LinuxCPP1119

-第十一讲 泛型编程--编程实践提交入口

第十二讲 Linux系统编程基础

-12.1 提纲

--LinuxCPP1201

-12.2 程序执行环境(一)

--LinuxCPP1202

-12.3 程序执行环境(二)

--LinuxCPP1203

-12.4 程序执行环境(三)

--LinuxCPP1204

-12.5 程序执行环境(四)

--LinuxCPP1205

-12.6 输入输出(一)

--LinuxCPP1206

-12.7 输入输出(二)

--LinuxCPP1207

-12.8 文件系统

--LinuxCPP1208

-12.9 设备

--LinuxCPP1209

-12.10 库(一)

--LinuxCPP1210

-12.11 库(二)

--LinuxCPP1211

-12.12 makefile文件(一)

--LinuxCPP1212

-12.13 makefile文件(二)

--LinuxCPP1213

-12.14 makefile文件(三)

--LinuxCPP1214

-12.15 编程实践

--LinuxCPP1215

-第十二讲 Linux系统编程基础--编程实践提交入口

第十三讲 进程编程

-13.01 提纲

--LinuxCPP1301

-13.02 进程基本概念

--LinuxCPP1302

-13.03 信号

--LinuxCPP1303

-13.04 进程管理(一)

--LinuxCPP1304

-13.05 进程管理(二)

--LinuxCPP1305

-13.06 进程管理(三)

--LinuxCPP1306

-13.07 进程间通信(一)

--LinuxCPP1307

-13.08 进程间通信(二)

--LinuxCPP1308

-13.09 进程间通信(三)

--LinuxCPP1309

-13.10 进程间通信(四)

--LinuxCPP1310

-13.11 进程池

--LinuxCPP1311

-13.12 编程实践

--LinuxCPP1312

-第十三讲 进程编程--编程实践提交入口

第十四讲 线程编程

-14.1 提纲

--LinuxCPP1401

-14.2 线程基本概念

--LinuxCPP1402

-14.3 线程管理(一)

--LinuxCPP1403

-14.4 线程管理(二)

--LinuxCPP1404

-14.5 线程管理(三)

--LinuxCPP1405

-14.6 线程管理(四)

--LinuxCPP1406

-14.7 线程同步机制(一)

--LinuxCPP1407

-14.8 线程同步机制(二)

--LinuxCPP1408

-14.9 C++11线程库(一)

--LinuxCPP1409

-14.10 C++11线程库(二)

--LinuxCPP1410

-14.11 C++11线程库(三)

--LinuxCPP1411

-14.12 C++11线程库(四)

--LinuxCPP1412

-14.13 C++11线程库(五)

--LinuxCPP1413

-14.14 编程实践

--LinuxCPP1414

-第十四讲 线程编程--编程实践提交入口

第十五讲 网络编程

-15.1 提纲

--LinuxCPP1501

-15.2 Internet网络协议

--LinuxCPP1502

-15.3 套接字(一)

--LinuxCPP1503

-15.4 套接字(二)

--LinuxCPP1504

-15.5 编程实践

--LinuxCPP1505

-第十五讲 网络编程--编程实践提交入口

课程文档

-课程PDF文件

LinuxCPP0912笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。