当前位置:网站首页>[STL]list模拟实现
[STL]list模拟实现
2022-07-25 08:54:00 【Protein_zmm】
一、list源码学习
核心框架:
template <class T>
struct __list_node {
typedef void* void_pointer; // 结点指针
void_pointer next;
void_pointer prev;
T data;
};
template <class T, class Alloc = alloc>
class list {
protected:
typedef void* void_pointer;
typedef __list_node<T> list_node;
typedef list_node* link_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
public:
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
protected:
link_type node;
};
二、list模拟实现基本框架
namespace zmm
{
template <class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& data = T())
:_next(nullptr)
,_prev(nullptr)
,_data(data)
{
}
};
template <class T>
class list
{
typedef ListNode<T> Node;
public:
list()
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head; // 因为是双向循环链表
}
void push_back(const T& x)
{
Node* tail = _head->_prev;
Node* newNode = new Node(x);
tail->_next = newNode;
newNode->_prev = tail;
newNode->_next = _head;
_head->_prev = newNode;
}
private:
Node* _head;
};
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
}
}
在list中,我们如何去实现迭代器?此时it++就不是指向下一个位置了,因为链表每个结点可能存在的物理地址都不一样,这样子++就不对了,但是C++中提供了运算符重载。
__list_iterator<T>& operator++()
{
_node = _node->_next;
return *this;
}
// 后置++,返回++之前的值
__list_iterator<T>& operator++(int)
{
__list_iterator<T> tmp(*this);
_node = _node->_next;
return tmp;
}
bool operator!=(const __list_iterator<T>& it)
{
return _node != it._node;
}
因此综上,最初的基础框架就完成了
namespace zmm
{
template <class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& data = T())
:_next(nullptr)
,_prev(nullptr)
,_data(data)
{
}
};
template <class T>
struct __list_iterator
{
typedef ListNode<T> Node;
// typedef __list_iterator
Node* _node; // 构造出了结点
__list_iterator(Node* x) // 用结点的指针去构造迭代器
:_node(x)
{
}
T& operator*() // 可读可写
{
return _node->_data;
}
// 前置++,返回++之后的值
__list_iterator<T>& operator++()
{
_node = _node->_next;
return *this;
}
// 后置++,返回++之前的值
__list_iterator<T>& operator++(int)
{
__list_iterator<T> tmp(*this);
_node = _node->_next;
return tmp;
}
bool operator!=(const __list_iterator<T>& it)
{
return _node != it._node;
}
};
template <class T>
class list
{
typedef ListNode<T> Node;
public:
typedef __list_iterator<T> iterator;
// begin end都是用结点的指针去构造的迭代器
iterator begin()
{
// head->next就是第一个数据
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
list()
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head; // 因为是双向循环链表
}
void push_back(const T& x)
{
Node* tail = _head->_prev;
Node* newNode = new Node(x);
tail->_next = newNode;
newNode->_prev = tail;
newNode->_next = _head;
_head->_prev = newNode;
}
private:
Node* _head;
};
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
}
list模拟实现需要去实现拷贝构造、赋值重载(深拷贝)以及析构函数吗?
——不需要,使用默认生成的即可list<int>::iterator it = lt.begin();比如这句,就是浅拷贝,我们就是要让他们指向同一个位置,就是让节点的地址给it
析构也不需要去实现——迭代器是借助结点的指针去访问修改链表,结点属于链表,不属于迭代器,所以他不管释放,都不需要自己去实现
const迭代器
void print_list(const list<int>& lt) // 必须要加引用,否则就是浅拷贝
{
// 这里是没办法遍历的,lt是const对象,需要使用const迭代器
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
const迭代器与普通迭代器的区别:普通迭代器访问普通对象可读可写,而const只能读,那我们该如何去实现?
可以这样做吗?——不行
T& operator*() const
{
return _node->_data;
}
const T& operator*()
{
return _node->_data;
}
因为我们是容器为const,*it是迭代器去调用的,但迭代器还是原先的iterator,而非是const_iterator
所以还需要在实现一个const迭代器
template <class T>
struct __list_iterator
{
typedef ListNode<T> Node;
// typedef __list_iterator
Node* _node; // 构造出了结点
__list_iterator(Node* x) // 用结点的指针去构造迭代器
:_node(x)
{
}
__list_iterator<T>& operator++(int)
{
__list_iterator<T> tmp(*this);
_node = _node->_next;
return tmp;
}
// ...
};
template <class T>
struct __const_list_iterator
{
typedef ListNode<T> Node;
// typedef __list_iterator
Node* _node; // 构造出了结点
__const_list_iterator(Node* x) // 用结点的指针去构造迭代器
:_node(x)
{
}
const T& operator*() const
{
return _node->_data;
}
__const_list_iterator<T>& operator++()
{
_node = _node->_next;
return *this;
}
};
这样做我们就实现了一个普通的iterator和const_iterator迭代器,但是这样就有代码冗余了,所以还应该进一步的修改 template <class T, class Ref>增加了一个模板参数ref
之后修改为这个
// T& operator*() 原先版本
ref operator*()
{
return _node->_data;
}
在对class list进行修改
class list
{
typedef __list_iterator<T, T&> iterator;
typedef __list_iterator<T, const T&> const_iterator;
};
又加入了一个模板参数后,这样子就可以防止代码冗余

但是这里就算是const,也可以去用->去修改对象,所以我们要在传入一个参数Ptr
因此最终的框架就形成了:
template <class T, class Ref, class Ptr>
struct __list_iterator
{
typedef ListNode<T> Node;
// 由于类模板添加了一些新的参数,为了方便些,再次typedef
typedef __list_iterator<T, Ref, Ptr> self;
// typedef __list_iterator
Node* _node; // 构造出了结点
__list_iterator(Node* x) // 用结点的指针去构造迭代器
:_node(x)
{
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
};
template <class T>
class list
{
typedef ListNode<T> Node;
public:
typedef __list_iterator<T, T&, T*> iterator; // 普通迭代器返回T*
typedef __list_iterator<T, const T&, const T*> const_iterator; // // const迭代器返回T*
private:
Node* _head;
};
三、零散补充
3.1 vector与list区别
vector和list的区别
vector缺点:
连续的物理空间,是优势,也是一种劣势
优势:支持高效随机访问
劣势:
1、空间不够要增容, 增容代价比较大
2、可能存在一定的空间浪费,按需申请,会导致频繁的增容,所以一般会增容二倍左右(一般也不会造成每次都要增容)
3、头部或者中部插入删除需要挪动数据,效率低下(最致命的缺陷)
因此vector适合尾插尾删,随机访问
list很好地解决了vector的以上问题
1、list任意位置支持O(1)的插入删除(双向链表)
2、按需申请释放空间
因此list适合任意位置插入删除
本质上list和vector是两个互补的数据结构,在实际中,vector用的更多一些
STL中的所有容器都不保证线程安全
现在所提及的还只是链式结构,后面还有二叉树结构,哈希结构等
3.2 insert()
template <class T>
class list
{
typedef ListNode<T> Node;
public:
typedef __list_iterator<T, T&, T*> iterator; // 普通迭代器返回T*
typedef __list_iterator<T, const T&, const T*> const_iterator; // // const迭代器返回T*
private:
Node* _head;
};
// 这里insert后,pos会失效吗?——不会,插入数据后,pos位置没变
// pos位置前插入数据
iterator insert(iterator pos, const T& x) // 迭代器是一个对象,用.访问成员
{
// 返回值指向新插入的元素
Node* cur = pos._node; // 用迭代器拿到这个结点
Node* prev = cur->_prev;
Node* newNode = new Node(x);
prev->_next = newNode;
newNode->_prev = prev;
newNode->_next = cur;
cur->_prev = newNode;
return iterator(newNode);
}
// vector失效原因有两点,一点是扩容会导致失效,第二点是插入了以后,pos指向的值就变了
因此insert是不会有迭代器失效的问题
3.3 erase()
// erase之后pos会失效吗——会失效,pos指向的节点就没了
iterator erase(iterator pos)
{
assert(pos != end()); // 不能erase哨兵位
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
delete pos._node;
prev->_next = next;
next->_prev = prev;
return iterator(next);
}
修改版:
void clear()
{
// 清除掉链表里面所有的数据(不删除哨兵位)
iterator it = begin();
while (it != end())
{
/*iterator del = it++; // 双向迭代器不支持it+1,随机迭代器才可以 delete del._node;*/
// 也可以去复用erase
iterator del = it++;
erase(del);
// 也可以直接用erase(it++)去替换上面两行
}
_head->_next = _head;
_head->_prev = _head;
}
3.4 深拷贝
传统写法
// lt2(lt1)
list(const list<T>& lt) // lt就是lt1
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
// 传统写法:
for (auto e : lt)
{
// push_back前提是要初始化后才可以使用
push_back(e); // this的pushback
}
}
// lt2 = lt1
list<T>& operator=(const list<T>& lt)
{
if (this != <)
{
clear(); // 清除l2的所有数据也就是this
for (auto e : lt) // 遍历lt1
{
push_back(e);
}
}
return *this;
}
上面拷贝构造函数还是有问题,_head是随机值,与他交换后就会蹦,因此改为下面这种写法
list(const list<T>& lt)
{
_head = new Node();
_head->_prev = _head;
_head->_next = _head;
list<T> tmp(lt.begin(), lt.end());
std::swap(_head, tmp._head);
}
在添加一个构造函数
// 这里最好给个缺省值,因为val可能是日期类,vector等等
list(size_t n, const T& val = T()) // 用n个val去初始化和一个迭代器区间初始化可能存在冲突
{
_head = new Node();
_head->_prev = _head;
_head->_next = _head;
for (size_t i = 0; i < n; ++i)
{
push_back(val);
}
}

这里有两个构造函数,如果一个函数去调用他们,是去调用哪一个?——去调用类型最匹配的那一个
四、反向迭代器
反向迭代器的源码在stl_iterator.h文件中
template <class Iterator>
class reverse_iterator
{
protected:
Iterator current;
public:
typedef typename iterator_traits<Iterator>::iterator_category
iterator_category;
typedef typename iterator_traits<Iterator>::value_type
value_type;
typedef typename iterator_traits<Iterator>::difference_type
difference_type;
typedef typename iterator_traits<Iterator>::pointer
pointer;
typedef typename iterator_traits<Iterator>::reference
reference;
typedef Iterator iterator_type;
typedef reverse_iterator<Iterator> self;
public:
reverse_iterator() {
}
explicit reverse_iterator(iterator_type x) : current(x) {
}
reverse_iterator(const self& x) : current(x.current) {
}
#ifdef __STL_MEMBER_TEMPLATES
template <class Iter>
reverse_iterator(const reverse_iterator<Iter>& x) : current(x.current) {
}
#endif /* __STL_MEMBER_TEMPLATES */
iterator_type base() const {
return current; }
reference operator*() const {
Iterator tmp = current;
return *--tmp;
}
self& operator++() {
--current;
return *this;
}
self operator++(int) {
self tmp = *this;
--current;
return tmp;
}
self& operator--() {
++current;
return *this;
}
self operator--(int) {
self tmp = *this;
++current;
return tmp;
}
可以发现,反向迭代器里面是包含了正向迭代器,反向迭代器的++就是调用正向迭代器的–,因此反向迭代器实际上就是整形迭代器的一种封装
reference operator*() const {
Iterator tmp = current;
return *--tmp;
}
解引用是取的正向迭代器的前一个位置,为什么会这样子?
查看stl_list.h源码可以发现:
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
在传统上,我们定义rend和rbegin是这样的:
但是在源码中,定义的却不是这样子,而是下面的情况
这样设计的意义就是:
正向迭代器的开始就是反向迭代器的结束
正向迭代器的结束就是反向迭代器的开始
reverse_iterator rit = rbegin();
while (rit != rend())
{
*rit;
++rit;
}

这样子rit到达rend的时候,就停止了
因此operator*取前一个位置,主要目的就是为了让反向迭代器的开始和结束与正向迭代器对称
反向迭代器与正向迭代器的区别就是++、--的方向是相反的,所以反向迭代器封装正向迭代器即可,控制重载++、--的方向
反向迭代器的完整实现:
#pragma once
namespace zmm
{
template <class Iterator, class Ref, class Ptr>
class reverse_iterator
{
typedef reverse_iterator<Iterator, Ref, Ptr> self;
public:
reverse_iterator(Iterator it)
:_it(it)
{
}
// 反向迭代器的解引用为什么是返回前一个位置?
Ref operator*()
{
Iterator prev = _it;
return *--prev;
}
Ptr operator->()
{
return &operator*();
}
self& operator++()
{
--_it; // 反向迭代器的++是倒着走的
return *this;
}
self& operator--()
{
++_it;
return *this;
}
bool operator!=(const self& rit) const
{
return _it != rit._it;
}
private:
Iterator _it; // _it是一个正向迭代器,可以是任意容器的正向迭代器,是一个模板
};
}
五、适配器
适配器也叫作配接器
Iterator是哪个容器的迭代器,reverse_iterator就可以是配出哪个容器的反向迭代器——复用的体现
凡是要取模板参数的内嵌类型都是需要添加typename的
typedef reverse_iterator<Iterator, Ref, Ptr> self;
// 上面三个模板参数太多了,因此我们是可以去使用内嵌类型的
typedef typename Iterator::Ref Ref // 告诉编译器Iterator::Ref是一个类型,等类模板实例化之后在取
typedef typename Iterator::Ptr Ptr
加typename的场景:
vector<int>::iterator it; // 此时类模板已经实例化了,是int类型,所以不需要加typename
list<int>::iterator it;
typename vector<T>::iterator it; // 等类模板实例化之后再去取iterator
typename list<T>::iterator it;
边栏推荐
- This week's big news | FCC exposed Pico 4 VR all-in-one machine, and leipeng's parent company established a smart glasses laboratory
- 提高代码可续性的小技巧,以connectTo方法为例。
- Source code of short video live broadcast system
- Redis学习笔记
- Practice of establishing dynamic programming state transition equation
- How to choose a low code software development platform?
- 【npm】 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
- canvas动态图片头像晃动js特效
- 360 degree drag panorama plug-in tpanorama.js
- NVIDIA可编程推理加速器TensorRT学习笔记(二)——实操
猜你喜欢

How can hospitals achieve efficient and low-cost operation and maintenance? Is there any software that can meet it?

Redis学习笔记

Wechat applet ordering system graduation design of applet completion works (8) graduation design thesis template

Wechat reservation of small program completion works (5) assignment book of small program graduation project

Wechat reservation of completed works of applet graduation project (4) opening report

Huawei device remote login (Telnet, SSH) configuration

51 MCU peripherals: buzzer

Arcgis10.2 installation tutorial

【芝麻街一家】& Bert Bart RoBERTa

Wechat reservation of the completed works of the applet graduation project (6) opening defense ppt
随机推荐
(self drawn ugly picture) simple understanding tcp/ip three handshakes and four waves
Wechat reservation applet graduation design of applet completion works (1) development outline
Yolov5 environment configuration
Solutions to ten questions of leetcode database
25 Ph.D. degrees revoked
酷炫canvas动画冲击波js特效
BGP border gateway protocol basic knowledge points
canvas很多圆圈组成的文本js特效
Wechat reservation applet graduation design of applet completion works (2) applet function
Swift初始化器及可选链
Graduation project of wechat small program ordering system of small program completion works (1) development outline
51单片机内部外设:定时器和计数器
Recursive call to print every bit of an integer
Centernet network structure construction
Swift initializer and optional chain
[graduation project] cinema booking management system based on micro Service Framework
附加:在在下部分区/县(数据表)
When testing VPN, the IP found by the command line is inconsistent with that of Baidu search
flink sql怎么持久化?
附加:中半部分sql语句 区/县(数据表)