从零开始实现 C++ 双向链表:深入理解链表底层原理

文章目录

  • 前言:
  • 1. 主要数据结构
  • 2. 迭代器的实现
  • 3. 链表的实现
    • 3.1 基本结构
    • 3.2 链表的插入与删除
    • 3.3 其他成员函数
  • 4. 迭代器操作与遍历链表
  • 5. 拷贝构造与赋值运算符
  • 6. 总结

前言:

在 C++ 标准库中,std::list 是一种非常常用的数据结构,其底层采用了双向链表的实现。在实际开发中,双向链表是一种具有灵活插入和删除操作的数据结构,尤其适合那些需要频繁操作非连续内存数据的场景。本文将通过一个手动实现的双向链表类 list 来讲解双向链表的底层结构和实现原理。

1. 主要数据结构

在链表的实现中,节点是最基本的元素,每个节点存储数据以及指向前后节点的指针。为了支持双向操作,链表的每个节点都有两个指针,分别指向前驱节点和后继节点。下面的 list_node 是一个模板类,存储泛型类型 T 的数据。

template <class T>
struct list_node {T _data;               // 存储节点数据list_node* _next;      // 指向下一个节点list_node* _prev;      // 指向上一个节点// 构造函数list_node(const T& x = T()): _data(x), _next(nullptr), _prev(nullptr) {}
};

2. 迭代器的实现

在链表的操作中,迭代器是至关重要的,它提供了与链表元素交互的机制。通过迭代器,用户可以像使用数组指针一样访问链表的元素。我们实现了 list_iterator 模板类,用于模拟标准库的迭代器。它不仅支持对节点的访问,还支持前后移动和增减操作。

template <class T, class Ref, class Ptr>
struct list_iterator {typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> self;Node* _node;  // 当前迭代器指向的节点list_iterator(Node* node) : _node(node) {}Ref& operator*() { return _node->_data; }Ptr* operator->() { return &_node->_data; }// 前置++,移动到下一个节点self& operator++() {_node = _node->_next;return *this;}// 前置--,移动到上一个节点self& operator--() {_node = _node->_prev;return *this;}// 后置++,移动到下一个节点,返回之前的状态self operator++(int) {self tmp(*this);_node = _node->_next;return tmp;}// 后置--,移动到上一个节点,返回之前的状态self operator--(int) {self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const self& s) { return _node != s._node; }bool operator==(const self& s) { return _node == s._node; }
};

迭代器主要提供以下功能:

operator* 和 operator->:实现解引用操作,返回当前节点的数据。
operator++ 和 operator–:支持迭代器的前进和后退。
operator!= 和 operator==:支持迭代器的比较,判断是否到达链表末尾。
通过实现这些运算符,我们可以像操作指针一样操作链表中的元素。

3. 链表的实现

接下来,我们实现链表的主体类 list。这是一个模板类,可以存储任意类型的数据,并提供一些常见的链表操作。

3.1 基本结构

首先,我们在链表类中定义一个哨兵节点 _head,这个节点没有实际的数据作用,它的存在是为了简化链表的边界处理。通过让哨兵节点的 _next 和 _prev 指向自己,可以避免处理链表为空时的特殊情况。

template <class T>
class list {typedef list_node<T> Node;  // 节点类型
public:typedef list_iterator<T, T&, T*> iterator;  // 可修改的迭代器typedef list_iterator<T, const T&, const T*> const_iterator;  // 常量迭代器// 构造函数list() {empty_init();}// 拷贝构造函数list(const list<T>& lt) {empty_init();for (auto& e : lt) {push_back(e);}}// 赋值操作符list<T>& operator=(list<T> lt) {swap(lt);return *this;}// 析构函数~list() {clear();delete _head;_head = nullptr;}private:Node* _head;   // 哨兵节点size_t _size;  // 链表的大小// 初始化空链表void empty_init() {_head = new Node();_head->_next = _head;_head->_prev = _head;_size = 0;}
};

在 list 的实现中,除了常见的构造、析构函数,我们还实现了拷贝构造函数和赋值操作符。这确保了链表在被拷贝时能够正确复制内容。

3.2 链表的插入与删除

在双向链表中,插入和删除操作是其核心功能。我们通过 insert 函数将新元素插入到链表的指定位置。它首先获取要插入位置前后的节点,然后重新设置这些节点的指针,使新节点正确链接到链表中。

iterator insert(iterator pos, const T& val) {Node* cur = pos._node;Node* newnode = new Node(val);Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);
}

对于删除操作,我们通过 erase 函数实现。erase 函数移除指定位置的节点,并将该节点前后的节点重新连接。

iterator erase(iterator pos) {assert(pos != end());Node* del = pos._node;Node* prev = del->_prev;Node* next = del->_next;prev->_next = next;next->_prev = prev;delete del;--_size;return iterator(next);
}

3.3 其他成员函数

我们还实现了链表的其他常用操作,如 push_back 和 push_front,用于在链表尾部和头部插入元素。pop_back 和 pop_front 则用于删除尾部和头部的元素。

void push_back(const T& x) {insert(end(), x);
}void push_front(const T& x) {insert(begin(), x);
}void pop_back() {erase(--end());
}void pop_front() {erase(begin());
}

这些操作都依赖于 insert 和 erase 函数,实现起来相对简单。

4. 迭代器操作与遍历链表

我们为链表提供了 begin() 和 end() 函数,用于获取链表的起始和结束迭代器。通过这些迭代器,用户可以遍历整个链表,访问每个元素。

iterator begin() {return iterator(_head->_next);
}iterator end() {return iterator(_head);
}

可以通过以下代码遍历链表的元素:

list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);for (auto it = lt.begin(); it != lt.end(); ++it) {cout << *it << " ";
}
// 输出: 1 2 3

5. 拷贝构造与赋值运算符

我们实现了拷贝构造函数和赋值运算符,通过它们可以确保链表被正确复制。赋值运算符通过 swap 函数交换两个链表的内部结构,从而实现高效的赋值。

list<T>& operator=(list<T> lt) {swap(lt);return *this;
}void swap(list<T>& tmp) {std::swap(_head, tmp._head);std::swap(_size, tmp._size);
}

6. 总结

本文从底层实现的角度详细讲解了如何手动实现一个双向链表容器 list。我们设计了双向链表的数据结构,通过节点、迭代器、基本的插入、删除操作,最终实现了一个功能完整的链表容器。以下是本文的主要内容回顾:

1.双向链表节点设计:每个节点存储数据元素,同时通过两个指针 _prev 和 _next 链接到前一个和后一个节点。这种设计使得我们可以在链表中进行双向遍历,并支持 O(1) 时间复杂度的插入和删除操作。

2.迭代器的实现:为了让链表可以像标准库中的容器一样被遍历,我们实现了 list_iterator。通过运算符重载,用户可以使用迭代器访问链表元素,进行正向和反向遍历。迭代器操作封装了链表内部的指针操作,使链表的使用更加简洁直观。

3.链表类的实现

  1. 插入和删除:我们实现了常见的 insert 和 erase 操作,它们负责在指定位置插入新元素或移除已有元素。双向链表的优势在于,我们可以在常数时间内完成这些操作,而无需像数组那样需要移动后续元素。
  2. push_back 和 push_front:为了方便用户插入元素,我们提供了头部和尾部的插入操作,分别对应在链表的首位插入新元素。
  3. pop_back 和 pop_front:链表的首尾删除操作也被封装在这两个函数中,方便用户快速删除首尾元素。

4.迭代器操作与遍历:通过 begin() 和 end() 函数,我们可以使用 C++ 标准的范围遍历方式遍历链表的所有元素。这使得链表容器的使用方式与 C++ 标准库中的其他容器一致,降低了使用门槛。

5.拷贝构造与赋值运算符:为了确保链表可以被正确拷贝,我们实现了拷贝构造函数和赋值操作符。在赋值操作中,我们通过 swap 函数实现高效的资源交换,避免了复杂的手动赋值操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/882385.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VUE 仿神州租车-开放平台

项目背景&#xff1a; 神州租车是一家提供汽车租赁服务的公司&#xff0c;其API开放平台为开发者提供了访问神州租车相关服务和数据的接口。用VUE技术来仿照其开发平台。 成果展示&#xff1a; 首页&#xff1a; API文档&#xff1a; 关于我们&#xff1a;

牛只行为及种类识别数据集18g牛只数据,适用于多种图像识别,目标检测,区域入侵检测等算法作为数据集。数据集中包括牛只行走,站立,进食,饮水等不同类型的数据

18g牛只数据&#xff0c;适用于多种图像识别&#xff0c;目标检测&#xff0c;区域入侵检测等算法作为数据集。 数据集中包括牛只行走&#xff0c;站立&#xff0c;进食&#xff0c;饮水等不同类型的数据&#xff0c;可以用于行为检测 数据集中包含多种不同种类的牛只&#xff…

黑盒测试 | 挖掘.NET程序中的反序列化漏洞

通过不安全反序列化漏洞远程执行代码 今天&#xff0c;我将回顾 OWASP 的十大漏洞之一&#xff1a;不安全反序列化&#xff0c;重点是 .NET 应用程序上反序列化漏洞的利用。 &#x1f4dd;$ _序列化_与_反序列化 序列化是将数据对象转换为字节流的过程&#xff0c;字节流可以…

基于SpringBoot+Vue+uniapp的诗词学习系统的详细设计和实现

详细视频演示 请联系我获取更详细的演示视频 项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不…

Maxwell 底层原理 详解

Maxwell 是一个 MySQL 数据库的增量数据捕获&#xff08;CDC, Change Data Capture&#xff09;工具&#xff0c;它通过读取 MySQL 的 binlog&#xff08;Binary Log&#xff09;来捕获数据变化&#xff0c;并将这些变化实时地发送到如 Kafka、Kinesis、RabbitMQ 或其他输出端。…

0x3D service

0x3D service 1. 概念2. Request message 数据格式3. Respone message 数据格式3.1 正响应格式3.2 negative respone codes(NRC)4. 示例4.1 正响应示例:4.2 NRC 示例1. 概念 UDS(统一诊断服务)中的0x3D服务,即Write Memory By Address(按地址写内存)服务,允许客户端向服…

2024年中国工业大模型行业发展研究报告|附43页PDF文件下载

工业大模型伴随着大模型技术的发展&#xff0c;逐渐渗透至工业&#xff0c;处于萌芽阶段。 就大模型的本质而言&#xff0c;是由一系列参数化的数学函数组成的计算系统&#xff0c;且是一个概率模型&#xff0c;其工作机制是基于概率和统计推动进行的&#xff0c;而非真正的理解…

aardio 中最重要的控件:自定义控件使用指南

aardio虽然是个小众编程语言&#xff0c;但其在windows下做个小软件生成exe文件&#xff0c;确实方便。只是这个编程语言的生态圈小&#xff0c;文档的详细程度也完全无法和大的编程语言相提并论。今天介绍一下&#xff0c;aardio中的自定义控件如何使用。 这里我们只介绍如何做…

python 作业1

任务1: python为主的工作是很少的 学习的python的优势在于制作工具&#xff0c;制作合适的工具可以提高我们在工作中的工作效率的工具 提高我们的竞争优势。 任务2: 不换行 换行 任务3: 安装pycharm 进入相应网站Download PyCharm: The Python IDE for data science and we…

AnaTraf | TCP重传的工作原理与优化方法

目录 什么是TCP重传&#xff1f; TCP重传的常见触发原因 TCP重传对网络性能的影响 1. 高延迟与重传 2. 吞吐量的下降 如何优化和减少TCP重传 1. 优化网络设备配置 2. 优化网络链路 3. 网络带宽的合理规划 4. 部署CDN和缓存策略 结语 AnaTraf 网络性能监控系统NPM | …

餐饮店怎么标注地图位置信息?

随着市场竞争的日益激烈&#xff0c;商家若想在竞争中脱颖而出&#xff0c;就必须想方设法去提高自身的曝光度和知名度&#xff0c;为店铺带来更多的客流量。其中&#xff0c;地图标注便是一种简单却极为有效的方法。通过在地图平台上添加店铺位置信息&#xff0c;不仅可以方便…

Qt-系统文件相关介绍使用(61)

目录 描述 输⼊输出设备类 打开/读/写/关闭 使用 先初始化&#xff0c;创建出大致的样貌 输入框设置 绑定槽函数 保存文件 打开文件 提取文件属性 描述 在C/C Linux 中我们都接触过关于文件的操作&#xff0c;当然 Qt 也会有对应的文件操作的 ⽂件操作是应⽤程序必不…

【C语言】文件操作(1)(文件打开关闭和顺序读写函数的万字笔记)

文章目录 一、什么是文件1.程序文件2.数据文件 二、数据文件1.文件名2.数据文件的分类文本文件二进制文件 三、文件的打开和关闭1.流和标准流流标准流 2.文件指针3.文件的打开和关闭文件的打开文件的关闭 四、文件的顺序读写1.fgetc函数2.fputc函数3.fgets函数4.fputs函数5.fsc…

微信小程序上传组件封装uploadHelper2.0使用整理

一、uploadHelper2.0使用步骤说明 uploadHelper.js ---上传代码封装库 cos-wx-sdk-v5.min.js---腾讯云&#xff0c;对象存储封装库 第一步&#xff0c;下载组件代码&#xff0c;放置到自己的小程序项目中 第二步、 创建上传对象&#xff0c;执行选择图片/视频 var _this th…

npm install进度卡在 idealTree:node_global: sill idealTree buildDeps

ping一下源&#xff1a;ping http://registry.npm.taobao.org/ ping不通&#xff0c;原因&#xff1a;原淘宝npm永久停止服务&#xff0c;已更新新域名~~震惊&#xff01;&#xff01;&#xff01; 重新安装&#xff1a;npm config set registry https://registry.npmmirror.c…

推荐?还是踩雷?3款中英互译软件大盘点,你真的选对了吗?

作为一个爱到处跑的人&#xff0c;我特别明白旅行的时候能说会道有多重要。不管是跟当地人聊天&#xff0c;还是看路标、菜单&#xff0c;有个好用的翻译软件是肯定少不了的。今天&#xff0c;我打算给你们介绍3款中英文互译的翻译工具&#xff0c;帮你挑出最适合自己的那一个。…

机器学习:opencv--人脸检测以及微笑检测

目录 前言 一、人脸检测的原理 1.特征提取 2.分类器 二、代码实现 1.图片预处理 2.加载分类器 3.进行人脸识别 4.标注人脸及显示 三、微笑检测 前言 人脸检测是计算机视觉中的一个重要任务&#xff0c;旨在自动识别图像或视频中的人脸。它可以用于多种应用&#xff0…

Python和MATLAB锂电铅蓄电化学微分模型和等效电路

&#x1f3af;要点 对比三种电化学颗粒模型&#xff1a;电化学的锂离子电池模型、单粒子模型和带电解质的单粒子模型。求解粒子域内边界通量与局部电流密度有关的扩散方程。扩展为两个相的负或正电极复合电极粒子模型。模拟四种耦合机制下活性物质损失情况。模拟锂离子电池三参…

【PhpSpreadsheet】ThinkPHP5+PhpSpreadsheet实现批量导出数据

目录 前言 一、安装 二、API使用 三、完整实例 四、效果图 前言 为什么使用PhpSpreadsheet&#xff1f; 由于PHPExcel不再维护&#xff0c;所以建议使用PhpSpreadsheet来导出exlcel&#xff0c;但是PhpSpreadsheet由于是个新的类库&#xff0c;所以只支持PHP7.1及以上的版…

服务器数据恢复—RAID5阵列上层Linux操作系统中节点损坏的数据恢复案例

服务器数据恢复环境&#xff1a; 一台服务器上有一组由5块硬盘&#xff08;4块数据盘1块热备盘&#xff09;组建的raid5阵列。服务器安装Linux Redhat操作系统&#xff0c;运行一套基于oracle数据库的OA系统。 服务器故障&#xff1a; 这组raid5阵列中一块磁盘离线&#xff0c…