第3章 数组
数组是具有相同数据类型的、由多个数值组成的一种数据结构。在C#中,数组继承于System.Array类,属于引用类型。通过数组的索引或者下标编号引用数组中的元素。一个数组包含的元素个数称为数组的长度,该长度可以是一个Int32或者Int64的值。在C#中,使用ArrayList类作为动态数组,ArrayList中的元素类型为Object,即意味着在数组中可以包含多种元素类型。
3.1 数组的定义
在C#中能够定义的数组包括一维数组、多维数组、动态数组和交错数组。
只有一个下标的数组称为一维数组,是最常用的数组。具有多个下标的数组称为多维数组。数组元素个数未事先确定的数组称为动态数组。将数组作为元素的数组称为交错数组。本示例分别定义了这四种数组。
技术要点
本示例分别定义了一维数组、多维数组、动态数组和交错数组,技术要点如下。
● 所有的数组都继承于System.Array类。
● 通过定义数组下标的个数来定义数组的维数,下标定义的索引从0开始计数,n个元素的数组下标为0到n-1,数组元素为值类型数据类型时,默认值设置为0,数组元素为引用类型数据类型时,默认值为null。
● 数组属于引用类型的数据类型。
● C#使用ArrayList实现动态数组,ArrayList是Object数据类型元素的数组,实现了IList、ICollection、IEnumerable和ICloneable接口。ArrayList数组的大小可以根据需要动态添加。
实现步骤
(1)创建控制台应用程序项目,命名为“ArrayDeclare”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; using System.Collections; namespace ArrayDeclare { class Program { static void Main(string[] args) { int[] OneDimensional1 = new int[] { -1, 0, 1, 2, 3 };//声明并初始化数组 int[] OneDimensional2 = new int[5];//声明固定长度的数组 int[] OneDimensional3 = { 4,5,6,7,8};//直接初始化 //声明并直接初始化二维数组,长度在初始化完成后立即固定 int[,] TwoDimensional = { { 10,11,12,13,14}, {20,21,22,23,24} }; ArrayList DynamicArray = new ArrayList();//声明动态数组 DynamicArray.Add(14);//在动态数组中添加元素 DynamicArray.Add(18); DynamicArray.Add(17); DynamicArray.Add(12); DynamicArray.Add(11); DynamicArray.Sort(); int[][] JaggedArray = new int[5][];//声明交错数组 JaggedArray[0] = new int [5]{30,31,32,33,34}; JaggedArray[1] = new int[3] { 40,41,42 }; Console.WriteLine("OneDimensional1的第3个元素是:{0}", OneDimensional1[2]); Console.WriteLine("OneDimensional2的第3个元素是:{0}", OneDimensional2[2]); Console.WriteLine("OneDimensional3的第3个元素是:{0}", OneDimensional3[2]); Console.WriteLine("TwoDimensional的第1,3个元素的是:{0}", TwoDimensional[0, 2]); Console.WriteLine("DynamicArray的第3个元素是:{0}", DynamicArray[2]); Console.WriteLine("JaggedArray的第1,3个元素的是:{0}", JaggedArray[0][2]); Console.ReadLine(); } } }
(3)按F5键运行程序。程序运行结果如下所示。
OneDimensional1的第3个元素是:1 OneDimensional2的第3个元素是:0 OneDimensional3的第3个元素是:6 TwoDimensional的第1,3个元素的是:12 DynamicArray的第3个元素是:14 JaggedArray的第1,3个元素的是:32
源程序解读
(1)在本示例中使用了ArrayList类,该类位于System.Collections命名空间,需要在程序中引入该命名空间。
(2)本示例使用三种不同的方式定义了三个一维数组,其中OneDimensional2是未初始化的数组实例,int类型的元素属于值类型,此时数组内的元素值均为0。
(3)多维数组的多个元素长度一旦确定不可更改,TwoDimensional数组在赋值时,两个数组的长度必须一致。
(4)使用ArrayList实现动态数组,不仅可以动态添加和删除数组,而且还能通过ArrayList类提供的方法,对数组元素进行排序、反转等多种强大的操作。
(5)交错数组是包含数组的数组,即将数组作为元素,包含在外层的数组中,其中每个数组元素的长度可以不一致。
3.2 遍历数组元素
数组作为引用类型的数据类型,在C#中提供了foreach的遍历方法。本示例说明如何使用foreach遍历数组元素。
技术要点
本示例使用foreach遍历二维数组,技术要点如下。
● foreach循环在数组中遍历,不必考虑数组的长度和维数。
● ArrayList类的元素为Object类型,可以向该类的实例中添加不同数据类型的对象。
●在foreach循环中,数组元素为值类型时的数据类型将被封装,这种情况下,对数组中的值进行修改是无效的。
实现步骤
(1)创建控制台应用程序项目,命名为“TraversalArray”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; using System.Collections; namespace TraversalArray { class Program { static void Main(string[] args) { //声明并初始化字符串二维数组 string[,] Department = {{"Sales", "Product", "Engineer"},{"Warehouse", "Manager", "Resource" }}; foreach (string strDept in Department)//使用foreach遍历数组元素 { Console.WriteLine(strDept); } ArrayList myAl = new ArrayList();//声明动态数组 myAl.Add("The"); myAl.Add("number"); myAl.Add("is"); myAl.Add(103);//ArrayList允许不同数据类型的元素 foreach (Object objinarr in myAl)//使用foreach遍历数组元素 { Console.WriteLine(objinarr); } Console.ReadLine(); } } }
(3)按F5键运行程序。程序运行结果如下所示。
Sales Product Engineer Warehouse Manager Resource The number is 103
源程序解读
(1)在本示例中使用了ArrayList类,该类位于System.Collections命名空间,需要在程序中引入该命名空间。
(2)当ArrayList数组的元素包含多种数据类型时,使用Object作为遍历的数据类型才能够遍历所有的数组元素,如果需要遍历指定数据类型的元素,只需要在foreach语句中将Object改为遍历的数据类型即可。
3.3 操作数组
因为所有的数组都继承于Array类,所以所有的数组都能够使用Array类的方法。本示例说明了如何使用Array类中的方法对数组进行操作。
技术要点
本示例使用Array类中定义的方法来对数组进行操作,技术要点如下。
● 使用Array类中的CopyTo方法,实现对一维数组的复制操作。
● 使用Array类中的Clone方法,实现数组的浅表副本。当数组中的元素为引用类型时,位于两个数组相同位置的元素,指向内存中相同的位置。
● 使用Array类中的静态Copy方法,实现数组的复制。当数组中的元素为引用类型时,位于两个数组相同位置的元素,指向内存中不同的位置。
实现步骤
(1)打开VS2008,创建控制台应用程序项目,命名为“OperationOfArray”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace OperationOfArray { class Program { static void Main(string[] args) { int[] OneDimensional1 ={ 0, 1, 2, 3, 4 };//定义一个长度为5的一维数组 int[] OneDimensional2 = new int[7];//定义一个长度为7的一维数组 OneDimensional1.CopyTo(OneDimensional2, 0); MtTestClass[] TestClassArray1 = { new MtTestClass(1, 1), new MtTestClass(2, 2) }; //创建引用类型数组的浅表副本 MtTestClass[] TestClassArray2 = (MtTestClass[])TestClassArray1.Clone(); TestClassArray2[1].test1 = 3;//修改副本中的值,此操作将改变克隆源数组的元素 TestClassArray2[1].test2 = 3; int[,] TwoDimensional1 ={ { 0, 1, 2, 3, 4 }, { 10, 11, 12, 13, 14 } }; //定义一个长度分别为2,5的二维数组 int[,] TwoDimensional2 = new int[3,7];//定义一个长度分别为3,7的二维数组 TwoDimensional2 = (int[,])TwoDimensional1.Clone(); //定义一个交错数组 int[][] JaggedArray1 = { new int[] { 20, 21, 22},new int[]{23, 24 }, new int[]{25, 26, 27, 28 } }; int[][] JaggedArray2= new int[3][]; Array.Copy(JaggedArray1, JaggedArray2,3); Console.WriteLine("OneDimensional2第4个元素为:" + OneDimensional2[3]); Console.WriteLine("OneDimensional2的长度为:" + OneDimensional2.Length); Console.WriteLine("TwoDimensional2第2,4个元素为:" + TwoDimensional2[1,3]); Console.WriteLine("JaggedArray的长度为:" + JaggedArray2.GetLength(0)); Console.WriteLine("JaggedArray第3个元素的长度为:" + JaggedArray2[2].GetLength(0)); Console.WriteLine("TestClassArray1第2个元素的test1成员值为: "+TestClassArray1[1].test1); Console.ReadLine(); } class MtTestClass//测试Clone引用类型数组的类 { public int test1; public int test2; public MtTestClass(int t1, int t2) { test1 = t1; test2 = t2; } } } }
(3)按F5键运行程序,运行结果如下所示。
OneDimensional2第4个元素为:3 OneDimensional2的长度为:7 TwoDimensional2第2,4个元素为:13 JaggedArray的长度为:3 JaggedArray第3个元素的长度为:4 TestClassArray1第2个元素的test1成员值为:3
源程序解读
(1)本示例首先在两个一维数组之间进行复制操作,然后在两个二维数组之间进行创建浅表副本的操作,接着在两个引用类型的数组之间进行创建浅表副本的操作,最后定义一个交错数组。
(2)CopyTo操作只能在两个一维数组之间进行,而Copy操作可以按照指定长度复制两个数组之间的元素,Clone操作是创建浅表副本。
(3)在引用类型数组中,Clone操作创建源数组的浅表副本,在本示例中,MtTestClass类的数组TestClassArray2是TestClassArray1的浅表副本,在修改TestClassArray2的元素值时,TestClassArray1的元素同时被改变。当使用new关键字和MtTestClass的构造函数修改TestClassArray2中的元素时,TestClassArray1中的元素不会同时改变,这是一个需要注意的地方,即两个数组在托管内存中处于不同的位置,并不存在引用关系,仅仅是两个克隆数组相同位置的元素,指向同一个托管内存位置而已。
(4)交错数组的本质是一个以数组为元素的数组,作为元素的数组,长度可以不一致,元素类型则必须是声明数组时指定的数据类型。
3.4 获取数组属性
数组具有从Array类继承的属性,这些属性描述了数组的特征,包括数据的长度、维数和同步访问对象。Array类实现System.Collections.IList接口,定义了该接口需要的属性,这些为了实现System.Collections.IList接口的属性,在初始化时被Array类内部定义为一个固定的值。从这些属性能够了解数组的一些特点。本示例说明了如何获取和使用数组的这些属性。
技术要点
本示例定义一维数组、多维数组和交错数组,通过Array类内部的定义,获取并使用数组的属性,技术要点如下。
● Array类为了实现System.Collections.IList接口,包含该接口的IsFixedSize和IsReadOnly属性。这两个属性分别描述数组是否是长度固定和只读的,对于数组,IsFixedSize属性的值固定为true,IsReadOnly的值固定为false。
● Array类还实现了System.Collections.ICollection接口,包含该接口的IsSynchronized和SyncRoot属性。IsSynchronized属性描述是否使用同步的方式访问数组,对于数组该属性固定为false。SyncRoot属性表示同步数组的对象,当使用SyncRoot属性同步时,对数组的操作在同步对象上进行,而不是在数组上直接操作。
●数组的Length属性表示数组中所有维度中元素的个数的总和。LongLength用于数组元素总数大于Int32的场合。
● 数组的Rank属性表示数组的维度,交错数组的维度总是为1。
实现步骤
(1)创建控制台应用程序项目,命名为“GetArrayProperties”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace GetArrayProperties { class Program { static void Main(string[] args) { int[] MyIntArray = new int[] { 1, 2, 3, 4, 5 };//定义一个一维数组 //定义一个二维数组 long[,] MyLongArray = new long[,] { { 6, 7, 8, 9, 10 }, { 11, 12, 13, 14, 15 }, { 16, 17, 18, 19, 20 } }; //定义一个交错数组 float[][] MyFloatArray = { new float[] { 1.0f, 1.1f, 1.2f }, new float[] { 1.3f, 1.4f, 1.5f } }; Console.WriteLine("MyIntArray长度是否固定:" + MyIntArray.IsFixedSize); Console.WriteLine("MyIntArray是否只读:" + MyIntArray.IsReadOnly); Console.WriteLine("MyIntArray是否同步访问:" + MyIntArray.IsSynchronized); Console.WriteLine("MyIntArray长度:" + MyIntArray.Length); Console.WriteLine("MyIntArray维度:" + MyIntArray.Rank); Console.WriteLine("MyIntArray同步访问对象:" + MyIntArray.SyncRoot); Console.WriteLine("-----------------------------------------"); Console.WriteLine("MyLongArray长度是否固定:" + MyLongArray.IsFixedSize); Console.WriteLine("MyLongArray是否只读:" + MyLongArray.IsReadOnly); Console.WriteLine("MyLongArray是否同步访问:" + MyLongArray.IsSynchronized); Console.WriteLine("MyLongArray长度:" + MyLongArray.Length); Console.WriteLine("MyLongArray维度:" + MyLongArray.Rank); Console.WriteLine("MyLongArray同步访问对象:" + MyLongArray.SyncRoot); Console.WriteLine("-----------------------------------------"); Console.WriteLine("MyFloatArray长度是否固定:" + MyFloatArray.IsFixedSize); Console.WriteLine("MyFloatArray是否只读:" + MyFloatArray.IsReadOnly); Console.WriteLine("MyFloatArray是否同步访问:" + MyFloatArray.IsSynchronized); Console.WriteLine("MyFloatArray长度:" + MyFloatArray.Length); Console.WriteLine("MyFloatArray维度:" + MyFloatArray.Rank); Console.WriteLine("MyFloatArray同步访问对象:" + MyFloatArray.SyncRoot); Console.WriteLine("-----------------------------------------"); lock (MyIntArray.SyncRoot)//锁定数组同步对象的示例 { Console.Write("MyIntArray的元素列表:"); foreach (int iValue in MyIntArray) { Console.Write(iValue+","); } } Console.ReadLine(); } } }
(3)按F5键运行程序,运行结果如下所示。
MyIntArray长度是否固定:True MyIntArray是否只读:False MyIntArray是否同步访问:False MyIntArray长度:5 MyIntArray维度:1 MyIntArray同步访问对象:System.Int32[] ----------------------------------------- MyLongArray长度是否固定:True MyLongArray是否只读:False MyLongArray是否同步访问:False MyLongArray长度:15 MyLongArray维度:2 MyLongArray同步访问对象:System.Int64[,] ----------------------------------------- MyFloatArray长度是否固定:True MyFloatArray是否只读:False MyFloatArray是否同步访问:False MyFloatArray长度:2 MyFloatArray维度:1 MyFloatArray同步访问对象:System.Single[][] ----------------------------------------- MyIntArray的元素列表:1,2,3,4,5,
源程序解读
(1)本示例定义了一维的整型数组MyIntArray、多维的长整型数组MyLongArray和交错的浮点型数组MyFloatArray,分别获取并输出这三个数组的属性。
(2)本示例说明了SyncRoot属性的用法,在使用foreach遍历数组的过程,是非线程安全的,即其他线程或者外部对象会在foreach未完成遍历数组的情况下,修改了数组的值。为了能够在使用foreach遍历数组时,保证数组访问的线程安全,需要在foreach循环遍历数组时锁定数组。本示例的最后给出了锁定数组,在锁定内部遍历数组的实现代码,以保证在使用foreach循环时保持数组访问的线程安全。
3.5 数组参数的使用
数组是一种引用类型的数据类型,当数组作为方法的参数传递的时候,在方法内部可以修改数组的元素。使用ref关键字声明的数组参数,必须在方法外部明确赋值,使用out关键字声明的数组参数,必须在方法内部明确赋值。本示例说明了使用in/out和ref关键字声明的三种情况时,如何将数组作为参数传递数据。
技术要点
本示例主要说明了in/out和ref关键字声明的数组参数的使用,技术要点如下。
● in关键字声明的参数不需要特别声明,在参数之前使用in关键字反而会引起编译错误。
● 数组作为in关键字声明的参数传递时,数组作为引用类型的数据类型。向方法提供位于托管堆的数组内存对象。在方法内部使用new关键字重新创建数组参数时,程序将分配位于托管堆的另外一个位置用于存储创建的数组参数对象,而不是指向原有的托管堆内存位置。
● 数组作为ref关键字声明的参数传递时,数组必须在调用前在方法外部事先明确赋值,在方法内部对数组所作的任何修改,包括使用new重新创建数组,都将反映到原先的数组上。
● 数组作为out关键字声明的参数传递时,数组必须在方法内部明确赋值,在方法内部对数组所作的任何修改,也将反映到原先的数组上。
实现步骤
(1)打开VS2008,创建控制台应用程序项目,命名为“ArrayAsParameter”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace ArrayAsParameter { class Program { static void Main(string[] args) { int[] NewChangeArray = new int[5] { 1, 2, 3, 4, 5 }; NewChange(NewChangeArray);//调用重新创建数组并修改值的方法 Console.WriteLine("调用NewChange方法后的数组:"+ ShowArray(NewChangeArray)); int[] OnlyChangeArray = new int[5] { 1, 2, 3, 4, 5 }; OnlyChange(OnlyChangeArray);//调用直接修改数组值的方法 Console.WriteLine("调用OnlyChange方法后的数组:"+ ShowArray(OnlyChangeArray)); //ref关键字声明的数组参数必须在方法外部明确赋值 int[] RefArray = new int[] { 6, 7, 8, 9, 10 }; MethodWithRefArrayParam(ref RefArray);//使用ref关键字声明数组参数 Console.WriteLine("调用MethodWithRefArrayParam方法后的数组:" + ShowArray(RefArray)); int[] OutArray=new int[]{20,21,22}; MethodWithOutArrayParam(out OutArray);//使用out关键字声明数组参数 Console.WriteLine("调用MethodWithOutArrayParam方法后的数组:" + ShowArray(OutArray)); Console.ReadLine(); } static void NewChange(int[] arrayparam) { //在方法中使用new关键字重新创建数组 arrayparam = new int[] { 1, 2, 3, 4, 5 }; arrayparam[2] = 6; } static void OnlyChange(int[] arrayparam)//不在方法中使用new关键字重新创建数组 { arrayparam[2] = 6; } static void MethodWithRefArrayParam(ref int[] arrayparam) { //即使在方法中使用new关键字重新创建数组,数组仍然指向原先的内存空间 arrayparam = new int[] { 32, 33, 34 }; arrayparam.SetValue(11, 2); } static void MethodWithOutArrayParam(out int[] arrayparam) { //out关键字声明的数组参数必须在方法内部明确赋值 arrayparam = new int[] { 11, 12, 13, 14, 15 }; } static string ShowArray(int[] ArrayWillShow) { string list = ""; foreach (int iValue in ArrayWillShow) { list = list + iValue.ToString() + ","; } list = list.Remove(list.Length-1); return list; } } }
(3)按F5键运行程序,运行结果如下所示。
调用NewChange方法后的数组:1,2,3,4,5 调用OnlyChange方法后的数组:1,2,6,4,5 调用MethodWithRefArrayParam方法后的数组:32,33,11 调用MethodWithOutArrayParam方法后的数组:11,12,13,14,15
源程序解读
(1)在NewChange方法中,使用new关键字重新创建数组参数,数组参数arrayparam和NewChangeArray数组分别指向托管内存中的不同位置,arrayparam中数组元素的修改对NewChangeArray数组没有影响。
(2)在OnlyChange方法中,不使用new关键字重新创建数组,数组参数arrayparam和NewChangeArray数组指向托管内存中的同一位置,arrayparam中数组元素的修改将同时修改NewChangeArray数组。
(3)使用ref关键字声明的数组参数,必须在调用方法前在方法外部明确赋值,在方法内部对arrayparam中数组元素的修改,将同时修改RefArray数组中的元素。
(4)使用out关键字声明的数组参数,必须在方法内部明确赋值,在方法内部对arrayparam中数组元素的修改,将同时修改OutArray数组中的元素。
(5)本示例最后提供一个ShowArray方法,以字符串的形式输出整型数组元素的列表。