复习:J&C:集合概述 / 迭代器模式 / ER模型 / 仓储模式
C#几乎已经完全不会使用非泛型的集合了。所有泛型集合都在该命名空间下:
using System.Collections.Generic;
List<string> student = new List<string>();//一个空的List List<string> student = new List<string> { "wf", "xr", "jym" };//初始化List的同时装入3个元素
C#中List和数组的使用方式非常相像:
student[0] = "dfg";//赋值(或更改) Console.WriteLine(student[0]);//取值
那为什么要引入List呢,数组不香么?数组真不香,^_^:
增
student.Add("xm"); //添加到最后 student.Insert(3, "xm"); //插入到指定位置
删
student.Remove("xr"); student.RemoveAt(0); student.clear();
改:同数组,用[],但@想一想@:为什么可以在一个对象上使用方括号呢?(复习:索引器,源代码查看演示)
查:
Console.WriteLine(student.BinarySearch("dfg"));//二分查找 Console.WriteLine(student.IndexOf("jym"));
Console.WriteLine(student.Contains("jym"));
这里特别要注意的是BinarySearch(),需要List中的元素是有序排列的!
复习:为什么用C#“把质数装到数组”很难实现?
#体会#:数组,长度是固定的;List,可以一直Add()或Insert()的……
以下结合源代码演示:
private T[] _items;索引器中的证据
public T this[int index] { get { // ...... return _items[index];
if (_size == _items.Length) EnsureCapacity(_size + 1);说明:List中用_capacity来确定数组的长度,(并且通过属性Capacity暴露)
_items = new T[capacity];默认为4,
private const int _defaultCapacity = 4;当数组无法继续装下更多元素时,就会“双倍扩容”,设置新的capacity:
int newCapacity = _items.Length == 0 ? DefaultCapacity : _items.Length * 2;
演示:Count和Capacity的关系
字典(键值对集合),可以使用自定义的TValue类来无限扩展
实例化一个空的Dictionary
Dictionary<string, int> scores = new Dictionary<string, int>();构建键值对:
scores["atai"] = 85; scores["zm"] = 95; scores["wpz"] = 90;或者在实例化的同时存入:
scores = new Dictionary<string, int> { {"atai", 85 }, {"zm", 95 } };
@试一试@:如果出现重复的键,会发生什么情况?
其他常用方法:(都是围绕key展开的)
scores.Add("wpz", 90); scores.Remove("wpz"); scores.ContainsKey("zm"); Console.WriteLine(scores["atai"]);
C#中用得不多,因为它比较“怪异”:当元素出现重复的时候(复习),它自动的把重复元素“吞”了,不做任何“提示”:
HashSet<int> ages = new HashSet<int> { 16, 23, 25, 3, 3}; Console.WriteLine(ages.Count); //4,因为3和3重复 ages.Add(5); Console.WriteLine(ages.Count); //5,5是非重复的 ages.Add(16); //不会报错! Console.WriteLine(ages.Count); //还是5
想不想打人?O(∩_∩)O哈哈~
演示:List和Dictionary的祖先类:
#体会:继承/抽象的力量#
小插曲:LinkedList实现了ICollection,但没有Add()方法,为什么?(复习:接口的显式实现)
void ICollection<T>.Add(T value)
复习:迭代器模式
所有集合元素都可以被遍历:
ISet<int> set = new HashSet<int> { 1, 7, 3, 12, 8 }; IEnumerator<int> enumerator = set.GetEnumerator(); while (enumerator.MoveNext()) { Console.WriteLine(enumerator.Current); }
这样写代码有点“累赘”,所以C#引入了一种新的书写方式(语法糖):
foreach (var id in set)//id代表每一次遍历时的集合元素 {
Console.WriteLine(id);
}
上述foreach在编译后还是会变成while形式。
ILDASM演示:get_Current() 和MoveNext() 被调用
IL_006f: br.s IL_0086 IL_0071: ldloc.1 IL_0072: callvirt instance !0 class [System.Runtime]System.Collections.Generic.IEnumerator`1<class CSharp.Student>::get_Current() IL_0077: stloc.2 IL_0078: nop IL_0079: ldloc.2 IL_007a: callvirt instance string CSharp.Person::get_Name() IL_007f: call void [System.Console]System.Console::WriteLine(string) IL_0084: nop IL_0085: nop IL_0086: ldloc.1 IL_0087: callvirt instance bool [System.Runtime]System.Collections.IEnumerator::MoveNext() IL_008c: brtrue.s IL_0071 IL_008e: leave.s IL_009b
任何一个类,只要实现了IEnumerable,就可以被foreach。演示:
foreach (var id in student) { Console.WriteLine(id);
public class Student : IEnumerable<double>要实现IEnumerable,就需要一个IEnumerator的对象:
public IEnumerator<double> GetEnumerator()
这个对象得我们自己声明,通常就放在当前类中,形成一个内部类:
class ScoreEnumerator : IEnumerator<double>{
接下来关键是要实现:
public double Current => scores[index];
public bool MoveNext() { index++; return index < scores.Length;
其中,scores是由构造函数传入的,index是我们自己声明的:
private double[] scores; private int index; public ScoreEnumerator(double[] scores) { index = 0; this.scores = scores;
演示:foreach循环,会依次调用:GetEnumerator() -> MoveNext() -> Current
说明:以上代码#常见面试题:注意和List集合for循环的区别#
foreach (var student in students) { //student = new Student();但是,要区分:
student = new Student();//原有的student对象被彻底替换 student.Name += "fg";//仍然保留原来的student对象,只是改变它的Name
当我们不愿意很麻烦的实现IEnumerable时,可以利用:yield
static IEnumerable<int> GetNumbers() { yield return 0; yield return 1; yield return 2; yield return 3; }
注意:
for (int i = 0; i < 5; i++) { if (i%2 == 0) { yield break; } yield return i; }
见:J&C:集合概述 / 迭代器模式 / ER模型 / 仓储模式 1-2题
多快好省!前端后端,线上线下,名师精讲
更多了解 加: