目录
一、定义ListNode结点类
因为list容器是用双向循环链表来实现的,所以每一个结点都有指向下一个结点的next指针和指向上一个结点的prev指针;
template<class T>
struct ListNode
{
T _value;
ListNode<T>* _next;
ListNode<T>* _prev;
ListNode(const T& val = T())
:_value(val)
, _next(nullptr)
, _prev(nullptr)
{}
};
二、封装一个迭代器类
这里为什么要自己封装一个迭代器类呢?
首先list容器中的结点不是连续存储的,那么如果使用ListNode*类型的迭代器的话,++操作并不能移动到下一个结点,所以需要我们自己封装一个迭代器类;
这个迭代器类的原理是:将指向这个结点的指针封装成一个迭代器类,这样在进行++操作时,就可以通过指针来获取下一个结点的位置,从而实现++功能了;
迭代器的参数也是通过模板来接收的,可以看到模板参数有三个:T,Ptr,Ref;
T:结点指针的类型;
Ptr:T*或者const T*类型,是->运算符重载函数operator->的返回值类型;
Ref:T&或者const T&类型,是解引用运算符重载函数operator*的返回值类型;
使用三个模板参数的好处是:当使用const迭代器时,不需要我们自己再封装一个const迭代器的类,只需要通过这个模板参数来区别const和非const迭代器就可以;
template<class T, class Ptr, class Ref>
class ListIterator
{
public:
typedef ListNode<T> Node;
typedef ListIterator<T, Ptr, Ref> Self;
Node* _node;
//构造
ListIterator(Node* node)
:_node(node)
{}
//这里不需要显示写出拷贝构造和赋值运算符重载还有析构函数
//因为迭代器类是封装的指向结点的指针,并没有额外的资源,所以只需要默认的浅拷贝即可
//解引用 operator*
Ref operator*()
{
//解引用返回的是结点的数值
return _node->_value;
}
//前置operator++
Self& operator++()
{
//迭代器向后移动
_node = _node->_next;
return *this;
}
//后置operator++
Self operator++(int)
{
//保存原来的位置
Self tmp = *this;
//将该迭代器向后移动
_node = _node->_next;
return tmp;
}
//前置operator--
Self& operator--()
{
_node = _node->_prev;
return *this;
}
//后置operator--
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
//operator!=
bool operator!=(const ListIterator& it)
{
//判断两个迭代器对象里面的_node成员是否相等
return _node != it._node;
}
//operator==
bool operator==(const ListIterator& it)
{
return _node == it._node;
}
//operator-> 理论上是:it.operator->()->_year;实际上是:it->_year;省略了一个->
Ptr operator->()
{
//返回的是一个指向数据的指针
return &(_node->_value);
}
};
三、模拟实现list容器
(一)定义
此处标明:以下成员函数都是写在my_list类内部的
template<class T>
class my_list
{
public:
typedef ListNode Node;
typedef ListIterator<T, T*, T&> iterator;
typedef ListIterator<T, const T*, const T&> const_iterator;
//成员函数
private:
Node* _header;
};
(二)构造函数
//构造
my_list()
{
_header = new Node;
//形成循环结构
_header->_next = _header->_prev = _header;
}
(三)拷贝构造函数
//拷贝构造
my_list(const my_list<T>& lst)
{
//创建新的头结点
_header = new Node;
_header->_next = _header->_prev = _header;
//将lst中的每个结点值插入到新的my_list中
for (const auto& e : lst)
{
push_back(e);
}
}
(四)赋值运算符重载函数
这里赋值运算符重载使用了简便的写法,通过接收参数时的拷贝,再直接将两个对象的头指针交换,就可以直接完成赋值的深拷贝操作;
//赋值运算符重载
my_list<T>& operator=(my_list<T> lst)
{
//交换两个头指针
swap(_header, lst._header);
return *this;
}
(五)析构函数
//析构
~my_list()
{
clear();
delete _header;
}
(六)迭代器
这里的返回值都是需要调用迭代器构造函数来构造一个迭代器对象返回的;
iterator begin()
{
//返回头结点下一个结点的迭代器对象
return iterator(_header->_next);
}
iterator end()
{
//返回头结点的迭代器对象
return iterator(_header);
}
const_iterator begin() const
{
//返回头结点下一个结点的迭代器对象
return const_iterator(_header->_next);
}
const_iterator end() const
{
//返回头结点的迭代器对象
return const_iterator(_header);
}
(七)Capacity
//Capacity
//获取结点个数
size_t size() const
{
size_t count = 0;
for (const auto& e : *this)
{
count++;
}
return count;
}
//判空
bool empty() const
{
return _header->_next == _header;
}
(八)元素访问Access
//Access
//获取最后一个元素
T& back()
{
return _header->_prev->_value;
}
//获取第一个元素
T& front()
{
return _header->_next->_value;
}
const T& back() const
{
return _header->_prev->_value;
}
const T& front() const
{
return _header->_next->_value;
}
(九)Modify
1、插入
(1)在任意结点前插入
因为list里的结点不是连续存储的,所以插入之后迭代器并不会失效,还是只想原来的结点;
插入的位置传入的是迭代器,所以也不用考虑越界的问题;
//任意位置前插入
void insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newNode = new Node(val);
prev->_next = newNode;
newNode->_prev = prev;
newNode->_next = cur;
cur->_prev = newNode;
}
(2)头插、尾插
头插和尾插都是对insert的一个复用
//尾插
void push_back(const T& val)
{
insert(end(), val);
}
//头插
void push_front(const T& val)
{
insert(begin(), val);
}
2、删除
(1)删除任意位置的元素
与插入不同,删除会导致当前的迭代器失效,所以返回值是当前位置的下一个位置的迭代器;
当然,不能够删除头结点,所以当传入的删除位置是头结点的位置时,不做任何操作;
//删除任意位置元素,返回值:被删除数据的下一个位置
iterator erase(iterator pos)
{
//不能删除end迭代器指向的数据,即_header节点
if (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);
}
//等于end,则返回,不作任何操作
return pos;
}
(2)头删、尾删
头删和尾删是对erase的一个复用
//尾删
void pop_back()
{
erase(--end());
}
//头删
void pop_front()
{
erase(begin());
}