无私分享(C#高级编程第6版doc)第05章 数组.doc_第1页
无私分享(C#高级编程第6版doc)第05章 数组.doc_第2页
无私分享(C#高级编程第6版doc)第05章 数组.doc_第3页
无私分享(C#高级编程第6版doc)第05章 数组.doc_第4页
无私分享(C#高级编程第6版doc)第05章 数组.doc_第5页
已阅读5页,还剩12页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

目 录第5章 数 组25.1 简单数组25.1.1 数组的声明25.1.2 数组的初始化25.1.3 访问数组元素35.1.4 使用引用类型35.2 多维数组45.3 锯齿数组55.4 Array类65.4.1 属性65.4.2 创建数组75.4.3 复制数组75.4.4 排序95.5 数组和集合接口115.5.1 IEumerable接口115.5.2 ICollection接口115.5.3 IList接口115.6 枚举125.6.1 IEnumerator接口125.6.2 foreach语句135.6.3 yield语句135.7 小结17第5章 数 组如果需要使用同一类型的多个对象,就可以使用集合和数组。C#用特殊的记号声明和使用数组。Array类在后台发挥作用,为数组中元素的排序和过滤提供了几个方法。使用枚举器,可以迭代数组中的所有元素。本章讨论如下内容:简单数组多维数组锯齿数组Array类数组的接口枚举5.1 简单数组如果需要使用同一类型的多个对象,就可以使用数组。数组是一种数据结构,可以包含同一类型的多个元素。5.1.1 数组的声明在声明数组时,应先定义数组中元素的类型,其后是一个空方括号和一个变量名。例如,下面声明了一个包含整型元素的数组:int myArray;5.1.2 数组的初始化声明了数组后,就必须为数组分配内存,以保存数组的所有元素。数组是引用类型,所以必须给它分配堆上的内存。为此,应使用new运算符,指定数组中元素的类型和数量来初始化数组的变量。下面指定了数组的大小。提示:值类型和引用类型请参见第3章。myArray = new int4;在声明和初始化后,变量myArray就引用了4个整型值,它们位于托管堆上,如图5-1所示。图 5-1警告:在指定了数组的大小后,如果不复制数组中的所有元素,就不能重新设置数组的大小。如果事先不知道数组中应包含多少个元素,就可以使用集合。集合请参见第10章。除了在两个语句中声明和初始化数组之外,还可以在一个语句中声明和初始化数组:int myArray = new int4;还可以使用数组初始化器为数组的每个元素赋值。数组初始化器只能在声明数组变量时使用,不能在声明数组之后使用。int myArray = new int4 4, 7, 11, 2;如果用花括号初始化数组,还可以不指定数组的大小,因为编译器会计算出元素的个数:int myArray = new int 4, 7, 11, 2;使用C#编译器还有一种更简化的形式。使用花括号可以同时声明和初始化数组,编译器生成的代码与前面的例子相同:int myArray = 4, 7, 11, 2;5.1.3 访问数组元素数组在声明和初始化后,就可以使用索引器访问其中的元素了。数组只支持有整型参数的索引器。提示:在定制的类中,可以创建支持其他类型的索引器。创建定制索引器的内容请参见第6章。通过索引器传送元素号,就可以访问数组。索引器总是以0开头,表示第一个元素。可以传送给索引器的最大值是元素个数减1,因为索引从0开始。在下面的例子中,数组myArray用4个整型值声明和初始化。用索引器0、1、2、3就可以访问该数组中的元素。int myArray = new int 4, 7, 11, 2;int v1 = myArray0; / read first elementint v2 = myArray1; / read second elementmyArray3 = 44; / change fourth element警告:如果使用错误的索引器值(不存在对应的元素),就会抛出IndexOutOfRangeException类型的异常。如果不知道数组中的元素个数,则可以在for语句中使用Length属性:for (int i = 0; i myArray.Length; i+)Console.WriteLine(myArrayi);除了使用for语句迭代数组中的所有元素之外,还可以使用foreach语句:for (int val in myArray)Console.WriteLine(val);提示:foreach语句利用了本章后面讨论的IEnumerable和IEnumerator接口。5.1.4 使用引用类型不但能声明预定义类型的数组,还可以声明定制类型的数组。下面用Person类来说明,这个类有两个构造函数、自动实现的属性Firstname和Lastname、以及ToString()方法的一个重写:public class Personpublic Person()public Person(string firstName, string lastName)this. firstname = firstName;this.lastname = lastName;public string Firstname get; set; public string Lastname get; set; public override string ToString()return String.Format(0 1, firstName, lastName);声明一个包含两个Person元素的数组,与声明一个int数组类似:Person myPersons = new Person2;但是必须注意,如果数组中的元素是引用类型,就必须为每个数组元素分配内存。若使用了数组中未分配内存的元素,就会抛出NullReferenceException类型的异常。提示:第14章介绍了错误和异常的详细内容。使用从0开始的索引器,可以为数组的每个元素分配内存:myPersons 0 = new Person(Ayrton, Senna);myPersons 1 = new Person(Michael, Schumacher);图5-2显示了Person数组中的对象在托管堆中的情况。myPersons是一个存储在堆栈上的变量,该变量引用了存储在托管堆上的Person元素数组。这个数组有足够容纳两个引用的空间。数组中的每一项都引用了一个Person对象,而这些Person对象也存储在托管堆上。图 5-2与int类型一样,也可以对定制类型使用数组初始化器:Person myPersons = new Person(Ayrton, Senna),new Person(Michael, Schumacher) ;5.2 多维数组一般数组(也称为一维数组)用一个整数来索引。多维数组用两个或多个整数来索引。图5-3是二维数组的数学记号,该数组有三行三列。第一行的值是1、2和3,第三行的值是7、8和9。图 5-3在C#中声明这个二维数组,需要在括号中加上一个逗号。数组在初始化时应指定每一维的大小(也称为阶)。接着,就可以使用两个整数作为索引器,来访问数组中的元素了:int, twodim = new int3, 3;twodim0,0 = 1;twodim0,1 = 2;twodim0,2 = 3;twodim1,0 = 4;twodim1,1 = 5;twodim1,2 = 6;twodim2,0 = 7;twodim2,1 = 8;twodim2,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(threedim0,1,1);5.3 锯齿数组二维数组的大小是矩形的,例如33个元素。而锯齿数组的大小设置是比较灵活的,在锯齿数组中,每一行都可以有不同的大小。图5-4比较了有33个元素的二维数组和锯齿数组。图中的锯齿数组有3行,第一行有2个元素,第二行有6个元素,第三行有3个元素。图 5-4在声明锯齿数组时,要依次放置开闭括号。在初始化锯齿数组时,先设置该数组包含的行数。定义各行中元素个数的第二个括号设置为空,因为这类数组的每一行包含不同的元素数。之后,为每一行指定行中的元素个数:int jagged = new int3;jagged0 = new int2 1, 2;jagged1 = new int6 3, 4, 5, 6, 7, 8;jagged2 = new int3 9, 10, 11;迭代锯齿数组中所有元素的代码可以放在嵌套的for循环中。在外层的for循环中,迭代每一行,内层的for循环迭代一行中的每个元素:for ( int row = 0; row jagged.Length; row+)for ( int element = 0; element jaggedrow.Length; element+)Console.WriteLine(row: 0, element: 1, value: 2,row, element, jaggedrow.element);该迭代显示了所有的行和每一行中的各个元素:row: 0, element: 0, value: 1row: 0, element: 1, value: 2row: 1, element: 0, value: 3row: 1, element: 1, value: 4row: 1, element: 2, value: 5row: 1, element: 3, value: 6row: 1, element: 4, value: 7row: 1, element: 5, value: 8row: 2, element: 1, value: 9row: 2, element: 2, value: 10row: 2, element: 3, value: 115.4 Array类用括号声明数组是C#中使用Array类的记号。在后台使用C#语法,会创建一个派生于抽象基类Array的新类。这样,就可以使用Array类为每个C#数组定义的方法和属性了。例如,前面就使用了Length属性,还使用foreach语句迭代数组。其实这是使用了Array类中的GetEnumerator()方法。5.4.1 属性Array类包含的如下属性可以用于每个数组实例。本章后面还将讨论其他更多的属性。表 5-1属 性说 明LengthLength属性返回数组中的元素个数。如果是一个多维数组,该属性会返回所有阶的元素个数。如果需要确定一维中的元素个数,则可以使用GetLength()方法LongLengthLength属性返回int值,而LongLength属性返回long值。如果数组包含的元素个数超出了32位int值的取值范围,就需要使用LongLength属性,来获得元素个数Rank使用Rank属性可以获得数组的维数5.4.2 创建数组Array类是一个抽象类,所以不能使用构造函数来创建数组。但除了可以使用C#语法创建数组实例之外,还可以使用静态方法CreateInstance()创建数组。如果事先不知道元素的类型,就可以使用该静态方法,因为类型可以作为Type对象传送给CreateInstance()方法。下面的例子说明了如何创建类型为int、大小为5的数组。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的数组。下面的例子就创建了一个包含23个元素的二维数组。第一维基于1,第二维基于0:int lengths = 2, 3;int lowerBounds = 1, 10;Array racers = Array.CreateInstance(typeof(Person), lengths, lowerBounds);SetValue()方法设置数组的元素,其参数是每一维的索引:racers.SetValue(new Person(Alain, Prost), 1, 10);racers.SetValue(new Person(Emerson, Fittipaldi), 1, 11);racers.SetValue(new Person(Ayrton, Senna), 1, 12);racers.SetValue(new Person(Ralf, Schumacher), 2, 10);racers.SetValue(new Person(Fernando, Alonso), 2, 11);racers.SetValue(new Person(Jenson, Button), 2, 12);尽管数组不是基于0的,但可以用一般的C#记号将它赋予一个变量。只需注意不要超出边界即可:Person, racers2 = (Person,) racers;Person first = racers21, 10;Person last = racers22, 12;5.4.3 复制数组因为数组是引用类型,所以将一个数组变量赋予另一个数组变量,就会得到两个指向同一数组的变量。而复制数组,会使数组实现ICloneable接口。这个接口定义的Clone()方法会创建数组的浅副本。如果数组的元素是值类型,就会复制所有的值,如图5-5所示:int intArray1 = 1, 2;int intArray2 = (int)intArray1.Clone();图 5-5如果数组包含引用类型,则不复制元素,而只复制引用。图5-6 显示了变量beatles和beatlesClone,其中beatlesClone是通过在beatles上调用Clone()方法来创建的。beatles和beatlesClone引用的Person对象是相同的。如果修改beatlesClone中一个元素的属性,就会改变beatles中的对应对象。Person beatles = new Person(John, Lennon),new Person(Paul, McCartney),;Person beatlesClone = (Person)beatles.Clone();图 5-6除了使用Clone()方法之外,还可以使用Array.Copy()方法创建浅副本。但Clone()方法和Copy()方法有一个重要区别:Clone()方法会创建一个新数组,而Copy()方法只是传送了阶数相同、有足够元素空间的已有数组。提示:如果需要包含引用类型的数组的深副本,就必须迭代数组,创建新对象。5.4.4 排序Array类实现了对数组中元素的冒泡排序。Sort()方法需要数组中的元素实现IComparable接口。简单类型,如System.String和System.Int32实现了IComparable接口,所以可以对包含这些类型的元素排序。在示例程序中,数组name包含string类型的元素,这个数组是可以排序的。String names = Christina Aguillera,Shakira,Beyonce,Gwen Stefani;Array.Sort(names);foreach (string name in names)Console.WriteLine(name);该应用程序的输出是排好序的数组:BeyonceChristina AguilleraGwen StefaniShakira如果修改Person类,使之执行IComparable接口。对LastName的值进行比较。LastName是string类型,而String类已经实现了IComparable接口,所以可以使用String类中CompareTo()方法的实现代码。如果LastName的值相同,就比较FirstName:public class Person : IComparablepublic int CompareTo(object obj)Person other = obj as Person;int result = this.LastName.CompareTo(other.LastName);if (result = 0)result = this.FirstName.CompareTo(other.FirstName);return result;/现在可以按照姓氏对Person对象数组排序了:Person persons = new Person(Emerson, Fittipaldi),new Person(Niki, Lauda),new Person(Ayrton, Senna),new Person(Michael, Schumacher),;Array.Sort (persons);foreach (Person p in persons)Console.WriteLine(p);使用Person类的排序功能,会得到按姓氏排序的姓名:Emerson FittipaldiNiki LaudaMichael SchumacherAyrton Senna如果Person对象的排序方式与上述不同,或者不能修改在数组中用作元素的类,就可以执行IComparer接口。这个接口定义了方法Compare()。IComparable接口必须由要比较的类来执行,而IComparer接口独立于要比较的类。这就是Compare()方法定义了两个要比较的变元的原因。其返回值与IComparable接口的CompareTo()方法类似。类PersonComparer实现了IComparer接口,可以按照firstName或lastName对Person对象排序。枚举PersonCompareType定义了与PersonComparer相当的排序选项:FirstName和LastName。排序的方式由类PersonComparer的构造函数定义,在该构造函数中设置了一个PersonCompareType值。Compare()方法用一个switch语句指定是按firstName还是lastName排序。public class PersonComparer : IComparerpublic enum PersonCompareType FirstName,LastName private PersonCompareType compareType; public PersonComparer(PersonCompareType compareType)this. compareType = compareType;public int Compare(object x, object y)Person p1 = x as Person;Person p1 = y as Person;Switch (compareType)case PersonCompareType.FirstName:return p1. FirstName. CompareTo(p2. FirstName);case PersonCompareType.LastName:return p1. LastName. CompareTo(p2. LastName);default:throw new ArgumentException(unexpexted compare type)现在,可以将一个PersonComparer对象传送给Array.Sort()方法的第二个变元。下面是按名字对persons数组排序:Array.Sort(persons,new PersonComparer(PersonComparer. PersonCompareType.FirstName);foreach (Person p in persons)Console.WriteLine(p);persons数组现在按名字排序:Ayrton SennaEmerson FittipaldiMichael SchumacherNiki Lauda提示:Array类还提供了Sort方法,它需要将一个委托作为变元。第7章将介绍如何使用委托。5.5 数组和集合接口Array类实现了IEumerable、ICollection和IList接口,以访问和枚举数组中的元素。由于用定制数组创建的类派生于Array抽象类,所以能使用通过数组变量执行的接口中的方法和属性。5.5.1 IEumerable接口IEumerable是由foreach语句用于迭代数组的接口。这是一个非常特殊的特性,在下一节中讨论。5.5.2 ICollection接口ICollection接口派生于IEumerable接口,并添加了如表5-2所示的属性和方法。这个接口主要用于确定集合中的元素个数,或用于同步。表 5-2ICollection接口的属性和方法说 明CountCount属性可确定集合中的元素个数,它返回的值与Length属性相同IsSynchronizedSyncRootIsSynchronized属性确定集合是否是线程安全的。对于数组,这个属性总是返回false。对于同步访问,SyncRoot属性可以用于线程安全的访问。第19章介绍了线程和同步,探讨了如何用集合实现线程安全性CopyTo()利用CopyTo()方法可以将数组的元素复制到现有的数组中。它类似于静态方法Array.Copy()5.5.3 IList接口IList接口派生于ICollection接口,并添加了下面的属性和方法。Array类实现IList接口的主要原因是,IList接口定义了Item属性,以使用索引器访问元素。IList接口的许多其他成员是通过Array类抛出NotSupportedException异常实现的,因为这些不应用于数组。IList接口的所有属性和方法如表5-3所示。表 5-3IList 接 口说 明Add()Add()方法用于在集合中添加元素。对于数组,该方法会抛出NotSupportedException异常Clear()Clear()方法可清除数组中的所有元素。值类型设置为0,引用类型设置为nullContains()Contains()方法可以确定某个元素是否在数组中。其返回值是true或false。这个方法会对数组中的所有元素进行线性搜索,直到找到所需元素为止IndexOf()IndexOf()方法与Contains()方法类似,也是对数组中的所有元素进行线性搜索。不同的是,IndexOf()方法会返回所找到的第一个元素的索引Insert()Remove()RemoveAt()对于集合,Insert()方法用于插入元素,Remove()和RemoveAt()可删除元素。对于数组,这些方法都抛出NotSupportedException异常IsFixedSize数组的大小总是固定的,所以这个属性总是返回trueIsReadOnly数组总是可以读/写的,所以这个属性返回false。第10章将介绍如何从数组中创建只读属性ItemItem属性可以用整型索引访问数组5.6 枚举在foreach语句中使用枚举,可以迭代集合中的元素,且无需知道集合中的元素个数。图5-7显示了调用foreach方法的客户机和集合之间的关系。数组或集合执行带GetEumerator()方法的IEumerable接口。GetEumerator()方法返回一个执行IEumerable接口的枚举。接着,foreach语句就可以使用IEumerable接口迭代集合了。图 5-7提示:GetEnumerator()方法用IEnumerable接口定义。foreach语句并不真的需要在集合类中执行这个接口。有一个名为GetEnumerator()的方法,返回实现了IEnumerator接口的对象就足够了。5.6.1 IEnumerator接口foreach语句使用IEnumerator接口的方法和属性,迭代集合中的所有元素。这个接口中的属性和方法如表5-4所示。表 5-4IEnumerator接口的方法和属性说 明MoveNext()MoveNext()方法移动到集合的下一个元素上,如果有这个元素,该方法就返回true。如果集合不再有更多的元素,该方法就返回falseCurrent属性Current返回光标所在的元素Reset()Reset()方法将光标重新定位于集合的开头。许多枚举会抛出NotSupportedException异常5.6.2 foreach语句C#的foreach语句不会解析为IL代码中的foreach语句。C#编译器会把foreach语句转换为IEnumerable接口的方法和属性。下面是一个简单的foreach语句,它迭代persons数组中的所有元素,并逐个显示它们:foreach (Person p in persons)Console.WriteLine(p);foreach语句会解析为下面的代码段。首先,调用GetEnumerator()方法,获得数组的一个枚举。在while循环中- 只要MoveNext()返回true- 用Current属性访问数组中的元素:IEnumerator enumerator = persons. GetEnumerator();while (enumerator.MoveNext()Person p = (Person) enumerator.Current;Console.WriteLine(p);5.6.3 yield语句C# 1.0使用foreach语句可以轻松地迭代集合。在C# 1.0中,创建枚举器仍需要做大量的工作。C# 2.0添加了yield语句,以便于创建枚举器。yield return语句返回集合的一个元素,并移动到下一个元素上。yield break可停止迭代。下面的例子是用yield return语句实现一个简单集合的代码。类HelloCollection包含GetEnumerator()方法。该方法的实现代码包含两个yield return语句,它们分别返回字符串Hello和World。using System;using System.Collection;namespace Wrox.ProCSharp.Arrayspublic class HelloCollectionpublic IEumerator GetEumerator()yield return Hello;yield return World; 警告:包含yield语句的方法或属性也称为迭代块。迭代块必须声明为返回IEnumerator或IEnumerable接口。这个块可以包含多个yield return语句或yield break语句,但不能包含return语句。现在可以用foreach语句迭代集合了:public class Program HelloCollection helloCollection = new HelloCollection();foreach (string s in helloCollection)Console.WriteLine(s);使用迭代块,编译器会生成一个yield 类型,其中包含一个状态机,如下面的代码所示。yield 类型执行IEnumerator和IDisposable接口的属性和方法。在下面的例子中,可以把yield 类型看作内部类Enumerator。外部类的GetEnumerator()方法实例化并返回一个新的yield 类型。在yield 类型中,变量state定义了迭代的当前位置,每次调用MoveNext()时,当前位置都会改变。MoveNext()封装了迭代块的代码,设置了current变量的值,使Current属性根据位置返回一个对象。public class HelloCollection public IEnumerator GetEnumerator()Enumerator enumerator = new Enumerator();return enumerator; public class Enumerator : IEnumerator, IDisposableprivate int state;private object 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.Collections.IEnumerator.Re

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论