IL系列文章之四:
Array in IL (续)
上次谈了Array中的一维数组,这次要谈的是多维数组和锯齿形数组。
多维数组其实就是数组的数组(好像不如Array of Array那么好听)。上次说过我们用IL可以做一些在C#中不可能做到的事情,比如定义一个数组,它的下界不是从0开始的而是任意一个值。闲话休说,先来看个二维数组的例子。
.assembly matrix{}
//it is a matrix with 2 dimensions
//dim1 from 3 to 5 and dim2 from 4 to 5
//the matrix like this could not be defined in C#
.class private auto ansi beforefieldinit matrix
extends [mscorlib]System.Object
{
.method public hidebysig static void Main () cil managed
{
.entrypoint
.maxstack 4
.locals init (int32[,] matrix)
ldc.i4.3// load the lower bound for dimension 1
ldc.i4.5// load the upper bound for dim 1
ldc.i4.4// load the lower bound for dim 2
ldc.i4.5// load the upper bound for dim 2
newobj instance void int32[,]::.ctor(int32,int32,int32,int32)
// ctor means constructor
stloc matrix
ldloc matrix
ldc.i4.3// index of 1st dimension
ldc.i4.4// index of 2nd dimension
ldc.i4.s 34// the value
call instance void int32[,]::Set(int32,int32,int32)//matrix[3,4] = 34
//a section of error code "matrix[1,1] = 11"
//------------------------------------------------------------------
//ldloc matrix
//ldc.i4.1
//ldc.i4.1
//ldc.i4.s 11
//call instance void int32[,]::Set(int32,int32,int32)
//------------------------------------------------------------------
//it will course an exception "System.IndexOutOfRangeException"
ldloc matrix
ldc.i4.3
ldc.i4.4
call instance int32& int32[,]::Address(int32,int32)
//generate a 32 bit integer instance of matrix element matrix[3,4]
//in order to conver it to string for output
call instance string [mscorlib]System.Int32::ToString()
call void [mscorlib]System.Console::WriteLine(string)
ret
}
}
在这个例子中既有二维数组的普通使用方法,又有下界不为0的定义方法。其实我也没有太多好说的了,我要说的大多都写在注释里了。多维数组的定义比一维数组要复杂一些,需要调用专门的构造函数“void int32[,]::.ctor(int32,int32,int32,int32)”来进行构造。还有一点要注意的了,这里使用的是“newobj”,而不是上次所用的“newarr”。就我们一般的二维数组而言下界都是从0开始的,所以实际上我最常用的是这样的一个构造函数“void int32[0…,0…]::.ctor(int32,int32)”,在这个构造函数中,我们只需要提供两个维的上就够了,下界默认从0开始。例子中间有一段注释掉的代码,我利用这段代码来检验IL中定义的非0下界数组是不是真的不从0开始。大家可以试一下这段代码,它一旦运行起来的话系统就会抛出一个"System.IndexOutOfRangeException",看来IL没用骗我们,的确是从我们定义的下界开始的。
总的看来虽然二维数组的定义要比一维数组复杂一些,但还是比较规整的。接下来我们要看的就是不太规整的东东了——锯齿数组(Jagged Array)。写文章离不了例子,我还是只有从例子开始。
//jagged.cs
class jagged
{
public static void Main ()
{
int[][] jagged;
jagged = new int[3][];
jagged[0] = new int[1];
jagged[1] = new int[2];
jagged[2] = jagged[1];
jagged[1][1] = 11;
System.Console.WriteLine(jagged[2][1].ToString());
}
}
通过它的输出可以看出这个锯齿数组的第3维(jagged[2])和第2维(jagged[1])其实是同一个一维数组,对第2维的处理就是对第3维的处理。
有些朋友可能已经看出来了,我的第一个例子——matrix,其实也是锯齿形的,但我们并不称其为锯齿数组。为什么呢?反编译上面的代码,看看真正的锯齿数组是怎么实现的。(和以前一样,为了大家看这方便,我对反编译出来的代码作了一些修改。)
.assembly jagged{}
.class private auto ansi beforefieldinit jagged
extends [mscorlib]System.Object
{
.method public hidebysig static void Main () cil managed
{
.entrypoint
.maxstack 4
.locals init (int32[][] jagged)
ldc.i4.3
newarr int32[]// the jagged array has 3 dimensions
//create a array of int pointer
stloc jagged// jagged = new int[3][];
ldloc jagged
ldc.i4.0//dimension
ldc.i4.1//length of dimension
newarr [mscorlib]System.Int32//jagged[0] = new int[1];
stelem.ref// set the int[] by reference
ldloc jagged
ldc.i4.1
ldc.i4.2
newarr [mscorlib]System.Int32
stelem.ref// it is like the first dimension
ldloc jagged
ldc.i4.2
//{load pionter of 2nd dimension by reference
ldloc jagged
ldc.i4.1
ldelem.ref
//}
stelem.ref// set the pointer of 3rd dimension
ldloc jagged
ldc.i4.1
ldelem.ref// load jagged[1][]
ldc.i4.1// jagged[1][1]
ldc.i4.s 11
stelem.i4// jagged[1][1] = 11
ldloc jagged
ldc.i4.2
ldelem.ref
ldc.i4.1
ldelema [mscorlib]System.Int32
call instance string [mscorlib]System.Int32::ToString()
call void [mscorlib]System.Console::WriteLine(string)
ret
}
}
在锯齿形数组中,我们首先定义的是一个三行一列的Pointer数组,每个Pointer指向一个Array的实例(当然有可以没有)。它的处理方式和一维数组很相似(本质上就是一维数组的集合),都是用的IL基本指令ldelem和stelem,不过这里是按引用的方式传递的。锯齿数组的结构如同下图所示,
锯齿形数组有一列指针数组,在对数组的操作时起到作用(我想主要是对数组元数的寻址)。C#语言在实现锯齿形数组的时候隐藏了这一列Pointer的细节,用户在使用的时候感觉不到它的存在。当然这是在为用户着想,使用户不会在使用锯齿形数组的时候将心思局限于某些技术细节之中。到这里为止,大家对数组也是很明了了吧,我也就到此为止了。