c++领域展开第十八幕——STL(list容器的了解和使用以及手撕模拟)超详细!!!!
文章目录
前言
本专栏上一篇博客把vector的有关实现和细节问题都讲解完毕
今天我们来学习 stl 库的另外一个容器——list
从认识到使用到手撕实现,我来手把手教你
fellow me
一、list的介绍和使用
1.1 list的介绍

文档链接在上面啦,大家可以翻译看看
双向循环链表图,list就相当于我们数据结构学习的双向循环链表
只不过在stl库里面给它进行了封装

1.2 list的使用
list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口。
1.2.1 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
代码展示
list<int> l1;// 构造空的l1 list<int>l2(4,100);// l2中放4个值为100的元素 list<int>l3(l2.begin(), l2.end());// 用l2的[begin(), end())左闭右开的区间构造l3 list<int>l4(l3);// 用l3拷贝构造l4// 以数组为迭代器区间构造l5int array[]={16,2,77,29}; list<int>l5(array, array +sizeof(array)/sizeof(int));// 列表格式初始化C++11 list<int> l6{1,2,3,4,5};1.2.2 list iterator的使用
此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点
函数声明——————接口说明
begin + end————返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin + rend————返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置
【注意】
- begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
- rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
代码展示
// 注意这里调用的是list的 begin() const,返回list的const_iterator对象for(list<int>::const_iterator it = l.begin(); it != l.end();++it){ cout <<*it <<" ";// *it = 10; 编译不通过}int array[]={1,2,3,4,5,6,7,8,9,0}; list<int>l(array, array +sizeof(array)/sizeof(array[0]));// 使用正向迭代器正向list中的元素// list<int>::iterator it = l.begin(); // C++98中语法auto it = l.begin();// C++11之后推荐写法while(it != l.end()){ cout <<*it <<" ";++it;} cout << endl;// 使用反向迭代器逆向打印list中的元素// list<int>::reverse_iterator rit = l.rbegin();auto rit = l.rbegin();while(rit != l.rend()){ cout <<*rit <<" ";++rit;}1.2.3 list capacity
函数声明————接口说明
empty ——————检测list是否为空,是返回true,否则返回false
size ———————返回list中有效节点的个数
这两个接口直接使用就行,这里就不展示代码了
1.2.4 list element access
函数声明————接口说明
front ——————返回list的第一个节点中值的引用
back ——————返回list的最后一个节点中值的引用
这两个接口也是直接使用就行
1.2.5 list modifiers
函数声明————————接口说明
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中的有效元素
代码展示
int array[]={1,2,3}; list<int>L(array, array +sizeof(array)/sizeof(array[0]));// 在list的尾部插入4,头部插入0 L.push_back(4); L.push_front(0);PrintList(L);// 删除list尾部节点和头部节点 L.pop_back(); L.pop_front();PrintList(L);int array1[]={1,2,3}; list<int>L(array1, array1 +sizeof(array1)/sizeof(array1[0]));// 获取链表中第二个节点auto pos =++L.begin(); cout <<*pos << endl;// 在pos前插入值为4的元素 L.insert(pos,4);PrintList(L);// 在pos前插入5个值为5的元素 L.insert(pos,5,5);PrintList(L);// 在pos前插入[v.begin(), v.end)区间中的元素 vector<int> v{7,8,9}; L.insert(pos, v.begin(), v.end());PrintList(L);// 删除pos位置上的元素 L.erase(pos);PrintList(L);// 删除list中[begin, end)区间中的元素,即删除list中的所有元素 L.erase(L.begin(), L.end());PrintList(L);// 用数组来构造listint array1[]={1,2,3}; list<int>l1(array1, array1 +sizeof(array1)/sizeof(array1[0]));PrintList(l1);// 交换l1和l2中的元素 list<int> l2; l1.swap(l2);PrintList(l1);PrintList(l2);// 将l2中的元素清空 l2.clear(); cout << l2.size()<< endl;list中还有一些操作,需要用到时大家可参阅list的文档说明
1.2.6 list的迭代器失效
前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无
效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入
时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭
代器,其他迭代器不会受到影响。
voidTestListIterator1(){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;}}// 改正voidTestListIterator(){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的使用和说明就到这里啦,下面我们来模拟实现一下list
二、list的模拟实现
前面在外面的vector的时候就已经模拟实现过,相信大家都有些熟悉了,其实list实现起来也差不多的,都是封装嘛
先用结构体封装一下迭代器
template<classT,classRef,classPtr>structlist_iterator{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;// 这里模版的是const迭代器 会返回引用或者取地址其他模版参数 Node* _node;list_iterator(Node* node):_node(node){} Ref operator*(){return _node->_data;} Ptr operator->(){return&_node->_data;} 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->_prev;return tmp;}booloperator!=(const Self& it){return _node != it._node;}booloperator==(const Self& it){return _node == it._node;}};然后就是封装list,实现迭代器,各种增删改查函数,还有默认成员函数
这里我就不像vector一样一个一个赘述啦,相信模拟实现过string和vector之后其实这些写起来也就顺手啦
template<classT>classlist{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 it(_head->_next);return it;} iterator end(){returniterator(_head);} const_iterator begin()const{returnconst_iterator(_head->_next);} const_iterator end()const{returnconst_iterator(_head);}voidempty_init()// 初始化{ _head =new Node; _head->_next = _head; _head->_prev = _head; _size =0;}list()// 默认构造{empty_init();}list(initializer_list<T> lt)// c++11支持 { }构造{empty_init();for(auto& e : lt){push_back(e);}}// lt2(lt1)list(const list<T>& lt)// 拷贝构造{empty_init();for(auto& e : lt){push_back(e);}}// lt1 = lt3 list<T>&operator=(list<T> lt)// 赋值重载{swap(lt);return*this;}voidswap(list<T>& lt)// list内部的 swap{ std::swap(_head, lt._head); std::swap(_size, lt._size);}~list(){clear();delete _head; _head =nullptr;}voidclear()// 清楚函数{ iterator it =begin();while(it !=end()){ it =erase(it);}} size_t size()const{return _size;}voidpush_back(const T& x){/*Node* newnode = new Node(x); Node* tail = _head->_prev; newnode->_prev = tail; tail->_next = newnode; newnode->_next = _head; _head->_prev = newnode;*/insert(end(), x);}voidpush_front(const T& x){insert(begin(), x);}voidpop_front(){erase(begin());}voidpop_back(){erase(--end());}voidinsert(iterator pos,const T& x){ Node* cur = pos._node; Node* prev = cur->_prev; Node* newnode =newNode(x); prev->_next = newnode; newnode->_prev = prev; newnode->_next = cur; cur->_prev = newnode;++_size;} iterator erase(iterator pos){assert(pos !=end()); Node* cur = pos._node; Node* nextNode = cur->_next; Node* prevNode = cur->_prev; prevNode->_next = nextNode; nextNode->_prev = prevNode;delete cur;--_size;returniterator(nextNode);}private: Node* _head; size_t _size;};经过几次的模拟实现stl,发现stl的容器大多都是类似的,有点期待后面的map和set
list 的模拟实现就到这里啦
list与vector的对比
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

总结
总结
C++ STL中的list是基于双向循环链表实现的序列容器,支持高效插入删除(O(1)时间复杂度),但随机访问效率较低。其核心特性包括:通过迭代器遍历元素(支持正向/反向迭代器)、插入操作不引发迭代器失效(删除仅影响被删节点迭代器)。模拟实现需封装节点结构体,设计泛型迭代器(重载++/--/*等操作),并实现深拷贝控制。与vector对比,list适合频繁增删场景,而vector更适合随机访问和内存连续需求。理解其底层实现有助于优化数据操作逻辑。
不要走开,小编持续更新中~~~~~