C#高编 - 数组
1.简单数组:
如果需要使用同一类型的多个对象,就可以使用数组。数组是引用类型,所以必须给它分配堆上的内存,为此,应使用new运算符。
数组的声明方式:
int[] myArray; myArray = new int[4]; 或 int[] myArray = new int[4]; 或 int[] myArray = new int[4]{4,7,11,2};//初始化器只能在声明变量时使用,不能再声明数组之后使用 或 int[] myArray = new int[]{4,7,11,2};//编译器会自动统计元素个数 或 int[] myArray = {4,7,11,2};
声明自定义类型的数组:
public class Person { public string FirstName{get;set;} public string LastName{get;set;} public override string ToString() { return String.Format("{0}{1}",FirstName,LastName); } } //调用 Person[] myPersons = new Person[2];
注意:如果数组中的元素是引用类型,就必须为每个数组元素分配内存。
myPerson[0] = new Person{FirstName = "Ayrton",LastName = "Senna"};
也可以对自定义类型使用数组初始化器:
Person[] myPerson2 = { new Person { FirstName = "Ayrton", LastName = "Senna"}, new Person { FirstName = "Michael",LastName = "Schumacher"} };
2.多维数组
声明二维数组,需在方括号中加上一个逗号。数组在初始化应指定每一维的大小(也成为阶)。声明数组之后,就不能修改其阶数了。
nt[,] twodim = new int[3,3]; twodim[0,0] = 1;twodim[0,1] = 2;twodim[0,2] = 3; twodim[1,0] = 4;twodim[1,1] = 5;twodim[1,2] = 6; twodim[2,0] = 7;twodim[2,1] = 8;twodim[2,2] = 9;
如果事先指定元素的值,也可以使用数组索引器来初始化二维数组。
int[,] twodim = { {1,2,3}, {4,5,6}, {7,8,9} };
声明三维数组
int[,,] threedim = { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } }, { { 9, 10 }, { 11, 12 } } };
3.锯齿数组
锯齿数组相比二维数组,大小设置比较灵活,每一行都可以有不同的大小。
在声明锯齿数组时,要依次放置左右括号。在初始化时,在第一对方括号中设置该数据包含的行数。定义各行中元素个数的第2个方括号设置为空。
int[][] jagged = new int[3][]; jagged[0] = new int[2] { 1,2 }; jagged[1] = new int[6] { 3,4,5,6,7,8 }; jagged[2] = new int[3] { 9,10,11 };
4.Array类
LongLength属性:如果数组包含的元素个数超出了整数的取值范围,就可以使用其来获得元素的个数。
Rank属性:可以获得数组的维数。
Array类是一个抽象类,所以不能使用构造函数来创建数组。但除了可以使用C#语法创建数组实例之外,还可以使用静态方法CreateInstance()创建数组。如果事先不知道元素的类型,则该静态方法就非常有用。
可以用SetValue()方法设置对应元素的值,用GetValue()方法读取对应元素的值。
Array intArray1 = Array.CreateInstance(typeof(int),5); for(int i = 0; i < 5, i++) { intArray1.SetValue(33,i); } for(int i = 0; i < 5, i++) { Console.WriteLine(intArray1.GetValue(i); }
还可以将已创建的数组强制转换成声明为int[]的数组:
int[] intArray2 = (int[])intArray1;
CreateInstance()的重载:创建多维数组和不基于0的数组。
int[] lengths = { 2,3 };//设置长度,2*3个元素的二维数组 int[] lowerBounds = { 1,10 };//第一维基于1,第二维基于10 Array racers = Array.CreateInstance(typeof(Person), lengths, lowerBounds); //SerValue()方法设置数组的元素,其参数是每一维的索引: racers.SetValue(new Person { FirstName = "Alain", LastName = "Prost" }, 1, 10); racers.SetValue(new Person { FirstName = "Emerson", LastName = "Fittipaldi" }, 1, 11); ......
尽管数组不是基于0,但可以用一般的C#表示法将它赋予一个变量。只需注意不要超出边界即可。
Person[,] racer2 = (Person[,])racers; Person first = racers2[1,10]; Person last = racers2[2,12];
5.复制数组:
使用Clone()方法,实现ICloneable接口
int[] intArray1 = { 1,2 }; int[] intArray2 = (int[])intArray1.Clone();
如果数组的元素是值类型,则会复制所有值。
如果数组包含引用类型,则不复制元素,而只复制引用。
使用Array.Copy()方法,创建浅表副本。
二者区别:Clone方法会创建一个新数组,而Copy方法必须传递阶数相同且有足够元素的已有数组。
4.2排序:
使用Array.Sort()方法,但需要数组中的元素实现IComparable接口。简单类型(如String和Int32)已实现IComparable接口,所以可以对包含这些类型的元素进行排序。
string[] names = { "Shakira","Beyonce" }; Array.Sort(names);
自定义类实现排序:
实现IComparable接口的CompareTo()方法,如果比较的对象相等,则该方法返回0,如果该实例应排在参数对象的前面,则返回小于0的值。反之返回大于0的值。
public class Person:IComparable<Person> { public int CompareTo(Person other) { if(other == null) throw new ArgumentNullException("other"); int result = this.LastName.CompareTo(other.LastName);//因为lastName属性是string简单类型,因此具有CompareTo()方法 if(result == 0) { result = this.FirstName.CompareTo(other.FirstName); } return result; } //... }
如果Person对象的排序方式与上述不同,或者不使用为数组中的元素,则可以实现IComparer接口或IComparer<T>接口,定义方法Compare(),它独立于要比较的类,所以需传入两个比较的参数。
6.数组协变
数组支持协变,这表示数组可以声明为基类,其派生类型的元素可以赋予数组元素。
数组协变只能用于引用类型,不能用于值类型。
7.ArraySegment<T>
结构ArraySegment<T>表示数组的一段。如果某方法应返回数组的一部分,或者给某方法传递数组的一部分,就可以使用数组段。
static int SumOfSegments(ArraySegment<int>[] segments) { ... }
8.枚举
- 在foreach中使用枚举,可以迭代集合中的元素,且无需知道集合中的元素个数。foreach语句使用了一个枚举器。
- 数组或集合实现带GetEumerator()方法的IEumerable接口。方法返回一个实现接口的枚举。接着,foreach语句就可以使用接口迭代集合了。
- foreach语句并不一定需要在集合类中实现这个接口,有一个名为GetEnumerator()的方法,它返回实现了IEnumerator接口的对象就足够了。
foreach语句:首先,调用GetEnumerator()方法,获得数组的一个枚举器。在while循环中—只要MoveNext()返回true—就用Current属性访问数组中的元素:
foreach(var p in persons) { Console.WriteLine(p); }
以上语句解析为下面的代码段
IEnumerator<Person> enumerator = persons.GetEnumerator(); while(enumetor.MoveNext()) { Person p = enumerator.Current; Colsole.Writeline(p); }
yield语句:返回集合的一个元素,并移动到下一个元素上。yield break可停止迭代。
包含yield语句的方法或属性也称为迭代块。迭代块可以声明为返回IEnumerator或IEnumerable接口,或者这些接口的泛型版本。可以包含多条yield return语句或yield break语句,但不能包含return语句。
注:在C#1.0中,使用foreach可以轻松迭代集合,但创建枚举器仍需要做大量工作,C#2.0添加了yield语句,以便于创建枚举器。
public class HelloCollection { public IEnumerator<string> GetEnumerator() { yield return "Hello"; yield return "World"; } }
public class MusicTitles { string[] names = { "Tubular Bells","Hergest Ridge", "Ommadawn","Platinum" }; public IEnumerator<string> GetEnumerator() { for(int i = 0;i<4;i++) { yield return names[i]; } } public IEnumerator<string> Reverse() { for(int i = 3;i>=0;i--) { yield return names[i]; } } public IEnumerable<string> Subset(int index,int length) { for(int i = 0;i<index + length;i++) { yield return names[i]; } } }
类MusicTiltles可以用默认方式迭代集合,用Reverse()方法逆序迭代标题,用Subset() 方法迭代子集。
迭代字符串数组的客户端代码先使用GetEnumerator()方法,但不必在代码中编写,因为是默认方法。其他方法需要指定。
var titles = new MusicTitles(); //正序 foreach( var title in titles) { Console.WriteLine(title); } //逆序 foreach(var title in titles.Reverse()) { Console.WriteLine(title); }
9.元组
数组合并了相同类型的对象,而元组合并了不同类型的对象。元组起源于函数编程语言(如F#),在这些语言中频繁使用元组。
.NET4定义了8个泛型Tuple类和一个静态Tuple类,它们用作元组的工厂。不同的Tuple类支持不同数量的元素。如Tuple<T1>包含一个元素,Tuple<T1,T2>包含两个元素。
10.结构比较
数组和元祖都实现接口IStructuralEquatable和IStructuralComparable。这两个接口是.NET4新增的,不仅可以比较引用,还可以比较内容。
这些接口都是显式实现的,所以在使用时需要把数组和元祖强制转换为这个接口。
接口一用于比较两个元祖或数组是否有相同的内容,接口二用于给元祖或数组排序。