第3章 数组、字符串和集合 在控制台学生成绩管理系统中,学生信息需要保存起来以便进行后续操作,如计算平均成绩、修改成绩、成绩统计等。显而易见的做法就是把这些数据保存到变量中,然后再读取变量的值。使用这种方式处理需要声明很多变量。在一个程序中书写很多变量声明很烦琐,数组为我们提供了声明一组相关变量的优雅方法。 在学习完本章内容后,读者将能够:  定义和使用一维数组来组织数据。  定义和使用多维数组来组织数据。  选择合适的字符方法完成字符串处理。  列举常用集合。  选择合适集合组织数据。 视频讲解 3.1数组 在控制台学生成绩管理系统中,用户不仅需要输入学生信息,而且也需要输出学生成绩或者统计学生成绩。本任务进一步完善控制台学生成绩管理系统的开发,完成学生信息的输入输出,如图31所示。 图31控制台学生成绩管理系统信息输出 (1) 新建项目。启动Visual Studio 2017,新建控制台程序GradeManagement。 (2) 修改代码。项目初始化后,修改Program.cs文件为如下代码: C#程序设计与应用开发微课视频版 第 3 章数组、字符串和集合 using System; using static System.Console; namespace GradeManagement { class Program { static void Main(string[] args) { const int NUM = 3;//学生人数 //声明二维数组,存放学生信息 string[,] student = new string[NUM, 7]; //方法调用 InputStudents(student, NUM); OutputStudents(student, NUM); Console.ReadKey(); } /// ///输入学生信息 /// static void InputStudents(string[,] student, int num) { Console.Clear(); for (int i = 0; i < num; i++) { WriteLine("请输入第{0}个学生的学号: ", i + 1); Write("学号: "); student[i, 0] = Console.ReadLine(); Write("姓名: "); student[i, 1] = Console.ReadLine(); Write("语文: "); student[i, 2] = Console.ReadLine(); Write("数学: "); student[i, 3] = Console.ReadLine(); Write("英语: "); student[i, 4] = Console.ReadLine(); //计算总分 int temp = Convert.ToInt32(student[i, 2]) + Convert.ToInt32(student[i, 3]) + Convert.ToInt32(student[i, 4]); student[i, 5] = Convert.ToString(temp); student[i, 6] = string.Format("{0:F2}", temp / 3.0); } } /// ///输出学生信息 /// static void OutputStudents(string[,] student, int num) { //输出学生成绩 Clear(); WriteLine("\t\t\t 学生成绩单"); WriteLine("\t\t\t制表时间: " + DateTime.Now.ToShortDateString()); WriteLine("|----------------------------------------------|"); WriteLine("| 学号 | 姓名 |语文|数学|英语|总 分|平均分|"); WriteLine("|----------------------------------------------|"); for (int i = 0; i < num; i++) { WriteLine("|{0,8}|{1,3}|{2,4}|{3,4}|{4,4}|{5,5}|{6,6:F2}|", student[i, 0], student[i, 1], student[i, 2], student[i, 3], student[i, 4], student[i, 5], student[i, 6]); WriteLine("|----------------------------------------------|"); } } } } (3) 编译和运行程序。按Ctrl+F5组合键运行该程序,运行结果如图31所示。 3.1.1数组基础 数组是一个存储相同类型元素的固定大小的顺序集合。数组是用来存储数据的集合,通常认为数组是一个同一类型变量的集合。例如,一组由7个double类型变量组成的数组可以按照下面的方法创建: double[ ] temperature = new double[7]; 这类似于声明下述7个有点怪异、类型为double的变量: temperature[0]、temperature[1]、temperature[2]、temperature[3]、temperature[4]、temperature[5]、temperature[6]。像这样在方括号[]中放置一个整数表达式的变量称为下标变量、索引变量或者数组元素。方括号中的整数表达式称为索引或者下标。 注意,下标的编号是从0开始计数的,而不是从1开始计数的。 这7个变量中的每一个变量都可以像任何其他double类型的变量那样使用。例如,下面所有语句在C#中都是允许的: temperature[3] = 32; temperature[6] = temperature[3] + 5; Console.WriteLine(temperature[6]); 当把这些下标变量看作一个整体,当作一个数据项时,就称它们为数组。在数组中,下标是数组元素的一部分,它并不一定是整数常量,也可以是任何整数表达式。由于下标可以是表达式,因此可以编写一个循环把各个值读入到数组temperature中: Console.WriteLine("请输入一周的温度: "); for(int index = 0 ; index <7 ; index++) temperature[index] =Convert.ToDouble(Console.ReadLine()); 用户可以在多行上(中间用回车符分隔)输入7个值。在读入数组值后,可以使用如下方式显示它们: Console.WriteLine("一周的温度情况如下: "); for (int index = 0 ; index <7 ; index++) Console.Write(temperature[index] + " "); Console.WriteLine(); 1. 声明和创建数组 可以使用new运算符来创建数组。当创建元素类型为Base_Type的数组时,语法如下: Base_Type[ ] Array_Name = new Base_Type[Length]; 例如,下面的语句创建一个名为pressure的数组,它等价于100个int类型变量: int[ ] pressure = new int[100]; 作为一种替代的方法,前面的语句也可以拆分为两个步骤: int[ ] pressure; pressure = new int[100]; 第一个步骤声明一个整数数组,第二个步骤为数组分配足够容纳100个整数的内存空间。数组的类型称为数组的基本类型,数组的基本类型可以是任何数据类型。在这个示例中,基本类型为int。数组中元素的个数称为数组的长度、容量或者大小。因此,这个样本数组pressure的长度为100,这意味着它具有下标变量pressure[0]~pressure[99]。注意,由于下标从0开始,长度为100的数组(例如pressure)没有下标变量pressure[100]。 2. 初始化数组 数组可以在声明时初始化。要完成这个任务,可以把各个下标变量的值放在大括号{}中,并把它们放在赋值运算符=的后面,如下所示: double[ ] reading = {3.3, 15.8, 9.7}; 数组的大小(也就是它的长度)被设置为可以容纳给定值的最小值(例如3)。因此,上述初始化声明等价于下述语句: double[ ] reading = new double[3]; reading[0] = 3.3; reading[1] = 15.8; reading[0] = 9.7; 如果没有对数组进行初始化,它们或许会自动初始化为基本类型的默认值。例如,如果没有对整数数组进行初始化,那么数组的第一个元素将会被初始化为0。但是,明确初始化数组将会使程序更为清晰。可以使用这两种方法中的一种初始化数组: 或者使用大括号,就像前面描述的那样,直接将值读入数组元素; 或者通过赋值,就像下面的for循环语句中所做的那样。 int[] count = new int[100]; for (int i=0; i<100;i++) count[i] = 0; 【编程示例】使用随机数生成一个含有10个元素的整数数组,接收插入数据的位置和数据后,将数据插入到指定位置,然后输出新数组。新建控制台程序UseArray,代码初始化后,在Program.cs文件中添加如下代码: static void Main(string[] args) { int[] myIntArray = new int[11];//声明数组 Random ram = new Random();//随机数对象 //利用循环完成数组初始化 for (int i = 0; i < 10; i++) { //随机生成[10,100]的整数 myIntArray[i] = ram.Next(9, 99) + 1; } //显示数组 DispalyArray(myIntArray, "before"); Write("\n 输入插入位置(0-9): "); int pos = Convert.ToInt32(ReadLine()); Write(" 输入插入数据: "); int number = Convert.ToInt32(ReadLine()); for (int k = myIntArray.Length - 1; k > pos; k--)//数组元素移位 { myIntArray[k] = myIntArray[k - 1]; } myIntArray[pos] = number; //输出插入数据后的数组 DispalyArray(myIntArray, "after"); Console.ReadKey(); } //显示数组 static void DispalyArray(int[] array,string when) { WriteLine("Array values " + when + " inserting"); foreach (int m in array)//遍历数组 { Write("{0,4}", m);//输出数组元素的值 } } 按Ctrl+F5组合键运行该程序,结果如图32所示。 图32示例程序的运行结果 3.1.2多维数组 具有多个下标的数组有时也很有用。例如,在学生成绩管理系统中,如果要保存50个学生的信息,可以使用一个下标表示学生所在行,用另外一个下标表示学生的某个数据项,这样就可方便地跟踪所有的学生信息。包含两个下标的数组称为二维数组。二维数组元素的C#表示方法如下: Array_Name[Row_index, Column_index] 例如,如果数组名为table,并且具有两个下标,那么table[2,2]是行下标为2、列下标为2的数据项。 具有多个下标的数组通常称为多维数组,它们几乎可以采用与一维数组相同的方式处理。典型地,在C#程序中,声明和创建一个二维数组的语法如下: Base_Type[,] Array_Name = new Base_Type[Length_1,Length_2]; 下面的语句声明名为student的数组,并创建它: string[,] student = new string[3,7]; 这个声明等价于下述两条语句: string[,] student; student = new string[3,7]; 注意,这个语法几乎与用于一维数组的语法相同。唯一的差别是,这里有两个下标。与一维数组中的下标变量相似,多维数组中的下标变量也是基本类型的变量,并且可以在具有基本类型变量允许使用的任何地方使用。 多维数组的初始化和一维数组类似,可以使用关键字new来动态初始化数组或者通过给定值的形式来指定数组中的全部内容。例如: double[,] hillHeight = new double[3,4]{{1,2,3,4},{2,3,4,5},{3,4,5,6}}; double[,] hillHeight = {{1,2,3,4},{2,3,4,5},{3,4,5,6}}; 3.1.3交错数组 前面介绍的多维数组每一行的元素都相同,因此可称为矩形数组。如果多维数组中每一行的元素个数都不同,这样就构成了交错数组。交错数组被称为数组中的数组,因为它的每个元素都是另一数组。图33比较了有3×3个元素的二维数组和交错数组。图33(b)中的交错数组有3行,第一行有2个元素,第二行有6个元素,第三行有3个元素。 图33二维数组与交错数组示例 在声明交错数组时,要依次放置开闭括号。在初始化交错数组时,先设置该数组包含的行数。定义各行中元素个数的第二个括号设置为空,因为这类数组的每一行都包含不同的元素数。然后,为每一行指定行中的元素个数。例如下面的代码: 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}; 迭代交错数组中所有元素的代码可以放在嵌套的for循环语句中。在外层的for循环语句中,迭代每一行,内层的for循环语句迭代一行中的每个元素,例如: for ( int row = 0; row < jagged.Length; row++) { for ( int element = 0; element ’a’; i--) { char old1 = (char)i; char new1 =(char)(i+1); greetingBuilder = greetingBuilder.Replace(old1,new1); } for(int i=’Z’; i>’A’; i--) { char old1 = (char)i; char new1 =(char)(i+1); greetingBuilder = greetingBuilder.Replace(old1,new1); } 这段代码也可以使用String.Replace()方法完成,但是由于Replace()需要分配一个新字符串,整个加密过程需要在堆上有一个能存储大约2800个字符的字符串对象,该对象最终等待被垃圾回收机制回收。在上述代码中,为存储字符串而分配的总的存储单元是用于StringBuilder实例的150个字符。 通过上面的介绍,可以看出StringBuilder与String在许多操作 (如插入、删除、替换)上是非常相似的。在操作性能和内存效率方面,StringBuilder要比String好得多,可以避免产生太多的临时字符串对象,特别是对于经常重复进行修改的情况更是如此。另外,String类提供了更多的方法,可以使开发能够更快地实现应用。在两者的选择上,一般而言,使用StringBuilder类执行字符串的任何操作,而使用String类存储字符串或显示最终结果。 1. 正则表达式 在编写字符串的处理程序时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。目前为止,许多编程语言和工具都包含对正则表达式的支持,C#也不例外,C#基础类库中包含有一个命名空间(System.Text.RegularExpressions)和一系列可以充分发挥规则表达式威力的类(Regex、Match、Group等)。借助网络资源,了解C#正则表达式的用法,可以提高程序设计技能。 2. 编码 众所周知,计算机只能识别二进制数字,如1010、1001。我们在屏幕所看到的文字,字符都是进行二进制转换后的结果。将 文字按照某种规则转换为二进制存储在计算机上,这一个过程叫字符编码(Encoding),反之就是 字符解码。目前存在多种字符编码方式,一组二进制数字根据不同的解码方式,会得到不同的结果,有时甚至会得到乱码。这也就是为什么我们打开网页时有时会是乱码,打开一个文本文件有时也是乱码,而换了一种编码就恢复正常了。CLR中的所有字符都是用16位Unicode来表示的。CLR中的Encoding就是用于字节和字符之间的转换的。 CLR中的Encoding是在System.Text命名空间下的,它是一个抽象类, 所以不能被直接实例化,它主要有如下派生类: ASCIIEncoding、UnicodeEncoding、UTF32Encoding、UTF7Encoding和UTF8Encoding。可以根据需要选择一个合适的Encoding来进行编码和解码; 也可以调用Encoding的静态属性ASCII、Unicode、UTF32、UTF7、UTF8来构造一个Encoding。其中Unicode是表示16位Encoding。调用静态属性和实例化一个子类的效果是一样的,如下代码。 Encoding encodingUTF8 = Encoding.UTF8; Encoding encodingUTF8 = new UTF8Encoding(true); 视频讲解 3.3集合 在程序开发中,虽然可以使用数组来管理一组具有相同数据类型的数据,但是,数组的大小是事先确定的,不便于数据的动态 编辑。例如,在学生成绩管理系统中,使用数组存储学生信息时,必须事先指定学生的人数,而在实际的系统,每个班的学生人数是不相同的,为了实现数据的动态添加,就需要使用集合来管理数据。本任务使用集合来进一步优化学生成绩管理系统的数据管理功能,如图35所示。 图35学生列表 (1) 新建项目。启动Visual Studio 2017,新建控制台项目ImprovetStudent。 (2) 修改代码。项目初始化以后,在主窗口显示的Program.cs文件中添加如下代码行: //学生结构体定义 public struct Student { public string id; public string name; public string grade1; public string grade2; public string grade3; public int total; public int average; } //主函数 static void Main(string[] args) { const int NUM = 3;//学生人数 Student stu;//声明学生结构 //建立学生列表 List listStudent = new List(); //输入学生信息 for (int i = 0; i < NUM; i++) { Console.Write("输入第{0}个学生学号: ", i + 1); stu.id = Console.ReadLine(); Console.Write("输入第{0}个学生姓名: ", i + 1); stu.name = Console.ReadLine(); Console.Write("输入第{0}个学生语文成绩: ", i + 1); stu.grade1 = Console.ReadLine(); Console.Write("输入第{0}个学生数学成绩: ", i + 1); stu.grade2 = Console.ReadLine(); Console.Write("输入第{0}个学生英语成绩: ", i + 1); stu.grade3 = Console.ReadLine(); //计算总分和平均分 stu.total = Convert.ToInt32(stu.grade1) + Convert.ToInt32(stu.grade2) + Convert.ToInt32(stu.grade3); stu.average = stu.total / 3; listStudent.Add(stu);//添加学生到学生列表 } //输出学生成绩 Console.WriteLine("学生成绩单"); Console.WriteLine("|-------------------------------------------|"); Console.WriteLine("| 学号 | 姓名 |语文|数学|英语|总 分|平均分|"); Console.WriteLine("|-------------------------------------------|"); //遍历学生列表 foreach (var s in listStudent) { Console.WriteLine("|{0,8}|{1,3}|{2,4}|{3,4}|{4,4}|{5,5}|{6,6:f2}|", s.id, s.name, s.grade1, s.grade2, s.grade3, s.total, s.average); Console.WriteLine("|-------------------------------------------|"); } } (3) 编译和运行程序。按Ctrl+F5组合键运行该程序,运行结果如图35所示。 3.3.1集合基础 对于很多应用程序,需要创建和管理相关对象组。有两种方式可以将对象分组: 创建对象数组以及创建对象集合。数组对于创建和处理固定数量的强类型对象最有用。集合提供一种更灵活的处理对象组的方法。与数组不同,处理的对象组可根据程序更改的需要动态地增长和收缩。 集合是类,因此必须声明新集合后,才能向该集合中添加元素。许多常见的集合是由.NET Framework提供的,每一类型的集合都是为特定用途设计的。可以通过使用 System.Collections.Generic命名空间中的类来创建泛型集合。在.NET Framework 2.0之前,不存在泛型。现在泛型集合类通常是集合的首选类型。泛型集合类是类型安全的,如果使用值类型,是不需要装箱操作的。如果要在集合中添加不同类型的对象,且这些对象不是相互派生的,例如在集合中添加int和string对象,就只需基于对象的集合类。 表32列出了System.Collections.Generic 命名空间中一些常用集合类的功能。 表32System.Collections.Generic 命名空间中一些常用集合类的功能 类描述 Dictionary表示根据键进行组织的键/值对的集合 List表示可通过索引访问的对象的列表。提供用于对列表进行搜索、排序和修改的方法 Queue表示对象的先进先出 (FIFO) 集合 SortedList表示根据键进行排序的键/值对的集合,而键基于的是相关的 IComparer 实现 Stack表示对象的后进先出 (LIFO) 集合 在.NET Framework 4 中,System.Collections.Concurrent 命名空间中的集合可提供有效的线程安全操作,以便从多个线程访问集合项。当有多个线程并发访问集合时,应使用 System.Collections.Concurrent 命名空间中的类代替 System.Collections.Generic 和 System.Collections 命名空间中的对应类型。 System.Collections命名空间中的类不会将元素存储为指定类型的对象,而是存储为object类型的对象。只要有可能,就应使用 System.Collections.Generic或System.Collections.Concurrent 命名空间中的泛型集合来替代 System.Collections 命名空间中的旧类型。 表33列出了一些 System.Collections 命名空间中常用的集合类的用法。 表33System.Collections 命名空间中常用的集合类的用法 类描述 ArrayList表示大小根据需要动态增加的对象数组 Hashtable表示根据键的哈希代码进行组织的键/值对的集合 Queue表示对象的先进先出 (FIFO) 集合 Stack表示对象的后进先出 (LIFO) 集合 3.3.2列表 .NET Framework为动态列表提供了类ArrayList和List。ArrayList代表了可被单独索引的对象的有序集合。它基本上可以替代一个数组。但是,与数组不同的是,可以使用索引在指定的位置添加和移除项目,动态数组会自动重新调整它的大小。它也允许在列表中进行动态内存分配、增加、搜索各项并排序。System.Collections.Generic命名空间中的类List的用法非常类似于System.Collections命名空间中的ArrayList类,这个类实现了IList、ICollection和IEnumerable接口。 对于新的应用程序,通常可以使用泛型类List替代非泛型类ArrayList,而且ArrayList类的方法与List非常相似,所以本节将只讨论如何使用List类。 1. 创建列表 调用默认的构造函数就可以创建对象列表。在泛型类List中,必须在声明中为列表的值指定类型。下面的代码说明了如何声明一个包含int和string的列表。 List intList = new List (); List strList =new List (); 使用默认的构造函数创建一个空列表。元素添加到列表中后,列表的容量就会扩大为可接纳4个元素。如果添加了第5个元素,列表的大小就重新设置为包含8个元素。如果8个元素还不够,列表的大小就重新设置为16,每次都会将列表的容量重新设置为原来的2倍。为节省时间,如果事先知道列表中元素的个数,就可以用构造函数定义其容量。下面的代码创建一个容量为10个元素的集合。如果该容量不足以容纳要添加的元素,就把集合的大小重新设置为20或者40,每次都是原来的2倍。 List intList = new List(10); 使用Capacity属性可以获取和设置集合的容量。 intList.Capacity = 20; 容量与集合中元素的个数不同。集合中元素的个数可以用Count属性读取。当然,容量总是大于或等于元素个数。只要不把元素添加到列表中,元素个数就是0。 Console.WriteLine(intList.Count); 如果已经将元素添加到列表中,且不希望添加更多的元素,就可以调用TrimExcess()方法,去除不需要的容量。但是,重新定位是需要时间的,所以如果元素个数超过了容量的90%,TrimExcess()方法将什么也不做。 int List.TrimExcess(); 2. 添加元素 使用Add()方法可以给列表添加元素。Add()方法将对象添加到列表的结尾处,例如: List intList = new List(); intList.Add(1); intList.Add(2); List strList =new List(); strList.Add("one"); strList.Add("two"); 使用List类的AddRange()方法可以一次给集合添加多个元素。AddRange()方法的参数是IEnumerable类型对象,所以也可以传送一个数组,例如: strList.AddRange(new string[ ]{"one","two","three"}); 3. 插入元素 使用Insert()方法可以在列表的指定位置插入元素,位置从0开始索引。例如: intList.Insert(3,6); 方法InsertRange()提供了插入大量元素的容量,类似于前面的AddRange()方法。如果索引集大于集合中的元素个数,就抛出ArgumentOutOfRangeException类型的异常。 4. 访问元素 执行了IList和IList接口的所有类都提供了一个索引器,所以可以使用索引器,通过传送元素号来访问元素。第一个元素可以用索引值0来访问。例如指定intList[3],可以访问列表intList中的第4个元素: int num = intList[3]; 可以用Count属性确定元素个数,再使用for循环语句迭代集合中的每个元素,使用索引器访问每一项,例如: for (int i=0; i接口定义的Clear()方法。 6. 搜索 有不同的方式在集合中搜索元素。可以获得要查找的元素的索引,或者搜索元素本身。可以使用的方法有IndexOf()、LastIndexOf()、FindIndex()、FindLastIndex()、Find()和FindLast()。如果只检查元素是否存在,List类提供了Exists()方法。 方法IndexOf()需要将一个对象作为参数,如果在集合中找到该元素,这个方法就返回该元素的索引。如果没有找到该元素,就返回-1。IndexOf()方法使用IEquatable接口来比较元素。例如: int index= intList. IndexOf(3); 使用方法IndexOf(),还可以指定不需要搜索整个集合,但必须指定从哪个索引开始搜索以及要搜索的元素个数。 3.3.3队列 队列是其元素以先进先出(FIFO)的方式来处理的集合。先放在队列中的元素会先读取。队列的例子有在机场排的队、人力资源部中等待处理求职信的队列、打印队列中等待处理的打印任务、以循环方式等待CPU处理的线程。另外,还常常有元素根据其优先级来处理的队列。例如,在机场的队列中,商务舱乘客的处理要优先于经济舱的乘客。这里可以使用多个队列,一个队列对应一个优先级。在机场,这是很常见的,因为商务舱乘客和经济舱乘客有不同的登记队列。打印队列和线程也是这样。可以为一组队列建立一个数组,数组中的一项代表一个优先级。在每个数组项中,都有一个队列,其处理按照FIFO的方式进行。 在.NET的System.Collections命名空间中有非泛型类Queue,在System.Collections. Generic命名空间中有泛型类Queue。这两个类的功能非常类似,但泛型类是强类型化的,定义了类型T,而非泛型类基于Object类型。 在内部,Queue类使用T类型的数组,这类似于List类型。另一个类似之处是它们都执行ICollection和IEnumerable接口。Queue类执行ICollection、IEnumerable和ICloneable接口。Queue类执行IEnumerable和ICloneable接口。Queue泛型类没有执行泛型接口ICollection,因为这个接口用Add()和Remove()方法定义了在集合中添加和删除元素的方法。 队列与列表的主要区别是队列没有执行IList接口。所以不能用索引器访问队列。队列只允许添加元素,该元素会放在队列的尾部(使用Enqueue()方法),从队列的头部获取元素(使用Dequeue()方法)。 图36显示了队列的元素。Enqueue()方法在队列的一端添加元素,Dequeue()方法在队列的另一端读取和删除元素。用Dequeue()方法读取元素,将同时从队列中 图36队列 删除该元素。再调用一次Dequeue()方法,会删除队列中的下一项。 Queue和Queue 类的方法如表34所示。 表34队列常用方法 方法说明 Enqueue()在队列一端添加一个元素 Dequeue()在队列的头部读取和删除一个元素。如果在调用Dequeue()方法时,队列中不再有元素,就抛出InvalidOperationException异常 Peek()在队列的头部读取一个元素,但不删除它 Count返回队列中的元素个数 TrimExcess()重新设置队列的容量。Dequeue()方法从队列中删除元素,但不会重新设置队列的容量。要从队列的头部去除空元素,应使用TrimExcess()方法 Contains()确定某个元素是否在队列中,如果是,就返回true CopyTo()把元素从队列复制到一个已有的数组中 ToArray()ToArray()方法返回一个包含队列元素的新数组 下面的代码演示了Queue 类的基本用法。 Queue numbers = new Queue(); //实例化队列对象 //向队列中添加元素 numbers.Enqueue("one"); numbers.Enqueue("two"); numbers.Enqueue("three"); numbers.Enqueue("four"); numbers.Enqueue("five"); //遍历队列中的元素 foreach( string number in numbers ) { Console.Write("{0} ",number); } //调用队列方法 Console.WriteLine("\nDequeuing '{0}'", numbers.Dequeue()); Console.WriteLine("Peek at next item to dequeue: {0}", numbers.Peek()); Console.WriteLine("Dequeuing '{0}'", numbers.Dequeue()); 上面代码会产生如下的输出结果: one two three four five Dequeuing 'one' Peek at next item to dequeue: two Dequeuing 'two' 3.3.4字典 字典(Dictionary)表示一种复杂的数据结构,这种数据结构允许按照某个键来访问元素。字典也称为映射或散列表。字典的主要特性是能根据键快速查找值,也可以自由添加和删除元素,这有点像List,但没有在内存中移动后续元素的性能开销。 Dictionary泛型类提供了从一组键到一组值的映射。字典中的每个添加项都由一个值及其相关联的键组成。通过键来检索值的速度是非常快的,接近于O(1),这是因为Dictionary类是作为一个哈希表来实现的。在C#中,Dictionary提供快速的基于键值的元素查找。当有很多元素时可以使用它。它需要引用的命名空间是System.Collection.Generic。下面的代码演示了字典的基本用法: //定义 Dictionary openWith = new Dictionary(); //添加元素 openWith.Add("txt", "notepad.exe"); openWith.Add("bmp", "paint.exe"); //取值 Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]); //更改值 openWith["rtf"] = "winword.exe"; //查看 Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]); //遍历Key foreach (var item in openWith.Keys) { Console.WriteLine("Key = {0}", item); } //遍历value foreach (var item in openWith.Values) { Console.WriteLine("value = {0}", item); } 1. 泛型 泛型(Generic)是C# 2.0推出的新语法,不是语法糖,而是C# 2.0由框架升级提供的功能。在编程程序时,开发人员经常会遇到功能非常相似的模块,只是它们处理的数据不一样。但我们没有办法,只能分别写多个方法来处理不同的数据类型。这个时候,那么问题来了,有没有一种办法,用同一个方法来处理传入不同种类型参数的办法呢?泛型的出现就是专门来解决这个问题的。 泛型允许开发人员延迟编写类或方法中的编程元素的数据类型的规范,直到在程序中使用它时。换句话说,泛型允许编写一个可以与任何数据类型一起工作的类或方法。可以通过数据类型的替代参数编写类或方法的规范。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理指定的数据类型。 2. 使用集合初始值设定项初始化字典 Dictionary 包含键/值对集合。其Add()方法采用两个参数 : 一个用于键; 另一个用于值。若要初始化 Dictionary,一种方法是将每组参数括在大括号中, 另一种方法是使用索引初始值设定项。下面的代码示例中,使用类型 Dictionary 的实例初始化 StudentName。第一个初始化使用具有两个参数的Add()方法。编译器为每对Add键和int值生成对 StudentName 的调用。第二个初始化使用 Dictionary 类的公共读取/写入索引器方法: var students = new Dictionary() { { 111, new StudentName { FirstName="Sachin", LastName="Karnik", ID=211 } }, { 112, new StudentName { FirstName="Dina", LastName="Salimzianova", ID=317 } }, { 113, new StudentName { FirstName="Andy", LastName="Ruth", ID=198 } } }; var students2 = new Dictionary() { [111] = new StudentName { FirstName="Sachin", LastName="Karnik", ID=211 }, [112] = new StudentName { FirstName="Dina", LastName="Salimzianova", ID=317 } , [113] = new StudentName { FirstName="Andy", LastName="Ruth", ID=198 } }; 3.4知识点提炼 (1) C#提供了能够存储多个相同类型变量的集合,这种集合就是数组。数组是同一数据类型的一组值,它属于引用类型。 (2) 数组在使用之前必须先定义。一个数组的定义必须包含元素类型、数组维数和每个维数的上下限。 (3) 数组在使用之前必须进行初始化。初始化数组有两种方法: 动态初始化和静态初始化。动态初始化需要借助new运算符,为数组元素分配内存空间,并为数组元素赋初值。静态初始化数组时,必须与数组定义结合在一起,否则会出错。 (4) C#中的字符包括数字字符、英文字母、表达符号等。C#提供的字符类型按照国际上公认的标准,采用Unicode字符集。要得到字符的类型,可以使用System.Char命名空间中的内置静态方法。 (5) C#语言中,string类型是引用类型,其表示零或更多个Unicode字符组成的序列。字符串常用的属性有Length、Chars等。利用字符串类的方法可以实现对字符串的处理操作。 (6) 可变字符串类StringBuilder创建了一个字符串缓冲区,允许重新分配个别字符,这些字符是内置字符串数据类型所不支持的。 (7) 集合提供一种动态对数据分组的方法。.NET Framework提供了 泛型和非泛型两大集合类型。 3.5思考与练习 1. 下列关于数组访问的描述中,()是不正确的。 A. 数组元素索引是从0开始的 B. 对数组元素的所有访问都要进行边界检查 C. 如果使用的索引小于0或大于数组的大小,编译器将抛出一个IndexOutOfRangeException异常 D. 数组元素的访问从1开始,到Length结束 2. 数组pins的定义如下: int[] pins=new int[4]{9,2,3,1}; 则pins[1] =()。 A. 1B. 2C. 3D. 9 3. 有说明语句“double[,] tab=new double[2,3];”,那么下面叙述正确的是()。 A. tab是一个数组维数不确定的数组,使用时可以任意调整 B. tab是一个有两个元素的一维数组,它的元素初始值分别是2,3 C. tab是一个二维数组,它的元素个数一共有6个 D. tab是一个不规则数组,数组元素的个数可以变化 4. 下列关于数组的描述中,()是不正确的。 A. String类中的许多方法都能用在数组中 B. System.Array类是所有数组的基类 C. String类本身可以被看作是一个System.Char对象的数组 D. 数组可以用来处理数据类型不同的批量数据 5. 下面代码实现数组array的冒泡排序,画线处应填入()。 int[ ] array = { 20, 56, 38, 45 }; int temp; for (int i = 0; i < 3; i++) { for (int j = 0; j < ; j++) { if (a[j] < a[j + 1]) { temp = a[j]; array[j] = a[j + 1]; array[j + 1] = temp; } } } A. 4-iB. iC. i+1D. 3-i 6. 在C#中,将路径名“C:\Documents\”存入字符串变量path中的正确语句是()。 A. path="C:\\Documents\\";B. path="C://Documents//"; C. path="C:\Documents\";D. path="C:\/Documents\/"; 7. 从控制台输入班级人数,将每个人的年龄放入数组,计算所有人的年龄总和和平均年龄,并输出年龄最大的学生。 8. 编写一个控制台程序获取字符串中相同的字符及其个数。 9. 分拣奇偶数。将字符串"1 2 3 4 5 6 7 8 9 10"中的数据按照“奇数在前、偶数在后”的格式进行调整。