集合与泛型.doc_第1页
集合与泛型.doc_第2页
集合与泛型.doc_第3页
集合与泛型.doc_第4页
集合与泛型.doc_第5页
已阅读5页,还剩16页未读 继续免费阅读

下载本文档

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

文档简介

第2章 集合与泛型2.1 集合概述所谓集合是特殊元素们的一种聚合。集合的元素被称为是成员。集合有两个最重要的属性,一个是集合成员都是无序的,另一个则是集合的成员不会出现超过一次。在计算机科学领域内集合扮演着非常重要的角色,但是不把集合包含作为C#语言的一种数据结构。2.1.1 集合的定义人们把集合定义成相关成员的无序聚集,而且集合中的成员不会出现超过一次。集合书写成用一对闭合大括号包裹成员列表的形式,例如0,1,2,3,4,5,6,7,8,9。只要全部成员只书写一次,就可以按照任意顺序书写集合,所以此前的集合实例还可以写成9,8,7,6,5,4,3,2,1,0或其他任意成员组合的形式。为了使用集合需要知道一些有关集合的定义。1.不包含任何成员的集合称为空集合。全域是所有可能成员的集合。2.如果两个集合包含完全一样的成员,那么就认为这两个集合相等。3.如果第一个集合的全部成员都包含在第二个集合内,就认为第一个集合是第二个集合的子集。2.1.2 集合的操作下面描述了在集合上执行的基本操作。1.联合:由第一个集合中的所有成员和第二个集合中不包含在第一个集合的成员组成的新集合。2.交叉:由既属于第一个集合又属于第二个集合的成员组成的新集合。3.差异:由属于第一个集合但不属于第二个集合的成员组成的新集合。2.2 集合类ArrayList是与数组相当的集合类。还有其他类型的集合:队列、栈、有序表和哈希表,如下所示:l ArrayList动态数组l Queue队列l Stack栈l BitArray位数组l Hashtable散列表l SortedList有序表集合类位于System.Collections命名空间,集合类可以组合为集合,存储Object类型的元素。2.3 集合的一种实现HashTable类是.NET框架类库中较为有效的数据结构之一,它的存取速度较其它类而言要快。下面就以散列表作为存储结构来实现一个集合类,具体代码如下:using System;using System.Collections;/集合public class Set private Hashtable data; public Set() data = new Hashtable(); /添加成员。如果成员不在集合内,则添加到集合中 public void Add(Object item) if (!data.ContainsValue(item) data.Add(Hash(item), item); /根据成员各字符的ASCII码值计算散列值(在散列表中数据项以键值对形式存在) private string Hash(Object item) char chars; string s = item.ToString(); int hashValue = 0; chars = s.ToCharArray(); for (int i = 0; i aSet.Size) return false; foreach (Object key in this.data.Keys) if (!(aSet.data.Contains(key) return false; return true; /求差集。由属于第一个集合但不属于第二个集合的成员组成的新集合。 public Set Difference(Set aSet) Set tempSet = new Set(); foreach (Object hashObject in data.Keys) if (!(aSet.data.Contains(hashObject) tempSet.Add(datahashObject); return tempSet; public override string ToString() string s = ; foreach (Object key in data.Keys) s += datakey + ; return s; class Test static void Main() Set setA = new Set(); setA.Add(中国); setA.Add(日本); setA.Add(美国); setA.Add(韩国); Set setB = new Set(); setB.Add(日本); setB.Add(韩国); setB.Add(加拿大); Set setC = new Set(); setC = setA.Union(setB); Console.WriteLine(A: + setA.ToString(); Console.WriteLine(B: + setB.ToString(); Console.WriteLine(A union B: + setC.ToString(); setC = setA.Intersection(setB); Console.WriteLine(A intersect B: + setC.ToString(); setC = setA.Difference(setB); Console.WriteLine(A diff B: + setC.ToString(); setC = setB.Difference(setA); Console.WriteLine(B diff A: + setC.ToString(); if (setB.Subset(setA) Console.WriteLine(B is a subset of A); else Console.WriteLine(B is not a subset of A); Console.Read(); 运行结果如下:2.1 泛型概述泛型(Generic Type)是.NET Framework 2.0的一大特性。泛型的主要思想就是将算法与数据结构完全分离开来,使得一次定义的算法能够作用于多种数据结构,从而实现高度可重用的开发。通过泛型可以定义类型安全的数据结构,而没有必要使用实际的数据类型。这将显著提高性能并得到更高质量的代码,因为可以重用数据处理算法,而没有必要复制类型特定的代码。泛型是一个很强大的特性,对于集合类而言尤其如此。.NET 1.1中的大多数集合类都基于Object类型。.NET 从2.0开始提供了实现为泛型的新集合类。泛型不仅限于类,还可用于委托、接口和方法的泛型。在.NET 2.0之前,不存在泛型,在开发通用容器时,需要通用容器能存储各种类型的实例。在.NET Framework 1.1下,必须使用基于object类型的系统集合类(例如ArrayList,Stack,Queen等),但由于集合类中的项都是Object类型的,因此每次使用时都必须进行装箱拆箱操作,不仅大大降低了程序的性能,而且存在类型安全问题(Object类在编译期间没有类型安全性)。泛型(Generic Type)的出现较好的解决了上述问题,通过它,可以轻松创建指定类型的集合。泛型集合类不仅是类型安全的,而且使用时不需要进行烦琐的装箱拆箱操作。泛型是C# 2.0中的新增元素(类似于C+中模板)。这种机制允许将类名作为参数传递给泛型类型,并生成相应的对象。主要利用System.Collections.Generic命名空间下面的List泛型类来创建集合。语法如下:List ListOfT = new List( );其中的T就是所要使用的类型,既可以是简单类型,如string、int,也可以是用户自定义类型。下面看一个具体例子。using System;using System.Collections.Generic;struct Student public string sno; /学号 public string sname; /姓名 public int sage; /年龄 public Student(string sno, string sname, int sage) this.sno = sno; this.sname = sname; this.sage = sage; class SamplesGeneric static void Main() /创建Student对象 Student stu1 = new Student(001, 张三, 30); Student stu2 = new Student(002, 李四, 20); Student stu3 = new Student(003, 王五, 50); /创建类型为Student的对象集合 List students = new List(); /将Student对象放入集合 students.Add(stu1); students.Add(stu2); students.Add(stu3); /输出第1学生的姓名 Console.WriteLine(students0.sname); 运行结果如下图:下面介绍泛型的优点:1. 性能泛型的一个主要优点是性能。对值类型使用System.Collections命名空间的集合类,存储数据时需要把值类型转换为引用类型,获取数据时需要把引用类型转换为值类型,即需要进行装箱和拆箱操作。例如,下面例子在存取数据时就进行了装箱拆箱操作。ArrayList list = new ArrayList();list.Add(44); / boxing - convert a value type to a reference typeint i1 = (int)list0; / unboxing - convert a reference type to a value typeforeach (int i2 in list) Console.WriteLine(i2); / unboxing装箱和拆箱操作很容易使用,但性能损失比较大,迭代许多项时尤其如此。System.Collections.Generic命名空间中的List类不使用对象,而是在使用时定义类型。在下面的例子中,List类的泛型类型定义为int,所以int类型在JIT编译器动态生成的类中使用,不再进行装箱和拆箱操作:List list = new List();list.Add(44); / no boxing - value types are stored in the Listint i1 = list0; / no unboxing, no cast neededforeach (int i2 in list) Console.WriteLine(i2);2. 类型安全泛型的另一个特性是类型安全。与ArrayList类一样,如果使用对象,可以在这个集合中添加任意类型。下面的例子在ArrayList类型的集合中添加一个整数、一个字符串和一个MyClass类型的对象:ArrayList list = new ArrayList();list.Add(44);list.Add(mystring);list.Add(new MyClass();如果这个集合使用下面的foreach语句迭代,而该foreach语句使用整数元素来迭代,编译器就会编译这段代码。但并不是集合中的所有元素都可以转换为int,所以会出现一个运行异常:foreach (int i in list) Console.WriteLine(i);错误应尽早发现。在泛型类List中,泛型类型T定义了允许使用的类型。有了List的定义,就只能把整数类型添加到集合中。编译器不会编译这段代码,因为Add()方法的参数无效:List list = new List();list.Add(44);list.Add(mystring); / compile time errorlist.Add(new MyClass(); / compile time error3. 代码重用泛型允许更好地重用代码。泛型类可以定义一次,用许多不同的类型实例化。例如,System.Collections.Generic命名空间中的List类用一个int、一个字符串和一个MyClass类型实例化:List list = new List();list.Add(44);List stringList = new List();stringList.Add(mystring);List myclassList = new List();myClassList.Add(new MyClass();泛型类型可以在一种语言中定义,在另一种.NET语言中使用。2.2 创建泛型类泛型最显著的一点就是它参数化了类型,把类型作为参数抽象出来,从而使我们在实际的运用当中能够更好的实现代码的重复利用,同时它提供了更强的类型安全,更高的效率,不过在约束方面,它只支持显式的约束,这样在灵活性方面就显得不是那么好了。泛型之所以能够提供更高的效率是因为泛型在实例化的时候采用了on-demand的模式,即按需实例化,发生在JIT(Just In Time)编译时。下面来看如何定义一个泛型类,很简单,你只需要意识到一点类型在这里被参数化了:using System;using System.Collections;using System.Collections.Generic;/ / 定义一个泛型类,该类有一个类型参数T/ / 类型参数class Item /泛型类的类型参数可用于类成员 public T Value; public Item Next; public Item(T value) this.Value = value; this.Next = null; class Collection : IEnumerable public Item First = null; public Item Last = null; public void Add(T value) Item newItem = new Item(value); if (this.First = null) this.First = newItem; this.Last = newItem; else this.Last.Next = newItem; this.Last = newItem; public IEnumerator GetEnumerator() Item current = this.First; while (current != null) yield return current.Value; current = current.Next; IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); class SamplesGeneric static void Main() /使用string来实例化Collection类 Collection names = new Collection(); /调用泛型类中的方法 names.Add(张三); names.Add(李四); names.Add(王五); foreach (string name in names) Console.WriteLine(name); /同样,也可以使用int来实例化Collection类 Collection ages = new Collection(); /调用泛型类中的方法 ages.Add(25); ages.Add(34); ages.Add(47); foreach (int age in ages) Console.WriteLine(age); Console.Read(); 使用泛型类Collection,相当于定义了一个类模板,在具体使用时,需要什么类型即可将它实例化为什么类型,从而实现了代码重用。由于可以用任何类型实例化它,且无需装箱操作,因此比基于Object的非泛型方式获取了更高的性能。如果将Collection实例化为int类型,则使用Add ()方法传送非int值,就会出现一个编译错误,所以说它是类型安全的。使用泛型IEnumerable,foreach语句也是类型安全的,如果foreach语句中的变量不是int,也会出现一个编译错误。2.3 泛型类的特性在创建泛型类时,需要一些其他C#关键字。例如,不能把null赋予泛型类型。此时,可以使用default关键字。如果泛型类型不需要Object类的功能,但需要调用泛型类上的某些特定方法,就可以定义约束。1. 默认值在创建泛型类时,如果需要给类型T指定null,但是,不能把null赋予泛型类型。原因是泛型类型也可以实例化为值类型,而null只能用于引用类型。为了解决这个问题,可以使用default关键字。通过default关键字,将null赋予引用类型,将0赋予值类型。using System;using System.Collections;using System.Collections.Generic;class SamplesGeneric public T Value; public SamplesGeneric() this.Value = default(T);/如果T是值类型则default(T)=0;如果T是引用类型则default(T)=null; class Test static void Main() SamplesGeneric obj1 = new SamplesGeneric(); Console.WriteLine(obj1.Value); SamplesGeneric obj2 = new SamplesGeneric(); Console.WriteLine(obj2.Value); Console.Read(); 注意:default关键字根据上下文可以有多种含义。switch语句使用default定义默认情况。在泛型中,根据泛型类型是引用类型还是值类型,default关键字用于将泛型类型初始化为null或0。2. 约束C#中的泛型只支持显式的约束,因为这样才能保证C#所要求的类型安全,但显式的约束并非时必须的,如果不加约束,泛型类型参数将只能访问System.Object类型中的公有方法。“显式约束”由where子句表达,可以指定“基类约束”,“接口约束”,“构造器约束”,“值类型/引用类型约束”共四种约束。1、基类约束:class A public void F1() class B public void F2() class C where S: A / S继承自A where T: B / T继承自B / 可以在类型为S的变量上调用F1,/ 可以在类型为T的变量上调用F2 2、接口约束interface IPrintable void Print(); interface IComparable int CompareTo(T v);interface IKeyProvider T GetKey(); class Dictionary where K: IComparable where V: IPrintable, IKeyProvider / 可以在类型为K的变量上调用CompareTo, / 可以在类型为V的变量上调用Print和GetKey 3、构造器约束class A public A() class B public B(int i) class C where T : new() /可以在其中使用T t=new T(); C c=new C(); /可以,A有无参构造器C c=new C(); /错误,B没有无参构造器4、值/引用类型约束public struct A public class B class C where T : struct / T在这里面是一个值类型 C c=new C(); /可以,A是一个值类型C c=new C(); /错误,B是一个引用类型提示:在C#中,where子句的一个重要限制是,不能定义必须由泛型类型执行的运算符。运算符不能在接口中定义。在where子句中,只能定义基类、接口和默认构造函数。3. 继承如何实现泛型类的继承呢?这里需要满足下面两点中的任何一点即可:1、泛型类继承中,父类的类型参数已被实例化,这种情况下子类不一定必须是泛型类;2、父类的类型参数没有被实例化,但来源于子类,也就是说父类和子类都是泛型类,并且二者有相同的类型参数;/如果这样写的话,显然会报找不到类型T的错误class SubItem : Item /正确的写法应该是class SubItem : Item class SubItem : Item class SubItem : Item 4. 静态成员泛型类的静态成员只能在类的一个实例中共享。下面看一个例子。using System;using System.Collections.Generic;class SamplesGeneric public static T Value;class Test static void Main() SamplesGeneric.Value = 5; Console.WriteLine(SamplesGeneric.Value); SamplesGeneric.Value = hello; Console.WriteLine(SamplesGeneric.Value); Console.Read(); 2.4 泛型接口泛型接口的创建以及继承规则和泛型类是一样的,看下面的代码:using System;using System.Collections.Generic;interface IAdder T Add(T v1, T v2);class Adder1 : IAdder public int Add(int v1, int v2) return v1 + v2; class Adder2 : IAdder public string Add(string v1, string v2) return v1 + v2; class SamplesGeneric static void Main() Adder1 adder1 = new Adder1(); int sum = adder1.Add(5, 7); Console.WriteLine(sum); Adder2 adder2 = new Adder2(); string str = adder2.Add(hello, world); Console.WriteLine(str); Console.Read(); 2.5 泛型方法我们再来看泛型方法,C#的泛型机制只支持在方法声明上包含类型参数,也即是泛型方法。特别注意的是,泛型不支持在除了方法以外的其他类/接口成员上使用类型参数,但这些成员可以被包含在泛型类型中,并且可以使用泛型类型的类型参数。还有一点需要说的就是,泛型方法可以在泛型类型中,也可以存在于非泛型类型中。下面通过例子看一下泛型方法的声明与调用。using System;using System.Collections.Generic;class Exchange /声明一个泛型方法 public static void Swap(ref T x, ref T y) T temp; temp = x; x = y; y = temp; class SamplesGeneric static void Main() int i = 4; int j = 5; /在调用泛型方法时,对泛型方法的类型参数实例化 Exchange.Swap(ref i, ref j); Console.WriteLine(i = 0, j = 1, i, j); /泛型方法可以像非泛型方法那样调用 Exchange.Swap(ref i, ref j); Console.WriteLine(i = 0, j = 1, i, j); Console.Read(); 2.6 泛型委托再来看一下泛型委托,泛型委托支持在返回值和参数上应用类型参数,下面例子中定义了一个类型参数为T的委托,然后在类中利用委托调用方法:using System;using System.Collections.Generic;delegate string GetString(T

温馨提示

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

评论

0/150

提交评论