模拟实现STL中的list

目录

1.设计list的结点

2.设计list的迭代器

3.list类的设计总览

4.list类的迭代器操作

5.list类的四个特殊的默认成员函数

无参的默认构造函数

拷贝构造函数

赋值运算符重载函数

析构函数

6.list类的插入操作

7.list类的删除操作

8.list.hpp源代码


1.设计list的结点

STL中的list采用的底层数据结构是带头双向循环链表,我们也采用这种方式,因此,结点可以这样来设计。

代码如下:为了满足容器中可以存放各种类型的数据这一需求,我们使用模板元编程的思想,将结点设计为模板类型。

	/* 定义节点 */template<class T>       // T表示存储的数据的类型 struct ListNode{ListNode<T>* _prev; // 指向前一个结点ListNode<T>* _next; // 指向后一个结点T _data;            // 存储数据ListNode(const T& x = T()) // 构造函数:_next(nullptr), _prev(nullptr), _data(x){}};

2.设计list的迭代器

list的底层空间不像string和vector那样是连续的,因此,list的迭代器需要对结点的指针进行封装,来模拟指针的行为。比如:连续空间上的指针进行++操作,直接就能到达后一个数据的位置,但是不连续空间上的指针进行++操作不能到达后一个数据的位置。

同样,我们需要将迭代器设计为模板类型,我们设计三个模板参数,分别是:

  • T:存储的数据的类型。
  • Ref:存储的数据的引用类型,相当于T&。
  • Ptr:存储的数据的指针类型,相当于T*。
template<class T, class Ref, class Ptr>
struct __list_iterator
{typedef ListNode<T> Node;typedef __list_iterator<T, Ref, Ptr> self;// 成员变量Node* _node; // 对结点的指针进行封装
};

之所以遮掩设计是为了同时满足const对象和非const对象的需求,const对象需要调用const版本的迭代器,非const兑现需要调用非const版本的迭代器。

代码如下:迭代器模仿的是原生指针的行为,因此,我们实现迭代器的时候,至少需要实现以下操作。

  • 前置++和后置++
  • 前置--和后置--
  • 指针的解引用(*)操作
  • 指针的箭头(->)操作
  • 判断 相等 和 不相等 操作
/* * 迭代器* T:存储的元素类型* Ref:该类型的引用T&* Ptr:该类型的指针T*
*/
template<class T, class Ref, class Ptr>
struct __list_iterator
{typedef ListNode<T> Node;typedef __list_iterator<T, Ref, Ptr> self;/* 成员变量 */Node* _node; // 对结点的指针进行封装即可/* 成员函数 */__list_iterator(Node* x):_node(x){}// ++itself& operator++()        // 前置++,让当前迭代器指向下一个节点{_node = _node->_next;return *this;         // 返回++以后的值}// it++self operator++(int)      // 后置++,让当前迭代器指向下一个节点{//__list_iterator<T> tmp(*this);self tmp(*this);_node = _node->_next;return tmp;           // 返回++之前的值}// --itself& operator--()        // 前置--,让当前迭代器指向上一个节点{_node = _node->_prev;return *this;         // 返回--之后的值}// it--self operator--(int)      // 后置--,让当前迭代器指向上一个节点{self tmp(*this);_node = _node->_prev; // 返回--之前的值return tmp;}Ref operator*()           // 返回当前结点的数据域的引用{return _node->_data;}Ptr operator->()          // 返回当前结点中数据域的指针{return &(_node->_data);}bool operator!=(const self& s) // 判断两个迭代器是否不相等{return _node != s._node;}bool operator==(const self& s) // 判断两个迭代器是否相等{return _node == s._node;}
};

3.list类的设计总览

我们的list类设计如下:

  • 成员变量只有一个头结点的指针。
  • 成员函数涉及迭代器的相关操作四个特殊的成员函数,插入和删除操作。
template<class T> // T表示存储的数据的类型
class list
{
private:typedef ListNode<T> Node; // 对结点类型重命名Node* _head;              // 定义一个头结点
public:/* 迭代器的声明 */typedef __list_iterator<T, T&, T*> iterator;                   // 普通版本的迭代器typedef __list_iterator<T, const T&, const T*> const_iterator; // const版本的迭代器/* 迭代器的相关操作 */iterator begin();             // 返回第一个元素的迭代器iterator end();               // 返回最后一个元素的后一个位置的迭代器const_iterator begin() const; // 提供给const对象的beginconst_iterator end() const;   // 提供给const对象的end/* 四个特殊的成员函数 */list();                                // 无参的默认构造函数~list();                               // 析构函数list(const list<T>& lt);               // 拷贝构造list<T>& operator=(const list<T>& lt); // 赋值运算符重载函数/* 插入操作 */iterator insert(iterator pos, const T& x); // 在指定位置之前插入指定元素void push_back(const T& x);          	   // 尾插void push_front(const T& x);	           // 头插/* 删除操作 */iterator erase(iterator pos); // 删除指定位置的元素           void pop_back();	          // 尾删void pop_front();	          // 头删
};

4.list类的迭代器操作

begin()系列接口用于获取第一个元素的迭代器,也就是头结点后面那个元素的迭代器。

end()系列接口用于获取最后一个元素后面那个元素的迭代器,也就是头结点的迭代器。

为了满足const对象和非const对象的不同需求,我们提供const版本和非const版本。

/* 迭代器的声明 */
typedef __list_iterator<T, T&, T*> iterator;                   // 普通版本的迭代器
typedef __list_iterator<T, const T&, const T*> const_iterator; // const版本的迭代器iterator begin()         // 返回第一个元素的迭代器
{return _head->_next; // 利用单参数的构造函数进行隐式的类型转换
}iterator end()    // 返回最后一个元素的后一个位置的迭代器
{return _head; // 利用单参数的构造函数进行隐式的类型转换
}const_iterator begin() const // 提供给const对象的begin
{return _head->_next;
}const_iterator end() const   // 提供给const对象的end
{return _head;
}

5.list类的四个特殊的默认成员函数

无参的默认构造函数

  • 只需要构造出一个起哨兵作用的头结点即可

// 无参的默认构造函数
list()   : _head(new Node), _head->_next(_head), _head->_prev(_head);
{}

拷贝构造函数

  • list的拷贝构造函数可以复用尾插实现。(尾插在后面会讲解,先用起来)
// 拷贝构造
list(const list<T>& lt): _head(new Node), _head->_next(_head), _head->_prev(_head);
{for (const auto& e : lt){push_back(e); // 复用尾插实现}
}

赋值运算符重载函数

传统写法:释放赋值list的所有结点,然后复用尾插接口将被赋值list的所有结点尾插进来。

// 传统写法的赋值运算符重载函数
list<T>& operator=(const list<T>& lt)
{if (this != &lt){// 释放之前的所有结点iterator it = begin();while (it != end()){it = erase(it);}// 复用push_back尾插所有结点for (const auto& e : lt){push_back(e);}}return *this;
}

 现代写法:

  • 现代写法依赖于交换函数,所以我们先实现交换两个list的函数,交换两个list只需要交换list中的 _head 指针即可。
  • 在现代写法中,我们以传值传参的方式传递参数,此时的参数就相当于被赋值对象的一份临时拷贝,也就是复用拷贝构造函数,然后将当前对象与这个临时拷贝的对象进行交换。并且这个临时对象析构的时候,自动调用析构函数,析构的是赋值对象之前的值,避免了手动释放结点。
void swap(list<T>& tmp)
{std::swap(_head, tmp._head);
}// 现代写法的赋值运算符重载函数
list<T>& operator=(const list<T> lt)
{swap(lt);return *this;
}

析构函数

list类涉及到资源的管理,析构函数需要显示给出,在析构的时候,需要逐个释放结点。

// 析构函数
~list()
{// 逐个释放节点iterator it = begin();while (it != end()){it = erase(it);}// 释放哨兵位的头结点delete _head;_head = nullptr;
}

6.list类的插入操作

在指定位置之前插入

// 在指定位置之前插入指定元素iterator insert(iterator pos, const T& x)
{// prev newnode curNode* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);// 连接prev和newnodeprev->_next = newnode;newnode->_prev = prev;// 连接newnode和curnewnode->_next = cur;cur->_prev = newnode;return newnode;
}

尾插:在最后一个结点之后插入

// 尾插
void push_back(const T& x)
{// head -> …… -> tail -> newnode ->headNode* newnode = new Node(x);Node* tail = _head->_prev;// 连接tail和newnodetail->_next = newnode;newnode->_prev = tail;// 连接newnode和headnewnode->_next = _head;_head->_prev = newnode;
}// 复用insert实现尾插
void push_back(const T& x)
{insert(end(), x);
}

头插:复用insert在begin()之前插入。

// 头插
void push_front(const T& x)
{insert(begin(), x);
}

7.list类的删除操作

删除指定位置的元素:

// 删除指定位置的元素
iterator erase(iterator pos)
{assert(pos != end());// prev cur nextNode* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;// 断开要删除结点和前后结点的连接prev->_next = next;next->_prev = prev;// 释放要删除的结点delete cur;// 返回指向被删除结点的后一个结点的迭代器return next;
}

复用erase实现尾删和头删:

  • 尾删:end()的上一个位置就是尾结点。
  • 头删:begin()就是第一个结点的位置。
// 尾删
void pop_back()
{erase(--end());
}// 头删
void pop_front()
{erase(begin());
}

8.list.hpp源代码

#include <assert.h>namespace xy
{/* 定义节点 */template<class T>       // T表示存储的数据的类型 struct ListNode{ListNode<T>* _prev; // 指向前一个结点ListNode<T>* _next; // 指向后一个结点T _data;            // 存储数据ListNode(const T& x = T()) // 构造函数:_next(nullptr), _prev(nullptr), _data(x){}};/* * 迭代器* T:存储的元素类型* Ref:该类型的引用T&* Ptr:该类型的指针T**/template<class T, class Ref, class Ptr>struct __list_iterator{typedef ListNode<T> Node;typedef __list_iterator<T, Ref, Ptr> self;/* 成员变量 */Node* _node; // 对结点的指针进行封装即可/* 成员函数 */__list_iterator(Node* x):_node(x){}// ++itself& operator++()        // 前置++,让当前迭代器指向下一个节点{_node = _node->_next;return *this;         // 返回++以后的值}// it++self operator++(int)      // 后置++,让当前迭代器指向下一个节点{//__list_iterator<T> tmp(*this);self tmp(*this);_node = _node->_next;return tmp;           // 返回++之前的值}// --itself& operator--()        // 前置--,让当前迭代器指向上一个节点{_node = _node->_prev;return *this;         // 返回--之后的值}// it--self operator--(int)      // 后置--,让当前迭代器指向上一个节点{self tmp(*this);_node = _node->_prev; // 返回--之前的值return tmp;}Ref operator*()           // 返回当前结点的数据域的引用{return _node->_data;}Ptr operator->()          // 返回当前结点中数据域的指针{return &(_node->_data);}bool operator!=(const self& s) // 判断两个迭代器是否不相等{return _node != s._node;}bool operator==(const self& s) // 判断两个迭代器是否相等{return _node == s._node;}};template<class T> // T表示存储的数据的类型class list{private:typedef ListNode<T> Node; // 对结点类型重命名Node* _head;              // 定义一个头结点public:/* 迭代器的声明 */typedef __list_iterator<T, T&, T*> iterator;                   // 普通版本的迭代器typedef __list_iterator<T, const T&, const T*> const_iterator; // const版本的迭代器iterator begin()         // 返回第一个元素的迭代器{return _head->_next; // 利用单参数的构造函数进行隐式的类型转换}iterator end()    // 返回最后一个元素的后一个位置的迭代器{return _head; // 利用单参数的构造函数进行隐式的类型转换}const_iterator begin() const // 提供给const对象的begin{return _head->_next;}const_iterator end() const   // 提供给const对象的end{return _head;}/* 四个特殊的成员函数 */// 无参的默认构造函数list()   : _head(new Node), _head->_next(_head), _head->_prev(_head);{}// 拷贝构造list(const list<T>& lt): _head(new Node), _head->_next(_head), _head->_prev(_head);{for (const auto& e : lt){push_back(e);}}// 传统写法的赋值运算符重载函数list<T>& operator=(const list<T>& lt){if (this != &lt){// 释放之前的所有结点iterator it = begin();while (it != end()){it = erase(it);}// 复用push_back尾插所有结点for (const auto& e : lt){push_back(e);}}return *this;}void swap(list<T>& tmp){std::swap(_head, tmp._head);}// 现代写法的赋值运算符重载函数list<T>& operator=(const list<T> lt){swap(lt);return *this;}// 析构函数~list(){iterator it = begin();while (it != end()){it = erase(it);}delete _head;_head = nullptr;}/* 插入操作 */// 在指定位置之前插入指定元素iterator insert(iterator pos, const T& x){// prev newnode curNode* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);// 连接prev和newnodeprev->_next = newnode;newnode->_prev = prev;// 连接newnode和curnewnode->_next = cur;cur->_prev = newnode;return newnode;}// 尾插void push_back(const T& x){// head -> …… -> tail -> newnode ->headNode* newnode = new Node(x);Node* tail = _head->_prev;// 连接tail和newnodetail->_next = newnode;newnode->_prev = tail;// 连接newnode和headnewnode->_next = _head;_head->_prev = newnode;}//void push_back(const T& x)//{//	insert(end(), x);//}// 头插void push_front(const T& x){insert(begin(), x);}/* 删除操作 */// 删除指定位置的元素iterator erase(iterator pos){assert(pos != end());// prev cur nextNode* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;// 断开要删除结点和前后结点的连接prev->_next = next;next->_prev = prev;// 释放要删除的结点delete cur;// 返回指向被删除结点的后一个结点的迭代器return next;}// 尾删void pop_back(){erase(--end());}// 头删void pop_front(){erase(begin());}};
}

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

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

相关文章

spark 设置hive.exec.max.dynamic.partition不生效

spark脚本和程序中设置ive.exec.max.dynamic.partition不生效 正确写法&#xff1a; spark-submit \ --master yarn \ --deploy-mode client \ --driver-memory 1G \ --executor-memory 12G \ --num-executors 8 \ --executor-cores 4 \--conf spark.hadoop.hive.exec.max.dyna…

.NET SDK 各操作系统开发环境搭建

一、Win10&#xff08;推荐&#xff09; 1、VS 2022 社区版 # 下载地址 https://visualstudio.microsoft.com/zh-hans/downloads/ 2、.NET 6 SDK # 下载地址 https://dotnet.microsoft.com/zh-cn/download/dotnet/6.0 3、Hello World 如果需要使用旧程序样式时&#xff0c;则…

centos7.9安装mysql社区版

文章目录 场景安装 场景 今天把家里闲置的笔记本安装了centos&#xff0c;设置内网穿透做个人服务器用&#xff0c; 这里记录下安装mysql的过程 安装 安装mysql源 sudo yum install -y wget wget https://dev.mysql.com/get/mysql80-community-release-el7-5.noarch.rpm sudo r…

IDEA怎么定位java类所用maven依赖版本及引用位置

在实际开发中&#xff0c;我们可能会遇到需要搞清楚代码所用依赖版本号及引用位置的场景&#xff0c;便于排查问题&#xff0c;怎么通过IDEA实现呢&#xff1f; 可以在IDEA中打开项目&#xff0c;右键点击maven的pom.xml文件&#xff0c;或者在maven窗口下选中项目&#xff0c;…

CommandLineRunner、ApplicationRunner和@PostConstruct

在Spring Boot中&#xff0c;CommandLineRunner、ApplicationRunner 和 PostConstruct 都是常用的生命周期管理接口或注解&#xff0c;它们有不同的用途和执行时机&#xff0c;帮助开发者在Spring应用启动过程中进行一些初始化操作或执行特定任务。下面分别介绍它们的特点、使用…

【Golang】——Gin 框架中的模板渲染详解

Gin 框架支持动态网页开发&#xff0c;能够通过模板渲染结合数据生成动态页面。在这篇文章中&#xff0c;我们将一步步学习如何在 Gin 框架中配置模板、渲染动态数据&#xff0c;并结合静态资源文件创建一个功能完整的动态网站。 文章目录 1. 什么是模板渲染&#xff1f;1.1 概…

力扣 LeetCode 144. 二叉树的前序遍历(Day6:二叉树)

解题思路&#xff1a; 方法一&#xff1a;递归&#xff08;中左右&#xff09; class Solution {List<Integer> res new ArrayList<>();public List<Integer> preorderTraversal(TreeNode root) {recur(root);return res;}public void recur(TreeNode roo…

高级 SQL 技巧讲解

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言&#xff1a; SQL&#xff08;结构化查询语言&#xff09;是管理和操作数据库的核心工具。从基本的查询语句到复杂的数据处理&#xff0c;掌握高级 SQL 技巧不仅能显著提高数据分析的效率&#xff0c;还能解决业务中的复…

Java Server Pages (JSP):动态网页开发的基石

在Web开发的广阔领域中&#xff0c;Java Server Pages (JSP) 作为一种将Java代码与HTML内容相结合的服务器端技术&#xff0c;始终占据着举足轻重的地位。作为Java Enterprise Edition (Java EE) 的核心组成部分&#xff0c;JSP不仅为开发者提供了强大的动态网页生成能力&#…

pom中无法下载下来的类外部引用只给一个jar的时候

比如jar在桌面上放着,操作步骤如下&#xff1a; 选择桌面&#xff0c;输入cmd ,执行mvn install:install-file -DgroupIdcom -DartifactIdaspose-words -Dversion15.8.0 -Dpackagingjar -Dclassifierjdk11 -Dfilejar包名称 即可把jar包引入成功。

c语言学习21数组

1.1数组介绍 概念&#xff1a;数组就是 相同数据类型 的一组数据的集合 数组中每个数据 元素 用一个名字来命名这个集合 数组名 用编号区分 下表&#xff08;从0开始自动标号&#xff09; 当处理大量…

No Module named pytorchvideo.losses问题解决

问题描述 最近在跑X3D的源码时发现&#xff0c;在conda powershell prompt中安装了pytorchvideo&#xff0c;但是仍然报错&#xff1a;No Module named pytorchvideo.losses 解决方案&#xff1a; 直接去https://gitcode.com/gh_mirrors/py/pytorchvideo/overview?utm_sour…

LLM学习笔记(1).env文件与Anaconda虚拟环境有何区别?

刚入门&#xff0c;我打算跟着这个随笔走&#xff1a; 前言 LLM 应用开发实践笔记 会先从ChatGPT的中文文档开始&#xff1a; 入门 | OpenAI 官方帮助文档中文版 以上笔记和文档里的内容就不会在我的学习笔记里重复了&#xff0c;最后会把注意力转移到Hugging Face和论文上…

【软件测试】设计测试用例的万能公式

文章目录 概念设计测试用例的万能公式常规思考逆向思维发散性思维万能公式水杯测试弱网测试如何进行弱网测试 安装卸载测试 概念 什么是测试用例&#xff1f; 测试⽤例&#xff08;Test Case&#xff09;是为了实施测试⽽向被测试的系统提供的⼀组集合&#xff0c;这组集合包…

MySQL存储过程的详细说明

MySQL存储过程的详细说明 MySQL 存储过程是一种预编译的 SQL 语句集合&#xff0c;可以接受参数并返回结果。存储过程可以提高数据库的性能、可维护性和安全性。本文将详细介绍如何在 MySQL 中创建和使用存储过程&#xff0c;包括多个参数传入、返回输出和事务处理。 1. 存储…

在连通无向图中寻找欧拉回路(Eulerian Circuit)

在连通无向图中寻找欧拉回路(Eulerian Circuit) 问题描述解决方案概述算法步骤伪代码C代码示例如何在迷宫中找出一条路示例:在简单迷宫中应用欧拉回路结论问题描述 给定一个连通无向图 $ G = (V, E) $,我们需要找到一条路径,该路径正向和反向通过 $ E $ 中的每条边恰好一…

修改Android Studio项目配置JDK路径和项目Gradle路径的GUI工具

概述 本工具提供了一个基于Python Tkinter的图形用户界面&#xff08;GUI&#xff09;&#xff0c;用于帮助用户搜索并更新Android Studio项目中的config.properties文件里的java.home路径&#xff0c;以及workspace.xml文件中的last_opened_file_path路径。该工具旨在简化手动…

ANSYS Maxwell:3PH 感应电机 - 第 1 部分 - 力与热耦合

在此博客中&#xff0c;我们使用 Ansys RMxprt 创建了 3PH 感应电机的 1D 模型&#xff0c;并从设计中自动开发具有所有设置、边界条件和激励的麦克斯韦模型。 ANSYS RMxprt 1D 模型 - 3PH 感应电机设计 请参阅上一篇博客下面的链接&#xff0c;了解如何设置电机设计的 RMxp…

岁月沉淀:梳理多年积累的笔记精华

1 最近&#xff0c;看着自己笔记系统中的各种电子笔记&#xff0c;真是挺多的&#xff0c;积攒了自己20年来工作的各种内容&#xff0c;其中也包括c和c编程的笔记。 自从工作以后&#xff0c;我就养成了随时做笔记的习惯。 很多内容&#xff0c;我只要学过一遍&#xff0c;问…