list是一个双向链表结构
每一个节点(_node)都包含_prev节点,_next节点和数据_date,这说明_node的类型将会是一个结构体。
list的end和begin是相互链接的,我们将end移动前面便于理解,如图
_node结构体:
struct _list_node
{
_list_node*_prev;
_list_node*_next;
T _date;
_list_node(T val=T()) //默认构造函数,将成员初始化
:_prev(nullptr)
,_next(nullptr)
,_date(val)
{}
}
迭代器的成员:
list的访问不同于vector用operator[]重载函数下标直接访问,我们需要获取节点的指针进行访问,此外还要实现指针的++,解引用,!=,等函数,就将这些封装成迭代器_list_iterator,其成员对象就是Node*_node,也就是节点的地址,其他的成员函数实现访问功能。
为了后面const与非const变化使用,我们需要一个模板
1.模板的使用
template<class T ,class Ref, class Ptr>
typedef _list_iterator<T, Ref, Ptr> self;
typedef _list_iterator<T,T&,T*> iterator;
typedef _list_iterator<T,const T&,const T*> const_iterator;
当我们创建了一个迭代器类型的变量 it
1.1
iterator it;
此时的 it 调用的就是第3行的模板,当用Ref返回it中存储的值引用(it._node->_date)的时候,Ref对应的就是T&,允许通过改变返回值改变节点中的_date。
Ref operator*()
{
return _node->_date;
}
当用Ptr返回it中存储的_node(该节点的地址)(it._node->)的时候,Ptr对应的就是T*。
1.2
const_iterator it;
此时的 it 调用的就是第4行的模板,当用Ref返回it中存储的值引用(it._node->_date)的时候,Ref对应的就是const T&,不允许通过改变返回值改变节点中的_date。
当用Ptr返回it中存储的_node(该节点的地址)(it._node->)的时候,Ptr对应的就是const T*。
总结:我们只要在创建迭代器变量的时候确定是const_iterator还是iterator类型,后序若是不小心改变了const修饰对象的引用,系统也会报错。
2._list_iterator成员函数
namespace GuYu
{
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 = nullptr)
:_node(node)
{}
// 前置++
self& operator++() // 先++,再返回,返回的是加过后的迭代器,不需要创建临时变量,所以可以引用返回
{
_node = _node->_next;
return *this; // *this=it,it中存储的_node已经变为下一个_node(_node->_next)
}
// 后置++
self operator++(T)
{
_list_iterator tem(this->_node);
_node = _node->_next;
return tem; // 有拷贝函数的参与 (开辟空间,将tmp的值拷贝到新开辟的空间中) // 出函数,tem销毁
}
// 前置--
self& operator--()
{
_node = _node->_prev;
return *this;
}
// 后置--
self operator--(T)
{
_list_iterator tem(this->_node);
_node = _node->_prev;
return tem; // 有拷贝函数的参与
}
Ref operator*() // 传了this指针(&it)
{
return _node->_date; // this->_node->_next
}
bool operator!=(const _list_iterator& lis)
{
return _node != lis._node;
}
};
}
3.list的实现
3.1 list默认构造函数
list()
{
_head = new Node();
_head->_prev = _head;
_head->_next = _head;
}
3.2 list的begin()函数
const_iterator begin()const
{
return const_iterator(_head->_next);
}
在return的时候发生了一次创建临时变量,等效于以下写法
const_iterator begin()const
{
iterator it(_head->_next); // 可以替换 但是所有调用共享一个 it 影响后序结果
return it;
}
3.3 push_back()
void push_back(T val)
{
Node* newnode = new Node(val);
Node* nodetail = _head->_prev; // 找到最后一个节点
_head->_prev = newnode;
newnode->_next = _head;
newnode->_prev = nodetail;
nodetail->_next = newnode;
}
3.4 基本框架
namespace GuYu
{
template<class T>
// 各种获取值(_date)和节点(_node)在struct _list_iterate 已经完成,在struct list用不到Ref Ptr
struct list
{
typedef _list_node<T> Node;
typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, const T&, const T*> const_iterator;
list()
{
_head = new Node();
_head->_prev = _head;
_head->_next = _head;
}
~list()
{
this->clear();
_head = nullptr;
}
const_iterator begin()const
{
return const_iterator(_head->_next);
}
iterator begin()
{
return iterator(_head->_next);
}
const_iterator end()const
{
return const_iterator(_head);
}
iterator end()
{
return iterator(_head);
}
void push_back(T val)
{
Node* newnode = new Node(val);
Node* nodetail = _head->_prev; // 找到最后一个节点
_head->_prev = newnode;
newnode->_next = _head;
newnode->_prev = nodetail;
nodetail->_next = newnode;
}
list(const list<T>& lis)
{
_head = new Node();
_head->_prev = _head;
_head->_next = _head;
const_iterator it = lis.begin();
while (it != lis.end())
{
this->push_back(*it);
it++;
}
//for (auto e : lis)
//{
// this->push_back(e);
//}
}
iterator erase(iterator it) // 返回删除迭代器的下一个迭代器,也就是_node->_next
{
Node* cur = it._node;
//获取下一节点的迭代器
iterator itnext(cur->_next);
// 将前后node链接
cur->_prev->_next = cur->_next;
cur->_next->_prev = cur->_prev;
// 删除it位置的node
delete it._node;
return itnext;
}
void clear()
{
iterator it = this->begin();
while (it != this->end())
{
erase(it++); // 调用了++重载函数 it先++,返回的是原来的it // 返回的是临时对象的值 用erase传值而不是引用
}
//最后会只剩下_head ( end() )
}
list<T>& operator=(const list<T>& lis) // 传的list要与迭代器it保持一致的const , 否则找不到合适的begin()和end()
{
if (this == &lis)
{
return *this;
}
//首先要清空this 中的数据,只保留_head用于尾插
this->clear();
const_iterator it = lis.begin();
while (it != lis.end())
{
this->push_back(*it);
it++;
}
}
// 为什么不传引用 1. 避免意外发生,将数据改变 2.STL的一致性,erase splice 都采用迭代器传值 3.迭代器通常很小,传值的开销与传引用的开销几乎相同
iterator insert(iterator it,T val)
{
Node* newnode = new Node(val); //创建新节点,并赋值_date
Node* cur = it._node;
Node* prev = cur->_prev;
// 将新建的节点插入 cur 和 prev 两个节点之间
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode); // 传值返回插入的新节点
}
void print()const
{
const_iterator it = this->begin();
while (it != this->end())
{
cout << *it << endl;
++it; // ++it 减少空间拷贝
}
}
private:
Node* _head;
};
}
4.测试结果
5. list的优点缺点
优点:任意位置插入删除数据效率高,o(1)
缺点:不支持随机访问
list 可以与vector相辅相成,将list的数据拷贝到vector中,再支持排序,二分查找,堆算法。