C#编程语言进阶(二)
常用泛型数据结构类
1.List
List概念:
List<T>是 C# 中最常用的 动态数组(泛型集合)。它提供了高效的增删改查操作,是 System.Collections.Generic命名空间的一部分。
List声明:
List<int> list = new List<int>();
List的使用:
1.增加操作
List<int> list = new List<int>();
// 单个加
list.Add(1);
// 批量加
List<int> list2 = new List<int>();
list.AddRange(list2);
// 插入
list.Insert(0, 999);
2.删除操作
// 删除指定元素
list.Remove(1);
// 删除指定位置元素
list.RemoveAt(2);
// 清空
list.Clear();
3.查找操作
// 得到指定位置元素
Console.WriteLine(list[0]);
// 查找元素是否存在
list.Contains(3);
// 正向查找元素
list.Indexof(4);
// 反向查找元素
list.LastIndexof(4);
4.修改操作
list[0] = 99;
5.遍历
// 元素个数
Console.WriteLine(list.Count);
// 容量
Console.WriteLine(list,Capacity);
// for循环遍历
for(int i = 0; i < list.Count; i++)
{
Console.WriteLine(list[i]);
}
// foreach 遍历
foreach(var item in list)
{
Console.WriteLine(item);
}
2.Dictionary(字典)
Dictionary概念:
Dictionary<TKey, TValue>是 C# 中最常用的 键值对集合,它基于哈希表实现,提供高效的查找、插入和删除操作。键值对类型从Hashtable的object变为了可以自己定制的泛型
Dictionary和Hashtable的区别,Dictionary需要指定键值对的类型,Hashtable键值对可以是任何数据类型
Dictionary<int, string> dict = new Dictionary<int, string>();
dict.Add(1,"张三");
Hashtable hash = new Hashtable();
hash.Add(1,"李四");
hash.Add("12","王五");
Dictionary声明:
Dictionary<int, string> dictionary = new Dictionary<int, string>();
Dictionary的使用:
1.增加操作
dictionary.Add(1,"123");
dictionary.Add(2,"1234");
2.删除操作
删除只能通过键Key删除
// 删除
dictionary.Remove(1);
// 清空
dictionary.Clear();
3.查找操作
找不到直接报错
dictionary[0];
// 判断是否存在某个元素
// 通过键Key检测
dictionary.ContiansKey(1);
// 通过值检测
dictionary.ContainsValue("123");
4.修改操作
dictionary[1] = "111";
5.遍历
// 遍历所有键
foreach(var item in dictionary.Keys)
{
Console.WriteLine(dictionary[item]);
}
// 遍历所有的值
foreach(var item in dictionary.Values)
{
Consloe.WriteLine(item);
}
// 键值对一起遍历
foreach(KeyValuePair<int, string> item in dictionary)
{
Consloe.WriteLine("键:" +item.Key + "值:" + item.Value);
}
3.顺序存储与链式存储
顺序存储概念:
顺序存储是指在内存中开辟连续的存储空间来存放数据。
1.线性表
是由n个具有相同特性的数据元素的有限序列。比如数组、AarrayList、Stack、Queue、链表等等
链式存储概念:
链式存储的存储空间不是连续的,而是通过指针将各个元素连接起来。链表是典型的链式存储结构。链式存储由数据域和指针域两部分构成,指针域存储下一个元素的地址。
1.单链表实现
// 单链表节点
class LinkedNode<T>
{
public T value; // 存储的具体数据
public LinkedNode<T> nextNode; // 存储下一个元素
// 构造函数
public LinkedNode(T value)
{
this.value = value;
}
}
// 单链表管理器
class LinkedList<T>
{
public LinkedNode<T> head; // 头节点
public LinkedNode<T> last; // 尾节点
// 增加
public void Add(T value)
{
LinkedNode<T> node = new LinkedNode<T>(value);
if(head == null)
{
head = node;
last = node;
}
else
{
last.nextNode = node;
node = last; // 尾节点后移
}
}
// 减少
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.value;
break;
}
node = node.nextNode; // 进行寻找
}
}
}
// 遍历链表
LinkedNode<int> node = linkedList.head;
while (node!= null)
{
Console.WriteLine(node.value);
node = node.nextNode;
}
2.双向链表
// 双向链表节点
clas LinkedNode<T>
{
public LinkedNode<T> frontNode; // 存储前一节点
public T value;
public LinkedNode<T> nextNode; // 存储下一节点
public LinkedNode(T value)
{
this.value = value
}
}
// 双向链表管理器
class LinkedList<T>
{
public int count; // 数量
public LinkedNode<T> head;
public LinkedNode<T> last;
// 增加
public void Add(T value)
{
LinkedNode node = new LinkNode<T>(value);
if(head == null)
{
head = node;
last = node;
}
else
{
last.nextNode = node;
node.frontNode = last;
last = node;
}
++count;
}
// 通过下标移除
public void Remove(int index)
{
// 判断有没有越界
if(index >= count || index < 0)
{
return;
}
int tempCount = 0;
LinkedNode<T> tempnode = head; // 临时变量
while(ture) // 找到对应位置节点
{
if(tempCount == index)
{
if(tempnode.frontNode != null)
{
tempnode.frontNode.nextNode = tempnode.nextNode;
}
if(tempnode.nextNode != null)
{
tempnode.nextNode.frontNode = tempnode.frontNode;
}
// 如果是头节点需要改变头节点的指向
if(index == 0)
{
head = head.nextNode;
}
else if(index = count - 1)
{
last = last.frontNode;
}
count--;
break;
}
++tempCount;
tempnode = tempnode.nextNode;
}
}
}
LinkedList<int> link = new LinkedList<int>();
link.Add(1);
link.Add(2);
link.Add(3);
link.Add(4);
link.Remove(3);
// 双向链表遍历
// 正向遍历
LinkedNode<int> head = link.head; // 取出头节点
while(head != null)
{
Console.WriteLine(link.value);
head = head.nextNode;
}
// 逆向遍历
LinkedNode<int> last = link.last;
while(last != null)
{
Console.WriteLine(link.value);
last = last.forntNode;
}
4.LinkedList
LinkedList概念:
LinedList是一个C#为我们封装好的类,它的本质是一个可变类型的泛型双向链表。
LinkedList声明:
LinkedList<int> linkedlist = new LinkedList<int>();
LinkedList使用:
1.增加操作
// 往尾部加
linkedlist.AddLast(10);
// 往头部加
linkedlist.AddFirst(12);
// 添加到某节点之后
LinkListNode<int> n = linkedlist.Find(10); // 在链表上查找值为10的节点
linkedlist.AddAfter(n, 15);
// 添加到某节点之前
linkedlist.AddBefore(n, 29);
2.删除操作
// 移除头节点
linkedlist.RemoveFirst();
// 移除尾部节点
linkedlist.RemoveLast();
// 移除指定节点
linkedlist.Remove(10);
// 清空
linkedlist.Clear();
3.查找操作
// 查找头节点
LinkedListNode<int> first = linkedlist.First;
// 查找尾节点
LinkedListNode<int> list = linkedlist.Last;
// 查找指定值节点
LinkedListNode<int> node = linkedlist.Find(10);
// 判断是否存在
if(linkedlist.Contains(1))
{
Console.WriteLine("存在");
}
4.修改操作
// 修改需要先得到节点,然后得到节点的value再改变
linkedList.First.Value = 10;
5.遍历
// 1.foreach遍历
foreach(var item in linkedlist)
{
Console.WriteLine(item);
}
// 2.通过节点遍历
// 正向遍历
LinkedListNode<int> head = linkedlist.First;
while(head != null)
{
Console.WriteLine(head.Value);
head = head.Next;
}
// 反向遍历
LinkedListNode<int> last = linkedlist.Last;
while(last != null)
{
Console.WriteLine(last.Value);
last = head.Previous;
}
5.泛型栈和泛型队列
Stack <int> stack = new Stack <int>();
Queue <int> queue = new Queue <int>();
6.委托
委托概念:
委托是函数的容器,可以理解成表示函数(方法)的变量类型。
用来存储、传递函数。
委托的本质是一个类,用来定义函数的类型(返回值和参数类型)。
不同的函数必须对应和各自“格式”的委托
作用:可以延迟执行某个方法
基本语法:
关键字:delegate
语法:访问修饰符 delegate 返回值 委托名(参数列表);
注意:访问修饰符默认不写为public
// 注意同一语句块中不能重名
public delegate void MyFun();
委托的使用:
// 定义一个委托类型,返回值类型为void,参数类型为空
public delegate void MyFun();
// 定义一个委托类型,返回值类型为int,参数类型为int
delegate int MyFun2(int a);
static void Main(string[] args)
{
// 委托装载函数
Myfun f = new MyFun(Fun);
Console.WriteLine("1");
Console.WriteLine("2");
Console.WriteLine("3");
// 调用委托
f.Invoke(); // 输出 12
MyFun f2 = Fun; // 等效于 MyFun f2 = new MyFun(Fun);
f2(); // 等效于 f2.Invoke();
}
static void Fun()
{
Console.WriteLine("12");
}
委托常用在:
1.作为类的成员
2.作为函数的参数
class Test
{
public MyFun f; // 声明一个委托类型的成员变量
public MyFun2 f2; // 声明一个委托类型的成员变量
// 委托作为函数参数
public void TestFun(MyFun f1, MyFun2 f2)
{
// 可以先处理一些别的逻辑,当这些逻辑处理完成后,再执行传入的函数
}
}
委托变量可以存储多个函数(多播委托)
MyFun ff = Fun; // 等效于 MyFun ff = new MyFun(Fun);
ff += Fun; // 多播委托
ff(); // 调用委托 ,Fun会被调用两次(按添加顺序执行)
// 减 移除容器中指定的函数
// 多减不会报错
ff -= Fun;
系统定义好的委托
// 使用系统自带委托需要引用using System;
// Action 无返回值的委托
Action action = Fun;
// 可以传n个参数的委托,无返回值的 系统提供了0到16个参数的委托
Action<int, string> action2 = Fun4;
// 可以指定返回值类型的泛型委托
Func<string> func = Fun3; // 泛型委托
// 可以传n个参数的委托,有返回值的 系统提供了0到16个参数的委托
// Func<参数1类型, 参数2类型, ..., 返回值类型>
Func<int, int> func2 = Fun2;
static void Fun()
{
}
static int Fun2(int a)
{
return a * 2;
}
static string Fun3()
{
return "";
}
static void Fun4(int a, string b)
{
}
7.事件
事件的概念:
事件是 C# 中基于委托的发布-订阅模式实现,用于实现对象间的松耦合通信。
事件是基于委托的存在;事件是委托的安全包裹;让委托的使用更具有安全性
注意:event必须与委托类型结合使用
事件的使用:
语法:
访问修饰符 event 委托类型 事件名
class Test
{
// 声明事件
public event Action MyEvent;
public Test()
{
// 订阅事件
MyEvent += TestFun;
// 取消订阅
MyEvent -= TestFun;
}
public void TestFun()
{
}
public void DoEvent()
{
MyEvent?.Invoke();
// 等效于
if(MyEvent != null)
{
MyEvent();
}
}
}
static void Main(string[] args)
{
Test t = new Test();
// 事件不能在外部调用,只能在类的内部封装调用
t.DoEvent();
}
事件的使用:
1.事件是作为 成员变量 存在于类中
2.委托怎么用 事件就怎么用
3.事件不能作为临时变量在函数中使用
注意:它只能作为成员存在于类和接口以及结构体中
事件和委托的区别:
1.事件和委托的使用基本是一样的,事件可以说是特殊的委托。
2.事件不能在类外部赋值,只能加减(只能用赋值运算符);也不能在类外部调用,委托可以
3.事件不能作为函数的临时变量,但委托可以。
为什么有事件:
1.防止外部随意置空委托
2.防止外部随意调用委托
3.事件相当于委托进行了一次封装,让其更加安全
8.匿名函数
匿名函数概念:
匿名函数就是没有名字的函数,匿名函数的使用主要是配合委托和事件进行使用,脱离委托和事件是不会使用匿名函数的。常作为一次性使用的代码块存在。
基本语法:
// 语法
delegate (参数列表)
{
函数逻辑
};
// (声明匿名函数后需要用一个委托或者事件来存储它)
1.无参无返回值
Action a = delegate()
{
};
2.有参无返回值
Action<int> a1 = delegate(int value)
{
};
3.有返回值
Func<string> b = delegate()
{
};
匿名函数的使用:(一般情况会作为函数参数传递或者作为函数返回值)
1.函数中传递委托参数时
2.委托或者事件赋值时
class Test
{
public Action action;
// 作为参数传递
public void Dosomthinhg(int a, Action fun)
{
Console.WriteLine(a);
fun();
}
// 作为返回值
public Action GetFun()
{
return delegate() { };
}
}
1.作为参数
Action a = delegate()
{
};
Test t = new Test();
t.Dosomthinhg(100, a);
// 等效于
t.Dosomthinhg(100, delegate () { });
2.作为返回值
Action a2 = t.GetFun();
a2();
// 一步到位
t.GetFun()();
匿名函数的缺点:
添加到委托或者事件容器中,不记录无法单独移除。因为匿名函数没有名字,所以没有办法指定移除某一个匿名函数
9.Lambad表达式
Lambad表达式概念:
Lambda 表达式 是一种用于创建匿名函数的简洁方式。它除了写法不同外,使用上和匿名函数一模一样,都是和委托或者事件配合使用的。
Lambad表达式使用:
// 1.Lambad语法
(参数列表) =>
{
函数体
};
// 无参无返回值
Action a = () =>
{
Console.WriteLine("无参无返回值的lambad");
};
// 有参无返回值
Action<int> a2 = (int value) =>
{
};
// 甚至参数类型都可以省略 参数类型和委托或事件容器一致
Action<int> a3 = (value) =>
{
};
// 有参有返回值
Func<string, int> b1 = (value) =>
{
return 1;
};
闭包:
基本概念:
它允许 匿名函数(Lambda 表达式或 delegate)捕获并访问其外部作用域的变量,即使外部方法已经执行完毕。闭包在 事件处理、异步编程、LINQ 查询 等场景中非常常见,但如果不正确使用,可能会导致 内存泄漏 或 意外的行为。
内层函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止
注意:改变量提供的值并非变量创建时的值,而是在父函数范围内的最终值
闭包的使用:
class Test
{
public event Action action;
public Test()
{
int value = 10;
// 这里形成了闭包
// 因为当构造函数执行完毕时,其中声明的临时变量value的生命周期被改变了
action = () =>
{
Console.WriteLine(value); // 输出 10
};
for(int i = 0; i < 10; i++)
{
// 每一次进入循环都是新的index
int index = i;
action += () =>
{
Console.WriteLine(i); // 输出的是10;
Console.WriteLine(index); // 输出0到10
};
}
}
public void DoEvent()
{
action();
}
}
static void Main(string[] args)
{
Test t =new Test();
t.DoEvent();
}
更多推荐


所有评论(0)