当前位置: 首页 > news >正文

【C++】: list介绍以及模拟实现

【C++】: list介绍以及模拟实现

List是STL中的双向链表,我们通常使用它来作为一种可灵活进行**数据更换(即插入和删除)**的数据结构。

std::list 是 C++ STL 中的一个容器类,底层实现是 双向链表,相比于 vector(底层是数组),它在插入和删除元素时性能更好(不需要移动元素),但在随机访问方面效率较低。

它定义在头文件 <list> 中。

我们来看看它基本的特性都有哪些。

一、基本特性

特性说明
存储结构双向链表(每个节点含有前驱和后继指针)
随机访问不支持(不像 vector[i]
插入/删除效率高效,O(1),特别适合频繁插入和删除
内存使用vector 更高,因为每个元素多了两个指针
迭代器稳定性插入删除不会使其他元素的迭代器失效

)

二、list的使用

因为list的接口较多,而且大部分都是STL其他数据结构的同类接口,所以这里只介绍它较为特殊和带有自身特性的。

访问接口

接口功能
front() / back()获取首/尾元素的引用
begin() / end()获取迭代器范围(正向)
rbegin() / rend()反向迭代器范围

修改接口

接口功能
push_back(val) / push_front(val)尾部/头部插入元素
pop_back() / pop_front()删除尾部/头部元素
insert(pos, val)在迭代器 pos 前插入元素
insert(pos, n, val)插入 nval
insert(pos, first, last)插入一段区间
emplace(pos, args...)原地构造插入
erase(pos)删除 pos 位置的元素
erase(first, last)删除区间元素
clear()清空链表

链表操作接口

接口功能
remove(val)删除所有等于 val 的元素
remove_if(pred)根据条件删除元素
unique()删除连续重复元素
sort()排序(归并排序)
reverse()反转链表
merge(list& other)合并两个有序链表,other 清空
splice(pos, other)other 整体剪切到 pos
splice(pos, other, it)other 的一个元素移动过来
splice(pos, other, first, last)移动区间元素

三、list的迭代器详解

对于 std::list 来说,它的迭代器是一个双向迭代器(Bidirectional Iterator),支持:

  • 向前 ++it
  • 向后 --it

而对于反向迭代器来说,它的 ++ 就是正向迭代器的 – ,灵活变通,可以互相成为对方。

另外关于迭代器失效问题,可参考下表。

操作是否导致迭代器失效说明
insert() 插入❌ 不失效(除非插入位置的迭代器)其他迭代器仍有效
push_front/back()❌ 不失效因为底层是链表,不会移动其它元素
erase(it) 删除it 失效,其他迭代器仍有效必须用返回的新迭代器继续
clear() 清空✅ 所有迭代器失效全部节点销毁
splice(), merge()✅ 涉及元素的迭代器失效非目标容器的迭代器失效

总结起来就是:在 std::list 中,只有 你删除了元素清空容器 时,相关迭代器才会失效;插入和其他元素无关的操作不会导致其他迭代器失效,这也是它比 vector 更安全的一大优势。

四、list模拟实现

好,接下来就是list 的模拟实现。

实现了一个简化版的 C++ 双向链表模板类 List<T>,并自定义了双向迭代器与反向迭代器,功能上模拟了 std::list 的许多核心操作。🧩 代码详解:自定义双向链表 joolin::List<T>


命名空间声明

namespace joolin { ... }

节点结构体 ListNode<T>

template <typename T>
struct ListNode {T data;ListNode<T>* prev;ListNode<T>* next;ListNode(const T &val = T()) : data(val), prev(nullptr), next(nullptr) {}
};
  • data: 存储节点中的值。
  • prev, next: 分别指向前一个和后一个节点。
  • 构造函数:默认值为 T(),指针为空。

用于实现双向链表结构的核心单元。


类定义 List<T>

基本成员

private:ListNode<T> *head;ListNode<T> *tail;size_t size;
  • head, tail: 指向链表的头和尾。
  • size: 当前链表中元素数量。

嵌套类:正向迭代器 Iterator

class Iterator {
private:ListNode<T>* node;public:Iterator(ListNode<T>* n = nullptr) : node(n) {}T& operator*() { return node->data; }...
};
  • 自定义一个双向迭代器类,支持:
    • 解引用 *
    • 前向移动 ++
    • 后向移动 --
    • 比较运算符 ==, !=

可以用它像 STL 一样遍历链表:

for (auto it = list.begin(); it != list.end(); ++it) {std::cout << *it << std::endl;
}

嵌套类:反向迭代器 ReverseIterator

class ReverseIterator { ... }
  • 功能类似 Iterator,但:
    • ++ 实际上是向前走 prev
    • 用于从尾到头遍历链表。

类似于 std::list::rbegin()rend()


构造 & 析构函数

List() : head(nullptr), tail(nullptr), size(0) {}~List() {while (head) {ListNode<T>* temp = head;head = head->next;delete temp;}tail = nullptr;size = 0;
}
  • 默认构造函数:初始化为空链表。
  • 析构函数:释放所有节点的内存。

清空链表 clear()

void clear() {while (head) {ListNode<T>* temp = head;head = head->next;delete temp;}tail = nullptr;size = 0;
}

和析构函数类似,但不销毁对象,只清空元素。


排序函数 sort()

void sort() {if (size < 2) return;for (ListNode<T>* i = head; i != nullptr; i = i->next) {for (ListNode<T>* j = i->next; j != nullptr; j = j->next) {if (i->data > j->data) {std::swap(i->data, j->data);}}}
}
  • 使用 冒泡排序 对链表排序(稳定、适用于链表)。
  • 排序的是 data 值,而不是节点指针。

插入元素:push_back()push_front()

void push_back(const T &value) { ... }
void push_front(const T &value) { ... }
  • 创建新节点并插入尾部/头部。
  • 更新 tailhead 指针。
  • 空链表时两者处理方式相同。

删除元素:pop_back()pop_front()

void pop_back() { ... }
void pop_front() { ... }
  • 删除头或尾部元素。
  • 更新 headtail,释放内存。

元素访问接口

T front() const { ... }
T back() const { ... }
size_t get_size() const { return size; }
  • front()back():返回第一个和最后一个元素。
  • 抛出异常如果链表为空。
  • get_size():返回链表当前长度。

插入指定位置 insert(pos, value)

void insert(size_t pos, const T &value) { ... }
  • 在指定 pos 位置插入(0为头,size为尾)。
  • 如果越界则抛出 out_of_range 异常。
  • 中间插入时更新前后指针连接。

删除指定位置 erase(pos)

void erase(size_t pos) { ... }
  • 越界检查。

  • pos == 0 调用 pop_front()

  • pos == size - 1 错误!⚠️

    if(pos = size - 1)  // ❌ 错误!应该是 ==
    

    应该写成:

    if (pos == size - 1)
    
  • 中间删除时,断开连接、释放节点。


begin()/end() 与 rbegin()/rend()

Iterator begin() { return Iterator(head); }
Iterator end() { return Iterator(nullptr); }ReverseIterator rbegin() { return ReverseIterator(tail); }
ReverseIterator rend() { return ReverseIterator(nullptr); }

提供正向和反向遍历的起点与终点。


总代码如下:

#include <iostream>
namespace joolin
{template <typename T>struct ListNode{T data;// 两个指针ListNode<T> *prev;ListNode<T> *next;// 构造函数ListNode(const T &val = T()): data(val), prev(nullptr), next(nullptr) {}};template <typename T>class List{private:ListNode<T> *head;ListNode<T> *tail;size_t size;public://迭代器class Iterator{private:ListNode<T>* node;public:Iterator(ListNode<T>* n = nullptr) : node(n) {}T& operator *() { return node->data; }Iterator& operator++() {node = node->next;return *this;}Iterator operator++(int) {Iterator temp = *this;node = node->next;return temp;}Iterator& operator--() {node = node->prev;return *this;}Iterator operator--(int) {Iterator temp = *this;node = node->prev;return temp;}bool operator==(const Iterator& other) const { return node == other.node; }bool operator!=(const Iterator& other) const { return node != other.node; }};//返回迭代器Iterator begin() { return Iterator(head); }Iterator end() { return Iterator(nullptr); }//反向迭代器// 反向迭代器class ReverseIterator {private:ListNode<T>* node;public:ReverseIterator(ListNode<T>* n = nullptr) : node(n) {}T& operator*() { return node->data; }ReverseIterator& operator++() {node = node->prev;return *this;}ReverseIterator operator++(int) {ReverseIterator temp = *this;node = node->prev;return temp;}bool operator==(const ReverseIterator& other) const { return node == other.node; }bool operator!=(const ReverseIterator& other) const { return node != other.node; }};//返回反向迭代器ReverseIterator rbegin() { return ReverseIterator(tail); }ReverseIterator rend() { return ReverseIterator(nullptr); }// 构造函数List(): head(nullptr), tail(nullptr), size(0){}// 析构函数~List(){while (head){ListNode<T> *temp = head;head = head->next;delete temp;}tail = nullptr;size = 0;}//清除void clear(){while (head){ListNode<T>* temp = head;head = head -> next;delete temp;/* code */}tail =nullptr;size = 0;   }//排序void sort(){if(size < 2) return;for (ListNode<T>* i = head; i != nullptr; i = i->next) {for (ListNode<T>* j = i->next; j != nullptr; j = j->next) {if (i->data > j->data) {std::swap(i->data, j->data);}}}}// 增加void push_back(const T &value){ListNode<T> *newNode = new ListNode<T>(value);if (!head){head = tail = newNode;}else{tail->next = newNode;newNode->prev = tail;tail = newNode;}++size;}void push_front(const T &value){ListNode<T> *newNode = new ListNode<T>(value);if (!head){head = tail = newNode;}else{newNode->next = head;head->prev = newNode;head = newNode;}++size;}// 删除void pop_back(){if (!tail)return;if (tail == head){delete tail;head = tail = nullptr;}else{ListNode<T> *temp = tail;tail = tail->prev;tail->next = nullptr;delete temp;}--size;}void pop_front(){if (!head)return;if (head == tail){delete head;head = tail = nullptr;}else{ListNode<T> *temp = head;head = head->next;head->prev = nullptr;delete temp;}--size;}// 返回T front() const{if (!head) {throw std::runtime_error("List is empty!");}return head->data;}T back() const{if (!tail) {throw std::runtime_error("List is empty!");}return tail->data;}size_t get_size() const{return size;}// 插入和消除void insert(size_t pos, const T &value){if (pos > size)throw std::out_of_range("Position out of range");if (pos == 0){push_front(value);return;}if (pos == size){push_back(value);return;}ListNode<T> *current = head;for (size_t i = 0; i < pos - 1; i++){current = current->next;}ListNode<T> *newNode = new ListNode<T>(value);newNode->next = current->next;newNode->prev = current;current->next->prev = newNode;current->next = newNode;++size;}void erase(size_t pos){if (pos >= size) throw std::out_of_range("Position out of range");if(pos == 0){pop_front();return;}if(pos = size - 1){pop_back();return;}ListNode<T>* current = head;for (size_t i = 0; i < pos; i++){current = current -> next;}current->prev->next = current ->next;current->next->prev = current -> prev;--size;            }};} // namespace joolin
http://www.dtcms.com/a/405864.html

相关文章:

  • dlib 实战:人脸检测、关键点定位与疲劳检测的全流程实现
  • SpringBoot 整合机器学习框架 Weka 实战操作详解:MLOps 端到端流水线与知识图谱融合实践
  • 华为OD最新机试题A卷双机位-单词接龙-2025年
  • Python 爬虫(豆瓣top250)-享受爬取信息的快乐
  • Kafka选举机制深度解析:分布式系统中的民主与效率
  • 一文读懂费用分析:定义、分类与成本费用区别
  • 全国做网站找哪家好做宣传海报的网站
  • 【Linux】基础IO(3)
  • 【Redis学习】Redis中常见的全局命令、数据结构和内部编码
  • AI行业应用深度解析:从理论到实践
  • AI 伦理审查破局:从制度设计到实践落地的 2025 探索
  • RocketMQ面试问题与详细回答
  • 多传感器数据融合到base_link坐标系下
  • 阿里新开源Qwen3-Omni技术解析
  • Flink 流式分析事件时间、Watermark 与窗口
  • 解析前端框架 Axios 的设计理念与源码
  • 使用IOT-Tree消息流InfluxDB模块节点实现标签数据的时序数据库存储
  • 【深入理解JVM】垃圾回收相关概念与相关算法
  • 文档抽取技术:金融保险行业数字化转型的核心驱动力之一
  • 神秘魔法?耐达讯自动化Modbus TCP 转 Profibus 如何为光伏逆变器编织通信“天网”
  • 做庭院的网站佛山网站专业制作
  • wordpress开启多站点营销云官网
  • 企业AI 智能体(AI_Agent)落地开源方案:Dify、n8n、RAGFlow、FastGPT、AutoGen和OAP深度梳理与对比分析
  • Day51 时钟系统与定时器(EPIT/GPT)
  • Django 搭配数据库开发智慧园区系统全攻略
  • 前端基础知识---10 Node.js(三)
  • article.3345645398
  • 国内如何使用GPT-5-Codex
  • Xcode 26 could not locate developer disk image for this device 无法定位开发者磁盘镜像
  • 用Python打造离线语音控制浏览器:基于VOSK的实用案例