list文档介绍
https://cplusplus.com/reference/list/list/?kw=list
list的底层结构
cpp
template<class T>
class list {//list本体的结构
typedef listNode<T> Node;
typedef list_Iterator<T> iterator;//迭代器
public:
//函数实现
......
private:
Node* head;//头节点
size_t size;//链表的有效结点个数(不包含哨兵位)
};
list的使用
list构造及迭代器使用
此处我们可以将迭代器认为是指针,这个指针指向链表的某个节点
|------------------------------------------------------------------------------------|-------------------------------------------------------------|
| 构造函数(constructor) | 接口说明 |
| list (size_type n, const value_type& val = value_type()) | 构造的 list 中包含 n 个值为 val 的 元素 |
| list() | 构造空的 list |
| list (const list& x) | 拷贝构造函数 |
| list (InputIterator first, InputIterator last) | 用 [first, last) 区间中的元素构造 list |
|-------------|--------------------------------------------------------------------------------------------------------------------------------|
| 函数声明 | 接口说明 |
| begin+end | 返回第一个元素的迭代器 + 返回最后一个元素下一个位置的迭代器 |
| rbegin+rend | 返回第一个元素的 reverse_iterator, 即 end 位置 , 返回最后一个元素下一个位 置的 reverse_iterator, 即 begin 位置 |
cpp
list<int>it(5, 1);
for (auto e : it)
cout << e << " ";
list<int>it1(it.begin(), it.end());
for (auto e : it1)
cout << e << " ";
list<int>it2(2);
list<int>it3(it2);//拷贝构造
for (auto e : it2)
cout << e << " ";
list的空间问题
|-----------|-----------------------------------------------------------|
| empty | 检测 list 是否为空,是返回 true ,否则返回 false |
| size | 返回 list 中有效节点的个数 |
cpp
list<int> v(5,1);
list.empty();//判断是否为空
list.size();
list的元素获取
|-----------|----------------------------------|
| 函数声明 | 接口说明 |
| front | 返回 list 的第一个节点中值的引用 |
| back | 返回 list 的最后一个节点中值的引用 |
cpp
list<int> v(5,1);
list.front();//获取第一个元素1
list.back();//获取最后一个元素1
list的修改
|------------|------------------------------------------------|
| 函数声明 | 接口说明 |
| push_front | 在 list 首元素前插入值为 val 的元素 |
| pop_front | 删除 list 中第一个元素 |
| push_back | 在 list 尾部插入值为 val 的元素 |
| pop_back | 删除 list 中最后一个元素 |
| insert | 在 list position 位置中插入值为 val的元素 |
| erase | 删除 list position 位置的元素 |
| swap | 交换两个 list 中的元素 |
| clear | 清空 list 中的有效元素 |
cpp
list<int>lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(5);
lt.push_back(5);
for (auto e : lt)
cout << e << " ";
lt.emplace_back((2, 3));
for (auto e : lt)
cout << e << " ";
reverse(lt.begin(), lt.end());
for (auto e : lt)
cout << e << " ";
//去重 前提已经排好序
lt.unique();
//排序
lt.sort();
//删除数据
lt.erase(lt.begin());//只支持迭代器,如果要删除中间的数据,只能先遍历
//插入
lt.insert(lt.begin(), 4);
lt.insert(lt.begin(), 4,3);//插入4个3的数
list<int>it;
it.insert(it.begin(),lt.begin(), lt.end());//迭代器插入
lt.remove(5);//删除一个值,但不需要迭代器,有就删除,没有不影响
it.merge(lt);//合并两个链表,按升序
迭代器失效问题
与前面的vector与string一样,链表也会出现迭代器失效问题
迭代器失效即迭代器所指向的节点的无 效,即该节点被删除了 。因为 list 的底层结构为带头结点的双向循环链表 ,因此 在 list 中进行插入 时是不会导致 list 的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭 代器,其他迭代器不会受到影响 。
cpp
void TestListIterator1()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> l(array, array+sizeof(array)/sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{
// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
l.erase(it);
++it;
}
}
// 改正
void TestListIterator()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> l(array, array+sizeof(array)/sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{
l.erase(it++); // it = l.erase(it);
}
}
list的模拟实现
节点结构的基本模板
cpp
template<typename T>
struct ListNode {
T _data;
ListNode<T>* _prev;
ListNode<T>* _next;
ListNode(const T& val = T())
//初始化列表
: _data(val)
, _prev(nullptr)
, _next(nullptr)
{}
};
list功能的基本结构
cpp
template <class T>
class list {
typedef list_node<T> Node;//
public:
/*typedef list_iterator<T> iterator;
typedef list_cnost_iterator<T> const_iterator;*/
typedef list_iterator <T,T&,T*> iterator;
typedef list_iterator <T,const T&,const T*> const_iterator;
void clear()
{
auto node = begin();
while (node != end())
{
node = erase(node); //就是这里,你没有接受下一个迭代器,导致使用失效的迭代器造成死循环了
}
}
void emInit()
{
_head = new Node;
_head->_next = _head->_prev = _head;
}
list(const list<T>& x)
{
emInit();
for (auto& e : x)
push_back(e);
}
void swap( list<T> x)
{
std::swap(_head, x._head);
std::swap(_size, x._size);
}
list<T>&operator=(const list<T> x)
{
swap(x);
return *this;
}
~list()
{
clear(); //应该是这里边死循环了-所以析构不完,函数没返回
delete _head;
_head = nullptr;
}
iterator begin()
{
//还需要一个构造函数构造
////有名对象
//iterator it(_head->_next);
//return it;
////匿名对象
//retutn iterator(_head->_next);
//隐式转换
return _head->_next;
}
const_iterator begin()const
{
return _head->_next;
}
const_iterator end()const
{
return _head;
}
iterator end()
{
//return _head->_prev;
return _head;
}
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;
--_size;
//清理完了,应该往下一个位置走
return next;
}
void pop_front()
{
erase(begin());
}
void pop_back()
{
erase( end());
}
list(){
_head = new Node(T());//在堆上开辟一个NOde类型的节点,并将该节点的地址赋给_head指针 有隐式this指针
_head->_next = _head;
_head->_prev = _head;
}
bool empty()
{
return _size == 0;
}
void push_back(const T& x)
{
/*Node* newnode = new Node(x);
Node* tail = _head->_prev;
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_next = newnode;*/
insert(end(), x);
}
void push_front(const T& x) {
insert(begin(), x);
}
void insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
prev->_next=newnode;
++_size;
}
private:
Node* _head;
size_t _size;
};
迭代器的模拟实现
cpp
template <class T,class Ref, class Ptr>
struct list_iterator
{
typedef list_node<T> Node;
typedef list_iterator<T,Ref,Ptr> Self;//iterator 其实还是节点的指针,但直接使用不可,所以进行封装,运算符重载
Node*_node;
//开辟一个空间,来接收传的节点,进行下面begin end的构造
//指针走默认构造 值拷贝不会有问题
list_iterator(Node*node)
:_node(node)
{ }
Ref operator*()
{
return _node->_data;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
//返回当前数据的data地址
Ptr operator->()
{
return &(_node->_data);
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator++(int)
{
Self temp(*this);//提前保存原来的节点
_node = _node->_next;
return temp;
}
Self operator--(int)
{
Self temp(*this);//提前保存原来的节点
_node = _node->_prev;
return temp;
}
};
list与vector的对比
|-------------------------|----------------------------|----------------------|
| | vector | list |
| 底 层 结 构 | 动态顺序表,连续空间 | 带头循环链表,非连续空间 |
| 随机访问 | 支持,时间复杂度可为O(1) | 不支持,访问时间复杂度为O(N) |
| 插入与删除 | 效率低,需要搬运元素,且涉及插入时空间不足,需要扩容 | 不需要搬运元素,时间复杂度可为O (1) |
| 空间利用率 | 物理上为连续空间,缓存利用率高 | 非连续空间,利用率低 |
| 迭代器 | 原生态指针 | 对原生态指针进行封装, 但还是节点的地址 |
| 迭代器失效 | 插入与删除都有可能 | 仅删除 |
| 使用场景 | 需要高效储存,大量连续访问时 | 需要大量插入数据, 非大量随机访问 |
vector插入失效原因:可能会扩容,导致旧的迭代器指向的空间被释放
删除失效原因:删除后,vector内部要搬运元素,会导致原指向被删元素或其后方元素的迭代器,会变成 "悬空" 状态。
list删除失效原因:底层是双向链表(不用搬动数据),删除元素只会导致指向被删除元素的迭代器失效,其他元素的不会
性能优化
使用vector时,可提前使用reserve进行开辟空间,避免频繁开辟空间,影响效率
使用list时,要善用list的sort与merge等函数