【C++】STL·List
1. list的介绍及使用
1.1list介绍
List文档介绍
1.2 list的使用
list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已 达到可扩展的能力。以下为list中一些常见的重要接口。
1.2.1 list的构造
list<int>lt1(5); 构造空的list
list<int>lt2(3, 2); 构造的list中包含n个值为val的元素
list<int>lt3(lt2); 拷贝构造函数
int arr[] = { 1,2,3,4,5 };
list<int>lt4(arr, arr + 5);用[first, last)区间中的元素构造list
1.2.2 list iterator的使用
lt1.begin(); 返回第一个元素的迭代器 lt1.end(); 返回最后一个元素下一个位置的迭代器lt1.rbegin(); 返回第一个元素的reverse_iterator, 即end位置lt1.rend(); 返回最后一个元素下一个位置的reverse_iterator, 即begin位置
1.2.3 list capacity
lt1.size(); 返回list中有效节点的个数lt1.empty(); 检测list是否为空,是返回true,否则返回false
1.2.4 list element access
lt1.front(); 返回list的第一个节点中值的引用lt1.back(); 返回list的最后一个节点中值的引用
1.2.5 list modifiers
lt1.push_back(1); 尾插lt1.push_front(1); 头插lt1.pop_back(); 尾删lt1.pop_front(); 头删lt1.insert(lt1.begin(), 1); 在list position 位置中插入值为val的元素lt1.erase(lt1.begin()); 删除list position位置的元素lt1.swap(lt2); 交换两个list中的元素lt1.clear(); 清空list中的有效元素
1.2.6 list的迭代器失效
迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
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);}
}
2. list的模拟实现
2.1 模拟实现list
#pragma once
#include<iostream>
#include<vector>
using namespace std;
namespace XiaoHai
{template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T()):_data(x), _next(nullptr), _prev(nullptr){ }};template<class T, class ref, class Ptr>struct Reverse_iterator{Iterator _it;typedef Reverse_iterator<Iterator, Ref, Ptr> Self;Reverse_iterator(Iterator it):_it(it){}Ref operator*(){iterator tmp(_it);--tmp;return *tmp;}Ptr operator->(){return &(operator*());}self operator++(int){self tmp(_it);--_it;return *tmp;}self operator--(int){self tmp(_it);++_it;return *tmp;}self operator++(){--_it;return *tmp;}self operator--(){++_it;return *tmp;}bool operator!=(const Self& s){return _it != s._it;}};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;}T* operator->(){return &_node->_data;}self& operator++(){_node = _node->_next;return *this;}self& operator++(int){__list_iterator<T>tmp(*this);_node = _node->_next;return tmp;}self& operator--(){_node = _node->_prev;return *this;}self& operator--(int){__list_iterator<T>tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const __list_iterator<T>& it)const{return _node != it._node;}bool operator ==(const __list_iterator<T>& it)const{return !(_node != it._node);}};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_iterator begin() const{return iterator(_head->_next);}const_iterator end() const{return iterator(_head);}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;}list(){empty_init();}//lt2(lt1)list(const list<T><){empty_init();for (const auto& e:lt){push_back(e);}}list(initializer_list<int>il){empty_init();for (const auto& e : il){push_back(e);}}void swap(list<T><){std::swap(_head, lt._head);std::swap(_size, lt._size);}//lt1 = lt2list<T>& operator=(const list<T>* lt){swap(lt);return *this;}/*list<T>& operator=(const list<T>* lt){if (this != <){clear();for (const auto& e : il){push_back(e);}}return *this;}*/~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}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());}void insert(iterator pos, const T& val){Node* cur = pos._node;Node* newnode = new Node(val);Node* prev = cur->_prev;//prev newnode curprev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;++_size;}iterator& erase(iterator pos){Node* cur = pos._node;Node* next = cur->_next;Node* prev = cur->_prev;//prev (cur) nextprev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);}size_t size(){return _size;}private:Node* _head;size_t _size = 0;};
}
2.2 list的反向迭代器
通过前面例子知道,反向迭代器的++就是正向迭代器的--,反向迭代器的--就是正向迭代器的++, 因此反向迭代器的实现可以借助正向迭代器,即:反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口进行包装即可。
template<class T, class ref, class Ptr>struct Reverse_iterator{Iterator _it;typedef Reverse_iterator<Iterator, Ref, Ptr> Self;Reverse_iterator(Iterator it):_it(it){}Ref operator*(){iterator tmp(_it);--tmp;return *tmp;}Ptr operator->(){return &(operator*());}self operator++(int){self tmp(_it);--_it;return *tmp;}self operator--(int){self tmp(_it);++_it;return *tmp;}self operator++(){--_it;return *tmp;}self operator--(){++_it;return *tmp;}bool operator!=(const Self& s){return _it != s._it;}};
3. list与vector的对比
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及 应用场景不同
vector | list | |
底层结构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(n) |
插入和删除 | 尾插效率高,任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容: 开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高, 不需要搬移元素,时间复杂度 为O(1) |
空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率 低,缓存利用率低 |
迭代器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
迭代器失效 | 插入操作如果导致底层空间重新开辟,则迭代器就会失效,删除操作不光会导致指向被删除元素的迭代器失效,删除元素后面的迭代器也会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |