【C++】STL之list容器的模拟实现

在这里插入图片描述

在这里插入图片描述

个人主页:🍝在肯德基吃麻辣烫
分享一句喜欢的话:热烈的火焰,冰封在最沉默的火山深处。

文章目录

  • 前言
  • 一、list的三个类的关系分析图
    • vector和list的区别
      • 1.节点的成员变量以及构造函数
      • 2.list的迭代器
  • 二、list的增删查改工作
    • 2.1insert()
    • 2.2erase()
    • 2.3 push_back(),pop_back(),push_front(),pop_front()
    • 2.4clear()
  • 三、list的默认成员函数
    • 3.1 构造函数
    • 2.2 拷贝构造
    • 2.3析构
  • 完整代码
  • 总结


前言

本文章进入C++STL之list的模拟实现。


一、list的三个类的关系分析图

在STL标准库实现的list中,这个链表是一个== 双向带头循环链表==。
在这里插入图片描述

说明:
list是一个类,成员变量为_head
节点类node,是每一个节点。
list的迭代器也升级成了类,成员变量为node。

  • 把迭代器升级成类是为了能够重载++,–,*,!=等可以用在vector迭代器上的操作。

vector和list的区别

vectorlist
底层结构是一块连续的空间不是连续的空间
随机访问支持下标随机访问不支持下标随机访问
插入和删除如果是头插头删,效率为O(n) ,如果插入时需要扩容,会付出更高的代价头插头删都很方便,O(1)的效率
空间利用情况底层为连续的动态空间,内存碎片小,空间利用率高底层是一块一块不连续的空间,内存碎片多,空间利用率较低
迭代器使用天然的原生迭代器将迭代器封装起来再对外开放
迭代器失效在进行插入删除时,插入/删除位置及其之后,空间不再属于自己,由于空间是连续的,其之后的迭代器全部失效在插入时迭代器不会失效,只有在删除时迭代器会失效,且不会影响其他迭代器
使用场景需要进行大量随机访问,需要高效存储,不关心插入删除。大量插入删除操作时,不关心随机访问

vector在内存中是一块连续的地址空间,vector下标就是天然的迭代器。
list在内存中并不连续,所以不支持随机访问。为了能够让list完成诸如vector的操作,比如:

list<int>:: iterator it = lt1.begin();
while (it != lt1.end())
{cout << *it << " ";++it;
}

我们对list的迭代器进行封装,重载各种操作符,以便完成上述的操作。

1.节点的成员变量以及构造函数

在数据结构之链表中,我们知道一个节点必须包含prev,next,val三个变量,在STL的list也是如此。

template<class T>
//class list_node 如果这样写,节点的所有成员都是私有的,无法直接访问
struct list_node
{list_node<T>* _prev;list_node<T>* _next;T _val;//节点的构造函数list_node(const T& val = T()):_prev(nullptr), _next(nullptr), _val(val){}
};

注意:
1.在C++,struct升级成了类,但如果不标明,struct的所有成员都是公有的。
2.类名不是类型,不能使用list_node*来作为指针的类型。要使用模板来作为类型。

2.list的迭代器

需要注意的一个点:

  • list的迭代器是用来访问的,而不是用来管理节点的空间,所以list的空间是由自己管理和释放的,我们知道,程序崩溃主要就是同一块空间释放两次。
  • 所以list的迭代器在进行拷贝构造时可以使用浅拷贝,不会造成程序的崩溃。
list<int>:: iterator it = lt1.begin();

对这行代码来说,迭代器不是直接赋值给it的,因为迭代器升级成了类,而不是一个指针。这里会调用迭代器的拷贝构造函数。

list迭代器代码如下:

//迭代器也封装起来,形成我们熟悉的*it,++it
template<class T, class Ref, class Ptr>
//class __list_iterator 如果这样写,迭代器是私有的,无法直接使用
//迭代器使用类模板,为了重载Ref,Ptr,设置3个模板参数
struct __list_iterator
{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;//等价于://typedef __list_iterator<T, T&, T*> self;Node* _node; //成员__list_iterator(Node* node):_node(node){}//返回引用,可读可写Ref operator*(){return _node->_val;}//返回地址Ptr operator->(){return &_node->_val;}//返回迭代器本身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) const{return _node != it._node;}bool operator==(const self& it) const{return _node == it._node;}};
  • 1.使用三个模板参数的原因:
    • 要求返回指针,如:用指针->访问成员的情况;或者返回迭代器本身的情况,如 ++ ,–操作。
  • 2.在重载operator->()时,返回的是val的地址,所以我们在调用该函数时,需要进行两次->操作才能访问成员变量。
    比如:
struct A
{A(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}int _a1;int _a2;
};bit::list<A>lt2;
lt2.insert(lt2.begin(), A(1, 1));
lt2.insert(lt2.begin(), A(2, 2));
list<A>::iterator it = lt2.begin();
while (it != lt2.end())
{//迭代器重载了*,返回T&,也就是A本身cout << it->_a1 << " " << it->_a1 << endl;++it;
}
cout << endl;

本质上,应该需要 it->->_al,it->->_a2才能够访问成员。
第一个it->是调用operator->重载,返回val的地址,第二个->是通过地址访问成员。
编译器为了简化操作,以及看起来没有那么别扭,对it->->_a1进行简化成了it->_a1。

  • 3.类模板中的Ref是Reference,是引用的意思,Ptr是Pointer,是指针的意思。通过这两个模板名可以知道迭代器需要支持&,和*的操作。
  • 4.迭代器被重命名成self,也就是迭代器本身,在++,–操作时,需要返回迭代器本身。

二、list的增删查改工作

2.1insert()

在pos位置之前插入val值。

首先要获取pos位置的前一个节点,记为posprev。
将新的节点插入即可。

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

list的插入没有迭代器的失效问题。

2.2erase()

删除pos位置的节点,并返回pos位置的下一个位置的迭代器。

iterator erase(iterator pos)
{assert(pos != end());Node* erapos = pos._node;Node* eraprev = erapos->_prev;Node* eranext = erapos->_next;eraprev->_next = eranext;eranext->_prev = eraprev;erapos->_prev = erapos->_next = nullptr;delete erapos;--_size;return eranext;
}

erase后pos位置的迭代器会失效,我们需要返回下一个位置的迭代器来防止继续访问出现失效的情况。
在这里插入图片描述
删除后如果迭代器不更新,继续访问会出现野指针问题。

2.3 push_back(),pop_back(),push_front(),pop_front()

这几个函数分别是:尾插,尾删,头插,头删,我们复用insert和erase即可完成。

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

2.4clear()

该函数是将链表的所有节点全部释放,哨兵位头节点除外。

//把所有的节点都释放了,除了哨兵位的头节点
void clear()
{iterator it = begin();while (it != end()){//为防止迭代器失效,erase后会返回pos位置的下一个位置,所以it不需要++it = erase(it);}_size = 0;
}

注意:erase()删除pos节点后,会返回pos位置的下一个位置,所以这里不需要++it


三、list的默认成员函数

3.1 构造函数

首先申请一个哨兵位的头节点。

void empty_init()
{_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;
}//构造函数,构造出一个链表
list()
{empty_init();
}

2.2 拷贝构造

由于我们将迭代器进行了封装升级,现在可以使用++it等操作。
拷贝构造是深拷贝,先new一个_head哨兵位的头节点,再逐个节点进行尾插。

list(list<T>& lt)
{empty_init();list<T>::iterator it = lt.begin();while (it != lt.end()){push_back(*it);++it;++_size;}}

2.3析构

调用clear函数释放所有空间,再释放头节点。

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

完整代码

namespace bit
{//c++不喜欢使用内部类,所以把节点类放在list外面//节点封装成类template<class T>//class list_node 如果这样写,节点的所有成员都是私有的,无法直接访问struct list_node{list_node<T>* _prev;list_node<T>* _next;T _val;//节点的构造函数list_node(const T& val = T()):_prev(nullptr), _next(nullptr), _val(val){}};//迭代器也封装起来,形成我们熟悉的*it,++ittemplate<class T, class Ref, class Ptr>//class __list_iterator 如果这样写,迭代器是私有的,无法直接使用//迭代器使用类模板,为了重载Ref,Ptr,设置3个模板参数struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;//等价于://typedef __list_iterator<T, T&, T*> self;Node* _node; //成员__list_iterator(Node* node):_node(node){}//返回引用,可读可写Ref operator*(){return _node->_val;}//返回地址Ptr operator->(){return &_node->_val;}//返回迭代器本身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) const{return _node != it._node;}bool operator==(const self& it) const{return _node == it._node;}};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;iterator begin(){//return iterator(_head->_next);return _head->_next; // 单参数的构造函数可以进行隐式类型转换,从节点的指针转换成一个类}iterator end(){//return iterator(_head);return _head;}const_iterator begin() const{return const_iterator(_head->_next);//return _head->_next; // 单参数的构造函数可以进行隐式类型转换,从节点的指针转换成一个类}const_iterator end() const{return const_iterator(_head);//return _head;}void empty_init(){_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;}//构造函数,构造出一个链表list(){empty_init();}//拷贝构造//lt2(lt1)list(list<T>& lt){empty_init();list<T>::iterator it = lt.begin();while (it != lt.end()){push_back(*it);++it;++_size;}}//析构~list(){clear();delete _head;_head = nullptr;}//把所有的节点都释放了,除了哨兵位的头节点void clear(){iterator it = begin();while (it != end()){//为防止迭代器失效,erase后会返回pos位置的下一个位置,所以it不需要++it = erase(it);}_size = 0;}void swap(list<T>& tmp){std::swap(_head, tmp._head);std::swap(_size, tmp._size);}//赋值运算符重载//先调用拷贝构造,再调用赋值重载//lt3 = lt1//lt1先拷贝构造给tmplist<T>& operator=(list<T> tmp){swap(tmp);//出了作用域,调用析构return *this;}size_t size() const{return _size;}//尾插传过来的是一个数据,而不是一个节点void push_back(const T& x){//Node* tail = _head->_prev;调用构造//Node* newnode = new Node(x);//tail->_next = newnode;//newnode->_prev = tail;//_head->_prev = newnode;//newnode->_next = _head;insert(end(), x);}void pop_back(){//Node* del = _head->_prev;//_head->_prev = del->_prev;//del->_prev->_next = _head;//del->_next = del->_prev = nullptr;//delete del;//左闭右开,需要--erase(--end());}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}//在pos位置之前插入val//不会出现迭代器失效问题iterator insert(iterator pos, const T& val){Node* newnode = new Node(val);Node* inspos = pos._node;Node* posprev = inspos->_prev;newnode->_next = inspos;inspos->_prev = newnode;newnode->_prev = posprev;posprev->_next = newnode;++_size;return posprev;}//迭代器封装成了类,所以需要通过迭代器找到节点//防止迭代器失效,返回删除节点的下一个iterator erase(iterator pos){assert(pos != end());Node* erapos = pos._node;Node* eraprev = erapos->_prev;Node* eranext = erapos->_next;eraprev->_next = eranext;eranext->_prev = eraprev;erapos->_prev = erapos->_next = nullptr;delete erapos;--_size;return eranext;}private:Node* _head;size_t _size;};
}

总结

本文章完成了list的常用接口的模拟实现。

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

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

相关文章

MFC第十六天 CFileDialog、CEdit简介、(线程)进程的启动,以及Notepad的开发(托盘技术-->菜单功能)

文章目录 CCommonDialogCFileDialogCEdit托盘技术进程的启动附录1:启动线程方式附录2:MFC对话框的退出过程 CCommonDialog 通用对话框 CCommonDialog 这些对话框类封装 Windows 公共对话框。 它们提供了易于使用的复杂对话框实现。 CFileDialog 提供用于打开或保存文件的标准对…

C#在工业自动化领域的应用前景如何?

在2021年&#xff0c;C#与工业自动化已经开始结合&#xff0c;并且这种趋势有望在未来继续发展。C#是一种功能强大的编程语言&#xff0c;其面向对象的特性、跨平台支持以及丰富的类库和工具&#xff0c;使其成为在工业自动化领域应用的有力工具。 我这里刚好有嵌入式、单片机…

Nautlius Chain主网正式上线,模块Layer3时代正式开启

Nautilus Chain 是在 Vitalik Buterin 提出 Layer3 理念后&#xff0c; 对 Layer3 领域的全新探索。作为行业内首个模块化 Layer3 链&#xff0c;我们正在对 Layer3 架构进行早期的定义&#xff0c;并有望进一步打破公链赛道未来长期的发展格局。 在今年年初&#xff0c;经过我…

Spring Cloud 之 Gateway 网关

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

springboot第30集:springboot集合问题

Logstash Logstash 是开源的服务器端数据处理管道&#xff0c;能够同时从多个来源采集数据、格式化数据&#xff0c;然后将数据发送到es进行存储。 ElasticSearch Elasticsearch 是基于JSON的分布式搜索和分析引擎&#xff0c;是利用倒排索引实现的全文索引。 KibanaKibana 能够…

<C语言> 自定义类型

1.结构体 结构体是一种用户自定义的数据类型&#xff0c;允许将不同类型的数据项组合在一起&#xff0c;形成一个更大的数据结构。结构体可以包含多个成员变量&#xff0c;每个成员变量可以是不同的数据类型&#xff0c;如整数、字符、浮点数等&#xff0c;甚至可以包含其他结构…

能源监测系统:实时监控+数据可视化

能源监测系统是应用物联网技术&#xff0c;对水、电、气、热等能源进行实时监测的系统&#xff0c;能够对各种设备数据进行智能化标准化的管理&#xff0c;从而建立起统一的管理优化平台&#xff0c;是积极响应国家节能降耗政策的典型模范&#xff0c;也是企业建设节能型工厂的…

阿里云声音复刻

阿里云声音复刻 个性化人声定制 阿里云个性化人声定制是智能语音交互产品自学习平台下的一部分 使用方式&#xff1a;https://help.aliyun.com/document_detail/456006.html 方式一&#xff1a;控制台界面定制使用方式 方式二&#xff1a;通过OpenAPI定制&#xff1a;在该页…

VSCode下载安装(保姆级--一步到胃)

前言 Visual Studio Code&#xff08;简称“VSCode” &#xff09;是Microsoft在2015年4月30日Build开发者大会上正式宣布一个运行于 Mac OS X、Windows和 Linux 之上的&#xff0c;针对于编写现代Web和云应用的跨平台源代码编辑器&#xff0c;可在桌面上运行&#xff0c;并且…

基于小波哈尔法(WHM)的一维非线性IVP测试问题的求解(Matlab代码实现)

&#x1f4a5;1 概述 小波哈尔法&#xff08;WHM&#xff09;是一种求解一维非线性初值问题&#xff08;IVP&#xff09;的数值方法。它基于小波分析的思想&#xff0c;通过将原始问题转化为小波空间中的线性问题&#xff0c;然后进行求解。以下是一维非线性IVP测试问题的求解…

javaee jstl表达式

jstl是el表达式的扩展 使用jstl需要添加jar包 package com.test.servlet;import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;import javax.servlet.ServletException; import javax.servlet…

CSS 实现 Turbo 官网 3D 网格线背景动画

转载请注明出处&#xff0c;点击此处 查看更多精彩内容 查看 Turbo 官网 时发现它的背景动画挺有意思&#xff0c;就自己动手实现了一下。下面对关键点进行解释说明&#xff0c;查看完整代码及预览效果请 点击这里。 简单说明原理&#xff1a;使用 mask-image 遮罩绘制网格&a…