基类的实例如何持有派生类的实例?

11

我已经是一个 .Net 程序员(不能说我是一个程序员)两年了。有一个问题我多年来一直不明白,那就是基类的实例如何可以持有派生类的实例?

假设我们有两个类:

class BaseClass
{
    public A propertyA;
    public B propertyB;
}

class DerivedClass :BaseClass
{
    public C propertyC;

}

这是怎么发生的:

BaseClass obj = new DerivedClass ()

我的意思是,BaseClass 的内存模型中并没有为新增的 propertyC 预留空间,那么它怎么可能仍然保存着 propertyC 的值呢?

另一方面,为什么不可能出现这种情况:

DerivedClass  obj = new BaseClass()

我原本以为这是正确的方式,因为DerivedClass的内存模型拥有了所有BaseClass的空间甚至更多。但事实并非如此,为什么呢?

我知道我在问一个非常愚蠢的问题,但是有人能否从内存或编译器的角度给我一个更详细的答案呢?


这与此处的问题完全相同,并且有一个非常好的回答来回答您的问题。https://dev59.com/wW445IYBdhLWcg3wTIZf - darnir
谢谢大家的出色回答!我明白了 :) 谢谢大家!!~ - NextStep
5个回答

5

由于BaseClass是一个引用类型,obj不是一个BaseClass对象。它是一个指向BaseClass对象的引用。具体地说,它是指向DerivedClass中的BaseClass部分的引用。


在我理解了所有其他答案之后,我明白了你的意思 :) 谢谢。 - NextStep

5

因为(这一概念经常被误解),保存指针的变量类型是独立于它所指向的实际具体类型的。

因此,当您编写以下内容时:

BaseClass obj = new DerivedClass () 

代码中的 BaseClass obj 部分在栈上创建了一个新的引用变量,编译器理解该变量保存对某个类型为 BaseClass 的对象或其派生类对象的引用。

而代码中的 = new DerivedClass() 部分实际上是创建了一个类型为 DerivedClass对象,将其存储在堆中,并在内存位置中存储了指向该对象的指针,即 obj 指向的内存位置。在堆中的实际对象一个 DerivedClass 类型的对象,这被称为该对象的具体类型

obj 变量被声明为 BaseClass 类型。这不是一个具体类型,它仅仅是一个变量类型,仅仅限制编译器将一个地址存储到变量中,该地址指向的对象一个 BaseClass 或从 BaseClass 派生的类型。

这样做是为了确保无论您将什么放入 obj 变量中,无论它是 BaseClass 的具体类型还是 DerivedClass 的具体类型,它都将具有 BaseClass 类型的所有方法、属性和其他成员。这告诉编译器,obj 变量的所有用法应该仅限于使用 BaseClass 类的那些成员。


难道不应该有一个指针来保存 new() 返回的内容吗? - Cătălina Sîrbu
1
是的,那就是 obj。像任何其他在任何编程语言中声明的变量一样,obj 只不过是指向内存地址的引用。在这种情况下,它指向的内存地址将包含指向堆栈上包含新 DerivedClass 的占用空间的内存块开头的地址。也就是说,obj 是一个指针。当然,它是 C# 指针,而不是 C 或 C++ 指针。 - Charles Bretana
哦,抱歉我不知道。我以为它是C / C++。 - Cătălina Sîrbu
没问题!这就是我们在这里的原因。 - Charles Bretana

2

当你说:

BaseClass obj = new DerivedClass ()

你并没有说“创建一个容器来包含这个BaseClass对象,然后将这个更大的DerivedClass对象塞进去”。

实际上,你创建的是一个DerivedClass对象,并且它是在足够容纳DerivedClass对象的内存空间中创建的。.NET框架从来没有失去过这个事实,即这个东西是特定的DerivedClass,并且它永远被视为这样。

然而,当你选择使用一个BaseClass对象变量时,你只是创建了一个对已经创建、分配和定义的对象的引用/指针,而你的指针只是稍微模糊了一点。

就像那个在房间另一边的红头发爱尔兰人略微超重的鸡农叫吉米,但你只是把他称为“那个人”。你含糊地描述他并不会改变他的身份或任何细节,即使你的含糊描述是完全准确的。


这真是一个有趣且易于理解的答案。谢谢!:) - NextStep

1

这个答案是从另一个用户jk在Stackoverflow上对于完全相同的问题所写的内容,原封不动地复制过来的。

如果我告诉你我有一只狗,你可以安全地假设我有一只狗。 如果我告诉你我有一个宠物,你不知道那个动物是一只狗,它可能是一只猫,甚至可能是长颈鹿。如果没有了解一些额外的信息,你不能安全地假设我有一只狗。 同样,派生对象是基类对象(因为它是子类),因此可以由基类指针指向。但是,基类对象不是派生类对象,因此无法分配给派生类指针。 (现在你听到的嘎吱声是比喻的拉伸) 假设你现在想给我的宠物买个礼物。 在第一种情况下,你知道它是一只狗,你可以给我买一条狗链,每个人都很高兴。 在第二种情况下,我没有告诉你我的宠物是什么,所以如果你要买礼物,你需要知道我没有告诉你的信息(或者只是猜测),你给我买了一条狗链,如果事实证明我真的有一只狗,每个人都很高兴。 然而,如果我真的有一只猫,那么我们现在知道你做出了错误的假设(转换),并且有一只不开心的猫被拴在了链子上(运行时错误)。

0
这是可能的,因为基类和派生类的内存分配方式不同。在派生类中,首先分配基类的实例变量,然后是派生类的实例变量。当一个基类引用变量被赋值给一个派生类对象时,它会看到它所期望的基类实例变量以及“额外”的派生类实例变量。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接