【C++】STL中List的基本功能的模拟实现

2024-06-07 20:28

本文主要是介绍【C++】STL中List的基本功能的模拟实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:在前面学习了STL中list的使用方法,现在我们就进一步的讲解List的一些基本功能的模拟实现,这一讲博主认为是最近比较难的一个地方,各位一起加油。

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述


目录标题

  • List的模拟实现
    • List三个基本类
      • 结点类接口的实现
      • list的正向迭代器类的实现
    • List正向迭代器的接口实现
      • 构造函数
      • operator*运算符重载
      • operator->运算符重载
      • operator前置++和--与后置++和--
      • operator==与operator!=
    • List类的接口的实现
      • 构造函数
      • begin()和end()
      • 尾插函数- push_back(const T& x)
      • insert(iterator pos, const T& val)插入(pos之前的位置)
      • push_front(const T& x)头插
      • iterator erase(iterator pos)删除pos位置的值
      • 尾删与头删
      • clear()清空list
      • size_t size() 查看链表元素
      • bool empty()查看链表元素是否为空
      • 拷贝构造函数
      • 析构函数
      • swap函数 交换链表中的元素
      • operator=运算符重载
    • 整体代码


List的模拟实现

List三个基本类

前面我们提到过,list本质上就是一个带头双向循环链表,这里我们要实现list的功能就要实现三个类:

  1. 模拟实现结点类
  2. 模拟实现迭代器的类
  3. 模拟list主要功能的类

结点类接口的实现

这里如果对带头双向链表不太熟悉的小伙伴可以去看看博主之前的文章带头双向循环链表

template <class T>
struct ListNode//链表的主体
{ListNode<T>* _prev;//C++中可不写struct,直接用名定义ListNode<T>* _next;T _data;//存节点的值ListNode(const T& x = T())//这个地方在讲模拟实现vector的时候也讲了,需要查看的可以看看之前的博客:_next(nullptr), _prev(nullptr), _data(x){}
};

看到这里很多小伙伴会有疑问为什么这里写的是ListNode*

  1. 在C++中是可以省略struct不写的,也就是说原本的样子应该是 struct ListNode * _prev
  2. 结构体模板或类模板在定义时可以不加 T,但 使用时必须加T

list的正向迭代器类的实现

template<class T, class Ref, class Ptr>
struct ListIterator//迭代器
{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;//T表示基本类型, Ref表示引用返回,Ptr指代指针返回Node* _node;//记录链表ListIterator(Node* node)//传过来的位置就是迭代器从哪个位置开始
}

这里大部分人会有因为为什么这里迭代器的模板会有三个参数?因为如果我们只是使用普通迭代器的话确实一个参数就够了,但是有的情况我们是需要使用const迭代器的,难道我们还要在写一个类来专门放 const类型的迭代器嘛?
而后文list类的模拟实现中,我对迭代器进行了两种typedef:
普通迭代器:typedef ListIterator<T, T&, T*> iterator;
const迭代器:typedef ListIterator<T, const T&, const T*> const_iterator;
在这里插入图片描述


List正向迭代器的接口实现

构造函数

这里我们通过传过来的结点完成构造,让迭代器指向传过来结点的位置即可

ListIterator(Node* node)//传过来的位置就是迭代器从哪个位置开始:_node(node)
{
}

operator*运算符重载

前面我们说到过,Ref本质就是引用返回,无非就是const还是非const的类型的区分

Ref operator*()
{return _node->_data;
}

operator->运算符重载

至于为什么要写->的运算符重载,就是我们在list的使用的过程中传过去的不一定就是内置类型,还有可能是自定义类型(如下所示)

struct A
{int _a1;int _a2;A(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}
};	
void test_list2()
{list<A> lt;A aa1(1, 1);A aa2 = { 1, 1 };lt.push_back(aa1);lt.push_back(aa2);lt.push_back(A(2, 2));lt.push_back({ 3, 3 });lt.push_back({ 4, 4 });list<A>::iterator it = lt.begin();while (it != lt.end()){cout << it->_a1 << ":" << it->_a2 << endl;//本质上编译器会省略一个->,所以实际上写的是it->_A->_a1cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;++it;}cout << endl;
}
Ptr operator->()//本质上就是重载自定义类型,帮你找到内置类型,然后再找到内置类型的数据
{return &_node->_data;
}

operator前置++和–与后置++和–

这里我们提一下,对于前置++和后置++还有–等,我们主要传一个int类型的数据来进行区分

Self& operator++()//前置++
{_node = _node->_next;return *this;
}Self operator++(int)//后置++,加上int以便区分
{Self tmp(*this);//浅拷贝就行了_node = _node->_next;return tmp;
}Self& operator--()//前置++
{_node = _node->_prev;return *this;
}Self operator--(int)//后置--
{Self tmp(*this);_node = _node->_prev;return tmp;
}

operator==与operator!=

代码思路:对于如何判断两个迭代器是否相等,我们只需要判断两个迭代器所指向的位置是否相等即可。

bool operator != (const Self& it)
{return _node != it._node;
}bool operator == (const Self& it)
{return _node == it._node;
}

List类的接口的实现

代码思路:这里我们主要是通过两个迭代器帮助我们去遍历list,然后一个const迭代器是只读的作用,一个非const迭代器是即可读又可写的作用

template <class T>
class list//链表
{typedef ListNode<T> Node;
public:typedef ListIterator<T, T&, T*> iterator;//正向迭代器typedef ListIterator<T, const T&, const T*> const_iterator;//const迭代器
private:Node* _head;size_t _size;//记录链表元素个数
};

构造函数

这里我们就采用双向带头链表的思路,初始化的时候让其的前驱指针和next指向他的哨兵位即可。

void empty_init()
{_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;
}
list()//默认构造
{empty_init();
}

begin()和end()

iterator begin()//begin应该是哨兵位的下一个结点
{return _head->_next;
}
iterator end()//因为是带头双向链表,所以通常没有尾部的这个说法,一般结束的时候就是在哨兵位这个结点就是尾结点
{return _head;
}const_iterator begin()const//只读的版本
{return _head->_next;
}const_iterator end() const
{return _head;
}

尾插函数- push_back(const T& x)

关于尾插这部分的内容,我们在之前数据结构那部分讲的挺详细的不懂的话可以看看博主之前的博客。

void push_back(const T& x)//尾插
{//insert(end(), x);Node* tail = _head->_prev;//找尾Node* newnode = new Node(x);//创建一个新的结点tail->_next = newnode;newnode->_prev = tail;//使newnode和头结点_head构成循环newnode->_next = _head;
}

insert(iterator pos, const T& val)插入(pos之前的位置)

这里我们会发现使用insert会改变了底层,会导致迭代器失效,所以使用的时候要及时更新迭代器。

void 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++;
}

push_front(const T& x)头插

这里我们可以顺带把尾插也给优化一下

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

iterator erase(iterator pos)删除pos位置的值

这里我们也需要注意的是,删除和插入数据都会导致迭代器失效,因此我们需要及时的更新迭代器

iterator erase(iterator pos)//删除会导致迭代器失效,故因此要返回迭代器的下一个位置
{assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;_size--;return iterator(next);
}

尾删与头删

void pop_back()//尾删
{erase(end() - 1);
}void pop_front()
{erase(begin());
}

clear()清空list

void clear()
{iterator it = begin();//通过迭代器依次遍历清除while (it != end()){it = erase(it);}
}

size_t size() 查看链表元素

size_t size() const
{return _size;
}

bool empty()查看链表元素是否为空

bool empty()
{return _size == 0;
}

拷贝构造函数

代码思路:我们只需要对链表的元素依次尾插到新的链表中即可

list(const list<T>& lt)
{empty_init();for (auto& e : lt){push_back(e);}
}

析构函数

~list()//析构
{clear();delete _head;_head = nullptr;
}

swap函数 交换链表中的元素

void swap(list<T>& it)//it要被修改
{std::swap(_head, it._head);std::swap(_size, it._size);
}

operator=运算符重载

list<T>& operator=(list<T> it)
{swap(*this,it);return *this;
}

整体代码

#include<iostream>
#include <assert.h>
using namespace std;namespace bit
{template <class T>struct ListNode//链表的主体{ListNode* _prev;//C++中可不写struct,直接用名定义ListNode* _next;T _data;ListNode(const T& x = T()):_next(nullptr), _prev(nullptr), _data(x){}};template<class T, class Ref, class Ptr>struct ListIterator//迭代器{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;//T表示基本类型, Ref表示引用返回,Ptr指代指针返回Node* _node;//记录链表ListIterator(Node* node)//传过来的位置就是迭代器从哪个位置开始:_node(node){}Ref operator*(){return _node->_data;}// list<int>::ListIterator it;  it->data;//list<Data>::ListIterator it;  it->Data->data;Ptr operator->()//本质上就是重载自定义类型,帮你找到内置类型,然后再找到内置类型的数据{return &_node->_data;}Self& operator++()//前置++{_node = _node->_next;return *this;}Self operator++(int)//后置++{Self tmp(*this);//浅拷贝就行了_node = _node->_next;return tmp;}Self& operator--()//前置++{_node = _node->_prev;return *this;}Self operator--(int)//后置--{Self tmp(*this);_node = _node->_prev;return tmp;}bool operator != (const Self& it){return _node != it._node;}bool operator == (const Self& it){return _node == it._node;}};template <class T>class list//链表{typedef ListNode<T> Node;public:typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T*> const_iterator;iterator begin(){return _head->_next;}iterator end(){return _head;}const_iterator begin()const{return _head->_next;}const_iterator end() const{return _head;}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list()//默认构造{empty_init();}// lt2(lt1)list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}~list()//析构{clear();delete _head;_head = nullptr;}void swap(list<T>& it)//it要被修改{std::swap(_head, it._head);std::swap(_size, it._size);}list<T>& operator=(list<T> it){swap(*this,it);return *this;}void push_back(const T& x)//尾插{//insert(end(), x);Node* tail = _head->_prev;//找尾Node* newnode = new Node(x);//创建一个新的结点tail->_next = newnode;newnode->_prev = tail;//使newnode和头结点_head构成循环newnode->_next = _head;}void push_front(const T& x)//头插{insert(begin(), x);}void 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++;}iterator erase(iterator pos)//删除会导致迭代器失效,故因此要返回迭代器的下一个位置{assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;_size--;return iterator(next);}void pop_back()//尾删{erase(end() - 1);}void pop_front(){erase(begin());}size_t size() const{return _size;}bool empty(){return _size == 0;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}private:Node* _head;size_t _size;};

好啦,今天的内容就到这里啦,下期内容预告stl中stack和queue的使用与模拟实现.


结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。


🌏🗺️ 这里祝各位接下来的每一天好运连连 💞💞

这篇关于【C++】STL中List的基本功能的模拟实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1040214

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

usaco 1.2 Transformations(模拟)

我的做法就是一个一个情况枚举出来 注意计算公式: ( 变换后的矩阵记为C) 顺时针旋转90°:C[i] [j]=A[n-j-1] [i] (旋转180°和270° 可以多转几个九十度来推) 对称:C[i] [n-j-1]=A[i] [j] 代码有点长 。。。 /*ID: who jayLANG: C++TASK: transform*/#include<