C#游戏开发【第18天】 | 深入理解队列(Queue)与栈(Stack):从基础到任务队列实战
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):从基础到任务队列实战
文章目录
- Langchain系列文章目录
- PyTorch系列文章目录
- Python系列文章目录
- C#系列文章目录
- 前言
- 一、队列(Queue):先进先出的数据排队策略
- 1.1 什么是队列?
- 1.1.1 定义与特性
- 1.1.2 生活中的类比
- 1.1.3 FIFO 原理图示
- 1.2 C# 中的 `Queue<T>`
- 1.2.1 如何创建队列
- 1.2.2 核心操作
- (1) 入队:`Enqueue(T item)`
- (2) 出队:`Dequeue()`
- (3) 查看队头元素:`Peek()`
- (4) 获取元素数量:`Count`
- 1.2.3 其他常用方法/属性
- 1.3 队列的应用场景
- 二、栈(Stack):后进先出的数据叠放策略
- 2.1 什么是栈?
- 2.1.1 定义与特性
- 2.1.2 生活中的类比
- 2.1.3 LIFO 原理图示
- 2.2 C# 中的 `Stack<T>`
- 2.2.1 如何创建栈
- 2.2.2 核心操作
- (1) 入栈:`Push(T item)`
- (2) 出栈:`Pop()`
- (3) 查看栈顶元素:`Peek()`
- (4) 获取元素数量:`Count`
- 2.2.3 其他常用方法/属性
- 2.3 栈的应用场景
- 三、游戏开发中的应用:任务队列
- 3.1 为什么需要任务队列?
- 3.2 使用队列实现任务队列
- 3.2.1 定义任务接口/基类
- 3.2.2 任务队列管理器
- 3.2.3 示例:玩家指令队列
- 3.3 栈在游戏中的应用场景(补充)
- 四、常见问题与注意事项
- 4.1 队列/栈为空时操作
- 4.2 线程安全
- 4.3 性能考量
- 五、总结
前言
欢迎来到《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 原理图示
我们可以用一个简单的图来表示队列的工作方式:
graph LRsubgraph "队列 (Queue)"direction LRA[元素1] --> B[元素2] --> C[元素3]endNew[新元素] -- 入队 (Enqueue) --> CA -- 出队 (Dequeue) --> Output[处理/移除]style A fill:#f9f,stroke:#333,stroke-width:2pxstyle 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<PlayerTask>
。
1.2.1 如何创建队列
创建队列非常简单,使用 new
关键字即可:
using System.Collections.Generic; // 引入命名空间// 创建一个存储整数的队列
Queue<int> numberQueue = new Queue<int>();// 创建一个存储字符串的队列
Queue<string> messageQueue = new Queue<string>();// 创建一个存储自定义任务对象的队列(假设已定义 PlayerTask 类)
// Queue<PlayerTask> taskQueue = new Queue<PlayerTask>();
1.2.2 核心操作
Queue<T>
提供了几个关键方法来操作队列:
(1) 入队:Enqueue(T item)
将一个元素添加到队列的尾部。
Queue<string> taskQueue = new Queue<string>();// 向队列中添加任务
taskQueue.Enqueue("加载地图"); // "加载地图" 在队头
taskQueue.Enqueue("生成玩家"); // "生成玩家" 在队尾
taskQueue.Enqueue("播放背景音乐"); // "播放背景音乐" 在新的队尾// 此刻队列内容 (从头到尾): ["加载地图", "生成玩家", "播放背景音乐"]
Console.WriteLine($"当前队列任务数: {taskQueue.Count}"); // 输出:当前队列任务数: 3
(2) 出队:Dequeue()
移除并返回队列头部的元素。如果队列为空,调用 Dequeue()
会抛出 InvalidOperationException
异常。
// 假设 taskQueue 内容为: ["加载地图", "生成玩家", "播放背景音乐"]if (taskQueue.Count > 0) // 检查队列是否为空是好习惯
{string nextTask = taskQueue.Dequeue(); // 移除并获取 "加载地图"Console.WriteLine($"处理任务: {nextTask}"); // 输出:处理任务: 加载地图Console.WriteLine($"剩余任务数: {taskQueue.Count}"); // 输出:剩余任务数: 2// 此刻队列内容: ["生成玩家", "播放背景音乐"]
}
else
{Console.WriteLine("任务队列为空!");
}
(3) 查看队头元素:Peek()
返回队列头部的元素,但不移除它。如果队列为空,调用 Peek()
也会抛出 InvalidOperationException
异常。
// 假设 taskQueue 内容为: ["生成玩家", "播放背景音乐"]if (taskQueue.Count > 0)
{string upcomingTask = taskQueue.Peek(); // 查看队头 "生成玩家"Console.WriteLine($"下一个任务是: {upcomingTask}"); // 输出:下一个任务是: 生成玩家Console.WriteLine($"队列任务数仍为: {taskQueue.Count}"); // 输出:队列任务数仍为: 2 (元素未被移除)
}
(4) 获取元素数量:Count
这是一个属性,返回队列中当前包含的元素数量。
// 假设 taskQueue 内容为: ["生成玩家", "播放背景音乐"]
int taskCount = taskQueue.Count;
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 原理图示
graph TDsubgraph "栈 (Stack)"direction TBC[元素3 (栈顶)] --> B[元素2] --> A[元素1 (栈底)]endNew[新元素] -- 入栈 (Push) --> CC -- 出栈 (Pop) --> Output[处理/移除]style C fill:#f9f,stroke:#333,stroke-width:2pxstyle 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
关键字:
using System.Collections.Generic; // 引入命名空间// 创建一个存储整数的栈
Stack<int> numberStack = new Stack<int>();// 创建一个存储字符串的栈
Stack<string> historyStack = new Stack<string>();// 创建一个存储游戏状态对象的栈(假设已定义 GameState 类)
// Stack<GameState> stateStack = new Stack<GameState>();
2.2.2 核心操作
Stack<T>
提供的主要操作方法如下:
(1) 入栈:Push(T item)
将一个元素添加到栈的顶部。
Stack<string> browserHistory = new Stack<string>();// 模拟浏览页面
browserHistory.Push("首页"); // "首页" 在栈底
browserHistory.Push("产品页");
browserHistory.Push("详情页"); // "详情页" 在栈顶// 此刻栈内容 (从顶到底): ["详情页", "产品页", "首页"]
Console.WriteLine($"当前历史记录数: {browserHistory.Count}"); // 输出:当前历史记录数: 3
(2) 出栈:Pop()
移除并返回栈顶部的元素。如果栈为空,调用 Pop()
会抛出 InvalidOperationException
异常。
// 假设 browserHistory 内容为: ["详情页", "产品页", "首页"]if (browserHistory.Count > 0) // 检查栈是否为空
{string lastVisited = browserHistory.Pop(); // 移除并获取 "详情页"Console.WriteLine($"按后退键,回到: {lastVisited}"); // 输出:按后退键,回到: 详情页 (实际应回到"产品页",Pop返回的是刚离开的页面)Console.WriteLine($"剩余历史记录数: {browserHistory.Count}"); // 输出:剩余历史记录数: 2// 此刻栈内容: ["产品页", "首页"]
}
else
{Console.WriteLine("没有更多历史记录了!");
}
注意: 在浏览器后退的场景中,Pop()
返回的是你 刚刚离开 的页面。栈顶现在是你 将要回到 的页面。
(3) 查看栈顶元素:Peek()
返回栈顶部的元素,但不移除它。如果栈为空,调用 Peek()
也会抛出 InvalidOperationException
异常。
// 假设 browserHistory 内容为: ["产品页", "首页"]if (browserHistory.Count > 0)
{string currentPage = browserHistory.Peek(); // 查看栈顶 "产品页"Console.WriteLine($"当前页面是: {currentPage}"); // 输出:当前页面是: 产品页Console.WriteLine($"栈中记录数仍为: {browserHistory.Count}"); // 输出:栈中记录数仍为: 2 (元素未被移除)
}
(4) 获取元素数量:Count
同样是一个属性,返回栈中当前包含的元素数量。
// 假设 browserHistory 内容为: ["产品页", "首页"]
int historyCount = browserHistory.Count;
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 定义任务接口/基类
首先,我们需要定义一个通用的“任务”表示。使用接口或抽象类是个好方法。
// 定义一个任务接口
public interface IGameTask
{// 执行任务的方法// 返回 true 表示任务完成,可以执行下一个任务// 返回 false 表示任务正在进行中,下一帧需要继续执行bool Execute();// (可选) 任务开始时调用的方法void Start();// (可选) 任务结束时调用的方法void End();
}// 示例:一个简单的移动任务
public class MoveToTask : IGameTask
{private UnityEngine.Transform _target; // 移动的目标对象private UnityEngine.Vector3 _destination;private float _speed;private bool _isStarted = false;public MoveToTask(UnityEngine.Transform target, UnityEngine.Vector3 destination, float speed){_target = target;_destination = destination;_speed = speed;}public void Start(){Console.WriteLine($"开始移动任务: 将 {_target.name} 移动到 {_destination}");_isStarted = true;}public bool Execute(){if (!_isStarted) Start(); // 确保Start被调用// 简单的移动逻辑 (实际项目中会更复杂)float step = _speed * UnityEngine.Time.deltaTime;_target.position = UnityEngine.Vector3.MoveTowards(_target.position, _destination, step);// 判断是否到达目的地 (允许一点误差)if (UnityEngine.Vector3.Distance(_target.position, _destination) < 0.1f){End(); // 调用结束方法return true; // 任务完成}return false; // 任务未完成}public void End(){Console.WriteLine($"移动任务完成: {_target.name} 已到达 {_destination}");}
}// 示例:一个简单的等待任务
public class WaitTask : IGameTask
{private float _duration;private float _startTime;private bool _isStarted = false;public WaitTask(float duration){_duration = duration;}public void Start(){Console.WriteLine($"开始等待任务: 等待 {_duration} 秒");_startTime = UnityEngine.Time.time;_isStarted = true;}public bool Execute(){if (!_isStarted) Start();if (UnityEngine.Time.time >= _startTime + _duration){End();return true; // 等待时间到,任务完成}return false; // 还在等待}public void End(){Console.WriteLine($"等待任务完成");}
}
注意: 上述代码使用了 UnityEngine
命名空间下的类,如 Transform
, Vector3
, Time
。你需要在一个 Unity 项目环境中才能完整运行。核心思想是定义具有 Execute
方法的任务。
3.2.2 任务队列管理器
接下来,创建一个管理任务队列的类。
using System.Collections.Generic;
using UnityEngine; // 假设在 Unity 环境中public class TaskQueueManager : MonoBehaviour
{private Queue<IGameTask> _taskQueue = new Queue<IGameTask>();private IGameTask _currentTask = null; // 当前正在执行的任务// Update 方法会在每一帧被 Unity 调用void Update(){ProcessNextTask();}// 添加新任务到队列尾部public void AddTask(IGameTask newTask){_taskQueue.Enqueue(newTask);Debug.Log($"新任务已添加,当前队列长度: {_taskQueue.Count}");}// 处理队列中的任务private void ProcessNextTask(){// 如果当前没有任务在执行,并且队列中有任务if (_currentTask == null && _taskQueue.Count > 0){// 从队列头部取出一个任务_currentTask = _taskQueue.Dequeue();// _currentTask.Start(); // Start 调用移到 Execute 内部确保执行Debug.Log($"开始处理下一个任务,剩余队列长度: {_taskQueue.Count}");}// 如果当前有任务在执行if (_currentTask != null){// 执行任务,如果返回 true,表示任务完成if (_currentTask.Execute()){// _currentTask.End(); // End 调用移到 Execute 内部确保执行_currentTask = null; // 标记当前任务为空,以便处理下一个Debug.Log("当前任务完成!");}// 如果返回 false,表示任务还在进行中,下一帧继续 Execute}}// (可选) 提供清空队列的方法public void ClearTasks(){_currentTask = null;_taskQueue.Clear();Debug.Log("任务队列已清空");}
}
3.2.3 示例:玩家指令队列
假设我们有一个 TaskQueueManager
组件挂载在场景中的某个 GameObject 上。我们可以这样添加和执行任务:
// 在另一个脚本中,获取 TaskQueueManager 实例
TaskQueueManager taskManager = FindObjectOfType<TaskQueueManager>(); // 或者通过其他方式获取引用// 获取需要移动的玩家对象
Transform playerTransform = GameObject.Find("Player").transform; // 假设玩家对象名为 "Player"// 创建一系列任务
IGameTask task1 = new MoveToTask(playerTransform, new Vector3(5, 0, 0), 2.0f);
IGameTask task2 = new WaitTask(1.5f); // 等待1.5秒
IGameTask task3 = new MoveToTask(playerTransform, Vector3.zero, 3.0f); // 移回原点// 将任务按顺序添加到队列
taskManager.AddTask(task1);
taskManager.AddTask(task2);
taskManager.AddTask(task3);// 之后,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。
// 安全地出队
if (myQueue.Count > 0)
{var item = myQueue.Dequeue();// ... process item
}// 安全地出栈
if (myStack.Count > 0)
{var item = myStack.Pop();// ... process item
}
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),看看如何用它来定义游戏中的有限状态。
如果你在学习过程中遇到任何问题,或者有更有趣的应用场景分享,欢迎在评论区留言讨论!
相关文章:
C#游戏开发【第18天】 | 深入理解队列(Queue)与栈(Stack):从基础到任务队列实战
Langchain系列文章目录 01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...
2.5路径问题专题:LeetCode 64. 最小路径和
动态规划解决最小路径和问题 1. 题目链接 LeetCode 64. 最小路径和 2. 题目描述 给定一个包含非负整数的 m x n 网格 grid,从网格的左上角出发,每次只能向右或向下移动一步,最终到达右下角。要求找到一条路径,使得路径上的数字…...
办公设备管理系统(springboot+ssm+jsp+maven)
基于springboot的办公设备管理系统(springbootssmjspmaven) 系统功能主要有: 欢迎页账号管理 管理员账号管理系统账号添加密码修改 普通管理员管理 用户管理用户添加用户查询 资产类型管理资产信息管理资产档案管理资产报表...
蓝桥杯2024JavaB组的一道真题的解析
文章目录 1.问题描述2.问题描述3.思路分析4.代码分析 1.问题描述 这个是我很久之前写的一个题目,当时研究了这个题目好久,发布了一篇题解,后来很多人点赞,我都没有意识到这个问题的严重性,我甚至都在怀疑自己…...
数据库--SQL
SQL:Structured Query Language,结构化查询语言 SQL是用于管理关系型数据库并对其中的数据进行一系列操作(包括数据插入、查询、修改删除)的一种语言 分类:数据定义语言DDL、数据操纵语言DML、数据控制语言DCL、事务处…...
SQL Server:Log Shipping 说明
目录标题 SQL Server Log Shipping与Oracle归档日志备份对比分析一、SQL Server Log Shipping的日志截断机制二、Oracle归档日志备份对比三、关键配置对比表四、最佳实践建议 如何修改和查看SQL Server默认备份配置防止自动删除?一、查看现有备份配置二、修改备份配…...
Zephyr实时操作系统初步介绍
一、概述 Zephyr是由Linux基金会托管的开源实时操作系统(RTOS),专为资源受限的物联网设备设计。其核心特性包括模块化架构、跨平台兼容性、安全性优先以及丰富的连接协议支持。基于Apache 2.0协议,Zephyr允许商业和非商业用途的自…...
【大模型系列篇】大模型基建工程:基于 FastAPI 自动构建 SSE MCP 服务器 —— 进阶篇
🔥🔥🔥 上期 《大模型基建工程:基于 FastAPI 自动构建 SSE MCP 服务器》中我们使用fastapi-mcp自动挂载fastapi到mcp工具,通过源码分析和实践,我们发现每次sse请求又转到了内部fastapi RESTful api接口&…...
深度学习deeplearn3
# Jupyter Notebook魔法命令,用于在Notebook中内联显示图表 %matplotlib inline# 导入NumPy库,用于高效的数值计算 import numpy as np# 从matplotlib_inline库导入backend_inline模块,用于设置图表显示格式 from matplotlib_inline import b…...
(九)图形管线
一图说明问题 顶点数据->顶点着色器->细分着色器->几何着色器->光栅化->片元着色器->颜色混合 创建图形管线函数放在后面位置 void MyApplication::initVulkan() { createInstance(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); cre…...
7-3 逆序的三位数
程序每次读入一个正3位数,然后输出按位逆序的数字。注意:当输入的数字含有结尾的0时,输出不应带有前导的0。比如输入700,输出应该是7。 输入格式: 每个测试是一个3位的正整数。 输出格式: 输出按位逆序…...
git从历史版本创建新分支或标签
git从某个历史版本创建标签 # 查看历史版本 git log git tag tag-v1.0 780e2a7fc714faf388ba71git从某个分支的指定历史版本中创建新分支 # 查看历史版本 git log # 从历史分支创建标签 git checkout -b new-branch 780e2a7fc714faf388ba71...
HTML 音频(Audio)学习笔记
一、HTML 音频概述 在 HTML 中,音频可以通过多种方式播放,但要确保音频在不同浏览器和设备上都能正常播放,需要掌握一些技巧。HTML5 引入了 <audio> 元素,为音频播放提供了一种标准方法,但在 HTML4 中ÿ…...
五种音频器件综合对比——《器件手册--音频器件》
目录 音频器件 简述 1. 扬声器(Speakers) 2. 麦克风(Microphones) 3. 放大器(Amplifiers) 4. 音频接口(Audio Interfaces) 5. 音频处理器(Audio Processors)…...
数据结构复习(单调栈,单调队列,KMP,manacher,tire,字符串哈希)
单调栈: 介绍: 单调栈用于解决"寻找每个元素左侧/右侧第一个比它小/大的元素"类问题。栈中元素保持单调性,时间复杂度O(n)。 维护一个严格递增栈。对于每个元素a[i],不断弹出栈顶比a[i]大的元素,剩下的栈顶即…...
(学习总结32)Linux 基础 IO
Linux 基础 IO 一、什么是 " 文件 "二、C 文件接口打开文件写文件读文件其它介绍 三、系统文件 I/O传递标志位系统接口写文件系统接口读文件部分系统调用接口介绍打开文件函数 open关闭文件函数 close写入文件函数 write读取文件函数 read 文件描述符 fdfd 0 & 1…...
操作系统(一):概念及主流系统全分析
目录 一.操作系统是什么 二.操作系统的分类 2.1 按应用场景分类 2.2 按实时性分类 2.3 按内核架构分类 2.4 按用户与任务分类 三.主流操作系统比较 四.未来趋势 一.操作系统是什么 操作系统(Operating System, OS)是计算机系统的核心软件&#x…...
三、GPIO
一、GPIO简介 GPIO(General Purpose Input Output)通用输入输出口GPIO引脚电平:0V(低电平)~3.3V(高电平),部分引脚可容忍5V 容忍5V,即部分引脚输入5V的电压,…...
Ceph异地数据同步之-RBD异地同步复制(上)
#作者:闫乾苓 文章目录 前言基于快照的模式(Snapshot-based Mode)工作原理单向同步配置步骤单向同步复制测试双向同步配置步骤双向同步复制测试 前言 Ceph的RBD(RADOS Block Device)支持在两个Ceph集群之间进行异步镜…...
fastapi完全离线环境(无外网)的访问Swagger所做特殊处理
在互联网环境中,只要 启动FastAPI 服务运行在本地机器上,访问 http://localhost:8000/docs(Swagger UI)就可以访问到Swagger界面,但是在完全离线环境(无外网)下如何访问Swagger页面呢࿱…...
在网络中加入预训练的多层感知机(MLP)有什么作用?
在网络中加入预训练的多层感知机(MLP)通常是为了引入先验知识、提升特征表示能力或dropout,具体作用取决于MLP的设计和预训练任务。以下是常见的应用场景和优势: 1. 特征融合与迁移学习:预训练的MLP可以作为特征提取器࿰…...
3.2/Q2,GBD数据库最新文章解读
文章题目:Temporal trends in the burden of vertebral fractures caused by falls in China and globally from 1990 to 2021: a systematic analysis of the Global Burden of Disease Study 2021 DOI:10.1186/s13690-025-01500-y 中文标题:…...
机器学习的一百个概念(9)学习曲线
前言 本文隶属于专栏《机器学习的一百个概念》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见[《机器学习的一百个概念》 ima 知识库 知识库广场搜索&…...
浅谈Tomcat数据源连接池
目录 为什么需要JDBC连接池 Tomcat JDBC Pool 相关参数 1. 基本配置 2. 连接池大小控制 3. 连接验证与测试 4. 空闲连接回收 5. 连接泄漏与超时 Tomcat JDBC Pool 源码分析(tomcat 8.5.3) DataSourceFactory DataSource ConnectionPool Pool…...
Techub 财报解读:Circle 冲刺 IPO,但收入增长难掩利润困局
作者:Techub 财报解读 撰文:Yangz,Techub News 4 月 1 日,Circle 向美国证券交易委员会(SEC)提交 S-1 文件,计划进行首次公开募股(IPO),股票代码为 CRCL&…...
C++中的链表操作
在C中,链表是一种常见的数据结构,它由一系列节点组成,每个节点包含数据部分和指向下一个节点的指针。C标准库(STL)中提供了std::list和std::forward_list两种链表实现,分别对应双向链表和单向链表。此外&am…...
Vue2 生命周期
文章目录 前言🔄 Vue2 生命周期流程(8个核心钩子)📝 代码中典型用法示例一、您的描述验证二、完整生命周期代码示例三、关键阶段行为说明🔍 常见问题 前言 提示:以下是本篇文章正文内容,下面案…...
2007-2022年 上市公司政府补助数据 -社科数据
上市公司政府补助数据(2007-2022年)-社科数据https://download.csdn.net/download/paofuluolijiang/90028547 https://download.csdn.net/download/paofuluolijiang/90028547 政府补助是指政府为支持企业发展,提供的资金或资源支持。对于上市…...
设计心得——状态机
一、状态机 在设计一些与硬件交互或者游戏等开发中,经常会听到状态机(State Machines)这个字眼,而在设计模式(GoF)中,又经常听到状态模式这个概念,它们之间有什么联系和不同呢&…...
python match case语法
学习路线:B站 普通的if判断 def if_traffic_light(color):if color red:return Stopelif color yellow:return Slow downelif color green:return Goelse:return Invalid colorprint(if_traffic_light(red)) # Output: Stop print(if_traffic_light(yellow)) …...
Lua中协程相关函数使用详解
目录 1. coroutine.create(f)2. coroutine.resume(co [, val1, ...])3. coroutine.yield([val1, ...])4. coroutine.status(co)5. coroutine.wrap(f)6. coroutine.running()7. coroutine.isyieldable()协程状态转换示例总结 Lua 中的协程(coroutine)提供…...
代码拟有感
最近的日子像被按了0.5倍速播放键。腱鞘炎让手腕转动时发出咯吱声,尾骨的钝痛让久坐变成酷刑,落枕的脖子和酸胀的手臂组成了“疼痛交响乐”——这些隐秘的、持续的身体抗议,让原本枯燥的代码练习变成了一场生理与意志的拉锯战。 我盯着屏幕苦…...
《实战AI智能体》MCP对Agent有哪些好处
首先MCP为Agent提供了标准化的方式来接入各种工具和数据源,无论是本地运行的工具,例如通过stdio服务器,还是远程托管的服务HTTP over SSE服务, Agent都可以通过统一的接口与它们进行交互,极大扩展了第三方工具库。 例如,在金融领域,Agent 可以接入股票分析的MCP工具。当…...
maptalks获取所有图层并把图层按照zIndex排序
maptalks获取所有图层并把图层按照zIndex排序 获取所有图层 通过调用 map.getLayers() 可以返回当前地图上所有的图层集合。此方法会返回一个数组形式的结果,其中包含了地图上的每一个图层层级对象。 图层属性中的 ZINDEX 每种图层类型(如矢量图层、…...
GUI-Guider 按钮按下 选项卡 右移动一个,到最右边停下
extern lv_ui guider_ui; // 在文件顶部添加// 在按钮事件中使用: lv_obj_t * tabview guider_ui.screen_tabview_1; // 替换为你的实际 TabView 名称 uint16_t current lv_tabview_get_tab_act(tabview); lv_tabview_set_act(tabview, current 1, LV_ANIM_ON); …...
让AI再次伟大-MCP-Client开发指南
👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家📕系列专栏:Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术、JVM原理、AI应用🔥如果感觉…...
sql工具怎么选?
为什么大多数主流工具又贵又难用? 有没有一款免费好用的sql工具? 像大多人朋友常用的sql工具,应该都遇到过这种情况, 用着用着收到了来自品牌方的律师函, 或者处理数据经常卡死, 再或者不支持国产数据库…...
video标签播放mp4格式视频只有声音没有图像的问题
video标签播放mp4格式视频只有声音没有图像的问题 这是由于视频格式是hevc(H265)编码的,这种编码格式视频video播放有问题主要是由于以下两种原因导致的: 1、浏览器没有开启硬加速模式: 开启方法(以谷歌浏览器为例)&a…...
问题解决:glog中的LOG(INFO)与VLOG无法打印
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作),由 李兆龙 确认,转载请注明版权。 文章目录 MotivationProcess glog版本为:https://github.com/google/glog/archive/…...
【第2月 day16】Matplotlib 散点图与柱状图
好的!以下是针对初学者的 Matplotlib 散点图与柱状图 学习内容,用简单易懂的语言和示例讲解: 一、散点图(Scatter Plot) 作用:展示两个变量之间的关系(如相关性、分布等)。 1. 核心…...
汽车 HMI 设计的发展趋势与设计要点
一、汽车HMI设计的发展历程与现状 汽车人机交互界面(HMI)设计经历了从简单到复杂、从单一到多元的演变过程。2012年以前,汽车HMI主要依赖物理按键进行操作,交互方式较为单一。随着特斯拉Model S的推出,触控屏逐渐成为…...
Vue 3 中按照某个字段将数组分成多个数组
方法一:使用 reduce 方法 const originalArray [{ id: 1, category: A, name: Item 1 },{ id: 2, category: B, name: Item 2 },{ id: 3, category: A, name: Item 3 },{ id: 4, category: C, name: Item 4 },{ id: 5, category: B, name: Item 5 }, ];const grou…...
06-Spring 中的事件驱动机制
Spring 中的事件驱动机制(ApplicationEvent 源码解析) 本小结主要总结Spring的事件,如果对于观察者模式比较熟悉的话,理解这个应该不难。 这块涉及的面试题相对简单,主要还是以日常使用为主。 另外在Spring的源码中也运…...
Python学习笔记(8)关于列表内置函数和多维列表
列表访问计数 索引直接访问 index()#获得首次出现指定元素的索引 index(value,[start,[end]] #控制搜索索引范围 counr()#获得指定元素在列表中出现的次数 len()#返回列表长度 成员资格判断 incount()返回0,代表不存在 列表切片 slice[起始偏移量 start:终止…...
【算法学习计划】回溯 -- 递归
目录 leetcode 面试题08.06.汉诺塔问题 leetcode 21.合并两个有序链表 leetcode 206.反转链表 leetcode 24.两两交换链表中的节点 leetcode 50. Pow(x, n) 本篇文章将是我们回溯专题的第一篇文章,在这里我先浅浅讲一下什么是回溯 其实就是递归,只不…...
Unity中 JobSystem使用整理
Unity 的JobSystem允许创建多线程代码,以便应用程序可以使用所有可用的 CPU 内核来执行代码,这提供了更高的性能,因为您的应用程序可以更高效地使用运行它的所有 CPU 内核的容量,而不是在一个 CPU 内核上运行所有代码。 可以单独使…...
【从零实现Json-Rpc框架】- 项目实现 - 服务端主题实现及整体封装
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
JavaScript基础-移动端常用开发框架
随着移动互联网的发展,越来越多的应用和服务需要支持移动设备。为了提高开发效率和用户体验,开发者们依赖于一些成熟的JavaScript框架来构建响应迅速、功能丰富的移动Web应用。本文将介绍几款广泛使用的移动端开发框架,并通过具体的示例展示它…...
Tree - Shaking
Vue 3 的 Tree - Shaking 技术详解 Tree - Shaking 是一种在打包时移除未使用代码的优化技术,在 Vue 3 中,Tree - Shaking 发挥了重要作用,有效减少了打包后的代码体积,提高了应用的加载性能。以下是对 Vue 3 中 Tree - Shaking …...
VSCode历史版本的下载安装
VSCode历史版本的下载安装 文章目录 VSCode历史版本的下载安装VSCode安装下载历史版本地址查询VSCode历史版本的 commit id 安装参考资料 VSCode安装 Windows版本:Windows10VSCode版本:VScode1.65.0(64位User版本)本文编写时间&a…...