首页 > 代码库 > [.net]数组

[.net]数组

  在C语言中,数组是比较简单,也使用比较多的一种基础的数据结构。常用的有一维数组,二维数组等。但是在C#中,使用最多的是List,Dictionary等一些集合类,因为用他们来操作同类型的数据,比数组更加方便。当然,C#的数组Array也通过实现一些接口,提供了访问和操作数据的一些便捷方法。而在C语言中,都是比较不容易实现或者使用不方便。这也就是C#作为一门面向对象的语言的好处,虽然由此带来的性能损失,尤其在做算法相关的问题的时候。但退一步讲,在如今硬件资源比较丰富的情况下,日常场景中,这种性能损失还是可以接受的。


 

  接下来就聊聊C#种数组的那些事儿。

  1.数组基本概念:

      学习C#数组,首先需要明确一点,C#的数组是引用类型,即使数据基类型是值类型。作为引用类型,自然就会有类型对象指针,同步索引块,开销字段(overhead)等。

  2.C#数组分类:

      C#支持多维数组,也支持交错数组。并且他们都可以是非0基的。所谓非0基,是指数组第一个元素的索引不是从0开始。相应的,.Net CLR本身支持非0基数组,但是CLS规范规定所有数组都应该是0基数组,主要是为了兼容性和跨平台性,毕竟.Net家族除了C#还有VB,F#,C++(托管的)等语言。
      概念解释:
      一维0基数组:它又称为SZ数组(Single-dimension,Zero-based)或者向量。它的性能是最好的,因为编译器可以为他生成特殊的IL指令,实现JIT编译时候的优化,诸如以下指令(newarr,ldelem,ldelema,ldlen,stelem)。

          使用如下:

int[] nums=new int[10];


      多维数组:又称矩形数组。从行列的角度来考虑的话,它的每一行的列数都必须时相等的。

          使用如下:

int[,] nums=new int[10,10];

 

      交错数组:用行列的角度来考虑的画,它的每一行的列数可以不相同。

int[][] nums=new int[10][];
for(int i=0;i<10;i++)
{
      nums[i]=new int[10];//交错数组,各数组长度可不一样
}

         交错数组这个定义方式,非常类似于C语言中的动态多维数组,在C语言中,我们常用以下方法来开辟动态的二维数组

int i;
int** nums=(int**) malloc(sizeof(int*)*10);
for(i=0;i<10;i++)
{
    nums[i]=(int*)malloc(sizeof(int)*10);//这儿10是个实例,更具需求设置
}

         二者的定义的确有相似的地方,但应该注意到他们在本质上时不同的。

 

 

  3.数组索引:

      数组的索引是数组的一个很核心的概念,因为我们对数组的访问一般都是通过索引来实现的,但对索引的不当处理,可能导致程序终止。

      在C语言中,数组的索引控制都需要程序员来实现。如果发生索引超出数组范围,导致越界访问内存数据,甚至修改内存数据,这些都是不可察觉的,但无疑它为系统的奔溃留下了伏笔;

      在C#中,由于CLR要保证数据的安全,CLR在运行代码的时候,如果检测到数组索引超出范围,会立即抛出System.IndexOutOfRangeException异常,从而终止错误的继续。当然也可以使用unsafe关键字,来忽略CLR的这种默认行为。

      C#支持非0基数组,意味着数组可以的索引可以从非0的数开始,可以使用Arrary.CreatInstance()方法来创建下限非零数组。

 

  4.数组元素初始化:

     C#中为数组的初试化提供了一些甜蜜的语法糖。通常我们用以下几种方式来初始化一个数组。
      

int[] age=new int[]{10,20,30};//大括号中的数据项称为数组初始化器(arrar,initializer)
var age=new int[]{10,20,30}
var age=new [] {10,20,30};//隐式类型推断

     C#的隐式类型推断为创建一个数组提供了良好的支持。

  5.数组转型:

      类型转换是编程种很常见的一件事儿,我们把数组的类型转换又称为数组协变性(array convariance),在数组的转型的时候,需要满足以下几点条件:
      ①数组维数相同
      ②基类型存在隐式或显式类型转化
      需要注意以下几点:

        CLR不允许值类型数组转型为其他任何类型数组,但可以通过Array.Copy()变通实现。
        Array.Copy():支持装箱,拆箱,以及加宽基元类型(比如int到double的转变),但它是浅拷贝,即如果数组基类型是引用类型,则只复制其引用。
        System.Buffer.BliockCopy() 支持基元类型,但不具有转型能力
        System.Array.ConstrainnedCopy() 不支持装箱、拆箱和向下类型转化(父类到子类的转换),但它是可靠的,数据安全的,要么成功复制一个数组,要么不会修改目标数组的任何数据

  6.数组接口:

      数组实现了一些常用的接口,相当于给数组插上了访问的翅膀。
      System.Array数组基类实现了ICollection,IEnumerable,IList的非泛型版本,因为多维数组和非0基数组的原因,没有实现泛型版本(原因我也不太清楚)
      CLR单独为一维0基数组实现了泛型版本接口,包括其基类型,但是System.ValueType和Object除外

  7.数组做参数:

      在使用中,不可避免的要把数组作为一个参数来使用,那么在使用的时候,需要注意以下几点
      ①作为方法实参时传递的是引用;
      ②如果需要返回一个数组类型的字段,建议使用Array.Copy(),返回一个从该字段复制的数组,这么做是为了保证OOP的开放封闭原则,因为通常字段是私有的,不能被外部方法修改的和使用的;
      ③对于返回数组的方法,如果数组元素个数为0,仍不建议返回一个null,而是应该返回一个空的数组,这么做的目的在于对于方法返回参数的使用这来说,免去了null判断,防止产生空引用异常的错误。

 

  8.数组原理:

      实际在CLR内部只支持两种类型数组:一维0基数组 和 下限未知的一维或多维数组;
      一维0基数组:可以使用特殊的IL指令(newarr,ldelem,ldelema,ldlen,stelem),让编译器产生优化代码。比如索引检查发生的时刻提前,循环判断的提前的。
      下限未知的一维或多维数组:每次数组访问前验证索引有效性

  一些使用C#数组的建议:

  ①用交错数组代替矩形数组
  ②Unsafe关键字访问数组时可关闭索引上下限检查,支持常见的值类型和值类型结构(在fixed语句中)

  ③可以在线程栈上分配数组,利用stackalloc语句,类似于C语言alloca语句 ,条件是必须为一维0基数组,数组基类型为值类型

 

内容均来自于Jeffrey Richter的《CLR Via C#》第四版一书,以及个人总结和心得。学习深入了解C#,建议从这本书开始学习。

[.net]数组