C#游戏开辟【第18天】 | 深入明确队列(Queue)与栈(Stack):从根本到使命队列实战

[复制链接]
发表于 2025-10-2 00:13:28 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
Langchain系列文章目次

01-玩转LangChain:从模子调用到Prompt模板与输出剖析的完备指南
02-玩转 LangChain Memory 模块:四种影象范例详解及应用场景全覆盖
03-全面把握 LangChain:从核心链条构建到动态使命分配的实战指南
04-玩转 LangChain:从文档加载到高效问答体系构建的全程实战
05-玩转 LangChain:深度评估问答体系的三种高效方法(示例天生、手动评估与LLM辅助评估)
06-从 0 到 1 把握 LangChain Agents:自界说工具 + LLM 打造智能工作流!
07-【深度剖析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
PyTorch系列文章目次

Python系列文章目次

C#系列文章目次

01-C#与游戏开辟的初次晤面:从零开始的Unity之旅
02-C#入门:从变量与数据范例开始你的游戏开辟之旅
03-C#运算符与表达式:从入门到游戏伤害盘算实践
04-从零开始学C#:用if-else和switch打造智能游戏逻辑
05-把握C#循环:for、while、break与continue详解及游戏案例
06-玩转C#函数:参数、返回值与游戏中的攻击逻辑封装
07-Unity游戏开辟入门:用C#控制游戏对象移动
08-C#面向对象编程根本:类的界说、属性与字段详解
09-C#封装与访问修饰符:掩护数据安全的利器
10-怎样用C#继续提升游戏开辟服从?Enemy与Boss案例剖析
11-C#多态性入门:从零到游戏开辟实战
12-C#接口王者之路:从入门到Unity游戏开辟实战 (IAttackable案例详解)
13-C#静态成员揭秘:共享数据与方法的利器
14-Unity 面向对象实战:把握组件化计划与脚本通讯,构建玩家对头交互
15-C#入门 Day15:彻底搞懂数组!从根本到游戏子弹管理实战
16-C# List 从入门到实战:把握动态数组,轻松管理游戏对头列表 (含代码示例)
17-C# 字典 (Dictionary) 完全指南:从入门到游戏属性表实战 (Day 17)
18-C#游戏开辟【第18天】 | 深入明确队列(Queue)与栈(Stack):从根本到使命队列实战




媒介

欢迎来到《C#游戏开辟从入门到醒目:50天全面把握》专栏的第18天!在前几天的学习中,我们已经打仗了数组(Array)、动态列表(List<T>)以及键值对聚集(Dictionary<TKey, TValue>)等常用的数据结构。本日,我们将深入探究两种非常紧张且具有光显特色的数据结构——队列(Queue) 和 栈(Stack)。它们固然不像List那样“万能”,但在特定场景下却能发挥出巨大的作用,尤其是在游戏开辟中,比方管理使命序列、实现打消/重做功能、处理处罚状态厘革等。本篇文章将带你从根本概念出发,彻底明确队列和栈的工作原理、C#中的实现方式、核心操纵,并通过一个游戏使命队列的实例,让你把握它们在现实项目中的应用。
一、队列(Queue):先辈先出的数据列队计谋

1.1 什么是队列?

1.1.1 界说与特性

队列(Queue)是一种依照 先辈先出(First-In, First-Out, FIFO) 原则的线性数据结构。你可以把它想象成现实生存中的列队:第一个来列队的人,第一个继续服务。在队列中,新元素总是被添加到队列的尾部(Rear/Tail),而元素的移除(或访问)则总是从队列的**头部(Front/Head)**举行。
核心特性总结:


  • 先辈先出 (FIFO): 最早进入队列的元素将开始被移出。
  • 线性结构: 数据元素之间存在一对一的逻辑关系。
  • 操纵受限: 只能在队列的两端举行操纵(尾部添加,头部移除)。
1.1.2 生存中的类比



  • 列队买票: 先到售票窗口列队的人先买到票。
  • 打印使命: 先提交的打印哀求先被打印机处理处罚。
1.1.3 FIFO 原理图示

我们可以用一个简单的图来表现队列的工作方式:
  1. graph LR
  2.     subgraph "队列 (Queue)"
  3.         direction LR
  4.         A[元素1] --> B[元素2] --> C[元素3]
  5.     end
  6.     New[新元素] -- 入队 (Enqueue) --> C
  7.     A -- 出队 (Dequeue) --> Output[处理/移除]
  8.     style A fill:#f9f,stroke:#333,stroke-width:2px
  9.     style New fill:#ccf,stroke:#333,stroke-width:2px
复制代码


  • 入队 (Enqueue): 新元素总是从队尾到场。
  • 出队 (Dequeue): 元素总是从队头移除。开始进入的元素1开始被移除。
1.2 C# 中的 Queue<T>

在 C# 中,.NET 库提供了泛型类 System.Collections.Generic.Queue<T> 来实现队列。泛型 <T> 意味着你可以创建一个存储任何特定范例元素的队列,比方 Queue<int>、Queue<string> 或 Queue<layerTask>。
1.2.1 怎样创建队列

创建队列非常简单,使用 new 关键字即可:
  1. using System.Collections.Generic; // 引入命名空间
  2. // 创建一个存储整数的队列
  3. Queue<int> numberQueue = new Queue<int>();
  4. // 创建一个存储字符串的队列
  5. Queue<string> messageQueue = new Queue<string>();
  6. // 创建一个存储自定义任务对象的队列(假设已定义 PlayerTask 类)
  7. // Queue<PlayerTask> taskQueue = new Queue<PlayerTask>();
复制代码
1.2.2 核心操纵

Queue<T> 提供了几个关键方法来操纵队列:
(1) 入队:Enqueue(T item)

将一个元素添加到队列的尾部。
  1. Queue<string> taskQueue = new Queue<string>();
  2. // 向队列中添加任务
  3. taskQueue.Enqueue("加载地图"); // "加载地图" 在队头
  4. taskQueue.Enqueue("生成玩家"); // "生成玩家" 在队尾
  5. taskQueue.Enqueue("播放背景音乐"); // "播放背景音乐" 在新的队尾
  6. // 此刻队列内容 (从头到尾): ["加载地图", "生成玩家", "播放背景音乐"]
  7. Console.WriteLine($"当前队列任务数: {taskQueue.Count}"); // 输出:当前队列任务数: 3
复制代码
(2) 出队:Dequeue()

移除并返回队列头部的元素。如果队列为空,调用 Dequeue() 会抛出 InvalidOperationException 非常。
  1. // 假设 taskQueue 内容为: ["加载地图", "生成玩家", "播放背景音乐"]
  2. if (taskQueue.Count > 0) // 检查队列是否为空是好习惯
  3. {
  4.     string nextTask = taskQueue.Dequeue(); // 移除并获取 "加载地图"
  5.     Console.WriteLine($"处理任务: {nextTask}"); // 输出:处理任务: 加载地图
  6.     Console.WriteLine($"剩余任务数: {taskQueue.Count}"); // 输出:剩余任务数: 2
  7.     // 此刻队列内容: ["生成玩家", "播放背景音乐"]
  8. }
  9. else
  10. {
  11.     Console.WriteLine("任务队列为空!");
  12. }
复制代码
(3) 查察队头元素:Peek()

返回队列头部的元素,但不移除它。如果队列为空,调用 Peek() 也会抛出 InvalidOperationException 非常。
  1. // 假设 taskQueue 内容为: ["生成玩家", "播放背景音乐"]
  2. if (taskQueue.Count > 0)
  3. {
  4.     string upcomingTask = taskQueue.Peek(); // 查看队头 "生成玩家"
  5.     Console.WriteLine($"下一个任务是: {upcomingTask}"); // 输出:下一个任务是: 生成玩家
  6.     Console.WriteLine($"队列任务数仍为: {taskQueue.Count}"); // 输出:队列任务数仍为: 2 (元素未被移除)
  7. }
复制代码
(4) 获取元素数量:Count

这是一个属性,返回队列中当前包罗的元素数量。
  1. // 假设 taskQueue 内容为: ["生成玩家", "播放背景音乐"]
  2. int taskCount = taskQueue.Count;
  3. Console.WriteLine($"队列中有 {taskCount} 个任务。"); // 输出:队列中有 2 个任务。
复制代码
1.2.3 其他常用方法/属性



  • Clear(): 移除队列中的全部元素。
  • Contains(T item): 判定队列中是否包罗指定的元素。
  • ToArray(): 将队列中的元素复制到一个新数组中(保持队列序次)。
1.3 队列的应用场景

队列的 FIFO 特性使其在以了局景中非常有用:


  • 使命调治: 按序次处理处罚一系列使命,如游戏中的指令队列、打印使命队列。
  • 广度优先搜索 (BFS): 在图或树的遍历中,用于存储待访问的节点。
  • 消息队列 (MQ): 在分布式体系中解耦服务,缓冲消息。
  • 缓冲区: 比方网络数据包的汲取与处理处罚,先辈来的数据包先被处理处罚。
  • 游戏开辟:

    • 玩家指令处理处罚: 按玩家输入序次执办法作。
    • AI 决议序列: AI 按照操持序次实行一系罗列动。
    • 事故处理处罚: 按事故发生序次处理处罚游戏事故。
    • 资源加载: 按需序次加载资源。

二、栈(Stack):后进先出的数据叠放计谋

2.1 什么是栈?

2.1.1 界说与特性

栈(Stack)是另一种紧张的线性数据结构,它依照 后进先出(Last-In, First-Out, LIFO) 原则。你可以把它想象成一摞盘子:末了放上去的盘子,开始被取走。在栈中,元素的添加(称为 压栈 或 入栈 Push)和移除(称为 弹栈 或 出栈 Pop)都只能在栈的同一端举行,这一端被称为 栈顶(Top)。
核心特性总结:


  • 后进先出 (LIFO): 末了进入栈的元素将开始被移出。
  • 线性结构: 数据元素之间存在一对一的逻辑关系。
  • 操纵受限: 只能在栈顶举行添加和移除操纵。
2.1.2 生存中的类比



  • 一摞盘子: 你总是从最上面取盘子,也把新洗好的盘子放在最上面。
  • 欣赏器汗青纪录: “退却”按钮总是带你回到上一个访问的页面(末了访问的页面先被“移除”)。
  • 函数调用栈: 步调在调用函数时,会将当前函数的状态(返回地点、局部变量等)压入调用栈,函数返回时再从栈顶弹出。
2.1.3 LIFO 原理图示

  1. graph TD
  2.     subgraph "栈 (Stack)"
  3.         direction TB
  4.         C[元素3 (栈顶)] --> B[元素2] --> A[元素1 (栈底)]
  5.     end
  6.     New[新元素] -- 入栈 (Push) --> C
  7.     C -- 出栈 (Pop) --> Output[处理/移除]
  8.     style C fill:#f9f,stroke:#333,stroke-width:2px
  9.     style New fill:#ccf,stroke:#333,stroke-width:2px
复制代码


  • 入栈 (Push): 新元素总是被添加到栈顶。
  • 出栈 (Pop): 元素总是从栈顶移除。末了入栈的 元素3 开始被移除。
2.2 C# 中的 Stack<T>

与队列雷同,C# 提供了泛型类 System.Collections.Generic.Stack<T> 来实现栈。
2.2.1 怎样创建栈

创建栈同样使用 new 关键字:
  1. using System.Collections.Generic; // 引入命名空间
  2. // 创建一个存储整数的栈
  3. Stack<int> numberStack = new Stack<int>();
  4. // 创建一个存储字符串的栈
  5. Stack<string> historyStack = new Stack<string>();
  6. // 创建一个存储游戏状态对象的栈(假设已定义 GameState 类)
  7. // Stack<GameState> stateStack = new Stack<GameState>();
复制代码
2.2.2 核心操纵

Stack<T> 提供的重要操纵方法如下:
(1) 入栈:Push(T item)

将一个元素添加到栈的顶部。
  1. Stack<string> browserHistory = new Stack<string>();
  2. // 模拟浏览页面
  3. browserHistory.Push("首页"); // "首页" 在栈底
  4. browserHistory.Push("产品页");
  5. browserHistory.Push("详情页"); // "详情页" 在栈顶
  6. // 此刻栈内容 (从顶到底): ["详情页", "产品页", "首页"]
  7. Console.WriteLine($"当前历史记录数: {browserHistory.Count}"); // 输出:当前历史记录数: 3
复制代码
(2) 出栈:Pop()

移除并返回栈顶部的元素。如果栈为空,调用 Pop() 会抛出 InvalidOperationException 非常。
  1. // 假设 browserHistory 内容为: ["详情页", "产品页", "首页"]
  2. if (browserHistory.Count > 0) // 检查栈是否为空
  3. {
  4.     string lastVisited = browserHistory.Pop(); // 移除并获取 "详情页"
  5.     Console.WriteLine($"按后退键,回到: {lastVisited}"); // 输出:按后退键,回到: 详情页 (实际应回到"产品页",Pop返回的是刚离开的页面)
  6.     Console.WriteLine($"剩余历史记录数: {browserHistory.Count}"); // 输出:剩余历史记录数: 2
  7.     // 此刻栈内容: ["产品页", "首页"]
  8. }
  9. else
  10. {
  11.     Console.WriteLine("没有更多历史记录了!");
  12. }
复制代码
注意: 在欣赏器退却的场景中,Pop() 返回的是你 刚刚离开 的页面。栈顶现在是你 将要回到 的页面。
(3) 查察栈顶元素:Peek()

返回栈顶部的元素,但不移除它。如果栈为空,调用 Peek() 也会抛出 InvalidOperationException 非常。
  1. // 假设 browserHistory 内容为: ["产品页", "首页"]
  2. if (browserHistory.Count > 0)
  3. {
  4.     string currentPage = browserHistory.Peek(); // 查看栈顶 "产品页"
  5.     Console.WriteLine($"当前页面是: {currentPage}"); // 输出:当前页面是: 产品页
  6.     Console.WriteLine($"栈中记录数仍为: {browserHistory.Count}"); // 输出:栈中记录数仍为: 2 (元素未被移除)
  7. }
复制代码
(4) 获取元素数量:Count

同样是一个属性,返回栈中当前包罗的元素数量。
  1. // 假设 browserHistory 内容为: ["产品页", "首页"]
  2. int historyCount = browserHistory.Count;
  3. Console.WriteLine($"栈中有 {historyCount} 条记录。"); // 输出:栈中有 2 条记录。
复制代码
2.2.3 其他常用方法/属性



  • Clear(): 移除栈中的全部元素。
  • Contains(T item): 判定栈中是否包罗指定的元素。
  • ToArray(): 将栈中的元素复制到一个新数组中(注意:数组中的序次通常是 LIFO,即栈顶元素在数组前面)。
2.3 栈的应用场景

栈的 LIFO 特性使其在以了局景中非常有用:


  • 函数调用: 管理函数调用的上下文(调用栈)。
  • 表达式求值: 如中缀表达式转后缀表达式(逆波兰表现法)并举行盘算。
  • 括号匹配: 查抄代码或文本中的括号是否正确配对。
  • 深度优先搜索 (DFS): 在图或树的遍历中,用于存储待访问的节点。
  • 打消/重做 (Undo/Redo):

    • 打消: 将操纵存入一个“打消栈”,打消时从栈顶取出操纵并实行其逆操纵,同时大概将该操纵存入“重做栈”。
    • 重做: 从“重做栈”取出操纵实行,并大概放回“打消栈”。

  • 游戏开辟:

    • 打消玩家操纵: 如棋类游戏、编辑器中的操纵。
    • 状态管理: 管理游戏状态的切换(比方停息菜单覆盖游戏界面,返回时规复)。
    • 路径查找回溯: 在某些寻路算法中纪录路径。

三、游戏开辟中的应用:使命队列

现在,让我们团结游戏开辟的场景,看看怎样使用队列来实现一个简单的使命队列体系。这在必要按序次实行一系列复杂操纵(如过场动画、新手引导步调、AI 举动序列)时非常有用。
3.1 为什么必要使命队列?

在游戏中,经常有必要按特定序次实行的动作或事故链。比方:


  • 新手引导: 体现提示 -> 等候玩家点击 -> 移动镜头 -> 体现下一个提示…
  • 剧情事故: 脚色A语言 -> 脚色B回应 -> 播放动画 -> 触发音效…
  • AI 举动: 移动到位置X -> 扫描对头 -> 如果发现对头则攻击 -> 否则继续巡逻…
如果直接用一长串的 if-else 大概嵌套调用来写,代码会变得非常复杂且难以维护。使命队列提供了一种更清楚、更模块化的方式来管理这些序列。
3.2 使用队列实现使命队列

3.2.1 界说使命接口/基类

起首,我们必要界说一个通用的“使命”表现。使用接口或抽象类是个好方法。
  1. // 定义一个任务接口
  2. public interface IGameTask
  3. {
  4.     // 执行任务的方法
  5.     // 返回 true 表示任务完成,可以执行下一个任务
  6.     // 返回 false 表示任务正在进行中,下一帧需要继续执行
  7.     bool Execute();
  8.     // (可选) 任务开始时调用的方法
  9.     void Start();
  10.     // (可选) 任务结束时调用的方法
  11.     void End();
  12. }
  13. // 示例:一个简单的移动任务
  14. public class MoveToTask : IGameTask
  15. {
  16.     private UnityEngine.Transform _target; // 移动的目标对象
  17.     private UnityEngine.Vector3 _destination;
  18.     private float _speed;
  19.     private bool _isStarted = false;
  20.     public MoveToTask(UnityEngine.Transform target, UnityEngine.Vector3 destination, float speed)
  21.     {
  22.         _target = target;
  23.         _destination = destination;
  24.         _speed = speed;
  25.     }
  26.     public void Start()
  27.     {
  28.         Console.WriteLine($"开始移动任务: 将 {_target.name} 移动到 {_destination}");
  29.         _isStarted = true;
  30.     }
  31.     public bool Execute()
  32.     {
  33.         if (!_isStarted) Start(); // 确保Start被调用
  34.         // 简单的移动逻辑 (实际项目中会更复杂)
  35.         float step = _speed * UnityEngine.Time.deltaTime;
  36.         _target.position = UnityEngine.Vector3.MoveTowards(_target.position, _destination, step);
  37.         // 判断是否到达目的地 (允许一点误差)
  38.         if (UnityEngine.Vector3.Distance(_target.position, _destination) < 0.1f)
  39.         {
  40.             End(); // 调用结束方法
  41.             return true; // 任务完成
  42.         }
  43.         return false; // 任务未完成
  44.     }
  45.     public void End()
  46.     {
  47.         Console.WriteLine($"移动任务完成: {_target.name} 已到达 {_destination}");
  48.     }
  49. }
  50. // 示例:一个简单的等待任务
  51. public class WaitTask : IGameTask
  52. {
  53.     private float _duration;
  54.     private float _startTime;
  55.     private bool _isStarted = false;
  56.     public WaitTask(float duration)
  57.     {
  58.         _duration = duration;
  59.     }
  60.      public void Start()
  61.     {
  62.         Console.WriteLine($"开始等待任务: 等待 {_duration} 秒");
  63.         _startTime = UnityEngine.Time.time;
  64.         _isStarted = true;
  65.     }
  66.     public bool Execute()
  67.     {
  68.          if (!_isStarted) Start();
  69.         if (UnityEngine.Time.time >= _startTime + _duration)
  70.         {
  71.             End();
  72.             return true; // 等待时间到,任务完成
  73.         }
  74.         return false; // 还在等待
  75.     }
  76.      public void End()
  77.     {
  78.         Console.WriteLine($"等待任务完成");
  79.     }
  80. }
复制代码
注意: 上述代码使用了 UnityEngine 定名空间下的类,如 Transform, Vector3, Time。你必要在一个 Unity 项目环境中才气完备运行。核心头脑是界说具有 Execute 方法的使命。
3.2.2 使命队列管理器

接下来,创建一个管理使命队列的类。
  1. using System.Collections.Generic;
  2. using UnityEngine; // 假设在 Unity 环境中
  3. public class TaskQueueManager : MonoBehaviour
  4. {
  5.     private Queue<IGameTask> _taskQueue = new Queue<IGameTask>();
  6.     private IGameTask _currentTask = null; // 当前正在执行的任务
  7.     // Update 方法会在每一帧被 Unity 调用
  8.     void Update()
  9.     {
  10.         ProcessNextTask();
  11.     }
  12.     // 添加新任务到队列尾部
  13.     public void AddTask(IGameTask newTask)
  14.     {
  15.         _taskQueue.Enqueue(newTask);
  16.         Debug.Log($"新任务已添加,当前队列长度: {_taskQueue.Count}");
  17.     }
  18.     // 处理队列中的任务
  19.     private void ProcessNextTask()
  20.     {
  21.         // 如果当前没有任务在执行,并且队列中有任务
  22.         if (_currentTask == null && _taskQueue.Count > 0)
  23.         {
  24.             // 从队列头部取出一个任务
  25.             _currentTask = _taskQueue.Dequeue();
  26.             // _currentTask.Start(); // Start 调用移到 Execute 内部确保执行
  27.             Debug.Log($"开始处理下一个任务,剩余队列长度: {_taskQueue.Count}");
  28.         }
  29.         // 如果当前有任务在执行
  30.         if (_currentTask != null)
  31.         {
  32.             // 执行任务,如果返回 true,表示任务完成
  33.             if (_currentTask.Execute())
  34.             {
  35.                 // _currentTask.End(); // End 调用移到 Execute 内部确保执行
  36.                 _currentTask = null; // 标记当前任务为空,以便处理下一个
  37.                 Debug.Log("当前任务完成!");
  38.             }
  39.             // 如果返回 false,表示任务还在进行中,下一帧继续 Execute
  40.         }
  41.     }
  42.     // (可选) 提供清空队列的方法
  43.     public void ClearTasks()
  44.     {
  45.         _currentTask = null;
  46.         _taskQueue.Clear();
  47.         Debug.Log("任务队列已清空");
  48.     }
  49. }
复制代码
3.2.3 示例:玩家指令队列

假设我们有一个 TaskQueueManager 组件挂载在场景中的某个 GameObject 上。我们可以如许添加和实行使命:
  1. // 在另一个脚本中,获取 TaskQueueManager 实例
  2. TaskQueueManager taskManager = FindObjectOfType<TaskQueueManager>(); // 或者通过其他方式获取引用
  3. // 获取需要移动的玩家对象
  4. Transform playerTransform = GameObject.Find("Player").transform; // 假设玩家对象名为 "Player"
  5. // 创建一系列任务
  6. IGameTask task1 = new MoveToTask(playerTransform, new Vector3(5, 0, 0), 2.0f);
  7. IGameTask task2 = new WaitTask(1.5f); // 等待1.5秒
  8. IGameTask task3 = new MoveToTask(playerTransform, Vector3.zero, 3.0f); // 移回原点
  9. // 将任务按顺序添加到队列
  10. taskManager.AddTask(task1);
  11. taskManager.AddTask(task2);
  12. taskManager.AddTask(task3);
  13. // 之后,TaskQueueManager 的 Update 方法会自动按顺序执行这些任务
复制代码
如许,玩家对象就会先移动到 (5, 0, 0),然后原地等候1.5秒,末了再移动回原点 (0, 0, 0)。代码逻辑清楚,易于扩展和修改。
3.3 栈在游戏中的应用场景(增补)

固然本节重点是使命队列,但栈在游戏中也有其独特用途:


  • 游戏状态管理: 进入停息菜单时,将当前游戏状态(如PlayingState)压栈,体现停息菜单(PausedState)。退出停息菜单时,弹栈规复到之前的状态。
  • 编辑器打消/重做: 对场景举行的每次修改(移动物体、改变颜色等)可以封装成一个操纵对象,放入打消栈。实行打消时,Pop 出操纵,实行其逆操纵,并放入重做栈。
  • 复杂的 AI 状态切换: 在某些 AI 计划模式(如分层状态机)中,栈可以用来管理状态的进入和退出序次。
四、常见题目与注意事项

4.1 队列/栈为空时操纵

对空的 Queue<T> 调用 Dequeue() 或 Peek(),大概对空的 Stack<T> 调用 Pop() 或 Peek(),都会抛出 System.InvalidOperationException。因此,在实行这些操纵前,务必查抄 Count 属性是否大于 0。
  1. // 安全地出队
  2. if (myQueue.Count > 0)
  3. {
  4.     var item = myQueue.Dequeue();
  5.     // ... process item
  6. }
  7. // 安全地出栈
  8. if (myStack.Count > 0)
  9. {
  10.     var item = myStack.Pop();
  11.     // ... process item
  12. }
复制代码
4.2 线程安全

尺度的 Queue<T> 和 Stack<T> 不是线程安全的。如果在多线程环境中使用它们(比方,一个线程添加使命,另一个线程处理处罚使命),大概会导致数据竞争和不可预料的效果。对于多线程场景,应使用 .NET 提供的线程安全聚集:


  • System.Collections.Concurrent.ConcurrentQueue<T>
  • System.Collections.Concurrent.ConcurrentStack<T>
这些并发聚集提供了原子性的操纵,可以在多线程环境下安全使用。
4.3 性能考量

对于 Queue<T> 和 Stack<T>:


  • Enqueue, Dequeue, Peek (对于 Queue)
  • Push, Pop, Peek (对于 Stack)
  • Count
这些根本操纵通常具有 O(1) 的匀称时间复杂度,意味着它们的实行时间不随集会集元素的数量增长而显着增长,服从非常高。
Contains 操纵的时间复杂度是 O(n),由于它大概必要遍历整个聚集来查找元素。如果必要频仍查抄元素是否存在,大概必要思量其他数据结构(如 HashSet<T> 或 Dictionary<TKey, TValue>)。
五、总结

本日我们深入学习了 C# 中的两种特别数据结构:队列(Queue)和栈(Stack)。它们的核心特性和应用场景可以总结如下:

  • 队列 (Queue):

    • 原理: 先辈先出 (FIFO)。
    • C# 实现: System.Collections.Generic.Queue<T>。
    • 核心操纵: Enqueue (入队), Dequeue (出队), Peek (查察队头), Count。
    • 应用: 使命调治、广度优先搜索、消息缓冲、游戏中的指令/事故/AI举动序列。

  • 栈 (Stack):

    • 原理: 后进先出 (LIFO)。
    • C# 实现: System.Collections.Generic.Stack<T>。
    • 核心操纵: Push (入栈), Pop (出栈), Peek (查察栈顶), Count。
    • 应用: 函数调用栈、表达式求值、括号匹配、深度优先搜索、打消/重做功能、游戏状态管理。

  • 游戏开辟实践: 我们通过一个使命队列管理器的实例,展示了怎样使用队列(Queue<IGameTask>)来管理和实行游戏中的有序使命,使代码结构更清楚、更易于维护。
  • 注意事项: 操纵空聚集大概导致非常(需查抄 Count),尺度聚集非线程安全(多线程用 ConcurrentQueue/ConcurrentStack),核心操纵性能高效(通常为 O(1))。
把握队列和栈不但能资助你写出更优雅、高效的代码,更能让你在办理特定题目时拥有更符合的工具。渴望通过本日的学习,你对这两种数据结构有了更深刻的明确!来日诰日,我们将学习 C# 中的罗列(Enum),看看怎样用它来界说游戏中的有限状态。
如果你在学习过程中碰到任何题目,大概有更风趣的应用场景分享,欢迎在品评区留言讨论!

回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表