我想知道当我们创建派生类的实例时,基类是如何在派生类之前自动实例化的。
我只想知道基类的成员如何占用内存,子类的引用如何访问它们。
让我们举一些例子:
class A
{
int x;
int y;
}
class B: A
{
int c;
}
如果创建 的新实例A
,则会在堆上创建一块内存。该内存将占用 8 个字节;4 个字节x
和 4 个字节y
。(我知道,为它的类型保留了更多的内存,等等,但我会把它留在这个范围之外)。
如果您创建 的新实例B
,则会创建另一块内存。不是两个,只有一个。所以没有子实例或任何东西。这块内存的长度为 12 个字节(4 个字节用于x
, 4 个字节用于 , 4y
个字节用于新字段z
。
当在堆上创建一块内存时,它总是会被零填充。因此,在这种情况下,所有字段都将具有其默认值0
。
如果两个类都有一个公共的无参数构造函数,这些构造函数会被自动调用。
class A
{
int x;
int y;
public A()
{
x = 1; y = 2;
}
}
class B: A
{
int c;
public B()
{
z = 3;
}
}
当一个新的实例B
被创建时,构造函数B
被调用。构造函数做的第一件事就是调用构造函数A
。A
将设置其字段x
和y
到1
和2
。然后程序返回到B
谁将z
使用该值进行初始化的构造函数3
。
的构造函数B
也可以写成(以表明它B
正在调用其 base 的构造函数A
):
public B()
: base()
{
z = 3;
}
对象是一次性创建的:字段的空间是根据需要为,中X : Y : Z
声明的字段总和(以及任何开销,作为 的隐含基础)所需空间的知识分配的。字段是继承的,所以an是.X
Y
Z
object
Z
X
Z
它们是自下而上初始化的,因为这是构造函数的工作方式;如果我们写:
class A : B
{
private int _a = 1;
public A() { Console.WriteLine("A"); }
}
class B {
private int _b = 1;
public B() { Console.WriteLine("B"); }
}
然后我们得到(对于B
):
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: ldc.i4.1
L_0002: stfld int32 B::_b
L_0007: ldarg.0
L_0008: call instance void [mscorlib]System.Object::.ctor()
L_000d: ldstr "B"
L_0012: call void [mscorlib]System.Console::WriteLine(string)
L_0017: ret
}
和A
:
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: ldc.i4.1
L_0002: stfld int32 A::_a
L_0007: ldarg.0
L_0008: call instance void B::.ctor()
L_000d: ldstr "A"
L_0012: call void [mscorlib]System.Console::WriteLine(string)
L_0017: ret
}
请注意,它在运行自己的本地构造函数代码之前调用基本构造函数。另请注意,字段初始化器甚至在此之前出现。
对此的一个简单解释是将继承视为复制机
所以让我们定义2个类
class Base
{
}
class Child : Base
{
}
现在您要创建一个子对象。Child 有自己的字段,但由于它从基类继承,它必须去复制其基类的所有字段,因此会自动创建。
类是对象的模板,继承只不过是可以在创建新模板时附加的可重复使用的模板。