【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容器的模拟实现,持续关注,订阅专栏,点赞收藏都是我创作的最大动力。
转载时请注明作者和出处。未经许可,请勿用于商业用途 )
更多文章请访问我的主页