zoukankan      html  css  js  c++  java
  • CLR笔记:13.数组

    CLR支持一维/多维/交错数组。
    两种声明方式:

                Array a;
                a 
    = new String[01];

                String[] s 
    = new String[5];

    注意,声明不要给与数组长度,因为此时还不分配内存;new时要指定长度。
            将数组声明为Array和像String[]这样带中括号的,效果是一样的,只是前者更灵活,当然类型不安全,可能会有运行期错误。

    所有数组都隐式派生于System.Array,后者派生自System.Object,所以数组是引用类型。
        值类型的数组,new时,会创建实际的对象,所有元素都初始化为0;
        引用类型的数组,new时,只是创建引用,不会创建实际的对象。

    CLS要求数组必须是0基数组。

    每个数组都关联了一些额外的信息,称为开销信息(Overhead)。
    JITer只在执行一次循环之前检查一次数组边界,而不是每次循环都这么做。

    13.1    数组的类型转换
        对于引用类型的数组,两个数组类型必须维数相同,并且从源元素类型到目标元素类型,必须存在一个隐式(向父类转)或显示(向子类转)的类型转换。
        对于值类型的数组,不能将其转换为其他任何一种类型——使用Array.Copy方法替代:

                Int32[] ildim = new Int32[5];
                Object[] obldim 
    = new Object[ildim.Length];
                Array.Copy(ildim, obldim, ildim.Length);

    Array.Copy还可以再复制每个数组元素时进行必要的类型转换:
        *将值类型的元素装箱为引用类型的元素
        *将引用类型的元素拆箱为值类型的元素
        *加宽CLR基元值类型,Int32到Double
        *如果两个数组的类型不兼容(从Object[]转型为IFormattable[]),就对元素进行向下类型转换。

    数组的协变性:将数组从一种类型转换为另一种类型,——会有性能损失,如下代码,会在运行期报错:

                String[] sa = new String[100];
                Object[] oa 
    = sa;

                oa[
    5= "Jax";
                oa[
    3= 1;  //运行期错误

    如果只是需要将数组中的某些元素复制到另一个数组,可以使用System.Buffer的BlockCopy()方法,执行速度比Array.Copy快,但是只支持基元类型,不具有转型能力。


    13.3    所有数组都隐式实现IEnumerable,ICollection,IList
    对于泛型接口,因为多维数组和非0基数组的问题,System.Array并不完全实现。
    只有一维0基的引用类型的数组,在创建时,会自动实现了IEnumerable<T>,ICollection<T>,IList<T>,T为这个数组的类型;还为T的所有基类型B实现了IEnumerable<B>,ICollection<B>,IList<B>接口。
    如FileStream[] fsArray,会自动实现了IEnumerable<FileStream>,ICollection<FileStream>,IList<FileStream>,同时会实现IEnumerable<Stream>,ICollection<Stream>,IList<Stream>,IEnumerable<Object>,ICollection<Object>,IList<Object>,所以fsArray可以作为参数传递给以下方法:

            void M1(IList<FileStream> fsList) { }
            
    void M2(ICollection<Stream> sCollection) { }
            
    void M3(IEnumerable<Object> oEnumerable) { }

    但是,对于一维0基的值类型的数组,在创建时,会自动实现了IEnumerable<T>,ICollection<T>,IList<T>,T为这个数组的类型;但是不会为其基类实现接口。如DateTime[] dtArray;这个值类型dtArray不能传递给上面的M3方法。


    13.4    数组的传递和返回
        Array.Copy返回的是浅Copy。
        数组最好是0长度,而不是null

    13.5    非0基数组
    使用Array.CreateInstance方法,可以指定数组中元素类型,数组维数,数组下界,数组每一维中元素个数,有若干重载,如下:

                //重载1,数组长度
                Decimal[] fsArray = (Decimal[])Array.CreateInstance(typeof(Decimal), 2);

                
    //重载2,数组长度用一个数组表示,指定多维数组的维数和各维上的长度4和5
                Int32[] lengths = 45 };
                Decimal[,] fsArray 
    = (Decimal[,])Array.CreateInstance(typeof(Decimal), lengths);

                
    //重载3,最后一个参数指定数组下界
                Decimal[] fsArray = (Decimal[])Array.CreateInstance(typeof(Decimal), 21);

                
    //重载4,最后一个数组参数,指定数组各维上的下界
                Int32[] lengths = 45 };
                Int32[] lowerBounds 
    = 12 };
                Decimal[,] fsArray 
    = (Decimal[,])Array.CreateInstance(typeof(Decimal), lengths, lowerBounds);

                
    //重载5,创建一个三维数组,后三个参数分别指定各维的长度
                Decimal[,,] fsArray = (Decimal[,,])Array.CreateInstance(typeof(Decimal), 234);

    可以使用GetLowerBound(维数);与GetUpperBound(维数);获取数组的边界
    在一位数组fsArray中,可以使用fsArray数组对象的GetValue()和SetValue()方法来操作数组,但是比较慢。

    13.6    数组访问性能
    对于1维数组,0基的typeof为String.String[];非零基为String.String[*]
    对于多维数组,都会显示String.String[,]。在运行时,CLR将多维数组视为非零基数组。

    访问1维零基数组 比 非零基1维数组 和 多维数组 速度快很多。这是因为:
        1.有特殊的IL指令,用于处理1维零基数组,而不用在索引中减去偏移量
        2.JIT会将索引范围检查代码从循环中取出,从而只执行一次检查。

    关于for循环遍历数组:

                Int32 a = new Int32[5];

                
    for (Int32 index = 0; index < a.Length; a++)
                

                    
    //对a[index]进行操作
                }

    Length是一个数组属性,调用一次后,JIT会将结果存入一个临时变量,以后每次循环迭代访问的都是这个变量,而不是再次计算长度,从而速度更快。
    在循环前,JIT编译器会自动生成代码检测有效范围:if((Length-1) < a.GetUpperBound(0)),只会检测一次。
    如果将Length保存在一个本地变量,在for循环时每次都会比较该变量,反而会损害性能。

    以上只适用于0基数组。非零基数组中,JIT编译器在循环中,每次都要检查制指定索引范围是否有效,此外还要从指定索引减去数组下界,从而降低了性能。

    提升性能的两个办法:
        使用交错数组(数组的数组)来代替多维数组。
        使用非安全数组代替非零基数组,在访问数组时关闭索引边界检查——只适用于基元值类型。

    使用非安全代码访问2维数组:

            //用不安全代码访问2维数组中所有元素
            public static unsafe void Unsafe2DimArrayAccess(Int32[,] a)
            
    {
                Int32 dim0LowIndex 
    = 0//等价于a.GetLowerBound(0)
                Int32 dim0HighIndex = a.GetUpperBound(0);

                Int32 dim1LowIndex 
    = 0//等价于a.GetLowerBound(1)
                Int32 dim1HighIndex = a.GetUpperBound(1);

                Int32 dim0Elements 
    = dim0HighIndex - dim0LowIndex;

                
    fixed (Int32* pi = &a[00])
                
    {
                    
    for (Int32 x = dim0LowIndex; x <= dim0HighIndex; x++)
                    
    {
                        Int32 baseOfDim 
    = x * dim0Elements;

                        
    for (Int32 y = dim1LowIndex; y <= dim1HighIndex; y++)
                        
    {
                            Int32 element 
    = pi[baseOfDim + y];
                        }

                    }

                }

            }


    13.7    非安全数组和内嵌数组

    非安全数组可以访问以下元素:
        托管堆上的数组
        非托管堆的数组
        线程堆栈上的数组

    在性能第一的前提下,避免在堆上分配数组对象,应该在线程堆栈上分配——使用stackalloc
    stackalloc只适用于创建1维0基值类型数组,而且值类型中决不能包括任何引用类型。见以下方法,将一个字符串倒置:

            public static void StackallocDemo()
            
    {
                
    unsafe
                
    {
                    
    const Int32 width = 20;

                    
    //在堆栈上分配数组
                    Char* pc = stackalloc Char[width];

                    String s 
    = "Jax Bao";

                    
    for (Int32 index = 0; index < width; index++)
                    
    {
                        pc[width 
    - index - 1= (index < s.Length) ? s[index] : ' ';
                    }


                    String newS 
    = new String(pc, 0, width);

                    Console.WriteLine(newS.Trim());
                }

            }

    在struct中定义数组,一般来说,数组本身在struct的外部。
    要使数组内嵌在struct中,要遵从:
        1.struct要用unsafe标记
        2.数组字段要用fixed标记
        3.数组必须是1维0基的
        4.数组类型必须是基元值类型

    采用内嵌数组实现的方法,将一个字符串倒置:

            public static void InlineArrayDemo()
            
    {
                
    unsafe
                
    {
                    CharArray ca;   
    //在堆栈上分配数组
                    Int32 widthInBytes = sizeof(CharArray);
                    Int32 width 
    = widthInBytes / 2;

                    String s 
    = "Jax Bao";

                    
    for (Int32 index = 0; index < width; index++)
                    
    {
                        ca.Characters[width 
    - index - 1= (index < s.Length) ? s[index] : ' ';
                    }


                    String newS 
    = new String(pc, 0, width);

                    Console.WriteLine(newS.Trim());
                }

            }


            
    public unsafe struct CharArray
            

                
    public fixed Char Characters[20];
            }

     

     


     

  • 相关阅读:
    从js的repeat方法谈js字符串与数组的扩展方法
    《代码大全》阅读笔记-14-组织直线型代码
    《代码大全》阅读笔记-13-不常见的数据类型
    《代码大全》阅读笔记-12-基本数据类型
    《代码大全》阅读笔记-11-变量名的力量
    《代码大全》阅读笔记-10-使用变量的一般事项
    《代码大全》阅读笔记-9-伪代码编程过程
    《代码大全》阅读笔记-8-防御式编程
    《代码大全》阅读笔记-7-高质量的子程序
    《代码大全》阅读笔记-6-可以工作的类
  • 原文地址:https://www.cnblogs.com/Jax/p/906854.html
Copyright © 2011-2022 走看看