当前位置: 首页 > 知识库问答 >
问题:

将调用什么函数?

彭成天
2023-03-14

之前我提出了一个问题,但没有得到充分回答,因此我决定重新制定我的问题,以了解正在发生的事情:

下面是我的类层次结构:

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 ");
    }
}

以下是执行代码

Random rnd = new Random();
var randomI = rnd.Next(0, 2);

I i = null;
if (randomI == 0)
{
     i = new B(); 
}
else
{
    i = new C();
}
i.f(); 

就像现在一样,它将输出 A 或 C。它不会输出 B。

这里有一个问题:你能解释一下如何通过这些步骤来决定调用什么函数吗?

  1. 当决定调用什么函数时 - 运行时还是编译时?
  2. 如何决定调用什么函数的机制是什么?

共有3个答案

从智志
2023-03-14

IMHO,有趣的部分是为什么:((I)newB())。f()打印

---->> A

将< code>B转换为< code>I,将使用基类方法。如果您想打印<代码>-

if (i is B)
   ((B)i).f();
else
   i.f();

当强制转换为< code>I时,这就是类声明的情况:

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();

如果你想让 cast 调用 B 的实现,让 B 直接实现 I:

class B : A, I

因此,当向我施法时,会发生以下情况:

// 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();

当然,这是真实情况的一个简单版本,但是它有助于理解这个概念

夏飞掣
2023-03-14

当决定调用什么函数时——运行时还是编译时?

在编译时,如果有人将< code>B强制转换为< code>I并对其调用< code>f,编译器会确定< code>A.f是要调用的方法。在运行时,如果涉及到< code>B的实例(比如说< code>C),它就会调用该方法。换句话说,关键的决定是在编译时做出的。

请注意,如果该方法是虚拟的,请参阅@YeldarKurmangaliyev的答案,了解它如何调用“继承链中的顶级方法”(但这不是这里的场景)。

如何决定调用什么函数的机制是什么?

规范的相关部分是13.4.5接口实现继承

类继承其基类提供的所有接口实现。如果不显式地重新实现接口,派生类就不能以任何方式更改它从基类继承的接口映射。

这就是为什么类 B:A 显示 A 但类 B:A,I 显示 B。由于使用后者,您正在显式重新实现接口。

规范中的示例(这基本上是您的场景):

类继承其基类提供的所有接口实现。如果不显式重新实现接口,派生类不能以任何方式更改它从其基类继承的接口映射。例如,在声明中

interface IControl
{
    void Paint();
}
class Control: IControl
{
    public void Paint() {...}
}
class TextBox: Control
{
    new public void Paint() {...}
}

TextBox中的Paint方法隐藏Control中的Paint方式,但它不会改变Control的映射。在IControl上涂漆。Paint,以及通过类实例和接口实例调用Paint将具有以下效果

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();          // invokes Control.Paint();
t.Paint();          // invokes TextBox.Paint();
ic.Paint();         // invokes Control.Paint();
it.Paint();         // invokes Control.Paint();

该规范还谈到了使用虚拟(这是比显式指定 B 实现 I 更常见的解决方案):

但是,当接口方法映射到类中的虚方法时,派生类有可能重写虚方法并改变接口的实现。

司寇羽
2023-03-14

在编译时,它绑定对I接口的调用,然后在运行时,它调用继承链中实现I. f()的顶级方法。

所以,在你的代码

A a = new A();
a.f();

B b = new B();
b.f();

将使编译器执行以下指令:

  • 创建类A的实例并分配给“A”
  • 取一个分配给“a”的对象并调用该方法,该方法位于继承链的顶部,实现了a.f()。在这种情况下,它是A.f()本身
  • 创建B类的实例并分配
  • 取一个分配给“a”的对象并调用该方法,该方法位于继承链的顶部,实现了B.f()。在这种情况下是B.f()本身

结果为“A”和“B”。

但是,当您这样做时:

I i;
i = new B();
i.f();

您使其编译以下说明:

    < li >声明变量“I” < li >创建一个新对象B,并将其分配给“I” < li >获取一个分配给“I”的对象并调用方法,该方法位于继承链的顶部并实现I.f()。它是A.f(),因为类< code>B不实现接口< code>I

在< code>i.f()行,它不知道< code>new B()被赋给了< code>i,它可能是从其他地方传递过来的。它只知道有一些实现< code>I的抽象对象实例,它需要调用它的< code>f()方法。

您可以考虑方法,例如具有不同名称的方法:

public class B : A
{
    // non overriding but hiding class A method
    public void anotherName()
    {
       Debug.Log("---->> B ");
    }
} 

A a = new A();  
a.f();
B b = new B();
b.anotherName();

I i = new B();
i.f(); // this will obviously call the `A.f()` because there is no such method in B

唯一的区别是不能为继承的类实例调用隐藏方法。

 类似资料:
  • 问题内容: 该功能运行什么?它只会运行吗? 问题答案: setState()将按以下顺序运行函数: 如果您的组件正在接收道具,它将使用上述功能运行该功能。

  • 基本问题:程序何时在C中调用类的析构函数方法?有人告诉我,每当对象超出范围或受到时都会调用它 更具体的问题: 1)如果对象是通过指针创建的,并且该指针后来被删除或给定一个新的地址来指向,它所指向的对象是否调用其析构函数(假设没有其他东西指向它)? 2) 接下来是问题1,什么定义了对象何时超出范围(与对象何时离开给定的{block}无关)。换句话说,什么时候对链表中的对象调用析构函数? 3) 你想手

  • 问题内容: 我有以下功能 这段代码: 我的问题可能有点难以理解,所以请忍受:是什么使该代码段与常规调用完全区分开,或者是什么使该代码段需要引用函数变量而不是常规调用?() 我怎么知道应该在哪里引用该函数,以及什么时候该真正调用它? 问题答案: 好吧,该属性期望对函数的引用,以便在单击元素时执行该函数。通常是: 要么 (但是,当然,最好使用和) 请注意,它们都是如何引用函数而不是调用。 当某些东西需

  • 问题内容: 代码很简单: 您会看到这里有一个函数,我们只在体内调用它一次。但是在控制台中,它打印两次: 您可以在此处观看现场演示:http : //plnkr.co/edit/tb8RpnBJZaJ73V73QISC?p=preview 为什么该函数已被调用两次? 问题答案: 在AngularJS中,用大括号括起来的任何东西都是一个在摘要循环 中至少 被求值 一次 的表达式。 __ Angular

  • 我有jQuery脚本,它发送ajax请求到servlet。它的工作原理是,它将文本数据正确地发布到servlet,但是然后它调用错误函数,而不是成功函数(我检查过了,servlet发送回ajax而不是空字符串)。 为什么ajax方法调用错误函数? 这是剧本的代码 下面是servlet的代码 在错误报告中,textStatus值为,errorThrown值为void。

  • 本文向大家介绍什么是JavaScript中的自调用匿名函数?,包括了什么是JavaScript中的自调用匿名函数?的使用技巧和注意事项,需要的朋友参考一下 在JavaScript中,用括号括起来的函数称为“立即调用函数表达式”或“自执行函数”。 包装的目的是为了命名空间并控制成员函数的可见性。它将代码包装在函数范围内,并减少了与其他库的冲突。这就是我们所说的立即调用函数表达式(IIFE)或自执行匿