c++领域展开第十八幕——STL(list容器的了解和使用以及手撕模拟)超详细!!!!

c++领域展开第十八幕——STL(list容器的了解和使用以及手撕模拟)超详细!!!!
在这里插入图片描述

文章目录

前言

本专栏上一篇博客把vector的有关实现和细节问题都讲解完毕
今天我们来学习 stl 库的另外一个容器——list
从认识到使用到手撕实现,我来手把手教你
fellow me

一、list的介绍和使用

1.1 list的介绍

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位置

【注意】

  1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
  2. 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更适合随机访问和内存连续需求。理解其底层实现有助于优化数据操作逻辑。
不要走开,小编持续更新中~~~~~
在这里插入图片描述

Read more

【算法竞赛】C/C++ 的输入输出你真的玩会了吗?

【算法竞赛】C/C++ 的输入输出你真的玩会了吗?

🔭 个人主页:散峰而望 《C语言:从基础到进阶》《编程工具的下载和使用》《C语言刷题》《算法竞赛从入门到获奖》《人工智能AI学习》《AI Agent》 愿为出海月,不做归山云 🎬博主简介 文章目录 * 前言 * 1. OJ(online judge)题目输入情况汇总 * 1.1 单组测试用例 * 1.2 多组测试用例 * 1.2.1 测试数据组数已知 * 1.2.2 测试数据组未知 * 1.2.3 特殊值结束测试数据 * 2. 输入时特殊技巧 * 2.1 含空格字符串的特殊处理方式 * 2.2 数字的特殊处理方式 * 3. scanf/printf 和

By Ne0inhk
【C++深学日志】C++“类”的完全指南--从基础到实践(一)

【C++深学日志】C++“类”的完全指南--从基础到实践(一)

假想一下,你是一个顶级汽车设计师,你的任务不是亲自拧紧每一个螺丝,而是要设计出一幅“汽车蓝图”,你在图纸上设计了一辆汽车所需的一切:车轮、车灯、V8发动机、方向盘等,你手上这份设计好的蓝图就相当于我们今天要讲的C++中的“类”,它规定了汽车的属性(例如:离合器)和方法(功能:换挡),它本身并不是一辆真正的汽车,只是你的一份设计规划,后续你交付给工厂,工厂按照你的设计蓝图,生产出了一辆汽车,这就是实例化,后续工厂有根据你的蓝图设计了一条流水线,每一辆从流水线上生产下来的车辆,都是里这个蓝图(类)的一个对象,他们都有蓝图定义的属性和功能。在C++中类就充当着蓝图的作用,它定义了对象拥有哪些属性,那么就和我一起来揭开这份“蓝图”的面纱吧。 1.类 1.1.类的定义 类的基本思想是数据抽象和封装,数据抽象是一种依赖于接口和实现的分离式编程技术,类的接口包括用户所能执行的操作,类的实现则是包括类的数据成员、负责接口实现的函数以及定义类所需的各种私有函数。封装实现了类的接口和实现的分离,封装后的类隐藏了他的视线细节,也就是说,

By Ne0inhk
Qt步进电机上位机控制程序源代码:跨平台C/C++编写,支持多种端口类型与详细注释

Qt步进电机上位机控制程序源代码:跨平台C/C++编写,支持多种端口类型与详细注释

Qt步进电机上位机控制程序源代码Qt跨平台C/C++语言编写 支持串口Tcp网口Udp网络三种端口类型 提供,提供详细注释和人工讲解 1.功能介绍: 可控制步进电机的上位机程序源代码,基于Qt库,采用C/C++语言编写。 支持串口、Tcp网口、Udp网络三种端口类型,带有调试显示窗口,接收数据可实时显示。 带有配置自动保存功能,用户的配置数据会自动存储,带有超时提醒功能,如果不回复则弹框提示。 其中三个端口,采用了类的继承与派生方式编写,对外统一接口,实现多态功能,具备较强的移植性。 2.环境说明: 开发环境是Qt5.10.1,使用Qt自带的QSerialPort,使用网络的Socket编程。 源代码中包含详细注释,使用说明,设计文档等。 请将源码放到纯英文路径下再编译。 3.使用介绍: 可直接运行在可执行程序里的exe文件,操作并了解软件运行流程。 本代码产品特点: 1、尽量贴合实际应用,细节考虑周到。 2、注释完善,讲解详细,还有相关扩展知识点介绍。

By Ne0inhk
【C++】深入拆解二叉搜索树:从递归与非递归双视角,彻底掌握STL容器的基石

【C++】深入拆解二叉搜索树:从递归与非递归双视角,彻底掌握STL容器的基石

【C++】深入拆解二叉搜索树:从递归与非递归双视角,彻底掌握STL容器的基石 * 摘要 * 目录 * 一、概念 * 二、 性能分析 * 三、key结构非递归模拟实现 * 1. 二叉搜索树的插入 * 2. 二叉搜索树的查找 * 3. 二叉搜索树的删除 * 4. 二叉搜索树的中序遍历 * 四、key结构递归的模拟实现 * 1. 递归与非递归二叉搜索树核心操作的对比 * 2. 递归插入 * 3. 递归查找 * 4. 递归删除 * 总结 摘要 二叉搜索树(BST)是一种重要的数据结构,它通过"左子树所有节点值小于根节点,右子树所有节点值大于根节点"的特性实现高效的元素组织。本文详细解析了BST的核心概念、性能特点,并分别通过非递归和递归两种方式完整实现了插入、查找、删除等关键操作,深入探讨了指针引用在递归实现中的巧妙应用,以及两种实现方式在时间复杂度、空间复杂度和适用场景上的差异。 目录

By Ne0inhk