What function will be called?
之前我问了一个回答不充分的问题,因此我决定重新制定我的问题,以了解发生了什么:
这是我的类层次结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | interface I { void f(); } class A : I { // non virtual method public void f() { Debug.Log("---->> A"); } } class B : A { // non overriding but hiding class A method public void f() { Debug.Log("---->> B"); } } class C : I { // non virtual method public void f() { Debug.Log("---->> C"); } } |
以下是执行代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
现在,它将输出A或C,而不输出B。
这里有一个问题:您能解释一下如何通过覆盖这些步骤来决定调用什么函数吗?
When the decision is made what function to call - runtime or compile
time?
在编译时,编译器确定,如果有人将
请注意,如果该方法是
What is the mechanism how to decide what function to call?
本规范的相关部分为13.4.5接口实现继承:
A class inherits all interface implementations provided by its base
classes. Without explicitly re-implementing an interface, a derived
class cannot in any way alter the interface mappings it inherits from
its base classes.
这就是为什么
规范中的示例(基本上是您的场景):
A class inherits all interface implementations provided by its base
classes. Without explicitly re-implementing an interface, a derived
class cannot in any way alter the interface mappings it inherits from
its base classes. For example, in the declarations
1 2 3 4 5 6 7 8 9 10 11 12 | interface IControl { void Paint(); } class Control: IControl { public void Paint() {...} } class TextBox: Control { new public void Paint() {...} } |
the Paint method in TextBox hides the Paint method in Control, but it
does not alter the mapping of Control.Paint onto IControl.Paint, and
calls to Paint through class instances and interface instances will
have the following effects
1 2 3 4 5 6 7 8 |
本规范还讨论了使用
However, when an interface method is mapped onto a virtual method in a
class, it is possible for derived classes to override the virtual
method and alter the implementation of the interface.
在编译期间,它绑定对
所以,在你的代码中
将使编译器执行以下指令:
- 创建类A的实例并分配给"A"
- 获取一个分配给"a"的对象并调用位于继承链顶部并实现a.f()的方法。在本例中,它本身就是a.f()。
- 创建类B的实例并分配
- 获取一个分配给"a"的对象,并调用位于继承链顶部并实现b.f()的方法。在这种情况下,它本身就是b.f()。
结果是"A"和"B"。
但是,当您这样做时:
1 2 3 |
您可以让它编译以下说明:
- 声明变量"i"
- 创建一个新对象b并将其分配给"i"
- 获取一个分配给"i"的对象并调用位于继承链顶部并实现i.f()的方法。它是a.f(),因为类
B 不实现接口I 。
在
您可以想到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
唯一的区别是不能为继承的类实例调用Hidden方法。
我认为这很可能混淆了(像我一样)在C++中了解虚拟与非虚拟方法的人,以及Java中的接口,后来必须弄清楚这两个如何在C语言中交互。
通过Java中的接口引用进行方法调度是比较简单的,因为Java中的所有方法都是虚拟的。当然,实例运行时类型决定了调用什么,这就是…在Java中。
这意味着在Java中,实例、方法名和实现之间的某些更复杂的关系是不可用的,这取决于您的观点,可以被认为是"好东西"或"坏东西"。
不管怎样,这是Java和C语言之间的最大区别之一,因为不仅在C语言中不需要虚拟的方法,它们也不是默认的。而且,在不了解实施细节的情况下,这将带来关键的洞察力:
如果一个方法在父类中没有被标记为
所以…如果一个类实现了一个带有非
但是:
没关系。即使是这样,在c
更新:目前排名最高的答案声称关键决策是在编译时做出的,虽然在特定的示例中实现可以做到这一点,但我不相信这一点——仅仅是因为这种方法不能概括。
1 2 3 | public void myMethod(I i) { i.f(); } |
在编译上述方法时,编译器完全不知道它应该调用什么实际实现。当用该行编译其他代码单元时,
1 |
编译器不知道需要专门解决
但最终,要真正执行给定的方法实现,必须等到运行时才能做出决定;无论是采用虚拟调度的形式,还是基于反射的怪兽,或者其他什么形式。
在语言级别,这只是实现细节。指定的行为是很重要的,这就是
imho,中间部分是为什么:
1 | ---->> A |
将
1 2 3 4 |
当强制转换到
1 2 3 4 5 | I -> A -> B |_ f() is implemented in subclasses, let's go one step down; I -> A -> B |_ f() is found, let's call A's f(); |
如果您希望强制转换调用B的实现,请直接使B实现I:
1 | class B : A, I |
因此,当投射到我身上时,这将发生:
1 2 3 4 5 6 7 8 9 | // Paths from I to B I -> A -> B I -> B // Shorter path, let's go via this one. I -> B |_ f() is implemented in subclasses, let's go one step down; I -> B |_ f() is found, let's call B's f(); |
当然,这是实际情况的一个简单版本,但它有助于理解这个概念