复习: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题
多快好省!前端后端,线上线下,名师精讲
更多了解 加: