Int i和int i=newint()的区别
作为引用类型中的字段:
Int i和int i=newint() 如果做为类中的字段时,因为类在实例时会自动完成字段的初始化工作,因此在使用上是没有区别的,注意,仅仅是在使用上是没有区别,为什么这么说,请看下面示例:
public class A
{
public int i;
public A()
{
}
}
在分析之前,首先我们要明白一句话“每个构造函数在调用时,都要负责初始化由这个类定义的所有字段”
查看IL代码,我们先来看下无参构造函数:
.methodpublic hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// 代码大小 10 (0xa)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void[mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: nop
IL_0008: nop
IL_0009: ret
}// end of method A::.ctor
从上面可以看到构造函数IL代码里面没有显示的代码去初始化字段i,这说明对于类中的字段都是都CLR自动去初始化的。
再看一段代码:
public class A
{
public int i;
//此处将会在构造函数中以“内联“方式进行初始
//它的初始过程是:
// 1、先由CLR在构造函数中统一初始
//2、以在构造函数中以“内联”方式进行初始
//3、根据构造函数传进的参数进行初始
public int j = new int();
publicA()
{
}
publicA(int x, int y)
{
i = x;
j = y;
}
}
查看无参的构造函数
.methodpublic hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// 代码大小 17 (0x11)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.0
IL_0002: stfld int32 CLR_BOX.A::j //“内联方式”初始,在此之前j和i已经统一进行了初始了.
IL_0007: ldarg.0
IL_0008: call instance void[mscorlib]System.Object::.ctor()
IL_000d: nop
IL_000e: nop
IL_000f: nop
IL_0010: ret
}// end of method A::.ctor
查看有参构造函数:
.methodpublic hidebysig specialname rtspecialname
instance void .ctor(int32 x,
int32 y) cilmanaged
{
// 代码大小 31 (0x1f)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.0
IL_0002: stfld int32 CLR_BOX.A::j //”内联“方式初始
IL_0007: ldarg.0
IL_0008: call instance void[mscorlib]System.Object::.ctor()
IL_000d: nop
IL_000e: nop
IL_000f: ldarg.0
IL_0010: ldarg.1
IL_0011: stfld int32 CLR_BOX.A::i//传参
IL_0016: ldarg.0
IL_0017: ldarg.2
IL_0018: stfld int32 CLR_BOX.A::j//传参
IL_001d: nop
IL_001e: ret
} //end of method A::.ctor
比较上面两个例子,应该可以明白了int i和int i=new int()作为类型中的字段中的一些区别了,它们的区别在于i的初始步骤有一些差异。
作为函数中的变量:
Int i和int i=new int()在做为函数中的变量的区别就非常明显了
Int i,在i没有赋值前是不能使用的,因为并没有new,CLR并没有对其初始
Int i=new int() 这里的i是已经初始化了,这里的new int()和引用类型的new有一些相同点也有一些不同点,相同点是,值类型和引用类型都可以通过默认构造函数进行初始,注意是默认构造函数,也就是无参构造函数,不同点是,值类型分配在栈上,引用类型分配在堆上,这点人人都知道,但是最注要的一点是,对于值类型的无参构造函数是由CLR内部调用的,值类型new默认构造参数时,并不会产生调用默认构造函数的IL代码
例:
public struct MyStruct
{
public int i;
public MyStruct(int j)
{
i = j;
}
}
MyStruct s=new MyStruct();代生的IL代码:
.methodprivate hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 16 (0x10)
.maxstack 1
.locals init ([0] valuetype CLR_BOX.MyStructm)
IL_0000: nop
IL_0001: ldloca.s m
IL_0003: initobj CLR_BOX.MyStruct //1、initobj并不是newobj 2、MyStruct后面没有跟.ctor()
IL_0009: call string[mscorlib]System.Console::ReadLine()
IL_000e: pop
IL_000f: ret
} // end of method Program::Main
所以,我们可以理解为什么值类型不能显示的重写无参构造函数了,因为没有意义,你重写的无参构造函数并不会产生调用这个无参构造函数的IL代码,所以你任何想在无参构造函数中去初始化结构体中字段的企图都不会实现,值类型的无参构造函数是由CLR内部执行的,是我们没法去显式的控制的
我们刚讲到他们的共同点,仅仅是都可以调用默认构造函数进行原始初始化
我们应该知道,对地值类型,你可以定义多个有参的构造函数,并且在构造函数内要初始化所有的字段,不然会导致编译出错,这是为什么?这是因为,如果值类型在调用有参的构造函数时CLR内部就不会调用默认构造函数进行全部的字段初始化,
例如:MyStruct m=new Mystruct(1);产生的IL代如下:
.methodprivate hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 17 (0x11)
.maxstack 2
.locals init ([0] valuetypeCLR_BOX.MyStruct m)
IL_0000: nop
IL_0001: ldloca.s m
IL_0003: ldc.i4.1
IL_0004: call instance voidCLR_BOX.MyStruct::.ctor(int32) //注意,这里仅仅是把有参构造函数当做普通的方法直接调用
IL_0009: nop
IL_000a: call string[mscorlib]System.Console::ReadLine()
IL_000f: pop
IL_0010: ret
} //end of method Program::Main
因此,你在调用有参的构造函数时,由于没有CLR内部全部统一初始的前提,所以不能只初始化一部分字段,必须全部初始化,以保证struct类型在使用时,所有的字段都有默认的值,当然这里仅仅是值类型,对于引用类型,无论调用无参的构造函数的有参的构造函数,都会按照一定的顺序进行,第一步是统一对所有字段进行默认初始化,第二步是检查是否有内联字段,进行初始,第三步是如果有传参初始则进行初始,正因为由于引用类型在实例的时候有第一步的保障,所以无论在无参或者有参构造函数中显示的初始化部分字段,但最终所有实例字段都会保证全部初始。
从以上可以得出三个问题的答案:
1、 为什么Int i在未显示初始时不能使用,而int i=new int()可以
2、 为什么struct类型不能显示写出默认构造函数
3、 为什么struct类型中,有参的构造函数中,必须要对所有的字段进行显示初始,而不能只初始化一步分