今天博主继续带来STL源码剖析专栏的第三篇博客了!
今天带来list的模拟实现!
话不多说,直接进入我们今天的内容!


前言

那么这里博主先安利一下一些干货满满的专栏啦!

手撕数据结构https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!
算法专栏https://blog.csdn.net/yu_cblog/category_11464817.html这里是STL源码剖析专栏,这个专栏将会持续更新STL各种容器的模拟实现。

STL源码剖析https://blog.csdn.net/yu_cblog/category_11983210.html?spm=1001.2014.3001.5482


实现过程中要注意的一些点

STL的list底层是用带头循环双向链表实现的,里面的增删查改算法也是数据结构中算比较基础的操作了,如果对这些操作还不熟悉的,可以通过博主给的传送门先看看C语言实现的版本,了解好基本的算法操作,再回来食用这篇文章。

【链表】双向链表的介绍和基本操作(C语言实现)【保姆级别详细教学】

实现重点:迭代器和反向迭代器

关于迭代器和反向迭代器的具体实现,博主在代码的注释里面详细解释!

现在先列出一些STL迭代器的一些基本的知识点:

这个反向迭代器 -- list可以用 vector也可以用!(博主在代码中呈现)
什么容器的反向我们适配不出来?
map set是可以的,什么不可以呢
只要正向迭代器支持 ++ ,-- 都可以适配出来
++一般容器都有,--不一定有
比如单链表 -- forward_list就没有--
1.forward_list 单链表
2.unordered_map
3.unordered_set

迭代器从角度分类:
forward_iterator ++
bidirectional_iterator ++ --
random_access_iterator ++ -- + -  随机位置迭代器

deque vector 随机迭代器
map set list 双向迭代器
forward_list 单向迭代器

forward_iterator
bidirectional_iterator
random_access_iterator
其实它们是一种继承的关系
双向是特殊的单向,随机是特殊的双向

MyList.h

#pragma once#include<iostream>
#include<algorithm>
#include<cassert>
#include<list>
#include"reverse_iterator.h"using namespace std;namespace yufc {template<class T>struct list_node {T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T()) //给缺省:_data(x), _next(nullptr), _prev(nullptr){}};template<class T, class Ref, class Ptr>struct __list__iterator {typedef list_node<T>Node;//标准的stl迭代器要检查类型的//如果不加下面几句用不了findtypedef bidirectional_iterator_tag iterator_category;typedef T value_type;typedef Ptr pointer;typedef Ref reference;typedef ptrdiff_t difference_type;Node* _node;__list__iterator(Node* node)//构造:_node(node) {}//核心控制行为bool operator!=(const __list__iterator& it)const {return _node != it._node;}//如果我们返回 const T& 我们就不能改了//对迭代器解引用 -- 注意,要返回引用 -- 可读可写Ref operator*() { // 同样,泛型化return _node->_data;}__list__iterator& operator++() {_node = _node->_next;return *this;}//继续完善bool operator==(const __list__iterator& it)const {return _node == it._node;}__list__iterator operator++(int) {__list__iterator tmp(*this);//先保存一下之前的值_node = _node->_next;return tmp;//只能传值返回了 -- 因为是临时对象}__list__iterator operator--(int) {__list__iterator tmp(*this);//先保存一下之前的值_node = _node->_prev;return tmp;//只能传值返回了 -- 因为是临时对象}__list__iterator& operator--() {_node = _node->_prev;return *this;}//stl里面还重载了一个 -> 毕竟迭代器就是[指针行为]的一种数据类型//什么时候会用 -> 呢? 数据类型是结构体(类的时候)就需要了//T* operator->() {Ptr operator->() { //泛型化 -- 你传什么类型就调用什么类型//重载不了 -- 因为返回值不同不构成重载return &(operator*());}};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;typedef __reverse_iterator<iterator, T&, T*>reverse_iterator;typedef __reverse_iterator<const_iterator, const T&, const T*>const_reverse_iterator;iterator begin() {return iterator(_head->_next);//begin()是哨兵位的下一个位置}iterator end() {return iterator(_head);//end()是哨兵位}reverse_iterator rbegin() {return reverse_iterator(end());//因为前面我们设计的是对称的,所以你的正就是我的反,你的反就是我的正}reverse_iterator rend() {return reverse_iterator(begin());}const_iterator begin() const {return const_iterator(_head->_next);//begin()是哨兵位的下一个位置}const_iterator end() const {return const_iterator(_head);//end()是哨兵位}list() {empty_init();}~list() {//如果我们写完析构 -- 再浅拷贝 -- 就会肯定会崩溃了//先clear一下,再把头弄掉就行了this->clear();delete _head;_head = nullptr;}void empty_init() {//创建并初始化哨兵位头节点_head = new Node;_head->_next = _head;_head->_prev = _head;}//这样写好之后我们的构造也直接调用这个empty_init()就行了//拷贝构造//现在写法 -- 复用构造函数 -- 这个构造函数是传一个迭代器区间的(不一定要是list的迭代器)template<class InputIterator>list(InputIterator first, InputIterator last) { //构造empty_init();//创建并初始化哨兵位头节点while (first != last) { //当然我们不能直接插入 -- 头节点要先弄好 -- 不然直接push直接崩push_back(*first);++first;}}void swap(list<T>& x) {std::swap(_head, x._head);}list(const list<T>& lt) {//直接复用list(InputIterator first, InputIterator last)构造函数就行//lt2(lt1)empty_init();//先把自己初始化一下list<T>tmp(lt.begin(), lt.end());//这个构造结果就是lt2想要的
#if 0std::swap(_head, tmp._head);//直接换头指针就行了
#endifswap(tmp);}list<T>& operator=(list<T> lt) {//lt是一个深拷贝临时对象,换过来 -- 还帮你释放swap(lt);return*this;}void clear() {//清数据 -- 哨兵位是要保留的iterator it = begin();while (it != end()) {it = erase(it);//erase之后 -- 就失效了 -- 但是会返回下一个位置的迭代器//所以it = erase(it) 这样写就行了}}void push_back(const T& x) {
#if 0Node* tail = _head->_prev;//尾节点Node* newnode = new Node(x);//简单的链接关系tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;
#endif//复用insertinsert(end(), x);}void push_front(const T& x) {insert(begin(), x);}iterator insert(iterator pos,const T&x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);//prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}void pop_back() {erase(--end());}void pop_front() {erase(begin());}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;return iterator(next);//返回下一个位置的迭代器}/// <summary>/// 迭代器 -- 重点之重点/// </summary>/// <typeparam name="T"></typeparam>/// 我们怎么用++去调用迭代器呢/// C++的两个精华 -- 封装、运算符重载//// 首先肯定要重载*,要重载++private:Node* _head;};void print_list(list<int>&lt) {for (auto e : lt) {cout << e << " ";}cout << endl;}void test() {list<int>lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);lt.push_front(0);lt.push_front(-1);list<int>::iterator it = lt.begin();while (it != lt.end()) {cout << *it << " ";++it;}cout << endl;for (auto e : lt) {//相应的,它也能跑了cout << e << " ";}cout << endl;lt.pop_back();lt.pop_back();for (auto e : lt) {//相应的,它也能跑了cout << e << " ";}cout << endl;lt.pop_front();lt.pop_front();for (auto e : lt) {//相应的,它也能跑了cout << e << " ";}cout << endl;//find//在3之前插入一个30auto pos = find(lt.begin(), lt.end(), 3);if (pos != lt.end()) {//pos会失效吗?不会lt.insert(pos, 30);*pos *= 100; // 迭代器指向的是3}for (auto e : lt) {cout << e << " ";}cout << endl;}void func(const list<int>& lt) {//现在想去遍历一下//肯定是跑不了的 -- 需要const迭代器list<int>::const_iterator it = lt.begin();while (it != lt.end()) {//*it = 10;cout << *it << " ";++it;}}void test2() {list<int>lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);func(lt);}void test3() {//深浅拷贝问题list<int>lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);list<int>copy = lt;*lt.begin() = 10;print_list(lt);cout << endl;print_list(copy);//如果是浅拷贝的话两个都改了 -- 两个list的头都指向同一个哨兵位头节点 -- 同一个链表lt.clear();cout << endl;print_list(lt);lt.push_back(1);print_list(lt);}void test4() {list<int>lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);list<int>copy;copy = lt;print_list(lt);print_list(copy);*lt.begin() = 10;print_list(lt);print_list(copy);}//反向迭代器测试void test5() {list<int>lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);lt.push_back(6);lt.push_back(7);lt.push_back(8);print_list(lt);//反向迭代器测试list<int>::reverse_iterator rit = lt.rbegin();while (rit != lt.rend()) {cout << *rit << " ";++rit;}cout << endl;}
}
//现在要去实现反向迭代器
//普通思维:拷贝一份正向迭代器 -- 修改一下
//大佬思维:
//1.list可以这样实现,那vector那些的怎么办呢?
//  vector也可以像list一样弄一个struct 去重载
//  原来vector迭代器直接++是不用operator的,因为本来这个行为就可以符合规范
//  但是现在我们需要实现一个vector的反向迭代器,我们就可以operator一个++ 实际上是--_ptr
//2.复用! -- 见vector的新头文件

reverse_iterator.h

namespace yufc {//复用,迭代器适配器template<class Iterator, class Ref, class Ptr>struct __reverse_iterator {Iterator _cur;typedef __reverse_iterator<Iterator, Ref, Ptr> RIterator;__reverse_iterator(Iterator it):_cur(it) {}//为了对称,stl源码进行了一些操作RIterator operator++() { //迭代器++,返回的还是迭代器--_cur;//反向迭代器++,就是封装的正向迭代器--return *this;}RIterator operator--() {++_cur;//反向迭代器++,就是封装的正向迭代器--return *this;}Ref operator*() {//return *_cur;//为什么这里需要拷贝一下对象呢?//因为解引用只是取一下数据,迭代器位置肯定是不能变的 -- 变了肯定会出问题的auto tmp = _cur;--tmp;return *tmp;}Ptr operator->() {return &(operator*());}bool operator!=(const RIterator& it) {return _cur != it._cur;}};
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1//总结:适配器的特点就是 -- 复用!!!#include"MyList.h"#if 0
// 正向打印链表
template < class T>
void PrintList(const yufc::list<T>&l)
{auto it = l.cbegin();while (it != l.cend()){cout << *it << " ";++it;}cout << endl;
}
// 测试List的构造
void TestList1()
{yufc::list<int> l1;yufc::list<int> l2(10, 5);PrintList(l2);int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };yufc::list<int> l3(array, array + sizeof(array) / sizeof(array[0]));PrintList(l3);yufc::list<int> l4(l3);PrintList(l4);l1 = l4;PrintList(l1);PrintListReverse(l1);
}
// PushBack()/PopBack()/PushFront()/PopFront()
void TestList2()
{// 测试PushBack与PopBackyufc::list<int> l;l.push_back(1);l.push_back(2);l.push_back(3);PrintList(l);l.pop_back();l.pop_back();PrintList(l);l.pop_back();cout << l.size() << endl;// 测试PushFront与PopFrontl.push_front(1);l.push_front(2);l.push_front(3);PrintList(l);l.pop_front();l.pop_front();PrintList(l);l.pop_front();cout << l.size() << endl;
}
void TestList3()
{int array[] = { 1, 2, 3, 4, 5 };bite::list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto pos = l.begin();l.insert(l.begin(), 0);PrintList(l);++pos;l.insert(pos, 2);PrintList(l);l.erase(l.begin());l.erase(pos);PrintList(l);// pos指向的节点已经被删除,pos迭代器失效cout << *pos << endl;auto it = l.begin();while (it != l.end()){it = l.erase(it);}cout << l.size() << endl;
}
#endif#if 0
int main() {//yufc::test();//yufc::test2();//yufc::test3();//yufc::test4();yufc::test5();return 0;
}
#endif//forward_iterator
//bidirectional_iterator
//random_access_iterator
//其实它们是一种继承的关系
//双向是特殊的单向,随机是特殊的双向//源码其实比我们写的版本还复杂得多
//很多版本控制
//迭代器萃取那些也可以了解一下
//萃取 -- 可以提高效率
//比如迭代器要相减
//萃取技术(很复杂的一个技术)可以萃取出迭代器的类型
//比如:如果是随机迭代器类型就直接相减,如果是双向迭代器就要不断++算距离
//萃取就提高了效率

尾声

看到这里,相信大家对list类的模拟实现已经有一定的了解了!list的模拟实现,是我们掌握STL的开始,后面,博主将会给大家带来stack等等STL容器的模拟实现,持续关注,订阅专栏,点赞收藏都是我创作的最大动力。

转载时请注明作者和出处。未经许可,请勿用于商业用途 )
更多文章请访问我的主页

@背包https://blog.csdn.net/Yu_Cblog?type=blog

【STL源码剖析】list模拟实现 | 适配器实现反向迭代器【超详细的底层算法解释】相关推荐

  1. STL(C++标准库,体系结构及其内核分析)(STL源码剖析)(更新完毕)

    文章目录 介绍 Level 0:使用C++标准库 0 STL六大部件 0.1 六大部件之间的关系 0.2 复杂度 0.3 容器是前闭后开(左闭右开)区间 1 容器的结构与分类 1.1 使用容器Arra ...

  2. STL源码剖析 stack 栈 概述->(使用deque双端队列 / list链表)作为stack的底层容器

    Stack是一种先进后出的数据结构,他只有一个出口 stack允许 新增元素.移除元素.取得最顶端的元素,但是无法获得stack的内部数据,因此satck没有遍历行为 Stack定义的完整列表 (双端 ...

  3. STL源码剖析(一)STL简介

    STL源码剖析(一)STL简介 文章目录 STL源码剖析(一)STL简介 一.STL概述 二.STL六大组件 2.1 容器(containers) 2.2 算法(algorithms) 2.3 迭代器 ...

  4. 《STL源码剖析》笔记——allocator

    六大组件间关系 部分STL文件包含关系 allocator包含于中: 实际实现于三个文件 : 1.stl_construct.h :对象的构造和析构 2.stl_alloc.h空间配置和释放 3.st ...

  5. STL源码剖析之配接器

    adapter(配接器)在STL组件的灵活组合运用上,扮演者转换器的角色.adapter来源于一种适配器模式,其功能是:将一个class接口转换为另一个class的接口,使得原本因接口不兼容而不能合作 ...

  6. SGI STL源码剖析——空间配置器

    SGI STL源码剖析--空间配置器 前言 空间配置器 SGI空间配置器 内存配置和对象构造 构造和析构 空间的配置和释放 第一级配置器 第二级配置器 空间配置 重新填充 重中之重的内存池 前言    ...

  7. 【有点狂的手撕STL】STL源码剖析精读 000

    STL源码剖析精读 前言 通过刷题感受到了C++中STL的妙用,十分的想要提高自己对于STL的理解以及运用能力,因此开设此专栏,并希望能够带领大家一起感受C++中STL的魅力. 一.STL简介 STL ...

  8. STL源码剖析学习七:stack和queue

    STL源码剖析学习七:stack和queue stack是一种先进后出的数据结构,只有一个出口. 允许新增.删除.获取最顶端的元素,没有任何办法可以存取其他元素,不允许有遍历行为. 缺省情况下用deq ...

  9. 《STL源码剖析》学习-- 1.9-- 可能令你困惑的C++语法1

    最近在看侯捷的<STL源码剖析>,虽然感觉自己c++看得比较深一点,还是感觉还多东西不是那么明白,这里将一些细小的东西或者概念记录一下. 有些东西是根据<C++编程思想>理解的 ...

最新文章

  1. Java 基础搞定了,还能学点什么?
  2. Linux系统vi编辑器执行命令,linux下vi编辑器命令
  3. 使用SQL脚本创建数据库,操作主键、外键与各种约束(MS SQL Server)
  4. Scss、elementUI引入、transition动画 - 学习笔记
  5. 用状态机STATE MACHINE实现有选择的文件转换
  6. Python3 isspace()方法
  7. eclipse中编译运行maven项目使用jetty
  8. 通过对i8042 键盘控制器编程控制鼠标
  9. SAS means 过程帮助
  10. JavaWbe学习总结之jQuery
  11. CSDN会员服务协议
  12. 旧版的rust怎么老是掉线_RUST服务器进不去 RUST掉线用什么加速器解决?
  13. Thinkphp6 baiy/think-async redis 异步代码执行/异步延迟执行/异步事件订阅
  14. 如何提高团队开发质量
  15. 训练趣题:黑与白 有A、B、C、D、E五人,每人额头上都帖了一张黑或白的纸。(此处用javascript实现)...
  16. Nginx 重定向 80 到443
  17. MDK5的watch窗口变量删除问题
  18. vue3最简单的在线md编辑器
  19. DiscuzX 数据字典 超详细
  20. 单片机STC89C52RC实现矩阵键盘(汇编语言版)

热门文章

  1. 怎么把pdf图片转换成cad呢?两种简单方法
  2. 【Java网络编程】获得实验室局域网中所有开机主机名称和IP地址,InetAddress类,在java中如何获取IP地址的方法
  3. python视频格式转换_将ppt文件转成mp4视频的Python脚本
  4. JSPatch 使用Demo
  5. logit regression
  6. 财务自由现金管理方法
  7. H5图片预览及上传(WEB前端)
  8. “你手上有几个offer?”,该怎么回答?
  9. Java毕业设计:企业公司人事管理系统(java+springboot+vue+mysql)
  10. oppo 手机侧滑快捷菜单_OPPO新专利让人大开眼界:侧滑屏和弹出屏 手机副屏使用的新途径...