模拟实现STL中的list

embedded/2024/11/22 2:03:14/

目录

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.ppmy.cn/embedded/139487.html

相关文章

淘宝 NPM 镜像源

npm i vant/weapp -S --production npm config set registry https://registry.npmmirror.com 要在淘宝 NPM 镜像站下载项目或依赖&#xff0c;你可以按照以下步骤操作&#xff1a; 1. 设置淘宝 NPM 镜像源 首先&#xff0c;你需要设置淘宝 NPM 镜像源以加速下载。可以通过…

多用户Markdown笔记应用Many Notes

什么是 Many Notes &#xff1f; Many Notes 是一款专为简约设计的 Markdown 笔记应用&#xff01;您可以轻松创建或导入您的笔记库&#xff0c;并立即整理您的想法。 软件特点&#xff1a; 多用户支持每个用户多个笔记库文件搜索树形视图浏览器&#xff0c;便于快速导航导入/…

ElasticSearch-全文检索(一)基本介绍

简介 Elasticsearch&#xff1a;官方分布式搜索和分析引擎 | Elastic 全文搜索属于最常见的需求&#xff0c;开源的Elasticsearch是目前全文搜索引擎的首选。 它可以快速地储存、搜索和分析海量数据。维基百科、StackOverflow、Github都采用它 Elastic的底层是开源库Lucene。但…

深度学习实验十一 卷积神经网络(2)——基于LeNet实现手写体数字识别实验

目录 一、数据 二、模型构建 三、模型训练及评价 四、打印参数量和计算量 五、模型预测 附&#xff1a;完整可运行代码 实验大致步骤&#xff1a; 一、数据 下载网站&#xff1a;MNIST数据集 之前的官网不能下载数据集了&#xff0c;403了&#xff0c;所以找到一个类似…

容器运行时 AND Docker

容器运行时 and Docker 什么是Docker Docker 使用 Google 公司推出的 Go 语言 进行开发实现&#xff0c;基于 Linux 内核的 cgroup&#xff0c;namespace&#xff0c;以及 AUFS 类的 Union FS 等技术&#xff0c;对进程进行封装隔离&#xff0c;属于 操作系统层面的虚拟化技术…

基于web的教务系统的实现(springboot框架 mysql jpa freemarker)

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

CSS(7):定位position:相对定位(relative)、绝对定位(absolute)、固定定位(fixed)和静态定位(static)

一.定位&#xff1a;将盒子定在某一个位置&#xff0c;其规则为&#xff1a;定位 定位模式 边偏移 。 二&#xff1a;定位模式 1.static静态定位: 元素无设置的时候就是static “position:static;” 2.relative相对定位&#xff1a;相对于当前位置进行移动&#xff0c;通过…

【Three.js基础学习】27.Modified materials

前言 补充&#xff1a;\node_modules\three\src\renderers\shaders 自Three.js第132版以来&#xff0c;位于ShaderLib/文件夹中的着色器现在按材质分组。 顶点代码和片段代码都在同一个文件中。 课程 学习如何改进3DS内置材质 改进网格标准材质 两种方法 1.使用Three.js钩子&am…