C#进阶 简单数据结构与泛型
参考链接:
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 : Father | where 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>();
更多推荐



所有评论(0)