常用泛型数据结构类

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();
}

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐