[无声]
嗨,你好,下面我们一起来学习呢多态及虚方法调用
多态呢是面向对象程序设计里面的一个很重要的一个特性
多态它的意思就是我同样的名字由系统 来决定它应该怎么处理,是用这样处理呢还是那样处理
所以它有两种呢这种形式的多态 一类呢就是编译时的多态。
也就是说我在编 译器在编译这个程序的时候呢,它就知道该调用哪个
比如说最常见的那就是什么呢,就是我们有两个,重载两个方法 一个呢是带
2 个参数,一个呢是带 3 个参数 那同样的名字,它就知道根据 3 个参数或 2
个参数来决定呢 它是调哪个方法,那我们就不用写两个不同的名字的方法
所以这就是说多态呢,也就是编译器就能决定 另外一个呢就是在一运行时候它来决定的。
比如说 要调一个对象的一个方法,那这个方法呢
由于我们一个对象它既可以,比如说这个 person 是一个人的一个方法,也可以呢是
一个 student 的一个方法,都叫 say hello, 都叫打招呼。
但它应该调这个人的 say hello 呢 还是调那个 student say
hello 呢,所以这个时候就是在运行时候来决定的
那么它最基本的一种方式呢就是叫做虚成员 调用虚成员来实现。
这个多态实现了高度的灵活性和抽象性 具体它怎么实现呢?
virtual 这样的一个方法 比如说,我们有一个对象,Shape
对象,形状对象 我们调用形状对象的 draw 画,画出来,调用这个形状画出来
那既有圆,那又有三角形,又有四边形 又有呢方形。
那我们同样地,由于它们都是 Shape 的 它们都是
Shape 的子类,所以我们都可以用所谓的上溯造型,也就是说 我可以把一个
Circle 当成一个形状,我可以把一个矩形呢赋给一个形状 引用,也可以把一个方形赋给形状引用。
那么它在调用的时候 它是调这个 Shape 里面的 draw
呢,还是调圆形里面的 draw 呢,还是调方形里面的 draw 呢?
如果我们声明它是一个虚方法的话,那么它就 会调用这个实际的它所引用的对象
也就是说它不会去调这个形式上的那个形状,不会 调这个形状的
draw 方法,而会调后面实际应用的那个方法 我们下面呢看看这个代码。
在这个代码里面呢我们 都调用了这个形状的,呃,表面上都是这个形状对象的
draw 但实际上呢,如果我们在形状里面
用了一个修饰语,在这个 draw 方法前面用了个 virtual。
那么 virtual 呢意味就是 它这个虚方法,所谓虚方法呢意味着我们表面上一个形状对象点
draw 呢 它有可能不是调的这个方法,而是调的它子
类的方法,比如说这个 Circle 里面呢它继承了 Shape 然后呢 Circle
里面有 override 来覆盖 来重写它父类的这个方法
同样的我们用矩形呢也是 override 这个方法
然后呢这个方形,矩形的一个子类,它也 override 这样一个方法。
所以我们在运行的时候呢,它不会去画 这个 Shape Drawing ,而会画呢
draw 一个圆,或着 draw 一个圆形 或者画四边形,或者呢画
4 个相同的 边长的形状,我们运行一下。
你看这里 虽然我们都是 Shape 这个引用这个对象呢
这个 draw 呢它实际上的结果呢是 override 那以后的那个形状
这个为什么它具有这种灵活性呢?这个为什么多态那么重要呢? 就是当我们在编写这个
Shape ,我们编这个方法的人呢我们看见的是一个 Shape
这个 Shape 的那个 draw 方法可以被子类来重写, override。
但是对 我们编这个函数的人来说呢,我们并不去关心呢你这里面的这些子类
所以呢实际上它就又高度地把这个形状这个对象在 我们看来我们就是
draw ,那就抽象化了,同时呢就带来它的灵活性 可以说呢虚方法调用这种机制呢是我们实现
运行时候的这个多态呢最重要的一个方法 也是面向对象里面最重要的一个特点
当然这里面呢就是要求我们在父类里面呢有一个 virtual
或者 abstract 来修饰,然后呢在子类里面呢要
override 要使用这样的方法呢来修饰,然后才能够表明呢它这是一个虚方法
当然另外你看几个特点我们就好理解了。
虚方法不能省略访问 控制符,因为省略了就是 private ,对吧。
那么也不能 static ,因为 static 呢 它是属于类的,它是属于类,它跟对象没关系
但我们刚才看到的都是具体的那个对象在用的时候 所以呢,子类呢在实现的时候必须用
override 如果你不写 override ,它认为是一个 new
了一个方法,那么这个 new 呢就相当于隐藏了父类的一个方法
所以呢,总的来说呢,一句话就是 父类要有这个
virtual 或 abstract 或者 override 来修饰 那么子类呢要有 override
来重写、 来覆盖 我们也可以说呢虚方法调用的那种方法呢,它是由对象实例的类型
它当真真正的实例的类型来决定的,而不是由它所声明的这个类型,也就是说我们
表面上声明它都是形状,但是它实际是一个圆 对吧,这个时候呢,所以如果虚方法呢它就是调圆的方法
如果是非虚方法呢,它调的是那声明的那个方法 我们下面看这个例子。
比如说这个 A 类里面呢,它有一个虚方法 G 和一个非虚方法呢
F ,然后 B 呢继承了 A B 呢里面呢它也有一个
F ,这个 F 呢又,它不是虚 不是 override
虚方法,所以我们要加个关键词 new ,表明它是新的一个 F 那在这个里面的这个
F 方法呢就不是虚方法 那这个 G 呢是 override,它是虚方法。
就是我们在用的时候 你再看,我们声明一个对象 B ,等于
new 一个 B 所以这里声明的这个对象它的,声明的类型是 B ,但是实际呢它是一个
B ,这是 然后在这里,声明它是 A ,但是它实际是
b ,对吧 实际是这个 new 的一个 B 这个对象。
那我们调 a.F 或 b.F 呢,由于 F
它是非虚的,所以它是调用的这个所声明的 这个 F。
而这个 G 呢,由于它是虚方法,所以它是这个
a.G 或 b.G 呢 它调用的都是 B.G。
所以我们运行一下 这里呢, A.F 和 B.F ,
然后 B.G 和 B.G 所以一个是,那我放大一点看啊
这里呢由于 F 呢它是 非虚的,所以它是由这个声明类型所决定的。
那么这个 B 呢 这个 G 方法呢,它是由实际对象这个 B 来决定的 所以这两个都是 B.
,而这个一个是 A.
,一个是 B.
所以我们在用的时候呢也要注意一个方法,它是虚的还是非虚的 当然在复杂的情况下呢
它在前面呢又有虚的,有 override ,到后面在某个地方又用个 new
,就变成非虚的 既有非虚的又有虚的,那么这个时候呢它就看这个声明的变量
最可派生的那个方法,如果到某个地方一 new ,就这个
继承关系一层一层地往下继承,在某一层次用了一个 new
那这个继承关系一下断开了,那它就,就到那个地方为止,所以这个叫最可派生的方法 这里呢
A 有一个虚方法 然后 B 呢 override 了这个虚方法。
到 C 这个类呢,它继承了 B ,但是它又 new 了一个方法 到 D 呢,它又,它又 override 了。
所以你看呢 new 了一个虚方法,所以这个就把这个中间呢这个
C 到 B 之间的这个 虚关系呢就中断了,然后 D 到 C 呢,它又有一个
override 和 C,所以在这个时候我们 用的这个 a.F、
b.F、 c.F 呢和 d.F 我们运行一下,来分析一下这个
a 呢、 b 呢、 c 呢都等于 d 那么为什么 a.F 显示为
B.F,而这两个为什么头两个显示为 B.F 呢?因为 A 和
B 呢之间它是一个虚的这个关系,而这个实际的这个 D
呢 实际这个对象呢所以它最可派生就是 B 那么到声明这个
C 和这个 D 这个变量呢,它最可派生呢就是这个 D