C++利用链表模板类实现简易队列

前言


队列(queue)是先进先出(first in, first out, FIFO)的线性结构:只在尾部插入,只在头部删除。用链表实现队列的好处是插入和删除都是 O(1),不需要像动态数组那样在头部删除时搬移元素,也不存在扩容时整体复制的问题。把节点和队列写成类模板,就能用同一份代码存放int、std::string或自定义类型。


这篇文章实现一个教学用的LinkedQueue<T>:单链表、带头尾指针、带元素计数,支持拷贝、移动、push、emplace、pop、front。重点不只是「能跑」,而是把资源管理的几条线讲清楚:析构要逐个释放节点,拷贝要深拷贝,移动后源对象要置空,拷贝构造中途抛异常不能泄漏。


需要先说明:本机没有 C++ 编译环境,下面的代码是按 C++17 标准逐行推演的,没有经过编译或运行。阅读时请重点关注所有权与边界条件,实际使用时再用自己的工具链验证。


一、结构设计与不变量


单链表队列需要两个指针:head_指向队首节点,tail_指向队尾节点;再加一个size_记录元素个数。节点Node含一个T值和一个next指针。只从尾部插入、只从头部删除,所以两个操作都不需要遍历。


这套结构有三条不变量,任何成员函数都必须维持:




不变量含义破坏后的后果



head_ == nullptr当且仅当队列为空空队列没有节点空判断错误,访问空指针

tail_ == nullptr当且仅当队列为空尾指针不能悬空下一次push写入已释放节点

size_等于实际节点数计数与链表一致size()撒谎,边界判断出错



为什么强调tail_的不变量?考虑只有一个元素的队列:head_和tail_指向同一个节点。执行pop()后,这个节点被删除,head_变成空,此时必须同时把tail_置空;否则tail_指向已释放内存,下一次push会通过tail_->next写入悬垂指针,属于未定义行为(undefined behavior, UB)。


pop()的返回类型设计成void,与标准库std::queue一致。如果要取队首值,先调用front()取得引用,把值拷贝或移动到局部变量,再调用pop()。不要让pop()返回引用,因为节点随即被释放,引用立刻悬垂。


二、模板节点与特殊成员函数


节点用变参模板构造函数,把参数完美转发给T的构造函数。这样push可以复用拷贝或移动,emplace可以原地构造,省掉一次临时对象。


队列本身遵循「五法则」(rule of five):析构、拷贝构造、拷贝赋值、移动构造、移动赋值。只要手写了其中一个,通常五个都要考虑。这里用「拷贝并交换」(copy-and-swap)实现拷贝赋值:先按值构造一个副本,再与*this交换。副本构造失败时原对象不受影响,满足强异常安全保证;交换本身只交换三个指针和计数,不会抛异常。


析构函数必须逐个delete节点。不要写成递归释放,例如if (head_) { Node* n = head_->next; delete head_; head_ = n; ... }这种迭代写法是安全的;如果写成「先递归释放next再释放自己」,链表很长时可能耗尽调用栈。下面的clear()采用迭代。


移动构造直接「偷走」三个成员,然后把源对象的三个成员置成空状态。被移动后的对象仍然可以析构、可以重新赋值,但不要读它的内容——它处于「有效但未指定」的状态。


// 适用于 C++17
#include <cstddef>
#include <stdexcept>
#include <utility>

template <typename T>
class LinkedQueue {
private:
struct Node {
T value;
Node* next;

template <typename... Args>
explicit Node(Args&&... args)
: value(std::forward<Args>(args)...), next(nullptr) {}
};

Node* head_ = nullptr;
Node* tail_ = nullptr;
std::size_t size_ = 0;

void clear() noexcept {
while (head_ != nullptr) {
Node* next = head_->next;
delete head_;
head_ = next;
}
tail_ = nullptr;
size_ = 0;
}

void copyFrom(const LinkedQueue& other) {
try {
for (Node* p = other.head_; p != nullptr; p = p->next) {
push(p->value);
}
} catch (...) {
clear(); // 构造中途失败:释放已建立的节点再抛出
throw;
}
}

public:
LinkedQueue() = default;

LinkedQueue(const LinkedQueue& other) {
copyFrom(other);
}

LinkedQueue(LinkedQueue&& other) noexcept
: head_(other.head_), tail_(other.tail_), size_(other.size_) {
other.head_ = nullptr;
other.tail_ = nullptr;
other.size_ = 0;
}

LinkedQueue& operator=(const LinkedQueue& other) {
if (this != &other) {
LinkedQueue tmp(other); // 先复制,失败则原对象不变
swap(tmp);
}
return *this;
}

LinkedQueue& operator=(LinkedQueue&& other) noexcept {
if (this != &other) {
clear();
head_ = other.head_;
tail_ = other.tail_;
size_ = other.size_;
other.head_ = nullptr;
other.tail_ = nullptr;
other.size_ = 0;
}
return *this;
}

~LinkedQueue() {
clear();
}

void swap(LinkedQueue& other) noexcept {
std::swap(head_, other.head_);
std::swap(tail_, other.tail_);
std::swap(size_, other.size_);
}

void push(const T& value) {
Node* node = new Node(value);
if (tail_ != nullptr) {
tail_->next = node;
} else {
head_ = node;
}
tail_ = node;
++size_;
}

void push(T&& value) {
Node* node = new Node(std::move(value));
if (tail_ != nullptr) {
tail_->next = node;
} else {
head_ = node;
}
tail_ = node;
++size_;
}

template <typename... Args>
void emplace(Args&&... args) {
Node* node = new Node(std::forward<Args>(args)...);
if (tail_ != nullptr) {
tail_->next = node;
} else {
head_ = node;
}
tail_ = node;
++size_;
}

void pop() {
if (head_ == nullptr) {
throw std::underflow_error("pop on empty queue");
}
Node* node = head_;
head_ = head_->next;
if (head_ == nullptr) {
tail_ = nullptr; // 删掉最后一个节点后尾指针必须清空
}
delete node;
--size_;
}

T& front() {
if (head_ == nullptr) {
throw std::underflow_error("front on empty queue");
}
return head_->value;
}

const T& front() const {
if (head_ == nullptr) {
throw std::underflow_error("front on empty queue");
}
return head_->value;
}

bool empty() const noexcept { return head_ == nullptr; }
std::size_t size() const noexcept { return size_; }
};

int main() {
LinkedQueue<int> q;
q.push(1);
q.push(2);
q.emplace(3);

while (!q.empty()) {
int value = q.front();
q.pop();
(void)value;
}
return 0;
}

三、异常安全与复杂度


push的异常安全来自「先分配,后链接」:new Node(...)要么成功,要么抛出,抛出时链表完全没变;链接和计数递增都是不会抛的操作。emplace同理。pop在空队列上抛std::underflow_error,其余操作只是移动指针和delete,不抛异常。


T的拷贝构造如果抛异常,new Node(value)里的new表达式会负责释放已分配的内存,节点不会泄漏;copyFrom的外层catch负责释放此前已经成功加入的节点。这两层保护缺一不可:前者保证单个节点不泄漏,后者保证整条链不泄漏。


复杂度方面,push、pop、front都是 O(1),empty、size也是 O(1)。代价是每个元素一次独立的堆分配,节点在内存中不连续,遍历时缓存局部性不如std::deque或std::vector。对绝大多数业务场景,这个代价可以接受;如果是高频入队出队且元素很小,标准库的std::deque往往更合适。


std::queue本身是容器适配器(container adaptor),默认基于std::deque,只暴露push、pop、front、back、empty、size。自己实现队列的价值主要是教学:理解所有权、异常安全和模板类的写法。生产代码优先用标准库容器。




操作时间复杂度可能抛异常备注



push/emplaceO(1)分配失败或T构造抛出链表保持原状

popO(1)空队列时抛underflow_error释放一个节点

frontO(1)空队列时抛underflow_error返回引用

拷贝构造O(n)T拷贝抛出中途失败会清理

移动构造O(1)noexcept源对象置空



常见坑点



  1. ❌pop只移动head_,不delete旧节点。


✅ 先保存Node* node = head_;,移动head_,再delete node;。



  1. ❌ 删除最后一个节点后忘记把tail_置空。


✅ 判断head_ == nullptr时同步执行tail_ = nullptr;。



  1. ❌ 拷贝构造函数里直接复制head_指针,两个队列共享同一条链。


✅ 遍历源链表,逐节点new一份值,做深拷贝。



  1. ❌ 移动构造只复制指针,不把源对象的指针置空。


✅ 偷取后立刻把源对象的head_、tail_置空,size_置零,否则两次析构会重复释放。



  1. ❌ 析构函数写成递归释放next,链表很长时栈溢出。


✅ 用while循环逐个保存next再删除当前节点。



  1. ❌ 让pop()返回T&或T*,调用方拿到后节点已被释放。


✅ 返回void,取值用front(),先复制再pop()。



  1. ❌ 拷贝构造中途push抛异常,已经建立的节点没人释放。


✅ 在copyFrom里捕获所有异常,调用clear()后重新抛出。



  1. ❌ 在push里先修改tail_再new,分配失败时链表结构已被破坏。


✅ 先new,成功后再链接并更新tail_与size_。


总结




设计点做法理由



队首/队尾head_删除、tail_插入两个方向都是 O(1)

资源释放迭代delete避免递归深度与泄漏

拷贝深拷贝 + 拷贝并交换强异常安全,避免自赋值问题

移动偷指针并置空源对象O(1),防止重复释放

取值front()返回引用,pop()返回void避免悬垂引用

异常先分配后链接失败时结构不变



链表模板队列的难点不在算法,而在所有权:谁分配、谁释放、失败时谁来清理。把这四条不变量和五法则写对,代码就具备可组合的基础;真要用于生产,优先选择std::queue或std::deque,把自研实现留在教学和理解原理的场景。




相关新闻

用Mock测试解决接口依赖问题:从工具选型到落地实践

用Mock测试解决接口依赖问题:从工具选型到落地实践

做接口联调这么多年&#xff0c;最烦的一件事就是“等”。前端等后端接口&#xff0c;后端等第三方服务&#xff0c;测试等环境稳定&#xff0c;最后所有人都在等别人。尤其是前后端并行开发那阵子&#xff0c;接口契约刚定完&#xff0c;前端已经把页面写了一半&#xff0c;后…

2026/10/20 18:09:32 阅读更多 →
Paperclip:面向多Agent团队的开源公司操作系统

Paperclip:面向多Agent团队的开源公司操作系统

1. 项目概述&#xff1a;Paperclip 不是插件&#xff0c;是多 Agent 协作的“神经中枢”你有没有试过让三个不同角色的 AI 同时干活——一个写方案、一个查资料、一个做 PPT&#xff0c;结果它们各自为政&#xff0c;互相不知道对方在干啥&#xff0c;甚至把同一份文档改出三个…

2026/10/20 18:09:32 阅读更多 →
逆向分析三步法:静态分析、动态调试与交叉验证

逆向分析三步法:静态分析、动态调试与交叉验证

1. 项目概述&#xff1a;这不是“破解游戏”的炫技&#xff0c;而是理解软件真实行为的底层能力 “[网安实践III] 实验3.逆向分析”这个标题&#xff0c;乍看像一门高校课程里的普通实验编号&#xff0c;但背后藏着信息安全领域最硬核、也最常被误解的一门基本功。它不是教你怎…

2026/10/20 18:09:32 阅读更多 →

最新新闻

企业信息收集一步到位:ENScan_GO 备案、APP、公众号聚合实战指南

企业信息收集一步到位:ENScan_GO 备案、APP、公众号聚合实战指南

企业信息收集一步到位&#xff1a;ENScan_GO 备案、APP、公众号聚合实战指南 【免费下载链接】ENScan_GO 一款基于各大企业信息API的工具&#xff0c;解决在遇到的各种针对国内企业信息收集难题。一键收集控股公司ICP备案、APP、小程序、微信公众号等信息聚合导出。支持MCP接入…

2026/10/20 19:21:40 阅读更多 →
风扇选型软件:从P-Q曲线到工作点计算与批量筛选

风扇选型软件:从P-Q曲线到工作点计算与批量筛选

简介&#xff1a;Optimiser风扇选型软件是Multi-Wing官方推出的一款轴流风扇选型工具&#xff0c;面向暖通空调、工业通风、冷却散热等领域的工程师与设备选型人员。使用者只需输入应用所需的风量、压力与能耗等参数&#xff0c;软件即可自动匹配莫迪温风扇型号&#xff0c;帮助…

2026/10/20 19:21:40 阅读更多 →
AI辅助项目管理实操:用CodeBuddy打造可对话的“活档案”

AI辅助项目管理实操:用CodeBuddy打造可对话的“活档案”

说实话&#xff0c;以前我对“用AI做项目管理”这事挺抵触的。项目管理讲究的是人对人的协作、决策和沟通&#xff0c;AI能插得上什么手&#xff1f;直到我连续带了三周迭代&#xff0c;发现自己一半精力被会议、同步、验收这些琐事磨掉了&#xff0c;才下定决心让CodeBuddy进项…

2026/10/20 19:21:40 阅读更多 →
免费开源 3D 打印切片软件 Bambu Studio:3 步出件,自动支撑、多色打印一次配齐

免费开源 3D 打印切片软件 Bambu Studio:3 步出件,自动支撑、多色打印一次配齐

免费开源 3D 打印切片软件 Bambu Studio&#xff1a;3 步出件&#xff0c;自动支撑、多色打印一次配齐 【免费下载链接】BambuStudio PC Software for BambuLab and other 3D printers 项目地址: https://gitcode.com/GitHub_Trending/ba/BambuStudio Bambu Studio 是一…

2026/10/20 19:21:40 阅读更多 →
SodaDownloader:汽水音乐链接解密提取FLAC/M4A的完整教程

SodaDownloader:汽水音乐链接解密提取FLAC/M4A的完整教程

简介&#xff1a;SodaDownloader是一款面向汽水音乐平台用户的便捷下载工具&#xff0c;只需复制分享链接即可解析歌曲信息&#xff0c;并支持FLAC无损音频和M4A高质量格式的解密与下载&#xff0c;让用户不进入平台也能获取高品质音乐文件。该源码工程压缩包共26个文件、仅72K…

2026/10/20 19:21:40 阅读更多 →
pandas动态时间窗口实现多周期销量统计

pandas动态时间窗口实现多周期销量统计

1. 项目概述&#xff1a;为什么“近X天销量统计”是业务分析的起点而非终点在某电商公司做数据支持的那两年&#xff0c;我每天早上八点准时打开BI看板&#xff0c;第一眼不是看总GMV&#xff0c;而是盯着“近7天销量趋势”那个小卡片——它像血压计一样&#xff0c;能快速判断…

2026/10/20 19:20:37 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →