参考链接:

https://www.yuque.com/gnocoaixus/kb/yyo2wy?singleDoc#https://www.yuque.com/gnocoaixus/kb/yyo2wy?singleDoc#

Stack

        Stack是一个C#为我们封装好的类,它的本质是Object[]数组,只是封装了特殊的存储规则

        Stack是栈存储容器,是一种先进后出的数据结构——即先存入的数据后获取,后存入的先获取。

使用逻辑

增与弹

//压栈(增)
stack.Push(1);
stack.Push("123");
stack.Push(true);
stack.Push(1.2f);
stack.Push(new Test())


//弹栈(栈中没有删除的概念,只有取的概念)
object v =stack.Pop();
console.writeLine(v)
v=stack.Pop();
console.writeLine(v)

这里弹出后,栈顶数据变成了1.2f,再次弹出打印就会变成该数据

查

        栈无法查看指定位置的元素,只能查看栈顶的内容——但也只是查看一下。

v=stack.Peek();


        查看某一个元素是否在栈中

if(stack.Contains(“123”))
{
    Console.writeLine(“存在123”)
}

改

        栈无法改变其中元素,只能压和弹,如果要改只能清空重来

#region 改
//实在要改 只有清空
stack.Clear();
stack.Push("1");
stack.Push(2);
stack.Push("哈哈");
#endregion

遍历

1、长度
Console.WriteLine(stack.Count);
使用 stack.Count 获取栈中元素的个数,并输出。


2、遍历
foreach(object item in stack)
{
    Console.WriteLine(item);
}
这里不用for是因为没有索引器
foreach 遍历的顺序是从栈顶到栈底(即后进先出的顺序)


3. 将栈转换为数组并遍历
object[] array = stack.ToArray();
for (int i = 0; i < array.Length; i++)
{
    Console.WriteLine(array[i]);
}
使用 stack.ToArray() 将栈转换为 object 类型的数组,然后使用传统的 for 循环遍历数组。
同样,遍历的顺序也是从栈顶到栈底。

装箱拆箱

由于用万物之父object存储数据,自然存在装箱拆箱
当往其中进行值类型存取时就是在装箱
当将值类型取出来转为就是使用时,就存在拆箱

Queue(队列)

本质是object[ ] 数组

与Stack(栈)的概念非常相似,一个是先进后出 一个是先进先出

增删查改

//队列的声明
Queue queue =new Queue();

//队列的添加
que.Enqueue(1);
que.Enqueue("这是字符串");
que.Enqueue(3.14159f);
que.Enqueue(true);
que.Enqueue(new MyClass());


//⭕取 取出先加入的对象
object o2 = que.Dequeue();
//返回 1

//⭕查
//1.查看队列头部元素 但不移除
o2 = que.Peek();
//返回 "这是字符串"

//2.查看元素是否存在队列中
if(que.Contains(3.14159f))
{
    Console.WriteLine("3.14159f 在队列中");
}
//打印结果为 3.14159f 在队列中

//⭕改 队列无法修改元素 只能清除重新Enqueue
que.Clear();

遍历

跟栈类似,两种遍历方式

que.Enqueue(1);
que.Enqueue(2);
que.Enqueue(3);


//⭕遍历
//长度 Count
o2 = que.Count;
//返回 3
foreach (object item in que)
{
    Console.WriteLine(item);
}
//打印结果为 1 2 3


//⭕将队列转为object数组后遍历
object[] arr = que.ToArray();
for (int i = 0; i < arr.Length; i++)
{
    Console.WriteLine(arr[i]);
}
//打印结果为 1 2 3


//循环出列
Console.WriteLine(que.Count);
//打印结果为 3
while ( que.Count>0 )
{
    o2 = que.Dequeue();
    Console.WriteLine(o2);
}
//打印结果为 1 2 3
Console.WriteLine(que.Count);

装箱拆箱

int a = 10;
que.Enqueue(a); //装箱
a = que.Dequeue(); //拆箱

Hashtalbe

主要作用是通过键去找值,可以提高查询数据的效率

键值对 一个key(键)对应一个value(值)
key1------>value1
key2------>value2
key3------>value3

增删查改

注意:这里查有两种方式:根据键和根据值来进行查找

Hashtable hash = new Hashtable();
//⭕增 参数1为键 参数2为值 都是object类所以什么类型数据都能存
//不能存相同的键
hash.Add(1, "数据1");
hash.Add("二", "数据2");
hash.Add(2.34f, "数据3");
hash.Add(true, 123312);

//⭕删 通过键去删除
hash.Remove(1);
//或者全部清空
hash.Clear();

//⭕查 
//1.通过键查看值 找不到则返回空
Console.WriteLine( hash["二"]);
//打印结果为 "数据二"

//2.查看键是否存在  Contains/ContainsKey
if(hash.Contains(2.34f))
{
    Console.WriteLine("存在键为2.34f");
}
//打印结果为 存在键为2.34f

//3.查看值是否存在  ContainsValue
if(hash.ContainsValue(123312))
{
    Console.WriteLine("存在值为123312");
}
//打印结果为 存在值为123312


//⭕改
//只能改 键对应的值的内容 无法修改键
hash[true] = "正确";
Console.WriteLine(hash[true]);
//打印结果为 正确

//得到键值对对数
hash.Count; 

遍历

        就先记住前两个就可以了,后两个有点抽象就先暂且搁置

//⭕遍历
//1.遍历所有键(这里既然把键遍历了,也顺便可以输出对应键的值)
foreach(object item in hash.Keys)
{
    Console.Write("键:"+item);
    Console.WriteLine("值:"+hash[item]);
}
//打印结果为: 键:2.34  值:数据3
//			  键:二    值:数据2
//			  键:True  值:正确



//2.遍历所有值
foreach(object item in hash.Values)
{
    Console.WriteLine("值:"+item);
}
//打印结果为: 数据3 数据2 正确



//3.键值对一起遍历
foreach(DictionaryEntry item in hash)
{
    Console.WriteLine("键:"+item.Key+"值:"+item.Value);
}
//打印结果为: 键:2.34  值:数据3
//			  键:二    值:数据2
//			  键:True  值:正确


//4.迭代器遍历法
IDictionaryEnumerator myEnumerator = hash.GetEnumerator();
bool flag = myEnumerator.MoveNext();
while (flag)
{
    Console.WriteLine("键:" + myEnumerator.Key + "值:" + myEnumerator.Value);
    flag = myEnumerator.MoveNext();
}

泛型

泛型是什么

泛型实现类型参数化,达到代码复用目的
通过类型参数化来实现同一份代码上操作多种类型

泛型相当于类型占位符
定义类或方法时使用替代符代表变量类型
当真正使用类或者方法时再具体指定类型

泛型类

泛型类和泛型接口
class 类名<泛型占位字母>
interface 接口名<泛型占位字母>

泛型函数
函数名<泛型占位字母>(参数列表)

注意:泛型占位字母可以有多个,用逗号分开

位置T 的范围代码示例
类泛型整个类都能用class EventCenter<T> → 字段、方法、属性都可以用 T
方法泛型只有该方法能用void EventTrigger<T>() → 只有方法体内能用 T

只有泛型类才能利用泛型声明:

// ✅ 类泛型:T 在整个类有效——T能不能直接用于声明是要看这个类是不是泛型类
public class EventCenter<T> : BaseManager<EventCenter<T>>
{
    private Dictionary<string, EventInfo<T>> eventDic;  // ✓ 认识 T
    
    public void EventTrigger(string eventName, T info) { }  // ✓ 认识 T,不需要再声明
}

// ❌ 非泛型类:方法自己的 T,字段看不到
public class EventCenter
{
    private Dictionary<string, EventInfo<T>> eventDic;  // ✗ 不认识 T!
    
    public void EventTrigger<T>(string eventName, T info)  // ← T 只活在这个括号里
    {
        // 方法体内认识 T
        EventInfo<T> localVar;  // ✓ 这里可以用 T
    }
}

泛型类

        这样可以理解参数化类型的含义——即声明T是什么类型,然后该类的变量T就是什么类型

        注意:下面两个泛型的使用:都是先声明类并告知泛型,然后在实例化对象

class MyClass<T>
{
    public T value;
}

//****************主函数***********************
MyClass<int> t = new MyClass<int>();
t.value = 10;

MyClass<string> t2 = new MyClass<string>();
t2.value = “123123”;

泛型占位字母有多个

//泛型占位字母有多个
class MyClass2<T1,T2,K,M>
{
    public T1 value1;
    public T2 value2;
    public K value3;
    public M value4;
}

MyClass2 <int, string, float, MyClass<int>> 
t3 = new MyClass2<int, string, float, MyClass<int>>();

泛型接口

定义泛型接口
interface MyInterface<T>
{
    T Value
    {
        get;
        set;
    }
}


实现泛型接口(指定具体类型)
class Myclass3:MyInterface<int>
{
    public int Value
    {
        get;
        set;
    }
}


// 定义一个实现 MyInterface<string> 的类
class MyStringClass : MyInterface<string>
{
    public string Value { get; set; }
}

这里有多种实例化的路线:

 方式一:直接使用具体类

MyClass3 obj = new MyClass3();
obj.Value = 42;
Console.WriteLine(obj.Value); // 输出 42

 方式二:通过接口引用(推荐,体现多态)

MyInterface<int> obj = new MyClass3();
obj.Value = 100;
Console.WriteLine(obj.Value); // 输出 100

这样写更灵活,未来可以替换为其他实现了 MyInterface<int> 的类。

接口引用如何体现多态:

假设除了 MyClass3,你还有一个新类也实现 MyInterface<int>:

原来的实例化代码:
MyInterface<int> obj = new MyClass3();
obj.Value = 100;
Console.WriteLine(obj.Value);

可无缝替换为:
MyInterface<int> obj = new AnotherIntClass(); // ← 只改这一行!
obj.Value = 200;
Console.WriteLine(obj.Value); // 输出 200

泛型函数

普通类中的泛型函数

using System;

// 普通类中泛型方法
internal class MyClass4
{
    public void MyFun<T>(T value)
    {
        Console.WriteLine(value);
    }

    public void TestFun<T, K, M>(T t, K k, M m)
    {
        Console.WriteLine($"T={t}, K={k}, M={m}");
    }
}

internal static class Program
{
    private static void Main(string[] args)
    {
        //****************主函数***********************
        var t4 = new MyClass4();
        t4.MyFun<string>("你好");

        var t5 = new MyClass4();
        t5.TestFun<string, int, bool>("nihao", 3, true);
    }
}

泛型类中的泛型函数

//1、注意这里是普通类,下面是泛型类,占位符T也属于其类名,这两个类不同名
class Test2

class Test2<T>
{
    public T value;

    //3、泛型方法:TestFun<K> 函数名后有尖括号,不能与泛型类的占位字母同名
    public void TestFun<K>(K k)
    {
        Console.WriteLine();
    }

    // 普通方法:TestFun(T t)
    public void TestFun(T t)
    {
        // 2、这个不叫泛型方法,因为T在类声明时已经指定,不能在使用函数时动态变化
    }
}

//这里解释了2为什么不是泛型方法,泛型类声明的时候已经将泛型函数中的传参定死了
//无法实现动态修改值
Test2<int> tt2 = new Test2<int>();
tt2.TestFun(10);

//这里表明了3是如何使用的
Test2<int> tt2 = new Test2<int>();
tt2.TestFun(10);
tt2.TestFun<string>("123");
tt2.TestFun<float>(1.2f);
tt2.TestFun(20);

泛型的作用

1.不同类型对象的相同逻辑处理就可以选择泛型
2.使用泛型可以一定程度避免装箱拆箱
 

class MyList<T>
{
    private T[] array;
        
    public void Add(T t)
    {

    }

    public void Remove(T t)
    {

    }
}

泛型约束

  • where T : struct (T必须为值类型)

  • where T : class (T必须为引用类型)

  • where T : new() (T必须有一个无参数的构造函数)

  • where T : <base class name> (T是继承自一个基类的)

  • where T : <interface name> (T实现了一个接口)

值的泛型约束

class Test1<T> where T:struct
{
    public T value;
    public void Fun<K>(K k)where K:struct
    {

    }
}

//1.<>内只能填值类型了
Test1<int> test1 = new Test1<int>();
test1.Fun<float>(2.6f);

引用类型泛型约束

//2.引用类型约束
class Test2<T> where T:class
{
    public T value;
    public void Fun<K>(K k)where K:class
    {

    }
}

//2.<>内只能填引用类型了
Test2<Random> test2 = new Test2<Random>();
test2.Fun<Random>(new Random());

无参构造公共函数的泛型约束

//3.存在无参公共构造函数——即访问权限为public
class Test3<T> where T:new()
{
    public T value;
    public void Fun<K>(K k)where K:new()
    {

    }
}

//3.<>内只能填 具有无参且公共的构造函数的类型 且非抽象类
Test3<Random> test3 = new Test3<Random>();
test3.Fun<Random>(new Random());

        这里还讲了有参构造函数与无参构造函数,值类型(struct)无参构造公共函数,相关内容。

派生类的泛型约束

class Father {}
class Son:Father {}
class Test4<T> where T:Father
{
    public T value;
    public void Fun<K>(K k)where K:Father
    {

    }
}

//4.<>内只能填指定的类或其子类:
//相当于Test4限制了能声明的类只能是Father本身或者Father的继承类
(能传入Son,因为其是Father子类,)
Test4<Father> test4 = new Test4<Father>();
test4.Fun<Son>(new Son());

接口的泛型约束

//5.某个接口的派生类型
interface IFly {}
class Player:IFly {}
class Test5<T> where T:IFly
{
    public T value;
    public void Fun<K>(K k)where K:IFly
    {

    }
}

Test5<Player> test5 = new Test5<Player>();
test5.Fun<Player>(new Player());

泛型继承的泛型约束

类型参数必须继承或实现另一个类型参数

//6.另一个泛型类型本身或者派生类型  类型一致或者是其派生类
class Test6<T,U> where T : U
{
    public T value;
    public void TestFun<K,V>(K k) where K : V
    {
    }
}

class Test4:IFly
{

}

主函数:
Test6<Test4, IFly> t6 = new Test6<Test4, IFly>();

与派生类的继承泛型约束的区别:

特性where T : Fatherwhere T : U
约束目标固定基类/接口(Father)另一个泛型参数(U)
灵活性只能用于 Father 及其子类可用于任意满足 T : U 的类型对
用途限制为某个类型体系表达泛型参数间的通用继承/实现关系

泛型的组合使用

关键就是where后面,两种限制类型之间添加“,”

//7.泛型的组合使用
class Test7<T> where T:class,new()
{
    public T value;
}

//7.<>内只能填引用类型 且是存在无参公共的构造函数
Test7<Son> test7 = new Test7<Son>();

多个泛型的约束

//8.多个泛型有约束
//where T : class:限制 T 只能是 引用类型(如 string、object、自定义类等)
//where K : struct:限制 K 只能是 非可空的值类型(如 int、bool、自定义 struct)
class Test8<T,K> where T: class where K:struct
{
    public T value;
    public K value2;
}

//8.<>1内只能引用类型<>2只能值类型
Test8<Son, int> test8 = new Test8<Son, int>();

更多推荐