list 是一个带头的双向循环链表
结点构造
参数用匿名对象,方便直接构造结点。
template<class T>
struct list_node
{
list_node<T>* _next;
list_node<T>* _prev;
T _data;
list_node(const T& val = T())
:_next(nullptr)
,_prev(nullptr)
,_data(val)
{}
};
迭代器的构造
迭代器本质上是结点的指针,但是 list 结点不是连续的,因此原生指针的特性必须由我们自己实现
迭代器是指针,结点不属于自己,不能释放结点,因此没有析构函数
参照源码逻辑:这里是写一个迭代器的模板,下面传参时可以根据类型的不同,来确定是实例化普通的迭代器还是 const 类型的迭代器。
用 self 代替模板,方便模板参数类型的替换
Ref 和 Ptr
注意 * 和 -> 这两个符号的重载:it 是一个 int*
*it = 10;
* 是对指针解引用,直接可以改变数据,因此返回的是数据的引用;
it->data;
-> 直接对指针指向的内容进行访问,返回的应该是存储数据的地址
而引用 Ref 和 Ptr 的目的就是为了区分迭代器类型
template<class T, class Ref, class Ptr>
struct _list_iterator
{
typedef list_node<T> Node;
typedef _list_iterator<T, Ref , Ptr> self;
Node* _node;
//没有析构函数--结点不属于迭代器,不能被迭代器释放
//拷贝构造和赋值重载--默认生成的浅拷贝就可以
_list_iterator(Node* node)
:_node(node)
{}
//T& 或者 const T&
Ref operator*()
{
return _node->_data;
}
//T* 或者 const T*
Ptr operator->()
{
return &(operator*());
}
self& operator++() //前置++
{
_node = _node->_next;
return *this;
}
self& operator++(int) //后置++
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
self& operator--() //前置--
{
_node = _node->_prev;
return *this;
}
self& operator--(int) //后置--
{
self tmp(*this);
_node = _node->_prevt;
return tmp;
}
bool operator!=(const self& it)
{
return _node != it._node;
}
bool operator==(const self& it)
{
return _node == it._node;
}
};
list 的对迭代器的实例化
在 list 中,通过控制模板参数的不同,来完成传参时,对不同迭代器的实例化。
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;
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
list()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
private:
Node* _head;
};
打印函数传参
void print_list(const list<int>& lt)
{
list<int>::const_iterator it = lt.begin();
while(it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
在构造 list 时,指定迭代器的三个模板参数类型,将 const 和普通迭代器区分开来;
例如当我们想打印出一个 list 中的数据时,给打印函数传过去的是 const list<int>& lt
那么此时用到 const_iterator 匹配到的就是如下模板:
typedef _list_iterator<T,const T&,const T*> const_iterator;
这样构建出的迭代器就不能修改数据了。
list 的构造函数
list 带头双向循环链表,我们在构造时主要是开辟头结点,令前后指针指向自身
empty_init 是初始化一个 list 的头结点
支持迭代器区间构造
现代写法完成拷贝构造
list()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
//拷贝构造传统写法
//list(const list<T>& lt)
//{
// _head = new Node();
// _head->_next = _head;
// _head->_prev = _head;
// for(auto e: lt)
// {
// push_back(e);
// }
//}
//
void empty_init()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
template<class InputIterator>
list(InputIterator first,InputIterator last)
{
empty_init();
while(first != last)
{
push_back(*first);
++first;
}
}
void swap(list<T>& lt)
{
std::swap(_head,lt._head);
}
//现代写法
list(const list<T>& lt)
{
empty_init();
list<T> tmp(lt.begin(),lt.end());
swap(tmp);
}
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
插入删除
尾插尾删和头插头删可以复用插入和删除
插入时总是插入在指定位置的前面,不会造成迭代器失效的问题
删除会造成迭代器失效,野指针问题,返回的是删除位置的下一个位置
void push_back(const T& x)
{
insert(end(),x);
}
void push_front(const T& x)
{
insert(begin(),x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
//插入在pos位置之前
void insert(iterator pos,const T& x)
{
Node* newnode = new Node(x);
Node* cur = pos._node;
Node* prev = cur->_prev;
newnode->_next = cur;
cur->_prev = newnode;
prev->_next = newnode;
newnode->_prev = prev;
}
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 next;
}
析构函数
调用析构函数前,先要将数据删除,再释放头结点
clear 可以删除全部结点,刚好保留头结点
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while(it != end())
{
it = erase(it);
}
}