【STL源码剖析】list模拟实现 | 适配器实现反向迭代器【超详细的底层算法解释】
今天博主继续带来STL源码剖析专栏的第三篇博客了!
今天带来list的模拟实现!
话不多说,直接进入我们今天的内容!
前言
那么这里博主先安利一下一些干货满满的专栏啦!
这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!手撕数据结构https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482
这里是STL源码剖析专栏,这个专栏将会持续更新STL各种容器的模拟实现。算法专栏https://blog.csdn.net/yu_cblog/category_11464817.html
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迭代器要检查类型的
//如果不加下面几句用不了find
typedef 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 0
std::swap(_head, tmp._head);//直接换头指针就行了
#endif
swap(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 0
Node* tail = _head->_prev;//尾节点
Node* newnode = new Node(x);
//简单的链接关系
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
#endif
//复用insert
insert(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 cur
prev->_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><) {
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之前插入一个30
auto 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与PopBack
yufc::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与PopFront
l.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模拟实现 | 适配器实现反向迭代器【超详细的底层算法解释】的更多相关文章
- STL源码剖析 迭代器(iterator)概念与编程技法(三)
1 STL迭代器原理 1.1 迭代器(iterator)是一中检查容器内元素并遍历元素的数据类型,STL设计的精髓在于,把容器(Containers)和算法(Algorithms)分开,而迭代器(i ...
- STL源码剖析之序列式容器
最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的<STL源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...
- STL"源码"剖析-重点知识总结
STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...
- 【转载】STL"源码"剖析-重点知识总结
原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点 ...
- (原创滴~)STL源码剖析读书总结1——GP和内存管理
读完侯捷先生的<STL源码剖析>,感觉真如他本人所说的"庖丁解牛,恢恢乎游刃有余",STL底层的实现一览无余,给人一种自己的C++水平又提升了一个level的幻觉,呵呵 ...
- 《STL源码剖析》环境配置
首先,去侯捷网站下载相关文档:http://jjhou.boolan.com/jjwbooks-tass.htm. 这本书采用的是Cygnus C++ 2.91 for windows.下载地址:ht ...
- STL源码剖析读书笔记之vector
STL源码剖析读书笔记之vector 1.vector概述 vector是一种序列式容器,我的理解是vector就像数组.但是数组有一个很大的问题就是当我们分配 一个一定大小的数组的时候,起初也许我们 ...
- STL"源码"剖析
STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略 ...
- 《STL源码剖析》相关面试题总结
原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...
- STL源码剖析 — 空间配置器(allocator)
前言 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中. 你完全可以实现一个直接向硬件存取空间的allocator. 下面介绍的是SGI STL提供的配 ...
随机推荐
- Goolge Kick Start Round A 2020 (A ~ D题题解)
比赛链接:kick start Round A 2020 A. Allocation 题目链接 题意 给出 \(N\) 栋房子的价格,第 \(i\) 栋房子的价格为 \(A_i\),你有 \(B\) ...
- 传统与现代可视化 PK:再生水厂二维工艺组态系统
前言 随着可视化技术的进步与发展,传统再生水厂组态系统所展示的组态页面已逐渐无法满足当前现阶段多样化的展示手段.使得系统对污泥处理处置及生产运行成本方面的监控.分析方面较为薄弱,急需对信息化应用成果和 ...
- vue tabBar导航栏设计实现4-再次抽取MainTabBar
系列导航 一.vue tabBar导航栏设计实现1-初步设计 二.vue tabBar导航栏设计实现2-抽取tab-bar 三.vue tabBar导航栏设计实现3-进一步抽取tab-item 四.v ...
- uniapp解决图形验证码问题及arraybuffer二进制转base64格式图片
https://www.cnblogs.com/huihuihero/p/13183031.html
- MINGW64 禁用 Bash 路径参数转换
MINGW64 可以让 Windows 无缝使用 Linux 命令,但是路径参数会被转换为 Windows 风格.例如: $ ./adb shell ls /system ls: C:/Program ...
- Redis 中bitMap使用及实现访问量
1. Bitmap 是什么 Bitmap(也称为位数组或者位向量等)是一种实现对位的操作的'数据结构',在数据结构加引号主要因为: Bitmap 本身不是一种数据结构,底层实际上是字符串,可以借助字符 ...
- 基于python+django的外卖点餐网站-外卖点餐系统
该系统是基于python+django开发的外卖点餐系统.适用场景:大学生.课程作业.毕业设计.学习过程中,如遇问题可以在github给作者留言. 演示地址 前台地址: http://food.git ...
- 43 干货系列从零用Rust编写负载均衡及代理,内网穿透方案完整部署
wmproxy wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代 ...
- AHB-SRAMC Design-02
AHB-SRAMC Design SRAMC(另外一种代码风格)解析 SRAM集成,顶层模块尽量不要写交互逻辑 module ahb_slave_if( input hclk, input hrest ...
- 例2.6 设计一个高效的算法,从顺序表L中删除所有值为x的元素,要求时间复杂度为0(n)空间复杂度为0(1)。
1.题目 例2.6 设计一个高效的算法,从顺序表L中删除所有值为x的元素,要求时间复杂度为0(n)空间复杂度为0(1). 2.算法思想 3.代码 void DeleteX(SeqList LA, Se ...