Dictionary
知识点一 Dictionary的本质
可以将Dictionary理解为 拥有泛型的Hashtable
它也是基于键的哈希代码组织起来的 键/值对
键值对类型从Hashtable的object变为了可以自己制定的泛型
知识点二 申明
需要引用命名空间 using System.Collections.Generic
Dictionary<int, string> dictionary = new Dictionary<int, string>();
知识点三 增删查改
增
注意:不能出现相同键
dictionary.Add(1, "123"); dictionary(键,"值");
dictionary.Add(2, "222");
dictionary.Add(3, "222");
//dictionary.Add(3, "123");
删
1.只能通过键去删除
删除不存在键 没反应
dictionary.Remove(1);
dictionary.Remove(4);2.清空
dictionary.Clear();
dictionary.Add(1, "123");
dictionary.Add(2, "222");
dictionary.Add(3, "222");
查
1.通过键查看值
找不到直接报错
Console.WriteLine(dictionary[2]);
Console.WriteLine(dictionary[4]);
Console.WriteLine(dictionary[1]);2.查看是否存在
根据键检测
if( dictionary.ContainsKey(4) )
{
Console.WriteLine("存在键为1的键值对");
}
根据值检测
if (dictionary.ContainsValue("1234"))
{
Console.WriteLine("存在值为123的键值对");
}改
Console.WriteLine(dictionary[1]);
dictionary[1] = "555";
Console.WriteLine(dictionary[1]);
知识点四 遍历
Console.WriteLine("**************");
Console.WriteLine(dictionary.Count);
1.遍历所有键
foreach (int item in dictionary.Keys)
{
Console.WriteLine(item);
Console.WriteLine(dictionary[item]);
}
2.遍历所有值
Console.WriteLine("**************");
foreach (string item in dictionary.Values)
{
Console.WriteLine(item);
}
3.键值对一起遍历
Console.WriteLine("**************");
foreach (KeyValuePair<int,string> item in dictionary)
{
Console.WriteLine("键:" + item.Key + "值:" + item.Value);
}
顺序存储和链式存储
请说出常用的数据结构有哪些?
答:数组、栈、队列、链表、树、图、堆、散列表
请描述顺序存储和链式存储的区别?
顺序存储:内存中用一组地址连续的存储单元存储线性表(地址存储)
链式存储:内存中用一组任意的存储单元存储线性表(任意地方)
知识点一 数据结构
数据结构
数据结构是计算机存储、组织数据的方式(规则)
数据结构是指相互之间存在一种或多种特定关系的数据元素的集合
比如自定义的一个 类 也可以称为一种数据结构 自己定义的数据组合规则不要把数据结构想的太复杂
简单点理解,就是人定义的 存储数据 和 表示数据之间关系 的规则而已常用的数据结构(前辈总结和制定的一些经典规则)
数组、栈、队列、链表、树、图、堆、散列表
知识点二 线性表
线性表是一种数据结构,是由n个具有相同特性的数据元素的有限序列
比如数组、ArrayList、Stack、Queue、链表等等
顺序存储和链式存储 是数据结构中两种 存储结构
知识点三 顺序存储
数组、Stack、Queue、List、ArrayList —— 顺序存储
只是 数组、Stack、Queue的 组织规则不同而已
顺序存储:
用一组地址连续的存储单元依次存储线性表的各个数据元素
知识点四 链式存储
单向链表、双向链表、循环链表 —— 链式存储
链式存储(链接存储):
用一组任意的存储单元存储线性表中的各个数据元素
LindedList<int> link = new LindedList<int>();
link.Add(1);
link.Add(2);
link.Add(3);
link.Add(4);
LinkedNode<int> node = link.head;
while(node != null)
{
Console.WriteLine(node.value);
node = node.nextNode;
}
link.Remove(2);
node = link.head;
while (node != null)
{
Console.WriteLine(node.value);
node = node.nextNode;
}link.Remove(1);
node = link.head;
while (node != null)
{
Console.WriteLine(node.value);
node = node.nextNode;
}
link.Add(99);
node = link.head;
while (node != null)
{
Console.WriteLine(node.value);
node = node.nextNode;
}
}
}
知识点五 自己实现一个最简单的单向链表
/// <summary>
/// 单向链表节点
/// </summary>
/// <typeparam name="T"></typeparam>
class LinkedNode<T>
{
public T value;
这个存储下一个元素是谁 相当于钩子
public LinkedNode<T> nextNode;
public LinkedNode(T value)
{
this.value = value;
}
}
/// <summary>
/// 单向链表类 管理 节点 管理 添加等等
/// </summary>
/// <typeparam name="T"></typeparam>
class LindedList<T>
{
public LinkedNode<T> head;
public LinkedNode<T> last;
public void Add(T value)
{
添加节点 必然是new一个新的节点
LinkedNode<T> node = new LinkedNode<T>(value);
if( head == null )
{
head = node;
last = node;
}
else
{
last.nextNode = node;
last = node;
}
}
public void Remove(T value)
{
if( head == null )
{
return;
}
if( head.value.Equals(value) )
{
head = head.nextNode;
如果头节点 被移除 发现头节点变空
证明只有一个节点 那尾也要清空
if( head == null )
{
last = null;
}
return;
}
LinkedNode<T> node = head;
while(node.nextNode != null)
{
if( node.nextNode.value.Equals(value) )
{
让当前找到的这个元素的 上一个节点
指向 自己的下一个节点
node.nextNode = node.nextNode.nextNode;
break;
}
}
}
}
知识点六 顺序存储和链式存储的优缺点
从增删查改的角度去思考
增:链式存储 计算上 优于顺序存储 (中间插入时链式不用像顺序一样去移动位置)
删:链式存储 计算上 优于顺序存储 (中间删除时链式不用像顺序一样去移动位置)
查:顺序存储 使用上 优于链式存储 (数组可以直接通过下标得到元素,链式需要遍历)
改:顺序存储 使用上 优于链式存储 (数组可以直接通过下标得到元素,链式需要遍历)
Linkedlist
知识点一 LinkedList
LinkedList是一个C#为我们封装好的类
它的本质是一个可变类型的泛型双向链表
知识点二 申明
需要引用命名空间
using System.Collections.Generic
LinkedList<int> linkedList = new LinkedList<int>();
LinkedList<string> linkedList2 = new LinkedList<string>();
链表对象 需要掌握两个类
一个是链表本身 一个是链表节点类LinkedListNode
知识点三 增删查改
增
1.在链表尾部添加元素
linkedList.AddLast(10);
2.在链表头部添加元素
linkedList.AddFirst(20);
3.在某一个节点之后添加一个节点
要指定节点 先得得到一个节点
LinkedListNode<int> n = linkedList.Find(20);
linkedList.AddAfter(n, 15);
4.在某一个节点之前添加一个节点
要指定节点 先得得到一个节点
linkedList.AddBefore(n, 11);
删
1.移除头节点
linkedList.RemoveFirst();
2.移除尾节点
linkedList.RemoveLast();
3.移除指定节点
无法通过位置直接移除
linkedList.Remove(20);
4.清空
linkedList.Clear();linkedList.AddLast(1);
linkedList.AddLast(2);
linkedList.AddLast(3);
linkedList.AddLast(4);
查
1.头节点
LinkedListNode<int> first = linkedList.First;
2.尾节点
LinkedListNode<int> last = linkedList.Last;
3.找到指定值的节点
无法直接通过下标获取中间元素
只有遍历查找指定位置元素
LinkedListNode<int> node = linkedList.Find(3);
Console.WriteLine(node.Value);
node = linkedList.Find(5);
4.判断是否存在
if( linkedList.Contains(1) )
{
Console.WriteLine("链表中存在1");
}
改
要先得再改 得到节点 再改变其中的值
Console.WriteLine(linkedList.First.Value);
linkedList.First.Value = 10;
Console.WriteLine(linkedList.First.Value);
知识点四 遍历
1.foreach遍历
foreach (int item in linkedList)
{
Console.WriteLine(item);
}
2.通过节点遍历
从头到尾
Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&");
LinkedListNode<int> nowNode = linkedList.First;
while (nowNode != null)
{
Console.WriteLine(nowNode.Value);
nowNode = nowNode.Next;
}从尾到头
Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&");
nowNode = linkedList.Last;
while (nowNode != null)
{
Console.WriteLine(nowNode.Value);
nowNode = nowNode.Previous;
}
Next:从前往后Previous:从后往前
泛型栈和队列
知识点一 回顾数据容器
变量
无符号
byte ushort uint ulong
有符号
sbyte short int long
浮点数
float double decimal
特殊
char bool string
复杂数据容器
枚举 enum
结构体 struct
数组(一维、二维、交错) [] [,] [][]
类
数据集合
using System.Collections;ArrayList object数据列表
Stack 栈 先进后出
Queue 队列 先进先出
Hashtable 哈希表 键值对
泛型数据集合
using System.Collections.Generic;List 列表 泛型列表
Dictionary 字典 泛型哈希表
LinkedList 双向链表
Statck 泛型栈
Queue 泛型队列
知识点二 泛型栈和队列
命名空间:using System.Collections.Generic;
使用上 和之前的Stack和Queue一模一样
Stack<int> stack = new Stack<int>();
Queue<object> queue = new Queue<object>();
委托
事件
知识点一 事件是什么
事件是基于委托的存在
事件是委托的安全包裹
让委托的使用更具有安全性
事件 是一种特殊的变量类型
知识点二 事件的使用
申明语法:
访问修饰符 event 委托类型 事件名;
事件的使用:
1.事件是作为 成员变量存在于类中
2.委托怎么用 事件就怎么用
事件相对于委托的区别:
1.不能在类外部 赋值
2.不能再类外部 调用
注意:
它只能作为成员存在于类和接口以及结构体中
class Test
{
委托成员变量 用于存储 函数的
public Action myFun;
事件成员变量 用于存储 函数的
public event Action myEvent;public Test()
{
事件的使用和委托 一模一样 只是有些 细微的区别
myFun = TestFun;
myFun += TestFun;
myFun -= TestFun;
myFun();
myFun.Invoke();
myFun = null;myEvent = TestFun;
myEvent += TestFun;
myEvent -= TestFun;
myEvent();
myEvent.Invoke();
myEvent = null;
}public void DoEvent()
{
if(myEvent != null)
{
myEvent();
}
}public void TestFun()
{
Console.WriteLine("123");
}
}
知识点三 为什么有事件
1.防止外部随意置空委托
2.防止外部随意调用委托
3.事件相当于对委托进行了一次封装 让其更加安全
class Program
{
static void Main(string[] args)
{
Console.WriteLine("事件");Test t = new Test();
委托可以在外部赋值
t.myFun = null;
t.myFun = TestFun;
t.myFun = t.myFun + TestFun;
t.myFun += TestFun;
事件是不能再外部赋值的
t.myEvent = null;
t.myEvent = TestFun;
虽然不能直接赋值 但是可以 加减 去添加移除记录的函数
t.myEvent += TestFun;
t.myEvent -= TestFun;/委托是可以在外部调用的
t.myFun();
t.myFun.Invoke();
事件不能再外部调用
t.myEvent();
只能在类的内部去封装 调用
t.DoEvent();Action a = TestFun;
事件 是不能作为临时变量在函数中使用的
event Action ae = TestFun;
}static void TestFun()
{}
}
总结
事件和委托的区别
事件和委托的使用基本是一模一样的
事件就是特殊的委托
主要区别:
1.事件不能在外部使用赋值=符号,只能使用+ - 。委托 哪里都能用
2.事件 不能在外部执行。 委托哪里都能执行
3.事件 不能作为 函数中的临时变量的。 委托可以
匿名函数
知识点一 什么是匿名函数
顾名思义,就是没有名字的函数
匿名函数的使用主要是配合委托和事件进行使用
脱离委托和事件 是不会使用匿名函数的
知识点二
基本语法 delegate (参数列表) { 函数逻辑 };
何时使用?
1.函数中传递委托参数时
2.委托或事件赋值时
知识点三 使用
1.无参无返回
这样申明匿名函数 只是在申明函数而已 还没有调用
真正调用它的时候 是这个委托容器啥时候调用 就什么时候调用这个匿名函数
Action a = delegate ()
{
Console.WriteLine("匿名函数逻辑");
};a();
2.有参
Action<int, string> b = delegate (int a, string b)
{
Console.WriteLine(a);
Console.WriteLine(b);
};b(100, "123");
3.有返回值
Func<string> c = delegate ()
{
return "123123";
};Console.WriteLine(c());
4.一般情况会作为函数参数传递 或者 作为函数返回值
Test t = new Test();
Action ac = delegate ()
{
Console.WriteLine("随参数传入的匿名函数逻辑");
};
t.Dosomthing(50, ac);
参数传递
t.Dosomthing(100, delegate ()
{
Console.WriteLine("随参数传入的匿名函数逻辑");
});
返回值
Action ac2 = t.GetFun();
ac2();
一步到位 直接调用返回的 委托函数
t.GetFun()();
知识点四 匿名函数的缺点
添加到委托或事件容器中后 不记录 无法单独移除Action ac3 = delegate ()
{
Console.WriteLine("匿名函数一");
};ac3 += delegate ()
{
Console.WriteLine("匿名函数二");
};ac3();
因为匿名函数没有名字 所以没有办法指定移除某一个匿名函数
此匿名函数 非彼匿名函数 不能通过看逻辑是否一样 就证明是一个
ac3 -= delegate ()
{
Console.WriteLine("匿名函数一");
};
ac3 = null;
ac3();
}static void TestFun()
{}
}class Test
{
public Action action;作为参数传递时
public void Dosomthing(int a, Action fun)
{
Console.WriteLine(a);
fun();
}作为返回值
public Action GetFun()
{
return delegate() {
Console.WriteLine("函数内部返回的一个匿名函数逻辑");
};
}public void TestTTTT()
{}
}
总结
匿名函数 就是没有名字的函数
固定写法
delegate(参数列表){}
主要是在 委托传递和存储时 为了方便可以直接使用匿名该函数
缺点是 没有办法指定移除
lambad表达式
知识点一 什么是lambad表达式
可以将lambad表达式 理解为匿名函数的简写
它除了写法不同外
使用上和匿名函数一模一样
都是和委托或者事件 配合使用的
知识点二 lambad表达式语法
匿名函数 delegate (参数列表) { }; lambad表达式 (参数列表) => { 函数体 };
知识点三 使用
1.无参无返回
Action a = () =>
{
Console.WriteLine("无参无返回值的lambad表达式");
};
a();
2.有参
Action<int> a2 = (int value) =>
{
Console.WriteLine("有参数Lambad表达式{0}", value);
};
a2(100);
3.甚至参数类型都可以省略 参数类型和委托或事件容器一致
Action<int> a3 = (value) =>
{
Console.WriteLine("省略参数类型的写法{0}", value);
};
a3(200);
4.有返回值
Func<string, int> a4 = (value) =>
{
Console.WriteLine("有返回值有参数的lambad表达式{0}", value);
return 1;
};
Console.WriteLine(a4("123123"));其它传参使用等和匿名函数一样
缺点也是和匿名函数一样的
Test t = new Test();
t.DoSomthing();
}
}知识点四 闭包
内层的函数可以引用包含在它外层的函数的变量
即使外层函数的执行已经终止
注意:
该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。class Test
{
public event Action action;public Test()
{
int value = 10;
这里就形成了闭包
因为 当构造函数执行完毕时 其中申明的临时变量value的声明周期被改变了
action = () =>
{
Console.WriteLine(value);
};for (int i = 0; i < 10; i++)
{
此index 非彼index
int index = i;
action += () =>
{
Console.WriteLine(index);
};
}
}public void DoSomthing()
{
action();
}
}
总结
匿名函数的特殊写法 就是 lambad表达式
固定写法 就是 (参数列表)=>{}
参数列表 可以直接省略参数类型
主要在 委托传递和存储时 为了方便可以直接使用匿名函数或者lambad表达式缺点:无法指定移除