C#中的数组用法详解
目录
如果需要使用同一类型的多个对象,可以使用数组和集合(后面介绍)。C#用特殊的记号声明,初始化和使用数组。Array类在后台发挥作用,它为数组中的元素排序和过滤提供了多个方法。使用枚举器,可以迭代数组中的所有元素。
如果需要使用不同类型的多个对象,可以使用Tuple(元组)类型。
一.简单数组(一维数组)
数组是一种数据结构,它可以包含同一个类型的多个元素。
1.数组的声明
在声明数组时,先定义数组中的元素类型,其后是一对空方括号和一个变量名。
int[] myArray;
2.数组的初始化
声明了数组之后,就必须为数组分配内存,以保存数组的所有元素。数组是引用类型,所以必须给它分配堆上的内存。为此,应使用new运算符,指定数组中元素的类型和数量来初始化数组的变量。
myArray = new int[4];
在声明和初始化数组后,变量myArray就引用了4个整数值,它们位于托管堆上:
在指定了数组的大小后,就不能重新设置数组的大小。如果事先不知道数组中应包含多少个元素,就可以使用集合。
除了在两个语句中声明和初始化数组之外,还可以在一个语句中声明和初始化数组:
int[] myArray = new int[4];
还可以使用数组初始化器为数组的每个元素复制。数组初始化器只能在声明数组变量时使用,不能在声明数组之后使用。
int[] myArray = new int[4]{1,3,5,7};
如果用花括号初始化数组,可以不指定数组的大小,因为编译器会自动统计元素的个数:
int[] myArray = new int[]{1,3,5,7};
也可以使用更简单的形式:
int[] myArray = {1,3,5,7};
3.访问数组元素
在声明和初始化数组之后,就可以使用索引器访问其中的元素了。数组只支持有整型参数的索引器。
索引器总是以0开头,表示第一个元素。可以传递给索引器的最大值是元素个数减1,因为索引从0开始:
int[] myArray = {1,3,5,7};
int v1 = myArray[0];
int v2 = myArray[1];
myArray[3] = 4;
可以使用数组的Length属性获取元素的个数。
4.数组中使用引用类型
数组除了能声明预定义类型的数组,还可以声明自定义类型的数组。
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];
myPersons[0] = new Person { FirstName = "Ayrton", LastName = "Senna" };
myPersons[1] = new Person { FirstName = "Michael", LastName = "Schumacher" };
如果数组中的元素是引用类型,就必须为每个数组元素分配内存。如果使用了数组中未分配内存的元素,就会抛出NullReferenceException类型的异常。
下面是内存情况:
对自定义类型也可以使用数组初始化器:
Person[] myPersons2 =
{
new Person { FirstName="Ayrton", LastName="Senna"},
new Person { FirstName="Michael", LastName="Schumacher"}
};
二.多维数组
多维数组用两个或多个整数来索引。
在C#中声明多维数组,需要在方括号中加上逗号。数组在初始化时应指定每一维的大小(也称为阶)。
int[,] 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}}
};
Console.WriteLine(threeDim[0,1,1]);
三.锯齿数组
二维数组的大小对应于一个矩形,而锯齿数组的大小设置比较灵活,在锯齿数组中,每一行都可以有不同的大小。
在声明锯齿数组时,要依次放置左右括号。在初始化锯齿数组时,只在第一对方括号中设置该数组包含的行数。定义各行中元素个数的第二个方括号设置为空,因为这类数组的每一行包含不同的元素个数。之后,为每一行指定行中的元素个数:
int[][] jagged = new int[3][];
jagged[0] = new int[2]{1,2};
jagged[1] = new int[4]{3,4,5,6};
jagged[2] = new int[3]{7,8};
迭代锯齿数组中的所有元素的代码可以放在嵌套的for循环中。在外层的for循环中迭代每一行,在内层的for循环中迭代一行中的每个元素:
for(int row = 0;row { for(int element = 0;element { Console.WriteLine("row:{0}, element:{1},value:{2}",row,element,jagged[row][element]); } } 四.Array类 用方括号声明数组是C#中使用Array类的表示法。在后台使用C#语法,会创建一个派生自抽象基类Array的新类。这样,就可以使用Array类为每个C#数组定义的方法和属性了。 Array类实现的其它属性有LongLength和Rank。如果数组包含的元素个数超出了整数的取值范围,就可以使用LongLength属性来获得元素个数。使用Rank属性可以获得数组的维数。 1.创建数组 Array类是一个抽象类,所以不能使用构造函数来创建数组。但除了使用C#语法创建数组实例之外,还可以使用静态方法CreateInstance()创建数组。如果事先不知道元素的类型,该静态方法就很有用,因为类型可以作为Type对象传递给CreateInstance()方法。 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的数组。 //创建一个2X3的二维数组,第一维基于1,第二维基于10: int[] lengths = { 2, 3 }; int[] lowerBounds = { 1, 10 }; Array racers = Array.CreateInstance(typeof(Person), lengths, lowerBounds); racers.SetValue(new Person { FirstName = "Alain", LastName = "Prost" }, index1: 1, index2: 10); racers.SetValue(new Person { FirstName = "Emerson", LastName = "Fittipaldi" }, 1, 11); racers.SetValue(new Person { FirstName = "Ayrton", LastName = "Senna" }, 1, 12); racers.SetValue(new Person { FirstName = "Michael", LastName = "Schumacher" }, 2, 10); racers.SetValue(new Person { FirstName = "Fernando", LastName = "Alonso" }, 2, 11); racers.SetValue(new Person { FirstName = "Jenson", LastName = "Button" }, 2, 12); Person[,] racers2 = (Person[,])racers; Person first = racers2[1, 10]; Person last = racers2[2, 12]; 2.复制数组 因为数组是引用类型,所以将一个数组变量赋予另一个数组变量,就会得到两个引用同一数组的变量。 数组实现ICloneable接口。这个接口定义的Clone()方法会复制数组,创建数组的浅表副本。 如果数组的元素是值类型,Clone()方法会复制所有值: int[] a1 = {1,2}; int[] a2 = (int[])a1.Clone(); 如果数组包含引用类型,只复制引用。 除了使用Clone()方法之外,还可以使用Array.Copy()方法创建浅表副本。 Person[] beatles = { new Person { FirstName="John", LastName="Lennon" }, new Person { FirstName="Paul", LastName="McCartney" } }; Person[] beatlesClone = (Person[])beatles.Clone(); Person[] beatlesClone2 = new Person[2]; Array.Copy(beatlesClone,beatlesClone2,2);//注意与Clone的语法区别,Copy需要传递阶数相同的已有数组。(还可以使用CopyTo()方法) 3.排序 Array类使用快速排序算法对数组中的元素进行排序。Sort()方法需要数组中的元素实现IComparable接口。因为简单类型(如String,Int32)实现IComparable接口,所以可以对包含这些类型的元素排序。 string[] names = { "Christina Aguilera", "Shakira", "Beyonce", "Gwen Stefani" }; Array.Sort(names); foreach (string name in names) { Console.WriteLine(name); } 如果对数组使用使用自定义类,就必须实现IComparable接口。这个接口只定义了一个方法CompareTo()方法,如果要比较的对象相等,该方法就返回0.如果该实例应排在参数对象的前面,该方法就返回小于i0de值。如果该实例应排在参数对象的后面,该方法就返回大于0的值。 public class Person : IComparable { public string FirstName { get; set; } public string LastName { get; set; } public override string ToString() { return String.Format("{0} {1}", FirstName, LastName); } public int CompareTo(Person other) { if (other == null) throw new ArgumentNullException("other"); int result = this.LastName.CompareTo(other.LastName); if (result == 0) { result = this.FirstName.CompareTo(other.FirstName); } return result; } } 客户端代码: Person[] persons = { new Person { FirstName="Damon", LastName="Hill" }, new Person { FirstName="Niki", LastName="Lauda" }, new Person { FirstName="Ayrton", LastName="Senna" }, new Person { FirstName="Graham", LastName="Hill" } }; Array.Sort(persons); foreach (Person p in persons) { Console.WriteLine(p); } 如果Person对象的排序方式与上述不同,或者不能修改在数组中用作元素的类,就可以实现IComparer接口或IComparer public enum PersonCompareType { FirstName, LastName } //通过使用实现了IComparer public class PersonComparer : IComparer { private PersonCompareType compareType; public PersonComparer(PersonCompareType compareType) { this.compareType = compareType; } #region IComparer public int Compare(Person x, Person y) { if (x == null) throw new ArgumentNullException("x"); if (y == null) throw new ArgumentNullException("y"); switch (compareType) { case PersonCompareType.FirstName: return x.FirstName.CompareTo(y.FirstName); case PersonCompareType.LastName: return x.LastName.CompareTo(y.LastName); default: throw new ArgumentException( "unexpected compare type"); } } #endregion } 客户端代码: Person[] persons = { new Person { FirstName="Damon", LastName="Hill" }, new Person { FirstName="Niki", LastName="Lauda" }, new Person { FirstName="Ayrton", LastName="Senna" }, new Person { FirstName="Graham", LastName="Hill" } }; Array.Sort(persons, new PersonComparer(PersonCompareType.FirstName)); foreach (Person p in persons) { Console.WriteLine(p); } 五.数组作为参数 数组可以作为参数传递给方法,也可以从方法中返回。 1.数组协变 数组支持协变。这表示数组可以声明为基类,其派生类型的元素可以赋值于数组元素。 static void DisPlay(object[] o) { //.. } 可以给该方法传递一个Person[]。 数组协变只能用于引用类型,不能用于值类型。 2.ArraySegment 结构ArraySegment ArraySegment static void Main() { int[] ar1 = { 1, 4, 5, 11, 13, 18 }; int[] ar2 = { 3, 4, 5, 18, 21, 27, 33 }; var segments = new ArraySegment { new ArraySegment new ArraySegment }; var sum = SumOfSegments(segments); Console.WriteLine("sum of all segments: {0}", sum); } static int SumOfSegments(ArraySegment { int sum = 0; foreach (var segment in segments) { for (int i = segment.Offset; i < segment.Offset + segment.Count; i++) { sum += segment.Array[i]; } } return sum; } 数组段不复制原数组的元素,但原数组可以通过ArraySegment 六.枚举集合 在foreach语句中使用枚举,可以迭代集合中的元素,且无需知道集合中元素的个数。foreach语句使用一个枚举器。foreach会调用实现了IEnumerable接口的集合类中的GetEumerator()方法。GetEumerator()方法返回一个实现IEnumerator接口的对象枚举。foreach语句就可以使用IEnumerable接口迭代集合了。 GetEumerator()方法在IEnumerable接口中定义。 1.IEnumerator接口 foreach语句使用IEnumerator接口的方法和属性,迭代集合中所有元素。IEnumerator接口定义了Current属性,来返回光标所在的元素,该接口的MoveNext()方法移动到集合的下一个元素上,如果有这个元素,该方法就返回true。如果集合不再有更多的元素,该方法就返回false. 这个接口的泛型版本IEnumerator 2.foreach语句 C#中foreach语句不会解析为IL代码中的foreach语句。C#编译器会把foreach语句转换为IEnumerator接口的方法和属性。 Person[] persons = { new Person { FirstName="Damon", LastName="Hill" }, new Person { FirstName="Niki", LastName="Lauda" }, new Person { FirstName="Ayrton", LastName="Senna" }, new Person { FirstName="Graham", LastName="Hill" } }; foreach (Person p in persons) { Console.WriteLine(p); } foreach语句会解析为下面的代码: IEnumerator while(enumerator.MoveNext()) { Person p = enumerator.Current; Console.WriteLine(p); } 3.yield语句 在C#2.0之前,foreach语句可以轻松的迭代集合,但创建枚举器需要做大量的工作。C#2.0添加了yield语句,以便创建枚举器。 yield return 语句返回集合的一个元素,并移动到下一个元素。yield break可停止迭代。 下面的例子实现返回两个字符串: public class HelloCollection { public IEnumerator { yield return "Hello"; yield return "World"; } } 客户端代码: var helloCollection = new HelloCollection(); foreach (string s in helloCollection) { Console.WriteLine(s); } 包含yield语句的方法或属性也称为迭代块。迭代块必须声明为返回IEnumerator或IEnumerable接口,或者这些接口的泛型版本。这个块可以包含多条yield return语句或yield break语句,但不能包含return语句。 使用迭代块,编译器会生成一个yield类型,其中包含一个状态机,如下面代码所示: yield类型实现IEnumerator和IDisposable接口的方法和属性。下面的例子可以把yield类型看作内部类Enumerator。外部类的GetEnumerator()方法实例化并返回一个新的yield类型。在yield类型中,变量state定义了迭代的当前位置,每次调用MoveNext()时,当前位置都会改变。MoveNext()封装了迭代块的代码,并设置了current变量的值,从而使Current属性根据位置返回一个对象。 public class HelloCollection { public IEnumerator { return new Enumerator(0); } public class Enumerator:IEnumerator { private int state; private string current; public Enumerator(int state) { this.state = state; } bool System.Collections.IEnumerator.MoveNext() { switch(state) { case 0: current="hello"; state =1; return true; case 1: current="world"; state =2; return true; case 2: break; } return false; } void System.Collection>IEnumerator.Reset() { throw new NotSupportedException(); } string System.Collections.Generic.IEnumerator { get { return current; } } object System.Collections.IEnumerator.Current { get { return current; } } void IDisposable.Dispose() {} } } yield语句会产生一个枚举器,而不仅仅生成一个包含的项的列表。这个枚举器通过foreach语句调用。从foreach中依次访问每一项,就会访问枚举器。这样就可以迭代大量的数据,而无需一次把所有的数据都读入内存。 (1).迭代集合的不同方式 可以使用yield return语句,以不同方式迭代集合。 类MusicTitles可以用默认方式通过GetEnumerator()方法迭代标题,该方法不必在代码中编写,也可以用Reverse()逆序迭代标题,用Subset()方法迭代子集合: public class MusicTitles { string[] names = { "Tubular Bells", "Hergest Ridge", "Ommadawn", "Platinum" }; public IEnumerator { for (int i = 0; i < 4; i++) { yield return names[i]; } } public IEnumerable { for (int i = 3; i >= 0; i--) { yield return names[i]; } } public IEnumerable { for (int i = index; i < index + length;i++) { yield return names[i]; } } } 客户端代码: var titles = new MusicTitles(); foreach (var title in titles) { Console.WriteLine(title); } Console.WriteLine(); Console.WriteLine("reverse"); foreach (var title in titles.Reverse()) { Console.WriteLine(title); } Console.WriteLine(); Console.WriteLine("subset"); foreach (var title in titles.Subset(2, 2)) { Console.WriteLine(title); } (2).用yield return 返回枚举器 public class GameMoves { private IEnumerator cross; private IEnumerator circle; public GameMoves() { cross = Cross(); circle = Circle(); } private int move = 0; const int MaxMoves = 9; public IEnumerator Cross() { while (true) { Console.WriteLine("Cross, move {0}", move); if (++move >= MaxMoves) yield break; yield return circle; } } public IEnumerator Circle() { while (true) { Console.WriteLine("Circle, move {0}", move); if (++move >= MaxMoves) yield break; yield return cross; } } } 客户端代码: var game = new GameMoves(); IEnumerator enumerator = game.Cross(); while (enumerator.MoveNext()) { enumerator = enumerator.Current as IEnumerator; } 这样会交替调用Cross()和Circle()方法。 七.元组(Tuple) 元组可以合并不同类型的对象。元组起源于函数编程语言,如F#。在.NET Framework中,元组可用于所有的.Net语言。 .NET Framework定义了8个泛型Tuple类和一个静态Tuple类,它们用作元组的工厂。不同的泛型Tuple类支持不同数量的元素。如,Tuple Tuple 元组也可以用静态Tuple类的静态Create()方法创建。Create()方法的泛型参数定了要实例化的元组类型: public static Tuple { int result = dividend / divisor; int reminder = dividend % divisor; return Tuple.Create } 可以用属性Item1和Item2访问元组的项: var result = Divide(5, 2); Console.WriteLine("result of division: {0}, reminder: {1}", result.Item1, result.Item2); 如果元组包含的项超过8个,就可以使用带8个参数的Tuple类定义。最后一个模板参数是TRest,表示必须给它传递一个元组。这样,就可以创建带任意个参数的元组了。 var tuple = Tuple.Create "Stephanie", "Alina", "Nagel", 2009, 6, 2, 1.37, Tuple.Create 八.结构比较 数组和元组都实现接口IStructuralEquatable和IStructuralComparable。这两个接口不仅可以比较引用,还可以比较内容。这些接口都是显式实现的,所以在使用时需要把数组和元组强制转换为这个接口。 IStructuralEquatable接口用于比较两个元组或数组是否有相同的内同,IStructuralComparable接口用于给元组或数组排序。 IStructuralEquatable接口示例: 编写实现IEquatable接口的Person类,IEquatable接口定义了一个强类型化的Equals()方法,比较FirstName和LastName的值: public class Person : IEquatable { public int Id { get; private set; } public string FirstName { get; set; } public string LastName { get; set; } public override string ToString() { return String.Format("{0}, {1} {2}", Id, FirstName, LastName); } public override bool Equals(object obj) { if (obj == null) return base.Equals(obj); return Equals(obj as Person); } public override int GetHashCode() { return Id.GetHashCode(); } #region IEquatable public bool Equals(Person other) { if (other == null) return base.Equals(other); return this.FirstName == other.FirstName && this.LastName == other.LastName; } #endregion } 创建两个包含相同内容的Person类型的数组: var janet = new Person { FirstName = "Janet", LastName = "Jackson" }; Person[] persons1 = { new Person { FirstName = "Michael", LastName = "Jackson" }, janet }; Person[] persons2 = { new Person { FirstName = "Michael", LastName = "Jackson" }, janet }; 由于两个变量引用两个不同数组,所以!=返回True: if (persons1 != persons2) Console.WriteLine("not the same reference"); 对于IStructuralEquatable接口定义的Equals方法,第一个参数是object类型,第二个参数是IEqualityComparer类型。调用这个方法时,通过传递一个实现了EqualityComparer if ((persons1 as IStructuralEquatable).Equals(persons2, EqualityComparer { Console.WriteLine("the same content"); } 元组示例: Tuple<>类提供了两个Epuals()方法:一个重写了Object基类中的Epuals方法,并把object作为参数,第二个由IStructuralEquatable接口定义,并把object和IEqualityComparer作为参数。 var t1 = Tuple.Create var t2 = Tuple.Create if (t1 != t2) Console.WriteLine("not the same reference to the tuple"); 这个方法使用EqualityComparer if (t1.Equals(t2)) Console.WriteLine("equals returns true"); 还可以使用TupleComparer类创建一个自定义的IEqualityComparer TupleComparer tc = new TupleComparer(); if ((t1 as IStructuralEquatable).Equals(t2, tc)) { Console.WriteLine("yes, using TubpleComparer"); } class TupleComparer : IEqualityComparer { #region IEqualityComparer Members public new bool Equals(object x, object y) { bool result = x.Equals(y); return result; } public int GetHashCode(object obj) { return obj.GetHashCode(); } #endregion } 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。 您可能感兴趣的文章: