数组
数组
一:简单数组
如果需要使用同一类型的对个对象,就可以使用数组。数组是一种数据结构,它可以包含同一类型的多个元素。
1.1:数组的声明:
在声明数组时,应先定义数组中元素的类型。其后是一对空方括号和一个变量名。例如:下面声明了一个包含整型元素的数组:
int[] MyArray;
1.2:数组的初始化:
声明了数组之后,就必须为整组分配内存,以保存数组中的所有的元素。数组是引用类型,所以必须给它分配堆上的内存。为此,应使用new运算符,指定数组中元素的类型和数量来初始化数组的变量。下面定义了数组的大小:
int[] MyArray = new int[4];
在声明和初始化数组之后,变量MyArray就引用了4个整数值,它们位于托管堆上,如图表示:
注意:在指定了数组的大小之后,如果不复制数组中的所有的元素,就不能重新设置数组中大小,如果事先不知道数组中应包含多少个元素,就可以使用集合。
除了在两个语句中声明和初始化数组之外,还可以在一个语句中声明和初始化数组。
int[] myArray = new int[4];
还可以使用数组初始化器为数组中的每个元素赋值。数组初始化器只能在声明数组变量时使用,不能再声明数组之后使用。
int[] myArray = new int[4] { 1, 2, 3, 4 };
如果用花括号初始化数组,则还可以不指定数组的大小。因为编译器会自动的帮助我们统计元素的个数。
int[] myArray = new int[] { 1, 2, 3, 4 };
使用c#编译器还有一种更加简化的形式,使用花括号可以同时声明数组和初始化数组。编译器生成的代码与前面的例子相同:
int[] myArray = { 1, 2, 3, 4 };
1.3:访问数组中的元素:
在声明和初始化数组后,就可以使用索引器访问其中的元素了。数组之只支持有整型参数的索引器。通过索引器传递元素的编号,就可以访问数组。索引器总是以0开头。表示第一个元素。可以传递给索引器的最大值是元素的个数减一。在下面的例子中,数组myArray用4个整型值声明和初始化。用索引器对应的值是0、1、2、3就可以访问数组中的元素了。
static void Main(string[] args) { int[] myArray = { 1, 2, 3, 4 }; int num1 = myArray[0]; //第一个值 int num2 = myArray[1]; //第二个值 int num3 = myArray[2]; //第三个值 int num4 = myArray[3]; //第四个值 myArray[3] = 5; //改变其中的值 Console.WriteLine(num1); Console.WriteLine(num2); Console.WriteLine(num3); Console.WriteLine(num4); Console.ReadKey(); }
如果使用错误的索引器(大于数组的长度),就会抛出异常。
如果不知道数组中的元素的个数,则可以在for语句中使用Length属性。
for (int i = 0; i < myArray.Length; i++) { Console.WriteLine(myArray[i]); }
除了使用for语句迭代数组中的所有的元素之外,还可以使用foreach语句(在后面讨论IEnumerable和IEnumerator接口):
foreach (var myArray in myArrays) { Console.WriteLine(myArray); }
1.4:使用引用类型:
除了能声明预定义类型的数组,还可以声明自定义类型的数组。下面使用Person类来说明,这个类有自动实现的属性FirstName和LastName,以及从Object类重写的ToString()方法。
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public override string ToString() { return string.Format("{0},{1}", this.FirstName, this.LastName); } }
声明一个包含两个Person元素中的数组与声明一个int数组类似:
Person[] myPersons = new Person[2];
但是必须注意:如果数组中的元素是引用类型,就必须为每个数组元素分配内存。若使用了数组中未分配了内存的元素,就会抛出异常。
使用从0开始的索引器,可以为数组中的每个元素分配内存:
myPersons[0] = new Person { FirstName = "张", LastName = "三" }; myPersons[1] = new Person { FirstName = "李", LastName = "四" };
如下图显示了Person数组中的对象在托管堆中的情况。myPersons是存储在栈上的一个变量。该变量引用了存储在托管堆上的Person元素对应的数组。这个数组有足够容纳两个引用的空间。数组中的每一项都引用了一个Person对象。而这些Person对象也存储在托管堆上。
与int类型一样,也可以对自定义类型使用数组初始化器:
Person[] myPersons = { new Person { FirstName = "张", LastName = "三" }, new Person { FirstName = "李", LastName = "四" } };
二:多维数组
一般数组(一维数组)用一个整数来索引。多维数组用两个或者多个整数来索引。如下图,是二维数组的数学表示法,该数组有3行3列,第一行的值是1,2,3,第三行的值是7,8,9。
在c#中声明一个二维数组,需要在方括号中加上一个逗号,数组在初始化时应指定每一维的大小(也称为阶)。接着,就可以使用两个整数作为索引器来访问数组中的元素了。
static void Main(string[] args) { int[,] twoim = new int[3, 3]; twoim[0, 0] = 1; twoim[0, 1] = 2; twoim[0, 2] = 3; twoim[1, 0] = 4; twoim[1, 1] = 5; twoim[1, 2] = 6; twoim[2, 0] = 7; twoim[2, 1] = 8; twoim[2, 2] = 9; }
注意:在声明数组之后,就不能修改其阶数了。
如果事先知道元素的值,则也可以使用数字索引器来初始化二维数组。在初始化数组时,使用一个外层的花括号,每一行用包含在外层花括号中的内层花括号来初始化。
int[,] twoim = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
使用数组初始化器事时,必须初始化数组中的每个元素,不能遗漏任何的元素。
三:Array数组
用方括号声明数组是c#中使用的Array类的表示法。在后台使用c#语法。会创建一个派生子抽象基类Array的新类。这样,就可以使用Array类为每个c#数组定义的方法和属性了。例如前面就使用了Length属性。或者使用foreach语句迭代数组。其实是使用了Array类的GetEnumerator()方法。
3.1:创建数组:
Array类是一个抽象类,所以不能使用构造函数来创建数组。但是除了可以使用c#语法创建数组实例之外,还可以使用静态方法CreatInstance()创建数组。如果事先不知道元素的类型。该静态方法就非常的有用。因为类型可以作为Type对象传递给CreatInstance()方法。
下面我们创建一个类型为int、大小为5的数组。CreatInstance()的第一个参数就是元素的类型。第二个参数是定义数组的大小。可以用SetValue()方法设置对应元素的值。用GetValue()读取定义元素的值。
static void Main(string[] args) { Array intArray = Array.CreateInstance(typeof(int), 5); for (int i = 0; i < 5; i++) { //值的设置 intArray.SetValue(33, i); } for (int i = 0; i < 5; i++) { //获取数组中的值 Console.WriteLine(intArray.GetValue(i)); } Console.ReadKey(); }
还可以将已创建的数组强制转换成声明为int[]的数组。
int[] intArray2 = (int[]) intArray;
3.2:数组的复制:
因为数组是引用类型,所以将一个数组变量赋予另一个数组变量。就会得到两个引用同一个数组的变量。而赋值数组,会使数组实现ICloneable接口。这个接口定义的Clone()方法会创建数组的浅表副本。
如果数组中的元素是值类型,以下代码段就会复制所有的值,如图所示:
int[] intArrays = { 1, 2 }; int[] intArrays_2 = (int[])intArrays.Clone();
如果数组中包含引用类型,则不是复制元素,而是只复制引用。beatles和beatlesClone引用的Person是相同的。如果修改beatles中的一个元素,就会改变beatles中对应对象。
Person[] beatles = { new Person { FirstName = "张", LastName = "三" }, new Person { FirstName = "李", LastName = "四" } }; Person[] beatlesClone = (Person[])beatles.Clone();
除了使用Clone()方法之外,还可以使用Array.Copy()方法创建浅表副本。但是Clone()和Copy()方法有一个重要的区别:Clone()方法会创建一个新数组,而Copy()方法必须传递阶数相同且有足够元素的已有数组。
3.3:排序:
Array类使用QuickSort算法对数组中的元素进行排序。Sort()方法需要数组中的元素实现IComparable接口。因为简单类型实现IComparable接口。所以可以对包含这些类型的元素排序。
我们首先来看一个数组:
static void Main(string[] args) { string[] names = { "Christina Aguilera", "Ahakira", "Beyonce", "Dady Gaga" }; //进行排序 Array.Sort(names); //数组中元素的反转 Array.Reverse(names); foreach (string name in names) { Console.WriteLine(name); } Console.ReadKey(); }
四:枚举
在foreach语句中使用枚举,可以迭代集合中的元素,且无需知道集合中的元素个数。foreach语句使用了一个枚举器。数组或者集合首先带GetEnmerator()方法的IEumerable接口。GetEumerator()方法返回一个实现IEumerable接口的枚举。接着foreach语句就可以使用IEumerable接口迭代集合了。
foreach 语句使用IEnumerator接口的方法和属性,迭代集合中的所有的元素。为此,IEnumerator定义了Current属性,来返回光标所在的元素。该接口的MoveNext()方法移动到集合的下一个元素上,如果有这个元素,该方法就返回true。如果集合中不在有更多的元素,该方法就返回false。
c#的foreach语句不会解析为IL代码中的foreach语句。c#编译器会把foreach语句转换为IEnumerable接口中的方法和属性。下面是一条简单的foreach语句。它迭代names数组中的所有的元素。
string[] names = { "Christina Aguilera", "Ahakira", "Beyonce", "Dady Gaga" };
foreach (string name in names) { Console.WriteLine(name); }
4.1:yield 语句
使用foreach语句可以轻松的迭代集合。c#2.0添加了yield语句。以便于创建枚举器。yield return 语句返回集合的一个元素,并移动到下一个元素中。yield break 可停止迭代。
public IEnumerable<string> GetEnumerator() { yield return "Hello"; yield return "World"; }
包含yield语句的方法或者属性也称为迭代块。迭代块必须声明为返回IEnumerator或者IEnumerable接口。这个块中可以包含多条yield return语句或者yield break语句。但是不能包含return 语句。
现在可以迭代foreach语句迭代集合了:
public void HelloWorld() { var helloCollection = new HelloCollection(); foreach (var s in helloCollection) { Console.WriteLine(s); } }