编程小技巧:多态原理
今天要跟大家分享的小技巧是关于多态的。多态是面向对象的程序设计最重要的一个特性。多态使得程序变得更加灵活更加抽象。那么多态究竟是什么呢?它在计算机内部到底是如何实现的呢?我们先用一句话来描述:多态就是同一种事物所表现出的多种不同的形态。比如我们在同一个类中编写两个互为重载的方法:
class A
{
public:
int max(int a, int b);
int max(float a, float b, float c);
};
在使用这个类的对象时,它对外暴露的调用方法都是max(),而它对外所表现出来的形态也是不一样的,给它传入2个参数时,它返回这2个数中较大的那一个,而给它传递3个参数,则返回这3个数中较大的那一个。这就是我们所说的同一个事物(至少对外表现出来的是同一个事物max()函数)在不同情况下(在传入2个参数和3个参数时)所表现出多种不同的形态(计算2个数的较大值和3个数的较大值)。
类的方法重载是多态中最简单的形式,理解起来也比较简单,接下来我们再来看看类与对象在继承机制中所表现出来的多态形式。我们来看这样的3个类:
这里,类A中的成员变量int i是protected它会被继承到B类和C类当中,A类中还有一个成员方法virtual void func()这是一个虚函数,它会被B类和C类重写。这3个类的代码实现如下:
class A
{
public:
A(int age = 0) :
i(age)
{
}
virtual ~A()
{
}
virtual void func()
{
cout
}
protected:
int i;
};
class B: public A
{
public:
B(int age = 0)
{
this->i = age;
}
virtual ~B()
{
}
virtual void func()
{
cout
}
};
class C: public A
{
public:
C(int age = 0)
{
this->i = age;
}
virtual ~C()
{
}
virtual void func()
{
cout
}
};
int main(void)
{
A* a0 = new A(20);
A* a1 = new B(21);
A* a2 = new C(22);
a0->func();
a1->func();
a2->func();
delete (a2);
delete (a1);
delete (a0);
return 0;
}
我们定义了3个A类的对象指针a0、a1、a2。在执行这3个指针所指向的对象的方法func时,它们对外的调用方式都是相同的,然而这3个方法所表现出来的形态却是不同的,这也就是我们所说的多态:
调用的3个方法完全相同
a0->func();
a1->func();
a2->func();
输出结果为:
My name is A. I'm 20 years old.
My name is B. I'm 21 years old.
My name is C. I'm 22 years old.
事实上,虽然a0、a1、a2的类型都是类A的指针类型,但它们在内存中的真正形态是不同的,我们再来仔细看看a0、a1、a2这3个变量定义的代码:
A* a0 = new A(20);
A* a1 = new B(21);
A* a2 = new C(22);
值得注意的是它们的类型都是 A* 但是它们在申请内存空间时(也就是使用new修饰符创建对象时)这3个相同类型的指针指向了3个不同类型的对象,分别为A类的对象、B类的对象和C类的对象,它们在内存中实际的内容是这样的:
也就是说同样的A类的3个指针a0、a1和a2它们所指向的实际对象不同,我们在创建一个对象时(使用new修饰符时)计算机已经在内存中创建了这个对象应该所具有的内存空间,其中也包含了由父类所继承下来的属性,而对外表现同一种类型的对象指针a0、a1和a2在执行对象操作时,实际上就是在执行这3个不同对象,所以会表现出3种不同的形态。
这里还有一个需要注意的细节:我们使用了virtual修饰符来修饰成员方法void func(),如果不使用virtual修饰修饰成员方法,那么这里的多态机制将不会起作用,因为C++规定使用一个基类的指针来调用子类的成员函数时,如果这个函数是虚函数(被virtual修饰)则调用的函数是子类的函数,如果这个函数不是虚函数则调用的是基类的函数。这也就是C++中的动态联编机制。C++与Java是采用了相反的机制来控制函数的重写:
C++ 规定:指定了virtual的函数才能被重写,没有被指定virtual的函数不能被重写;
Java规定:指定了final的函数不能被重写,没有被指定final的函数才能被重写。
今天的小技巧你学会了吗?
- 长文 | 手把手教你如何使用python进行数据分析(最好将文章代码自己码一遍)
- 回归与梯度下降法及实现原理
- 【宅男宅女们的福音】电影天堂最新电影爬取及搜索脚本
- 把模块有关联的放在一个文件夹中 在python2中调用文件夹名会直接失败 在python3中调用会成功,但是调用不能成功的解决方案
- numpy用法小结
- 凯撒密码加解密及破解实现原理
- linux bash Shell脚本经典 Fork炸弹演示及命令详解
- python易错盲点排查之+=与+的区别分析以及一些赋值运算踩过的坑
- Selenium2+python自动化57-捕获异常(NoSuchElementException)
- 你真的会用ABAP, Java和JavaScript里的constructor么?
- 【Python学习笔记之三】lambda表达式用法小结
- Selenium2+python自动化58-读取Excel数据(xlrd)
- 软件测试金字塔
- Selenium2+python自动化59-数据驱动(ddt)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Prometheus 常用 PromQL 语句
- 小游戏互动能力升级,获取未注册好友名单,定向分享
- 这年头还不会SpringBoot?
- PB级大规模Elasticsearch集群运维与调优实践
- 长假慢学,用TensorFlow做了个AI游戏
- 微服务平台之API授权
- 一些让人恶心的代码片段
- 一个依赖搞定 Spring Boot 反爬虫,防止接口盗刷!
- 还在用Swagger(丝袜哥)生成接口文档?我推荐你试试它...
- 技术分享 | 企业版监控工具 MEM 初探
- Java自动化测试(TestNg 10)
- LeetCode109:有序列表转二叉搜索树
- docker和docker-compose
- IDA-完整解析sig
- 干货 | 携程度假无线前端架构演进之路