5 数组和集合

上一章介绍了.NET中的字符串操作技术,下面继续介绍另一种常用的数据类型:数组和集合。同字符串一样,数组和集合也是Web开发中最重要的类型之一,用于表示一组性质相近的对象。

5.1 数组

数组能够按一定规律把相关的数据组织在一起,并通过“索引”或“下标”快速存取这些数据。本节首先介绍数组的基本概念,理解这些概念是学习使用数组的基础。

5.1.1 什么是数组

【本节示例参考:\示例代码\C05\Example_Array】

数组即一组数据,它把一系列数据组织在一起,成为一个可操作的整体。例如,当一个做事细心的妻子去超市买东西时,或许会事先列出一个清单:

(1)油;

(2)盐;

(3)酱;

(4)醋;

(5)毛毛熊;

(6)……

可以称这个清单为“需购物品”,它有规律地列出了其内部的数据,且其内部数据具有相同的性质。在程序语言中,可以称这样一个清单为数组:

    //声明数组
    String[] myStrArr =new String[5]{ "油", "盐", "酱", "醋", "毛毛熊" };

在数组中,其中的每一个元素对应排列次序下标。当使用其中的某个元素时,可以直接利用这个次序下标,具体如下:

    1.   //输出数组元素
    2.   for(int i=0;i<5;i++)
    3.   {
    4.      Page.Response.Write("myStrArr["+i+"]="+myStrArr[i]+"<br/>");
    5.   }

将输出数组myStrArr中所有的元素,如图5.1所示。

图5.1 输出数组元素

说明

同C语言和大部分语言一样,C#的下标也是从0开始,而不是从1开始。

数组中的元素可以是任意的类型,如上面给出的示例,其中的每个元素都是字符串类型。除此之外,元素还可以是其他的基本数据类型,甚至又是一个数组。如果数组的元素又是数组,那么这个数组称为多维数组,下面是一个二维数组的例子:

    //声明一个二维数组
    String[,] myStrArr2 =new String[3,2]{ { "油", "盐" }, { "《围城》", "《晨露》" }, { "毛毛熊", "Snoopy" } };

在这个例子中,数组的第一维包含了三个元素,而每一个元素又是一个数组,分别包含了两个元素。此时称数组的秩为2;第一维的元素类型为数组,长度为3;第二维的元素类型为字符串,长度为2。

通过这个具体的例子,读者可以思考“秩”、“维”、“元素类型”,以及“长度”的概念。同一维数组一样,多维数组中的每一个元素,也可以通过下标方式来引用,如:

myStrArr[0]指一维数组{“油”,“盐”}。

myStrArr[0,0]指字符串“油”。

下面的代码,可以输出myStrArr2中的所有元素。

代码5-1 输出数组元素:Default.aspx.cs

    1.   //输出二维数组元素
    2.   for(int i=0;i<3;i++)
    3.   {
    4.      Page.Response.Write("--myStrArr2["+i+"]<br/>");
    5.      for(int j=0;j<2;j++)
    6.      {
    7.          Page.Response.Write("----myStrArr2["+i+","+j+"]="+myStrArr2[i,j]+"<br/>");
    8.      }
    9.   }

输出结果如图5.2所示。

图5.2 分层输出二维数组

5.1.2 创建数组

【本节示例参考:\示例代码\C05\Array_Create】

在C#中,使用如下语法创建一个数组。

1.一维数组

    data type[]arr name=new data type[int length]

这种方式定义一个元素数据类型为data type、长度为length的数组arr name,例如:

    int[]myIntArr=new int[100];                  //定义一个长度为100的int数组
    string[]mystringArr=new string[100];           //定义一个长度为100的string数组
    object[]myObjectArr=new object[100];         //定义一个长度为100的object数组

其中,数据类型data type既可以是常用数据类型(如int、float等),也可以是对象(如String、StringBuilder等)。

    data type[]arr name=new data type[]{item1,item2,…,itemn}

这种方式定义一个元素数据类型为data type,并通过“=”运算符进行赋值,其初始值为所给出的元素{item1,item2,…,itemn}的个数,例如:

    int[]myIntArr2=new int[]{1,2,3};                   //定义一个int数组,长度为3
    string[]mystringArr2=new string[]{"油","盐"};         //定义一个string数组,长度为2

在这种定义下,不必给出数组的长度定义,数组的长度自动设置,为所给出的元素{item1,item2,…,itemn}的个数。即下面的两种定义完全相同:

    int[] myIntArr2=new int[]{1,2,3};
    int[] myIntArr2=new int[3]{1,2,3};

2.多维数组

    data type[,…,]arr name=new data type[int length1,int length2,…,int lengthn]

这种方式定义一个元素数据类型为data type,秩为n,各维长度分别为length1,length2,…,lengthn的数组arr name,例如:

    int[,]myIntArr=new int[10,100];               //定义一个10*100的二维int数组
    string[,,]mystringArr=new string[2,2,3];         //定义一个2*2*3的三维string数组

这里就定义了两个多维数组。也可以用下面的方法进行多维数组的定义:

    data_type[,…,] arr_name = new data_type[,…,]]
    {
        {item1, item2, … ,itemn}
        …
    }

例如:

    int[,]myIntArr2=new int[,]{{1,2,3},{-1,-2,-3}};                      //2*3的二维int数组
    string[,]mystringArr2=new string[,]{{"油","盐"},{"《围城》","《晨露》"}}; //2*2的二维string数组

同一维数组一样,在这种定义下,可以不必给出各维的长度定义,各维长度根据所给出的赋值元素自动确定。

3.交错数组

C#支持各个维度长度不同的多维数组,称为交错数组,也称为“数组的数组”。交错数组的定义如下:

    data type[][]…arr name=new data type[int length1][int length2]…

这个定义和定义多维数组非常类似,区别在于,交错数组必须单独初始化交错数组每一维中的元素。例如,下面定义一个第一维长度为3的交错数组:

    1.   int[][]myJaggedArray=new int[3][];
    2.   myJaggedArray[0]=new int[5];
    3.   myJaggedArray[1]=new int[4];
    4.   myJaggedArray[2]=new int[2];

这个交错数组myJaggedArray,每个元素都是一个一维整数数组。第一个元素是由5个整数组成的数组,第二个是由4个整数组成的数组,而第三个是由2个整数组成的数组。

    data_type[][]…arr_name=new data_type[][]…
    {
        new data_type[]{item1,…,new data_type[]itemn}
        …
    }

这种方式在声明数组的同时进行初始化,同样可以不指定各维的长度,例如:

    1.   int[][]myJaggedArray=new int[][]
    2.   {
    3.       new int[]{1,3,5,7,9},
    4.       new int[]{0,2,4,6},
    5.       new int[]{11,22}
    6.   };

5.1.3 数组基类Array

System.Array类是所有.NET中数组的基类,提供创建、操作、搜索和排序数组的方法,其属性和方法如图5.3所示。

图5.3 System.Array类

Array类常用属性和方法的简单说明,如表5.1所示。

表5.1 Array类常用属性/方法说明

5.1.4 访问数组元素

【本节示例参考:\示例代码\C05\Array_AccessItem】

访问数组的元素包括读取或设置某个元素的值,最基本的方法是通过下标定位元素,另外还可以使用GetValue/SetValue方法。

1.通过下标定位元素

C#中数组对其中的元素进行排序,并从0开始计数,这样每一个元素都会有一个唯一的下标,通过这个下标,就可以定位唯一的一个元素。下面通过示例来说明。

(1)一维数组:

    string[]myStrArr={"油",  "盐",  "酱",  "醋",  "毛毛熊"};

这里,myStrArr[0]=“油”;myStrArr[4]=“毛毛熊”。如果试图访问超过下标范围的数据,则会出现如下异常:

    System.IndexOutOfRangeException: 索引超出了数组界限

(2)多维数组:

    string[,] myStrArr2={{"油","盐"},{"《围城》","《晨露》"},{"毛毛熊","Snoopy"}};

定义之后,myStrArr2[0,0]=“油”;myStrArr2[2,1]=“Snoopy”。

(3)交错数组:

    1.   int[][]myJaggedArray=new int[][]
    2.   {
    3.       new int[]{1,3,5,7,9},
    4.       new int[]{0,2,4,6},
    5.       new int[]{11,22}
    6.   };

定义之后则有:myJaggedArray[0][0]=1;myJaggedArray[1][1]=2;myJaggedArray[2][1]=22。

下面的代码可以循环输出所有的交错数组元素。

代码5-2 输出交错数组元素:Default.aspx.cs

    1.   for(int i=myJaggedArray.GetLowerBound(0);i<=myJaggedArray.GetUpperBound(0);i++)
    2.   {
    3.       Console.WriteLine("item{0}",i);
    4.       for(int j=myJaggedArray[i].GetLowerBound(0);j<=myJaggedArray[i].GetUpperBound(0);j++)
    5.       {
    6.           Console.WriteLine("  item{0}{1}:{2}",i,j,myJaggedArray[i][j]);
    7.       }
    8.   }

2.使用GetValue/SetValue

GetValue方法定义如下:

    public object GetValue(params int[]indices);

其中,多个int型参数indices的含义为下标。方法返回一个object对象,这是C#中所有对象的基类,使用多态性,它可以指向所有的C#对象。下面的代码使用GetValue方法,循环输出一个二维数组所有元素。

代码5-3 使用GetValue输出二维数组元素示例:Default.aspx.cs

    1.   //定义二维数组
    2.   string[,]myStrArr2=new string[,]{{"油","盐"},{"《围城》","《晨露》"},{"毛毛熊","Snoopy"}};
    3.   //循环输出
    4.   for(int i=myStrArr2.GetLowerBound(0);i<=myStrArr2.GetUpperBound(0);i++)
    5.   {
    6.       Console.WriteLine("item{0}",i);
    7.       for(int j=myStrArr2.GetLowerBound(1);j<=myStrArr2.GetUpperBound(1);j++)
    8.       {
    9.           Console.WriteLine("  item{0}{1}:{2}",i,j,myStrArr2.GetValue(i,j));
    10.      }
    11.  }

SetValue的功能为数组的某个元素赋值,其定义及参数表同GetValue相似,不作赘述。

5.1.5 转化元素类型

【本节示例参考:\示例代码\C05\ Array_ConvertAll】

定义数组时,需要为其中的元素指定数据类型。有时候,在应用中需要重新转化元素的类型,这可以使用Array对象的CovertAll方法实现:

    public static TOutput[] ConvertAll<TInput,TOutput> (TInput[] array,Converter <TInput,TOutput>
    converter)

其中,类型参数TInput为源数组元素的类型,TOutput为目标数组元素的类型。参数array为要转换为目标类型的从零开始的一维Array,converter为一个Converter对象,用于将每个元素从一种类型转换为另一种类型。方法的返回值是目标类型的数组,包含从源数组转换而来的元素。

Convert对象表示将对象从一种类型转换为另一种类型的方法,其定义为:

    public delegate TOutput Converter<TInput,TOutput>(  TInput input)

其中,类型参数TInput为要转换的对象的类型,TOutput为要将输入对象转换到的类型。参数input为要转换的对象。方法的返回值为TOutput,它表示已转换的TInput。

下面的代码示例将一个元素类型为int的数组转化为string类型。

代码5-4 使用ConvertAll转化数据元素类型:Default.aspx.cs

    1.   protected void Page_Load(object sender,EventArgs e)
    2.   {
    3.      //定义一个整数类型的数组,并输出
    4.      int[]src={1,2,3};
    5.      Page.Response.Write("<br/>");
    6.      foreach(int i in src)
    7.      {
    8.          Page.Response.Write(i+":"+i.GetType()+"<br/>");
    9.      }
    10.
    11.     //将整数数组转化为字符串类型,并输出
    12.     string[]desc=Array.ConvertAll(src,
    13.         new Converter<int,string>(IntToString));
    14.     Page.Response.Write("<br/>");
    15.     foreach(string str in desc)
    16.     {
    17.         Page.Response.Write(str+":"+str.GetType()+"<br/>");
    18.     }
    19.  }
    20.
    21.  ///<summary>
    22.  ///Converter委托
    23.  ///</summary>
    24.  ///<param name="i">整数类型的数据</param>
    25.  ///<returns>由输入转化而成的字符串类型数据</returns>
    26.  public static string IntToString(int i)
    27.  {
    28.     return i.ToString();
    29.  }

代码首先在第3~9行定义了一个整数类型的数组src,并输出其中的元素和数据类型;

第11~13行利用Array类的ConvertAll方法将src转化为字符串类型,方法的第二个参数中使用了一个委托IntToString,其实现在第21~29行。这个委托功能简单,将一个输入的整数参数转化为字符串,并返回。

第14~19行将转化后的字符串数组输出。程序运行结果如图5.4所示。

图5.4 使用ConvertAll方法转化数组元素类型

5.1.6 遍历数组元素

【本节示例参考:\示例代码\C05\ Array_Traverse】

遍历数组是指访问数组中的全部元素一次并且仅一次。可以在遍历的过程中完成许多操作,如查找等。有两种方式可以遍历整个数组,具体如下。

1.使用GetLowerBound/GetUpperBound方法

GetLowerBound方法可以获取数组某一维上的最低下标,而GetUpperBound则可获取其最高下标,利用这两个参数和for语句,可以实现数组的遍历。

    public int GetLowerBound (int dimension)
    public int GetUpperBound (int dimension)

其中,参数dimension为需要获取上下标的数组维度。

下面的示例实现了对二维数组的遍历。

代码5-5 利用for语句遍历数组示例:Default.aspx.cs

    1.   //定义二维数组
    2.   string[,]myStrArr2=new string[,]{{"油","盐"},{"《围城》","《晨露》"},{"毛毛熊","Snoopy"}};
    3.   //遍历
    4.   for(int i=myStrArr2.GetLowerBound(0);i<=myStrArr2.GetUpperBound(0);i++)
    5.   {
    6.       for(int j=myStrArr2.GetLowerBound(1);j<=myStrArr2.GetUpperBound(1);j++)
    7.       {
    8.           //处理每一个元素
    9.       }
    10.  }

2.使用foreach

还可以使用更为简便的方法来实现数组的遍历,那就是foreach关键字,这种方法对于处理高维数组尤其方便。foreach语句格式如下:

    foreach (data_typt item_name in arr_name)
    {
        //处理每一个元素
    }

注意

foreach语句获取的元素是最深层的元素,因此,无论处理几维的数组,使用一层的foreach循环就可以了。

例如,下面的代码实现同样的二维数组遍历。

代码5-6 利用foreach遍历数组示例:Default.aspx.cs

    1.   //定义二维数组
    2.   string[,]myStrArr2=new string[,]{{"油","盐"},{"《围城》","《晨露》"},{"毛毛熊","Snoopy"}};
    3.   //遍历
    4.   foreach(string item in myStrArr2)
    5.   {
    6.       {
    7.           //处理每一个元素
    8.       }
    9.   }

5.1.7 排序数组元素

【本节示例参考:\示例代码\C05\ Array_Sort】

对数组进行排序是指按照一定的排序规则,如递增或递减规则,重新排列数组中的所有元素。可以使用Array类的Sort方法完成这个功能。Sort方法有多种重载方式,常用的形式如下:

    public static void Sort(Array array);

其中,参数array为待排序的数组。下面的示例首先定义了一个数组,含有元素{5,4,3,2,1},然后利用Sort方法对其排序。

代码5-7 利用Sort排序数组示例:Default.aspx.cs

    1.   ///<summary>
    2.   /// 利用Sort方法排序数组
    3.   ///</summary>
    4.   public void TestSort()
    5.   {
    6.      int[]myArr={5,4,3,2,1}; //定义数组
    7.
    8.      //输出原始数组:原始数组:5->4->3->2->1->
    9.      Page.Response.Write("原始数组:");
    10.     for(int i=0;i<myArr.Length;i++)
    11.         Page.Response.Write(myArr[i]+"->");
    12.
    13.     Array.Sort(myArr);    //对数组排序
    14.
    15.     Page.Response.Write("<br/>");
    16.
    17.     //并输出排序后的数组:1->2->3->4->5->
    18.     Page.Response.Write("排序以后数组:");
    19.     for(int i=0;i<myArr.Length;i++)
    20.         Page.Response.Write(myArr[i]+"->");
    21.  }

有时候需要进行所谓的关键字排序,例如,有两个数组arrSid和arrSname,分别代表一组学生的学号和姓名,如果想要根据学号顺序输出姓名,或反之,都需要使用数组的排序操作,那么,如何把这两个数组联系在一起排序呢?这时就可以使用Sort的下面这种形式进行关键字排序。

    public static void Sort(Array keys, Array items);

其中,参数keys代表关键字数组,而items代表另一个数组。利用Sort,下面的代码可实现上述需求。

代码5-8 利用Sort实现数组多关键字排序示例:Default.aspx.cs

    1.   ///<summary>
    2.   /// 利用Sort实现数组多关键字排序
    3.   ///</summary>
    4.   public void TestSortMultiKey()
    5.   {
    6.      //定义数组
    7.      int[]arrSid={5,4,3,2,1};
    8.      string[]arrSname={"张三","李四","王五","麻子","淘气"};
    9.
    10.     //输出原始数组:原始数组:张三(5)->李四(4)->王五(3)->麻子(2)->淘气(1)->
    11.     Page.Response.Write("原始数组:");
    12.     for(int i=0;i<arrSid.Length;i++)
    13.         Page.Response.Write(arrSname[i]+"("+arrSid[i]+"->");
    14.
    15.     //根据学号关键字排序
    16.     Array.Sort(arrSid,arrSname);
    17.     Page.Response.Write("<br/>");
    18.
    19.     //并输出排序后的数组:淘气(1)->麻子(2)->王五(3)->李四(4)->张三(5)
    20.     Page.Response.Write("排序以后数组:");
    21.     for(int i=0;i<arrSid.Length;i++)
    22.         Page.Response.Write(arrSname[i]+"("+arrSid[i]+"->");
    23.  }

示例非常简单,输出已经在注释中给出,因此不作详细说明。

5.1.8 查找数组元素

【本节示例参考:\示例代码\C05\ Array_Search】

在数组中查找元素,可以有两种解释:一是从整个数组中寻找到与给定值相同的元素,可以使用Array类的BinarySearch方法完成这个功能;二是判断数组中是否含有一个特定的元素,可以用Contains方法实现。

1.BinarySearch方法

BinarySearch使用二进制搜索算法在一维的排序Array中搜索值,注意必须是已经排序的数组。如果找到给定的值,则返回其下标;否则,返回一个负整数。其常用形式如下:

    public static int BinarySearch(Array array,object value);

其中,参数array为待搜索的数组,value为要寻找的元素值。下面的示例首先定义了一个数组,含有元素{5,4,3,2,1},然后利用BinarySearch方法返回其中的元素3的下标(2)。

代码5-9 利用BinarySearch搜索数组元素示例:Default.aspx.cs

    1.   ///<summary>
    2.   /// 利用BinarySearch二分搜索
    3.   ///</summary>
    4.   void TestBinarySearch()
    5.   {
    6.      //定义数组
    7.      int[]myArr={5,4,3,2,1};
    8.
    9.      //利用Sort排序
    10.     Array.Sort(myArr);
    11.
    12.     //利用BinarySearch二分搜索
    13.     int target=3;
    14.     int result=Array.BinarySearch(myArr,target);//2
    15.         Page.Response.Write(target+"的下标为"+result+"<br/>");  //2
    16.   }

2.Contains方法

Contains方法可以确定某个特定值是否包含在数组中,返回一个bool值。Array类的这个方法实际上是对IList接口中方法的实现,其常用形式为:

    bool IList.Contains(object value);

其中,参数value代表所要验证的元素值,下面的示例判断学生数组arrSname中是否包含“王五”。

代码5-10 利用Contains判断数组是否包含某个元素:Default.aspx.cs

    1.   ///<summary>
    2.   /// 利用Contains方法检查是否包含某个元素
    3.   ///</summary>
    4.   void TestContains()
    5.   {
    6.      //定义数组
    7.      string[]arrSname={"张三","李四","王五","麻子","淘气"};
    8.
    9.      //判断是否含有某值
    10.     string target="王五";
    11.     bool result=((System.Collections.IList)arrSname).Contains(target);
    12.         Page.Response.Write("是否包含"+target+"="+result+"<br/>"); //true
    13.  }

可以看到,在使用Contains方法时,需要首先将数组转换为IList(队列集合)对象。这是因为,本质上,数组是一种特殊的集合对象,因此可以把它转换为一个集合对象。对于集合,将在下一章中对其进行详细的讨论。

5.1.9 反转数组元素

【本节示例参考:\示例代码\C05\ Array_Reverse】

反转数组是指将一维数组中的全部或部分元素的顺序,按照其逆序重新排列。可以使用Array类的Reverse静态方法完成这个功能。其常用的形式为:

    public static int Reverse(Array array);
    public static int Reverse(Array array,int index, int length);

其中,参数index指定所要反转元素的起始下标,而length指定所要反转的元素个数。

第一个重载形式可以反转整个数组元素,参数array为待反转的数组。下面的示例首先定义了一个数组,含有元素{5,4,3,2,1},然后利用Reverse方法进行反转。

代码5-11 利用Reverse反转数组示例:Default.aspx.cs

    1.   ///<summary>
    2.   /// 利用Reverse方法反转数组
    3.   ///</summary>
    4.   public void TestReverse()
    5.   {
    6.      //定义数组
    7.      int[]myArr={5,4,3,2,1};
    8.
    9.      //输出原始数组:原始数组:5->4->3->2->1->
    10.     Page.Response.Write("原始数组:");
    11.     for(int i=0;i<myArr.Length;i++)
    12.         Page.Response.Write(myArr[i]+",");
    13.
    14.     Page.Response.Write("<br/>");
    15.
    16.     //对数组反转
    17.     Array.Reverse(myArr);
    18.
    19.     //并输出反转后的数组:1->2->3->4->5->
    20.     Page.Response.Write("反转以后数组:");
    21.     for(int i=0;i<myArr.Length;i++)
    22.         Page.Response.Write(myArr[i]+",");
    23.  }

5.1.10 复制数组

【本节示例参考:\示例代码\C05\ Array_Copy】

复制数组可以得到一个和原数组完全一样的新数组,可以用Array的Copy或CopyTo方法来实现。

1.Copy方法

在使用Copy方法进行数组复制操作之前,必须首先为新的数组分配空间,然后再通过复制操作向新的数组空间中填入元素值。静态方法Copy的常用重载形式为:

    public static void Copy(Array sourceArray,Array destinationArray,int length);

其中,参数sourceArray为源数组,destinationArray为目标数组,而length为要复制的元素数目,默认的复制操作从第一个元素开始。下面的示例首先定义了一个数组,含有元素{5,4,3,2,1},然后利用Copy方法获取一个新的数组,包含了源数组的前3项。

代码5-12 利用Copy复制数组示例:Default.aspx.cs

    1.   ///<summary>
    2.   /// 利用Copy静态方法复制数组
    3.   ///</summary>
    4.   public void TestCopy()
    5.   {
    6.      //定义数组
    7.      int[]myArr={5,4,3,2,1};
    8.
    9.      //输出原始数组:原始数组:5,4,3,2,1,
    10.     Page.Response.Write("原始数组:");
    11.     for(int i=0;i<myArr.Length;i++)
    12.         Page.Response.Write(myArr[i]+",");
    13.     Page.Response.Write("<br/>");
    14.
    15.     //复制数组
    16.     int[]newArr=new int[3];
    17.     Array.Copy(myArr,newArr,3);
    18.
    19.     //并输出反复制的数组:5,4,3,
    20.     Page.Response.Write("复制数组:");
    21.     for(int i=0;i<newArr.Length;i++)
    22.         Page.Response.Write(myArr[i]+",");
    23.  }

注意

在使用Copy进行复制数组之前,必须要首先定义一个新的数组,并对其分配空间。另外,还需保证所分配的空间足够容纳所要复制的元素。否则在复制时将出现异常:“System.ArgumentException:目标数组的长度不够”。

2.CopyTo方法

CopyTo和Copy方法的功能类似,但它是一个实例方法,即需要在数组对象上引用。另外,它只能复制源数组所有的元素,并可以控制元素在目标数组中存放的起始位置。其常用重载形式为:

    public virtual void CopyTo( Array array, int index);

其中,参数array代表所要复制的目标数组,index则表示开始复制的元素下标。下面的代码实现对源数组的复制,并从第3个位置开始存放。

代码5-13 利用CopyTo复制数组示例:Default.aspx.cs

    1.   ///<summary>
    2.   /// 利用CopyTo静态方法复制数组
    3.   ///</summary>
    4.   public void TestCopyTo()
    5.   {
    6.      //定义数组
    7.      int[]myArr={5,4,3,2,1};
    8.
    9.      //输出原始数组:原始数组:5,4,3,2,1,
    10.     Page.Response.Write("原始数组:");
    11.     for(int i=0;i<myArr.Length;i++)
    12.         Page.Response.Write(myArr[i]+",");
    13.     Page.Response.Write("<br/>");
    14.
    15.     //复制数组
    16.     int[]newArr=new int[7];
    17.     myArr.CopyTo(newArr,2);
    18.
    19.     //并输出复制的数组:0,0,5,4,3,2,1,
    20.     Page.Response.Write("复制数组:");
    21.     for(int i=0;i<newArr.Length;i++)
    22.         Page.Response.Write(newArr[i]+",");
    23.  }

另外,从输出结果还可以看到,在初始化int型数组时,其默认值为0。

5.2 集合

上面介绍的数组常常用来实现静态的操作,即不改变其空间大小,如查找、遍历等。数组也可以实现动态的操作,如插入、删除等,但不推荐使用,而应尽量使用本节介绍的集合来代替。

5.2.1 什么是集合

集合是指一组类似的对象。在.NET中,任意类型的对象都可以放入一个集合中。.NET中的集合类存在于System.Collections命名空间中,其常用的类和结构如图5.5所示。

图5.5 System.Collections命名空间常用类和结构

从图5.5中可以看出,System.Collections空间中的集合种类众多,主要包括如下。

1.一般集合

一般集合是常见的集合数据结构,包括哈希表、队列、堆栈、字典和列表等。对于这些集合的详细含义,读者可参考数据结构方面的资料,本书不作详细介绍,简单说明如下:

(1)列表(ArrayList):一个一维的动态数组,可以装载一组相似的数据元素。

(2)队列(Quene):先进先出的列表。

(3)堆栈(Stack):先进后出的列表。

(4)哈希表(Hashtable):集合中的每个元素都是一个<键(key),值(value)>对的列表。

(5)字典(DictionaryEntry):一个<键(key),值(value)>对。

2.专用集合

专用集合是具有特定用途的集合,如StringCollection类,其元素只能为String对象。

3.位集合

位集合的元素为位标志,每个元素都是一位,而不是一个对象,常用于操作数只包含1、0的集合。

5.2.2 列表类ArrayList

System.Collections.ArrayList类实现了可变大小的一维数组,其常用属性和方法如图5.6所示。

图5.6 ArrayList类的属性和方法

ArrayList类常用属性和方法的简单说明,如表5.2所示。

表5.2 ArrayList类常用属性/方法说明

下面各节,将详细介绍其中最常用的方法。

5.2.3 创建列表

【本节示例参考:\示例代码\C05\ArrayList_Create】

利用ArrayList的构造函数来创建一个新的列表,常用的形式如下:

    public ArrayList();
    public ArrayList(int capacity);

参数capacity可以指定所创建列表的初始容量。如果不指定,则初始容量为.NET的默认值16。下面的代码创建了两个列表对象:

    ArrayList arr1=new ArrayList();
    ArrayList arr2=new ArrayList(100);

其中,arr1的初始容量为16,arr2为100。目前,两者里面都是空的,没有任何元素。随着操作的进行,当列表中的元素达到其最大容量时,列表将自动将其容量增加1倍。另外,如果想要使用ArrayList,首先需要在代码头部引入命名空间:

    using System.Collections;

5.2.4 遍历列表

【本节示例参考:\示例代码\C05\ArrayList_Traverse】

1.使用foreach语句

遍历列表是指访问列表中的所有元素一遍,可以使用foreach语句完成这个功能。下例使用foreach输出列表arr1中的所有元素。

代码5-14 使用foreach遍历列表示例:Default.aspx.cs

    1.   ///<summary>
    2.   /// 利用Foreach语句遍历集合
    3.   ///</summary>
    4.   void TestForeach()
    5.   {
    6.      ArrayList arr1=new ArrayList();
    7.
    8.      //循环添加元素0~9
    9.      for(int i=0;i<10;i++)
    10.         arr1.Add(i);
    11.
    12.     //使用foreach遍历数组,输出所有元素
    13.     foreach(object item in arr1)
    14.     {
    15.         Page.Response.Write(item+"<br/>");
    16.     }
    17.  }

2.使用GetEnumerator方法

除了foreach之外,还可以使用ArrayList的GetEnumerator方法实现列表的遍历。其形式为:

    public virtual IEnumerator GetEnumerator();

该方法返回整个ArrayList的枚举对象IEnumerator。这个对象可以得到一个集合对象的所有元素,其最主要的属性为Current,用于获取集合中的当前元素。

主要的方法包括:

MoveNext:将枚举推进到集合的下一个元素。在传递到集合的末尾之后,枚举数放在集合中最后一个元素后面,且调用MoveNext会返回false。

Reset:将枚举设置为其初始位置,该位置位于集合中第一个元素之前。

下面的代码使用GetEnumerator实现上面foreach同样的列表遍历功能。

代码5-15 使用GetEnumerator遍历列表示例:Default.aspx.cs

    1.   ///<summary>
    2.   /// 利用GetEnumerator遍历数组
    3.   ///</summary>
    4.   void TestGetEnumerator()
    5.   {
    6.      ArrayList arr1=new ArrayList();
    7.
    8.      //循环添加元素0~9
    9.      for(int i=0;i<10;i++)
    10.         arr1.Add(i);
    11.
    12.     //使用GetEnumerator遍历数组
    13.     System.Collections.IEnumerator enm=arr1.GetEnumerator();
    14.     while(enm.MoveNext())
    15.     {
    16.         Page.Response.Write(enm.Current+"<br/>");
    17.     }
    18.  }

注意

最初,枚举数被定位于集合中第一个元素的前面。此时,调用Current会引发异常。因此在读取Current的值之前,必须调用MoveNext将枚举数提前到集合的第一个元素。

5.2.5 添加元素

【本节示例参考:\示例代码\C05\ArrayList_Add】

可以通过ArrayList的Add和AddRange方法,实现向一个列表中添加数据。两者的区别在于:Add一次只能添加一个元素,而AddRange一次可以添加多个元素,这多个元素需要放在一个集合或数组中。两者常用的形式如下:

    public int Add(object value);
    public void AddRange(ICollection c);

下面的示例中,首先定义了一个列表arr1,然后使用Add方法,向arr1中添加了两个元素,其中第一个为字符串对象“Hello”,第二个为一个整数对象1。

然后分别定义了两个列表arr2、arr3,并分别使用Add和AddRange方法试图将arr1中的所有数据都添加到arr2、arr3中。从结果中可以看出,只有使用AddRange才能实现这个目的,而使用Add方法则可以得到一个二维数组,第一维的元素为arr1。

代码5-16 向ArrayList中添加元素示例:Default.aspx.cs

    1.   ArrayList arr1=new ArrayList();
    2.
    3.   //向arr1中添加一个字符串对象“Hello”
    4.   object item=new object();
    5.   item="Hello";
    6.   arr1.Add(item);              //arr1:{"Hello"}
    7.   //向arr1中添加一个整数对象1
    8.   item=1;
    9.   arr1.Add(item);              //arr1:{"Hello",1}
    10.
    11.  //向另一个列表中添加arr1中的所有元素
    12.  ArrayList arr2=new ArrayList();
    13.  arr2.Add(arr1);              //arr2只有一个元素:{arr1}={{“Hello",1}}
    14.
    15.  ArrayList arr3=new ArrayList();
    16.  arr3.AddRange(arr1);         //arr3:{"Hello",1}

Add和AddRange方法只能将元素添加到列表的末尾,如果想要在列表的任意位置添加元素,则需要使用Insert方法。

5.2.6 插入元素

【本节示例参考:\示例代码\C05\ArrayList_Insert】

如前面所述,使用ArrayList的Insert和InsertRange方法,可以实现向一个列表中的任意位置添加数据。这两者的区别同Add与AddRange的区别类似,Insert一次只能添加一个元素,而InsertRange一次可以添加某个集合中的多个元素。两者常用的形式分别如下:

    public int Insert(int index, object value);
    public void InsertRange(int index, ICollection c);

其中,参数index指定元素所要插入的位置,从0开始索引。下面的示例中,首先定义了一个列表arr1,然后使用Add方法,向arr1中添加了两个元素,其中第1个为字符串对象“Hello”,第2个为一个整数对象1。然后,在两者之间插入一个整数型数组元素{1,2,3}。

代码5-17 向ArrayList中插入元素示例:Default.aspx.cs

    1.   ArrayList arr1=new ArrayList();
    2.
    3.   //向arr1中添加一个字符串对象“Hello”
    4.   object item=new object();
    5.   item="Hello";
    6.   arr1.Add(item);              //arr1:{"Hello"}
    7.   //向arr1中添加一个整数对象1
    8.   item=1;
    9.   arr1.Add(item);              //arr1:{"Hello",1}
    10.
    11.  //插入一个数组元素arr2
    12.  int[]arr2=new int[]{1,2,3};
    13.  arr1.Insert(1,arr2);

使用Insert方法把arr2插入在位置1后,arr1的结果如图5.7所示。可以看出,arr1的长度为3,下标1处的元素为数组arr2,里面又包括了3个整数元素。

图5.7 使用Insert后的arr1结果

如果把代码第13行的Insert改为InsertRange,则将会把数组中的所有元素插入到arr1中,而不是数组本身,结果将如图5.8所示。

图5.8 使用InsertRange后的arr1结果

5.2.7 删除元素

【本节示例参考:\示例代码\C05\ArrayList_Delete】

ArrayList中支持删除元素的方法分别如下:

    public void Remove(object obj);

该方法用于删除数组中特定对象obj的第一个匹配项。参数obj为要从ArrayList移除的Object。

    public void RemoveAt(int index);

该方法用于移除ArrayList的指定索引处的元素。参数index为要移除的元素的从0开始的索引。

    public void RemoveRange(int index,int count);

该方法用于从ArrayList中移除一定范围的元素。参数index为要移除元素的起始索引(从0开始计数),参数count为要移除的元素数。

下面的示例中,首先定义了一个列表arr1,使用Add方法添加进10个元素(整数0~9),然后分别使用上面的3种方法,分别删除掉元素3、元素5,以及元素{7, 8, 9}。

代码5-18 从ArrayList中删除元素示例:Default.aspx.cs

    1.   ArrayList arr1=new ArrayList();
    2.
    3.   //循环添加元素1~9
    4.   for(int i=0;i<10;i++)
    5.       arr1.Add(i);
    6.
    7.   //使用Remove(),删除掉3
    8.   arr1.Remove(3);
    9.
    10.  //使用RemoveAt(),删除掉5,注意:此时,元素5的下标为?
    11.  arr1.RemoveAt(4);
    12.
    13.  //使用RemoveRange(),一次删除掉7、8、9,注意,元素7的下标为?
    14.  arr1.RemoveRange(5,3);

5.2.8 简单排序

【本节示例参考:\示例代码\C05\ArrayList_SimpleSort】

对集合元素进行排序也是非常重要的操作之一,许多进一步的操作可以在排序的基础上进行,例如二分查找等。ArrayList使用Sort方法来实现排序功能,其常用形式如下:

    public virtual void Sort();

该形式可以对整个ArrayList中的元素进行排序。

下例简单使用了无参数的Sort方法,对一个列表中的元素进行排序。这时,默认的排序策略为非递减排序。

代码5-19 使用Sort方法对列表排序示例:Default.aspx.cs

    1.   ///<summary>
    2.   /// 利用Sort方法进行排序
    3.   ///</summary>
    4.   public void TestSort()
    5.   {
    6.      ArrayList arr1=new ArrayList();
    7.
    8.      //循环添加元素5~1
    9.      for(int i=5;i>0;i--)  //arr1:{5,4,3,2,1}
    10.         arr1.Add(i);
    11.
    12.     //使用Sort(),实现简单的非递减排序
    13.     arr1.Sort();          //arr1:{1,2,3,4,5}
    14.  }

5.2.9 复杂排序

【本节示例参考:\示例代码\C05\ArrayList_ComplexSort】

有时需要进行更为复杂的排序操作。例如,利用逆向比较策略(即1>2策略)进行排序等。这时需要首先利用IComparer接口实现一个具体的比较策略,然后再利用Sort方法进行排序,所用到的Sort方法形式如下:

    public virtual void Sort(IComparer comparer);

该方法使用指定的比较策略对整个ArrayList中的元素进行排序。其中,参数comparer为比较元素时要使用的比较策略,是对接口IComparer的实现。

下例中,首先利用IComparer接口方法,实现一个逆向比较策略类。IComparer中包含一个Compare接口,需要对这个接口进行实现,返回一个整数值来表示两个待比较对象x、y的大小关系。正数表示x>y;0表示x= =y;负数表示x<y。

代码5-20 使用Compare方法实现逆比较示例:Default.aspx.cs

    1.   public class myReverserClass:IComparer
    2.   {
    3.       //实现IComparer接口的Compare方法,实现逆比较
    4.       int IComparer.Compare(Object x,Object y)
    5.       {
    6.           if(Convert.ToInt32(x)>Convert.ToInt32(y))
    7.                return-1;
    8.           else if(Convert.ToInt32(x)==Convert.ToInt32(y))
    9.                return 0;
    10.          else
    11.               return 1;
    12.      }
    13.  }

然后,便可以使用这个比较器,结合Sort方法进行逆排序,如下所示:

    1.   ArrayList arr1=new ArrayList();
    2.
    3.   //循环添加元素1~5
    4.   for(int i=1;i<6;i++)        //arr1:{1,2,3,4,5}
    5.       arr1.Add(i);
    6.
    7.   //使用逆比较策略,实现逆排序
    8.   IComparer myComparer=new myCompareReverse();
    9.   arr1.Sort(myComparer);       //arr1:{5,4,3,2,1}

另外,当需要对数组中的某一部分进行排序时,可以使用Sort的如下形式:

    public virtual void Sort(int index,int count,IComparer comparer);

这里使用指定的比较策略对部分ArrayList中的元素进行排序。参数index为要排序的起始索引,count为要排序的范围的长度,comparer为比较元素时要使用的比较策略,是对接口IComparer的实现。

5.2.10 查找元素

【本节示例参考:\示例代码\C05\ArrayList_Search】

在集合中对特定元素的查找也是常用的操作之一,ArrayList提供了二分查找的方法BinarySearch,可以在O(logn)时间复杂度内完成查找,其常用形式如下:

    public virtual int BinarySearch(object value);

在整个已排序的ArrayList中搜索元素,并返回该元素从0开始的索引。

代码5-21首先使用默认的非递增简单排序方法对一个列表中的元素进行排序,然后使用BinarySearch方法搜索其中的特定元素,并输出其索引。

代码5-21 使用BinarySearch方法查找元素示例:Default.aspx.cs

    1.   ArrayList arr1=new ArrayList();
    2.
    3.   //循环添加元素10~1
    4.   for(int i=0;i<10;i++)
    5.       arr1.Add(10-i);
    6.
    7.   //使用BinarySearch,查找元素7,并输出
    8.   arr1.Sort();                      //首先需要进行排序
    9.   int idx=arr1.BinarySearch(7);
    10.  Page.Response.Write("arr["+idx+"]=7");//arr[6]=7

如果使用指定的排序策略对集合中的元素进行排序之后,相应地,也可以使用同样的排序策略,结合BinarySearch方法实现元素的查找。这时,其形式为:

    public virtual int BinarySearch(object value,IComparer comparer);

此时,将使用指定的比较器在整个已排序的ArrayList中搜索元素,并返回该元素从0开始的索引。参数value为要查找的元素,而Icomparer为指定的比较策略,可参考5.2.9节相关内容。

5.3 队列

上一节介绍了集合中的列表类ArrayList,下面介绍一个特殊的列表——队列。

5.3.1 什么是队列

队列实际上是一种特殊的列表。它对列表的操作进行了限制,要求列表中的元素必须满足先进先出的原则,这类似于现实生活中的排队,如图5.9所示。

图5.9 队列示意图

说明

队列及后面所介绍的堆栈等,都是非常重要的数据结构,掌握它们的思想及典型应用,在程序设计中具有重要的作用。但是,本书将不从程序设计的层次讨论Queue类的应用,而仅从集合操作的层次进行讨论。如果读者不了解数据结构的内容,笔者强烈建议补充一下这方面的知识。

5.3.2 队列类Queue

Queue类常用属性和方法如图5.10所示。

图5.10 Queue类的属性和方法

Queue类最主要的方法为入队操作Enqueue和出队操作Dequeue,分别完成在队列尾的添加新元素操作和在队列头的删除元素操作。其他Queue支持的方法,如遍历、清空等,和ArrayList类似,这里不作赘述。

5.3.3 创建队列

【本节示例参考:\示例代码\C05\Queue_Create】

利用Queue的构造函数来创建一个新的队列,常用的形式包括:

    public Queue ();
    public Queue (int capacity);
    public Queue( int capacity, float growFactor);

参数capacity可以指定所创建列表的初始容量,如果不指定,则初始容量为.NET的默认值32。而参数growFactor则指定当队列满后容量的增长率,新容量等于当前容量与growFactor的乘积,默认值为2.0。下面的代码创建了3个队列对象:

    Queue queue1=new Queue();
    Queue queue2=new Queue(100);
    Queue queue3=new Queue(100,1.5f);

其中,queue1的初始容量为32,queue2为100。目前,两者里面都是空的,没有任何元素。随着操作的进行,当列表中的元素达到其最大容量时,列表将自动增长至200。而对于queue3,其初始容量为100,当列表中的元素达到其最大容量时,列表将自动增长至150。

与ArrayList一样,如果想要使用Queue,首先需要在代码头部引入命名空间:

    using System.Collections;

5.3.4 元素入队

【本节示例参考:\示例代码\C05\Queue_Enqueue】

可以通过Queue的Enqueue,实现一个元素的入队操作,其形式如下:

    public virtual void Enqueue(object obj);

下面的示例中,首先定义了一个队列queue1,然后使用Enqueue方法,向其中添加了3个元素,其中第1个为字符串对象“Hello”,第2个为一个整数对象1,第3个为整数型数组arr1。

代码5-22 使用Enqueue元素入队示例:Default.aspx.cs

    1.   Queue queue1=new Queue();
    2.
    3.   //字符串:"Hello"入队
    4.   object item=new object();
    5.   item="Hello";
    6.   queue1.Enqueue(item);
    7.   //整数:1入队
    8.   item=1;
    9.   queue1.Enqueue(item);
    10.  //数组:arr1={1,2,3}入队
    11.  int[]arr1=new int[]{1,2,3};
    12.  queue1.Enqueue(arr1);

队列中的元素如图5.11所示。

图5.11 队列入队示例

5.3.5 元素出队

【本节示例参考:\示例代码\C05\Queue_Dequeue】

与Enqueue相反,可以通过Queue的Dequeue实现一个元素的出队操作,其形式如下:

    public virtual object Dequeue();

下面的示例中,首先定义了一个队列queue1,然后使用Enqueue方法,依次入队3个元素。然后再使用Dequeue方法,输出所有的元素。注意,这个输出的顺序和入队的顺序是一致的。

代码5-23 使用Dequeue元素出队示例:Default.aspx.cs

    1.   Queue queue1=new Queue();
    2.
    3.   //依次入队:"Hello"、1、{1,2,3}
    4.   queue1.Enqueue("Hello");
    5.   queue1.Enqueue(1);
    6.   queue1.Enqueue(new int[]{1,2,3});
    7.
    8.   //通过出队操作,输出队列中的所有元素,这个顺序,与入队的顺序一致
    9.   object outItem=new object();
    10.  while(queue1.Count>0)
    11.  {
    12.      outItem=queue1.Dequeue();
    13.      Page.Response.Write(outItem+"<br/>");
    14.  }

此时,代码输出的结果应与图5.11所示相同。

5.4 堆栈

上面介绍了具有先入先出特征的队列,下面继续介绍另一种具有先入后出特征的集合对象:堆栈。

5.4.1 什么是堆栈

与Queue一样,System.Collections.Stack实际上也是一种操作受限的列表,它要求列表中的元素必须满足先进后出的原则,如图5.12所示。

图5.12 堆栈示意图

5.4.2 堆栈类Stack

Stack类常用属性和方法如图5.13所示。其中,最主要的方法为入栈操作Push和出栈操作Pop,分别完成在堆栈的顶部添加和删除元素的操作。其他Stack支持的方法,如遍历、清空等,和ArrayList类似。

图5.13 Stack类的属性和方法

5.4.3 创建堆栈

【本节示例参考:\示例代码\C05\Stack_Create】

利用Stack的构造函数来创建一个新的堆栈,常用的形式包括:

    public Stack ();
    public Stack (int capacity);

参数capacity可以指定所创建列表的初始容量。如果不指定,则初始容量为.NET的默认值10。当堆栈中的元素达到其最大容量时,其容量将自动增加一倍。下面的代码创建了两个堆栈对象:

    Stack Stack1=new Stack();
    Stack Stack2=new Stack(100);

其中,Stack1的初始容量为10,Stack2为100。同ArrayList、Queue一样,如果想要使用Stack,首先需要在代码头部引入命名空间:

    using System.Collections;

5.4.4 元素入栈

【本节示例参考:\示例代码\C05\Stack_Push】

可以通过Stack的Push,实现一个元素的入栈操作,其形式如下:

public virtual void Push(object obj);

下面的示例中,首先定义了一个堆栈Stack1,然后使用Push方法,向其中添加了3个元素,其中第1个为字符串对象“Hello”,第2个为一个整数对象1,第3个为整数型数组arr1。

代码5-24 使用Push元素入栈示例:Default.aspx.cs

    1.   Stack stack1=new Stack();
    2.
    3.   //字符串:"Hello"入栈
    4.   object item=new object();
    5.   item="Hello";
    6.   stack1.Push(item);
    7.   //整数:1入栈
    8.   item=1;
    9.   stack1.Push(item);
    10.  //数组:arr1={1,2,3}入栈
    11.  int[]arr1=new int[]{1,2,3};
    12.  stack1.Push(arr1);

堆栈中的元素是如何存放的?把堆栈假想为一个盛放油饼的框子,那么“Hello”这张饼被压在最下面,整数1在中间,数组{1,2,3}在框子的最上面,效果如图5.14所示。

图5.14 堆栈出栈示例

5.4.5 元素出栈

【本节示例参考:\示例代码\C05\Stack_Pop】

与Push相反,可以通过Stack的Pop实现一个元素的出栈操作,其形式如下:

public virtual object Pop();

在下面的示例中,首先定义了一个堆栈Stack1,然后使用Push方法,依次入队3个元素。再使用Pop方法,输出所有的元素。这个输出的顺序和入队的顺序是相反的。

代码5-25 使用Pop元素出栈示例:Default.aspx.cs

    1.   Stack stack1=new Stack();
    2.
    3.   //依次入栈:"Hello"、1、{1,2,3}
    4.   stack1.Push("Hello");
    5.   stack1.Push(1);
    6.   stack1.Push(new int[]{1,2,3});
    7.
    8.   //利用Pop方法,循环输出stack1中所有的元素,注意,输出的顺序与入栈的顺序是相反的
    9.   object outItem=new object();
    10.  while(stack1.Count>0)
    11.  {
    12.      outItem=stack1.Pop();
    13.      Response.Write(outItem+"<br/>");
    14.  }

此时,代码输出的结果应与图5.14相同,就像从框子中把油饼逐个取出一样。

承上启下

■ 学完本章后,读者需要回答:

1.什么是数组?.NET基础类库中的System.Array和数组是什么关系?

2.能够使用数组完成以下操作。

(1)元素访问:GetValue

(2)转化类型:ConvertAll

(3)遍历数组:foreach

(4)排序数组:Sort

(5)查找元素:BinarySearch/Contains

(6)反转数组:Reverse

(7)复制数组:Copy/CopyTo

3.C#中的集合命名空间包括哪些类?

4.能够使用ArrayList类完成以下操作。

(1)添加元素:Add/AddRange

(2)插入元素:Insert/InsertRange

(3)删除元素:Remove/RemoveAt/RemoveRange

(4)排序元素:Sort

(5)查找元素:BinarySeach

5.Queue类和Stack类的特点是什么?如何实现队列及堆栈的以下操作。

(1)入队:Enqueue

(2)出队:Dequeue

(3)入栈:Push

(4)出栈:Pop

■ 在下一章中,读者将会了解:

1.利用VS.NET开发环境寻找语法错误和逻辑错误的技术。

2.利用Exception类和try-catch来捕捉程序异常的技术。

3.异常执行的先后顺序。