目录
list的实现
简单造个轮子(版本:1.0)
list在STL中的结构是一个带头双向链表,首先创建结点结构体作为链表单元:
template<T>
struct Listnode
{
T _val;
Listnode<T>* _next;
Listnode<T>* _prev;
//构造函数
Listnode(const T& val=T()):_val(val),_next(nullptr),_prev(nullptr)
{}
}
构建链表类:
template<T>
class mylist
{
public:
typedef Listnode<T> node;
//构造函数
mylist()
{
_head=new node;//头结点开辟结点空间
_head->_next=_head;//尾指向自己
_head->_prev=_head;//头指向自己
}
//尾插结点
push_back(const T& x)
{
node* newnode=new node(x);//用结点的构造函数new一个新结点
node* tail=_head->_prev;
tail->_next=newnode;
head->_prev=newnode;
newnode->_next=_head;
newnode->_prev=tail;
}
private;
node* _head;//成员变量为哨兵结点指针
};
调试看下效果:
这个刚做的轮子可以跑,但是收不回来(没有析构函数),别人也不能复制(没有拷贝和赋值重载)。。。缺的还很多,慢慢来。
为了更好的控制这个轮子,我决定先为其建立一个迭代器。
🚩迭代器
🚩list的迭代器 iterator
vector和string都是连续空间的,所以他们的迭代器可以是原生指针,其进行++和–操作可以直观的理解为地址空间上的前进和后退。
但是list的各个结点大部分情况下都不连续,其原指针的++和–操作并不会让其自主的指向另一个结点处,而是越界访问了相邻的无关空间。
所以我们需要自己创建一个迭代器类,重载其操作符,比如使用–和++操作能让结点变为自身的前后结点。
template <T>
class list_iterator
{
public:
typedef Listnode<T> node;
typedef list_iterator<T> self;//改个名字方便调用
//这里依旧使用链表结点指针作为我们的成员变量
node* _pnode;
//构造函数
list_iterator(node* x=nullptr):_pnode(x)
{}
//不用自己再定义拷贝构造,赋值运算符重载和析构
//使用编译器默认的即可,先思考一下
//前置++
self& operator++()
{
_pnode=_pnode->_next;
return *this;
}
//后置++ 使用int占位,与前置重载产生区别
self operator++(int)
{
//方法一:
// self tmp(_pnode);//构造tmp,留下当前位置
// _pnode=_pnode->_next;
// return tmp;
//方法二:
self tmp(*this);//使用编译默认的拷贝构造
++(*this);//复用前置++
return tmp;
}
//前置--
self& operator--()
{
_pnode=_pnode->_prev;
return *this;
}
//后置--
self opeartor--(int)
{
self tmp(*this);
--(*this);
return tmp;
}
bool operator==(const self& x)const
{
return _pnode==x._pnode;
}
bool operator!=(const self& x)const
{
return _pnode!=x._pnode;
}
T& operator*()
{
return _pnode->_val;
}
//虽然返回的是指针,但是使用时编译器会优化掉一个->
T* opeartor->()
{
return &(_pnode->_val);
}
};
迭代器(指针)的意义就在于指向原本的空间进行访问和修改,而无需另行开辟空间,这也就说明我们没有深拷贝的需要。所以使用编译器提供的默认的拷贝函数和赋值运算符重载即可,我们也没有释放空间的需求故不必自己写析构函数去释放空间。
始终记住一点,迭代器不是链表,不具备开辟空间和释放空间的责任,不可越俎代庖。
有了迭代器类之后,在mylist类中加入迭代器,同时创建begin()和end()函数:
template<T>
class mylist
{
public:
typedef Listnode<T> node;
typedef list_iterator<T> iterator;
//此处省略1.0的函数
...
//迭代器相关函数
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
private:
node* _head;
};
有了迭代器便可以遍历链表,我们将链表输出:
🚩const_iterator
先抛出一个问题:面对const修饰的mylist对象时,是否能使用const修饰的iterator迭代器(const iterator)?
考虑下段函数,我们将传入一个对象的const引用,然后使用迭代器循环遍历输出:
void Print_mylist(const mylist<int>& l)
{
const mylist<int>::iterator it = l.begin();
while (it != l.end())
{
cout << (*it) << ' ';
++it;
}cout << endl;
}
由于传入的是是const对象,begin()函数需要用const修饰,于是我们特别重载一个接受const版本的begin()const函数,并且继续使用原先的迭代器。
const iterator begin()const
{
return iterator(_head->_next);
}
由于对于const对象的链表结点只能读不能修改,于是在迭代器类(list_iterator)中重载 operator*()
函数,使函数返回 const
的引用。
//可读写版本
T& operator*()
{
return _pnode->_val;
}
//只读不写版本
const T& operator*()const
{
return _pnode->_val;
}
请问这种迭代器是否能满足要求呢?
答案是否定的❌——因为 ++it
被限制住了。
原因:const修饰的是mylist链表对象,并非修饰迭代器。假如使用const修饰了迭代器,固然迭代器不会修改指向的内容,但是同样也不能使用++ --来修改自己的位置,更不必谈遍历了。
如果it不用const修饰,那么 *
运算符将进入可修改的重载版本,但是这样可以对const的对象内容进行修改不符条件。
分析:实际上,我们所希望看到的const迭代器的目的是指针指向的值不能修改, 也就是重载的"->“和”*"运算符的返回值不能被修改, 指针本身的指向是可以修改的,这样才能去遍历链表.
我们依旧可以使用++和–来遍历容器(如果你不使用这两个功能, 那就不需要另外实现一个 const迭代器啦)
(不能简单地认为const修饰的对象要用const修饰的迭代器去遍历,我们应限制指针的修改行为,而不是限制指针的活动)
针对const对象的迭代器
所以我们需要制作一个新的迭代器:const_list_iterator
——只是限制*和->符号(只读),但是可以左右移动遍历(++ ,–)。
template<class T>
class const_list_iterator
{
public:
typedef Listnode<T> node;
typedef const_list_iterator<T> self;
//解引用
const T& operator*()//返回const引用不能修改指向内容
{
return _pnode->_val;
}
const T* operator->();//返回的const T* 不能修改其指向的内容
//其余用于迭代器移动的函数与list_iterator类相同
}
同时mylist函数中的begin()函数需重载一份 const_list_iterator
版本
//传入的是const对象
typedef const_list_iterator<T> const_iterator
const_iterator begin()const
{
return const_iterator(_head->_next);
}
const_iterator end()const
{
return const_iterator(_head);
}
合并两个迭代器
上述新建的 const_list_iterator类
有大部分代码是与 list_iterator类
是冗余的,我们需要将两者整合起来。
⭐上面两个类的不同之处在于,当成员函数的返回是 模板参数T的引用(T*)和指针(T*)
时:
list_iterator类
——返回的是普通引用 👉🏻 T&
和普通指针 👉🏻 T*
const_list_iterator类
——返回的const引用 👉🏻 const T&
和const指针 👉🏻 const T*
那么我们可以在 list_iterator类
上多设置两个模板参数 Ref、Ptr
代表引用与指针,分别在链表类中声明,两个不同的声明将会在实例化时生成区别!
版本 2.0
//迭代器类
template <class T,class Ref,class Ptr>
class list_iterator
{
public:
typedef Listnode<T> node;
typedef list_iterator<T,Ref,Ptr> self;
//Ref 的用处
Ref operator*()
{
return _pnode->_val;
}
//Ptr的用处
Ptr operator->()
{
return &_pnode->_val;
}
//其余函数不做改变
...
}
//链表类
template <class T>
class list
{
public:
typedef Listnode<T> node;
typedef list_iterator<T,T&,T*> iterator;//声明iterator实例化的模板参数类型
typedef list_iterator<T,const T&,const T*> const_iterator;//声明const_iterator实例化的模板参数类型
...
}
很巧妙的做法!
⚠ 注意:->
的重载是经过编译器实际优化过的,否则it->返回地址的话,实际使用上是 it->->_val
。
🚩造一个好用的轮子(版本3.0)
有了迭代器,我们再添加一些函数:
构造函数
迭代器区间构造:
template <class InputIterator>
mylist(InputIterator first,InputIterator last)
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
while (first != last)
{
push_back(*first);
++first;
}
}
填充:
mylist(size_t n, const T& x=T())
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
while(n)
{
push_back(x);
n--;
}
}
//此构造会与上方的迭代器区间构造产生冲突——非法的间接寻址
//因为两个int会自动匹配到迭代器构造,而对一个int类型解引用会导致非法访问。
//增加重载版本——可匹配两个int的构造
mylist(int n, const T& x = T())
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
while (n)
{
push_back(x);
n--;
}
}
拷贝构造
写法一:
//拷贝构造 -尾插
mylist(const mylist& l):_head(new node)
{
_head->_next=_head;
_head->_prev=_head;
const_iterator it = l.begin();
while (it != l.end())
{
push_back((it++)._pnode->_val);
}
}
写法二:
//拷贝构造 ——复用迭代器的构造函数
mylist(const mylist& l)
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
mylist<T> temp(l.begin(), l.end());// 复用构造
//temp与this互换哨兵结点
swap(temp._head, _head);
}
赋值运算符重载
写法一:
//赋值重载——尾插
mylist<int>& operator=(const mylist<int>& l)
{
if (this != &l)//防止自己为自己赋值
{
clear();
for (auto e : l)
{
push_back(e);
}
}
return *this;
}
写法二:
//赋值重载——在传值中复用拷贝构造
mylist<int>& operator=(mylist<int> l)
{
swap(_head, l._head);
return *this;
}
clear 函数
删除结点,只保留哨兵
void clear()
{
iterator it = begin();
while (it != end())
{
delete ((it++)._pnode);
}
_head->_next = _head;
_head->_prev = _head;
}
insert 函数
iterator insert(iterator pos,const T& x)
{
node* cur = pos._pnode;
node* before_cur = cur->_prev;
node* newnode = new node(x);
newnode->_next = cur;
newnode->_prev = before_cur;
before_cur->_next = newnode;
cur->_prev = newnode;
return iterator(newnode);
}
earse 函数
iterator erase(iterator pos)
{
//不能删除哨兵
assert(pos != end());
node* before_pos = pos._pnode->_prev;
node* after_pos = pos._pnode->_next;
before_pos->_next = after_pos;
after_pos->_prev = before_pos;
delete pos._pnode;
return iterator(after_pos);
}
push_front pop_back pop_front 函数
void push_front(const T& x)
{
insert(begin(),x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
🚩反向迭代器 (版本4.0)
我们建立一个反向迭代器来适配所有的迭代器,而不是给mylist制造专属的反向迭代器。
//reverse_iterator.h
namespace my_std
{
template <class Iterator, class Ref, class Ptr>
class reverse_iterator
{
public:
typedef reverse_iterator<Iterator,Ref,Ptr> self;
//构造函数
reverse_iterator(Iterator it):current(it)
{}
// *运算符重载
Ref opeartor*()
{
Iterator tmp(current);
return *(--tmp);//保证反向迭代器使用的左闭右开
}
//-> 运算符重载
Ptr operator->()
{
Iterator tmp(current);
return &operator*();
}
//前置++
self& operator++()
{
--current;
return *this;
}
//前置--
self& operator--()
{
++current;
return *this;
}
bool operator==(const self& it)
{
return current==it.current;
}
bool operator!=(const self& it)
{
return current!=it.current;
}
private:
Iterator current;//反向迭代器的本质实为正向迭代器的反向操作
};
}
于是我们可以在链表类中添加反向迭代器
#include "reverse_iterator.h"
template<class T>
class mylist
{
public:
typedef Listnode<T> node;
//迭代器
typedef list_iterator<T,T&,T*> iterator;
typedef list_iterator<T,const T&,const T*> const_iterator;
//反向迭代器
typedef my_std::reverse_iterator<iterator, T&, T*> reverse_iterator;
typedef my_std::reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
...
//首尾 反向迭代器
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
const_reverse_iterator rbegin()const
{
return const_reverse_iterator(end());
}
const_reverse_iterator rend()const
{
return const_reverse_iterator(begin());
}
...
}
测试:
这样适配的反向迭代器同时可以使用到别的容器迭代器中:
我们用上次所写的myvector.h函数来演示:
//myvector.h
#include "reverse_iterator.h"
template<class T>
class myvector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
typedef my_std::reverse_iterator<iterator, T&, T*> reverse_iterator;
typedef my_std::reverse_iterator<const_iterator,const T&,const T*> const_reverse_iterator;
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
...
}
代码全貌
list的重点在于迭代器类,迭代器与链表的成员变量皆为结点指针,但是不同的方法,将给予类不同的运作方式,可以好好咀嚼一下感受类型的力量!
已上传至gitee👉🏻mylist
青山不改 绿水长流