前言
list类是STL中封装的链表模板类,并且底层实现是以带头双向链表作为基础进行封装的,甚至一些 STL 版本中(比如 SGI STL),list容器的底层实现使用的是带头双向循环链表
list 容器中各个元素的前后顺序是靠指针来维系的,每个元素都配备了两个指针,分别指向它的前一个元素和后一个元素,list容器中的元素还可以分散存储在内存空间里(逻辑结构连续,物理结构分散)。
- list的优点:
1.在任意位置插入删除数据的效率高
2.不存在扩容的问题(拷贝数据、释放空间的代价)
2.list支持前后双向迭代
- list的缺点:
1.不支持下标的随机访问
2.list需要额外的空间保存每个结点的相关联信息
3.底层空间不连续可能会导致空间利用率低
- 应用场景:
应用于有大量插入和删除操作,且不用关心随机访问时间复杂度不友好的问题的场景。
一:标准库中的list类
list接口的使用与vector接口的使用非常的类似,这里我们就不做过多的介绍。
vector类接口的使用请参照我的另一篇博客:STL容器_vector类
1.1 list类的迭代器失效问题
迭代器失效即迭代器所指向的结点无效,即该节点被删除了。
因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入数据时是不会导致list的迭代器失效的(不需要挪动数据和扩容),只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
举个例子:删除list中的所有偶数
#include<iostream>
#include<list>
using namespace std;
int main(){
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(6);
list<int>::iterator it = lt.begin();
while (it != lt.end()){
if (*it % 2 == 0){
// lt.erase(it):会造成迭代器失效
it = lt.erase(it);
}
else{
it++;
}
}
for (const auto& e : lt){
cout << e << " ";
}
cout << endl;
}
输出结果:1 3 5
二:list类的模拟实现
在我们了解了list类的接口使用和底层结构的相关知识之后,下面我们来模拟实现一个list类。
2.1 模拟实现list
#include<assert.h>
namespace WJL{
// 1.List的结点类
template<class T>
struct _List_node{
_List_node<T>* _next;
_List_node<T>* _prev;
T _data;
_List_node(const T& x = T())
:_data(x)
, _next(nullptr)
, _prev(nullptr)
{}
};
// 2.List类的迭代器
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)
{}
// 解引用
Ref operator*(){
return _node->_data;
}
// operator++(++it)
Self& operator++(){
_node = _node->_next;
return *this;
}
// operator++(it++)
Self operator++(int){
Self tmp(*this);
//_node = _node->_next;
++(*this);
return tmp;
}
// operator--(--it)
Self& operator--(){
_node = _node->_prev;
return *this;
}
// operator--(it--)
Self operator--(int){
Self tmp(*this);
//_node = _node->_prev;
--(*this);
return tmp;
}
// !=
bool operator!=(const Self& it){
return _node != it._node;
}
// ==
bool operator==(const Self& it){
return _node == it._node;
}
// ->
Ptr operator->(){
return &_node->_data;
}
};
// 3.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;
iterator begin(){
return iterator(_head->_next);
}
iterator end(){
return iterator(_head);
}
// 返回值是const迭代器(不可修改)
const_iterator begin()const{
return const_iterator(_head->_next);
}
const_iterator end()const{
return const_iterator(_head);
}
// 带头双向循环链表
List(){
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
// 深拷贝
List(const List<T>& lt){
// 头结点
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
/*const_iterator it = lt.begin();
while (it != lt.end()){
push_back(*it);
++it;
}*/
for (const auto& e : lt){
push_back(e);
}
}
/*List<T>& operator=(const List<T>& lt){
if (this != <){
clear();
for (const auto& e : lt){
push_back(e);
}
}
return *this;
}*/
// operator现代写法
List<T>& operator=(List<T> lt){
// lt是传值拷贝构造出来的
swap(_head, lt._head);
// 交换之后 lt除了作用域释放掉被赋值的空间
return *this;
}
~List(){
clear();
delete _head;
_head = nullptr;
}
void clear(){
iterator it = begin();
while (it != end()){
erase(it++);
}
}
// 尾插
void push_back(const T& x){
//Node* newnode = new Node(x);
//Node* tail = _head->_prev; // 原list中的最后一个结点
//tail->_next = newnode;
//newnode->_prev = tail;
//newnode->_next = _head;
//_head->_prev = newnode;
insert(end(), x);
}
// 头插
void push_front(const T& x){
insert(begin(), x);
}
// 尾删
void pop_back(){
//erase(iterator(_head->_prev));
erase(--end());
}
// 头删
void pop_front(){
erase(begin());
}
// pos位置插入
void insert(iterator pos, const T& x){
// 取出pos位置结点的指针
Node* cur = pos._node;
Node* newnode = new Node(x);
Node* prev = cur->_prev;
// prev cur newnode
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
}
// pos位置删除
void erase(iterator pos){
// 头结点不能删
assert(pos != end());
// 取出pos位置结点的指针
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
delete cur;
prev->_next = next;
next->_prev = prev;
}
private:
Node* _head;
};
void test_list1(){
List<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
for (const auto& e : lt){
cout << e << " ";
}
cout << endl;
lt.pop_back();
List<int> lt2(lt);
List<int>::iterator it = lt2.begin();
while (it != lt2.end()){
cout << *it << " ";
++it;
}
cout << endl;
List<int> lt3;
lt3 = lt;
for (const auto& e : lt3){
cout << e << " ";
}
cout << endl;
}
}
2.2 vector和list的对比
- vector是一个可动态增长的数组
支持随机访问(很好的支持排序、二分查找等算法),但在头部或中间插入和删除数据的效率低,并且存在扩容问题。
- list是一个带头结点的双向循环链表
不支持随机访问,但在任意位置插入和删除数据的效率高,并且不存在扩容的问题。
- 小结
vector和list是两个相辅相成、互补的容器,在使用时根据具体场景再选择使用哪种容器。