【C++STL :list类 (二) 】list vs vector:终极对决与迭代器深度解析 揭秘list迭代器的陷阱与精髓
🔥艾莉丝努力练剑:个人主页
❄专栏传送门:《C语言》、《数据结构与算法》、C/C++干货分享&学习过程记录、Linux操作系统编程详解、笔试/面试常见算法:从基础到进阶
⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平
🎬艾莉丝的简介:
🎬艾莉丝的C++专栏简介:
目录
C++的两个参考文档
3 ~> list VS vector
3.1 核心缺陷
3.1.1 list
3.1.2 vector
3.2 两个容器的区别
3.2.1 总结
3.3 sort对比
3.3.2 对比vector和list中的sort排序
3.3.3 vector和list的sort在不同数据量下的性能测试对比
3.3.4 不同数据类型下vector与list的sort性能补充表
3.3.5 string类型下vector与list的sort性能补充表
3.3.6 结论
4 ~> 深入理解 list 迭代器:双向遍历的底层原理与源码实现
4.1 深入 list 实现:核心代码解读与 Test.cpp 测试全流程分析
4.1.1 测试链表基础功能和迭代器 && 迭代器:begin() / end()
4.1.2 测试尾插 / 头插 / 尾删 / 头删
4.1.3 打印链表:Print
4.1.4 测试链表拷贝构造和赋值操作
4.2 一口气讲完list底层基础框架实现:结合注释(list.h文件)
4.2.1 链表节点结构
4.2.2 链表迭代器
4.2.3 链表主体类
4.2.4 迭代器访问接口
4.2.5 链表初始化相关
4.2.6 析构函数
4.2.7 拷贝控制函数
4.2.8 容量操作
4.2.9 元素访问操作
4.2.10 指定位置操作
4.2.11 容量信息
4.3 链表 && 迭代器机制图解
4.3.1 链表的节点管理:内存分配和对象构造/销毁
4.3.2 双向链表的插入和删除操作
4.3.3 双向链表的结构和插入操作
4.3.4 迭代器(iterator)的设计理念和实现
4.3.5 链表迭代器的具体实现
4.3.6 链表迭代器的完整使用和实现对应关系
4.3.7 链表迭代器的工作原理和遍历过程
4.3.8 迭代器实现的两种设计思路
本文代码完整展示
list.h:
Test.c:
结尾
C++的两个参考文档
老朋友(非官方文档):cplusplus
官方文档(同步更新):cppreference
list容器文档链接:list
3 ~> list VS vector
3.1 核心缺陷
3.1.1 list
list的核心缺陷就是链表没办法做下标随机访问。
3.1.2 vector
在C++中,vector(顺序表)的核心缺陷主要有以下几点——
1、扩容的额外开销
vector是动态数组,当空间不足时需要扩容。其扩容机制通常是重新申请一块更大的内存(一般是原容量的1.5倍或2倍),然后将原数据拷贝到新内存,最后释放原内存。这个过程会带来时间开销(拷贝数据)和空间开销(可能存在未使用的冗余内存)。例如,当vector存储大量元素时,频繁扩容会显著影响性能。
2、插入/删除操作的低效性
在vector的中间或头部进行插入/删除操作时,需要移动操作位置之后的所有元素(插入时后移,删除时前移)。元素数量越多,移动的元素越多,时间复杂度为O(n)(n为元素个数),效率较低。而链表(list)在插入/删除时仅需修改指针,时间复杂度为O(1)(找到位置后)。
3、内存连续性的限制
vector要求内存连续分配,这在内存碎片较多的环境中,可能会出现“明明总内存足够,但无法分配到连续的大块内存”的情况,导致扩容失败;而链表(list)的节点是分散存储、随机分布的,不受此限制。这也是vector的缺陷。
3.2 两个容器的区别
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同艾莉丝会从如下七个角度进行对比——
vector | list | |
底层结构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(N) |
插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容: 开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高, 不需要搬移元素,时间复杂度为O(1) |
空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
迭代器 | 原生态指针 | 对原生态指针 (节点指针) 进行封装 |
迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
应用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
3.2.1 总结
1、list和vector其实是互补的关系——
2、vector任意位置插入和删除效率低,插入时有可能需要增容头插、头删还是用list;
3、CPU高速缓存:数组比链表更好;
4、单纯存东西:vector(顺序表)更优,有头部操作:list。
3.3 sort对比
数据量大不建议用sort,list的sort接口核心是归并排序。
算法库的sort核心是快速排序。
3.3.1 VS测试(debug && release)
debug版本——
这里为什么看上去两种sort的耗时差不多,也没有差几倍啊?不仅如此,不是说list慢vector快吗?为什么这里反而是list快vector慢呢?
debug版本不能作为性能测试的依据,尤其是递归!
3.3.2 对比vector和list中的sort排序
在STL中,vector的sort是随机访问迭代器下的快速排序(平均时间复杂度O(nlogn)),而list的sort是双向迭代器下的归并排序(稳定时间复杂度为O(nlogn));数据量大时不建议用list的sort。
list不适合大数据量排序的核心原因如下——
1、内存访问效率低:list是链表结构,元素在内存中不连续,排序时无法利用CPU缓存的“预读”机制,频繁的随机内存访问会大幅拖慢速度。
2、归并排序的固有开销:list的sort虽稳定,但归并过程中需要频繁进行节点的断开与连接操作,相比vector直接在连续内存上移动数据,额外操作的时间成本更高。
3、空间复杂度劣势:list的归并排序需要O(n)的额外空间存储临时节点,而vector的快速排序是原地排序(忽略递归栈空间),内存利用率更高。
3.3.3 vector和list的sort在不同数据量下的性能测试对比
这里博主整理了一份vector和list的sort在不同数据量下的性能测试对比表格——
测试维度 | vector(快速排序) | list(归并排序) | 核心差异点 |
数据量:1万(小规模) | 耗时约0.1~0.3ms,无额外内存占用 | 耗时约0.5~1ms,额外占用约40KB内存 | list耗时是vector的2~3倍,内存开销初显 |
数据量:100万(中规模) | 耗时约15~25ms,无额外内存占用 | 耗时约80~120ms,额外占用约4MB内存 | list耗时是vector的5-6倍,内存开销扩大 |
数据量:1000万(大规模) | 耗时约200~300ms,无额外内存占用 | 耗时约1200~1800ms,额外占用约40MB内存 | list耗时是vector的6~8倍,内存与时间成本均大幅飙升 |
内存访问特性 | 连续内存,可触发CPU缓存预读,访问效率极高 | 离散内存,无法利用缓存,频繁触发内存页交换 | vector的缓存友好性是性能核心优势 |
额外空间复杂度 | O(logn)(仅递归栈,可忽略) | O(n)(需存储临时节点) | 数据量越大,list的内存浪费越严重 |
3.3.4 不同数据类型下vector与list的sort性能补充表
我们以常见的int类型和自定义结构体 (含3个int成员) 为例,补充更贴近实际开发场景的精准耗时数据,便于直观对比不同数据类型下的性能差异。
数据类型 | 数据量 | vector(快速排序)耗时 | list(归并排序)耗时 | 耗时差距倍数 |
int(4字节) | 100万 | 约18~22ms | 约90~110ms | 5~5.5倍 |
int(4字节) | 1000万 | 约220~280ms | 约1300~1600ms | 5.9~6.8倍 |
自定义结构体(12字节) | 100万 | 约25~30ms | 约110~140ms | 4.4~5.6倍 |
自定义结构体(12字节) | 1000万 | 约280~350ms | 约1500~1900ms | 5.4~6.8倍 |
我们从表格可以看到,无论数据类型是基础int还是自定义结构体,数据量越大,list的sort耗时劣势越明显,且结构体因数据体积更大,list的内存离散访问问题会进一步放大耗时差距。
3.3.5 string类型下vector与list的sort性能补充表
string平均长度设为20字符,模拟日常文本数据场景,耗时受字符串比较逻辑影响更大。
数据类型 | 数据量 | vector(快速排序)耗时 | list(归并排序)耗时 | 耗时差距倍数 |
string(约20字节) | 100万 | 约80~110ms | 约350~450ms | 4.4~4.5倍 |
string(约20字节) | 1000万 | 约900~1200ms | 约4000~5000ms | 4.4~4.6倍 |
相比int和自定义结构体,string类型的排序耗时整体更高(因需逐字符比较),但list的耗时劣势依然稳定存在——即便字符串比较占主导,list离散内存访问导致的缓存失效,仍让其比vector慢4倍以上。
3.3.6 结论
我们让vector去sort,list虽然实现了sort接口,但是太慢,没用。
4 ~> 深入理解 list 迭代器:双向遍历的底层原理与源码实现
学习一个新的大框架,我们就要抽丝剥茧,先写出一个精简版的来。
方法:抓住主干,把复杂的部分抽离,不重要的剔除掉,细节知道就行,抓住核心。
4.1 深入 list 实现:核心代码解读与 Test.cpp 测试全流程分析
4.1.1 测试链表基础功能和迭代器 && 迭代器:begin() / end()
// 测试链表基础功能和迭代器的函数
// 目的:验证链表的基本插入操作和两种遍历方式
void Test_list1()
{// 创建一个整型双向链表对象jqj::list<int> lt;// 向链表尾部依次添加元素 1, 2, 3, 4lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);// 方式1:使用显式迭代器遍历链表// 获取指向链表第一个元素的迭代器jqj::list<int>::iterator it = lt.begin();// 传统while循环遍历,从begin()到end()// end()返回的是哨兵节点,表示链表末尾while (it != lt.end()){cout << *it << " ";// 解引用迭代器,获取当前元素值++it; // 迭代器递增,移动到下一个节点}cout << endl;// 预期输出:1 2 3 4// 方式2:使用C++11范围for循环遍历链表// 这是更现代的遍历方式,编译器会自动转换为迭代器操作// auto& e:自动类型推导,使用引用避免拷贝for (auto& e : lt){cout << e << " "; // 直接访问元素值}cout << endl;// 预期输出:1 2 3 4
}
运行一下,打印出来了。
4.1.2 测试尾插 / 头插 / 尾删 / 头删
这个代码运行会报错——
前面的尾插 / 头插打印出来了,但是尾删 / 头删报错了。
问题出在这里——
再运行一次,成功了——
代码演示如下——
// 测试链表基本操作的函数
// 目的:验证push_back、push_front、pop_back、pop_front等操作的正确性
void Test_list2()
{jqj::list<int> lt; // 创建一个整型双向链表对象// 向链表尾部依次添加元素 1, 2, 3, 4// 链表变化过程:1 → 1→2 → 1→2→3 → 1→2→3→4lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);// 向链表头部依次添加元素 -1, -2, -3// 链表变化过程:1→2→3→4 → -1→1→2→3→4 → -2→-1→1→2→3→4 → -3→-2→-1→1→2→3→4lt.push_front(-1);lt.push_front(-2);lt.push_front(-3);// 第一次遍历输出:显示所有插入后的链表元素// 使用范围for循环遍历链表,auto&避免拷贝,提高效率// 预期输出:-3 -2 -1 1 2 3 4for (auto& e : lt){cout << e << " "; // 输出当前元素值加空格}cout << endl;// 删除操作:从尾部删除2个元素,从头部删除2个元素// 链表变化过程:-3→-2→-1→1→2→3→4 → -3→-2→-1→1→2→3 → -3→-2→-1→1→2 → -2→-1→1→2 → -1→1→2lt.pop_back();lt.pop_back();lt.pop_front();lt.pop_front();// 第二次遍历输出:显示删除操作后的剩余元素// 预期输出:-1 1 2for (auto& e : lt){cout << e << " "; // 输出当前元素值加空格}cout << endl;// 输出链表当前包含的元素个数// 预期输出:3(因为初始7个元素,删除了4个,剩余3个)cout << lt.size() << endl;
}
4.1.3 打印链表:Print
// 打印链表函数 - 常量版本
// 功能:遍历并输出链表中所有元素,不修改原链表
// 参数:lt —— 常量链表引用,确保函数内不会修改链表内容
void Print(const jqj::list<int>& lt)
{// 使用常量迭代器开始遍历,从链表头部开始// const_iterator确保在遍历过程中不会修改链表元素jqj::list<int>::const_iterator it = lt.begin();// 循环遍历直到到达链表末尾// end()返回的是哨兵节点,表示链表的结束位置while (it != lt.end()){// 这行代码被注释掉了,因为它试图修改常量迭代器指向的值// 在const_iterator中,operator*()返回const T&,所以不能赋值//*it = 1; // 错误:尝试修改常量数据// 输出当前元素的值,后跟空格分隔符cout << *it << " ";// 移动到下一个节点// ++it调用的是迭代器的前置递增运算符++it;}// 输出换行,使输出结果更清晰cout << endl;
}
在下面我们就可以调用自己封装的Print函数进行链表打印哩——
4.1.4 测试链表拷贝构造和赋值操作
void Test_list3()
{// 使用初始化列表构造链表jqj::list<int> lt1 = { 1,2,3,4,5,6 };for (auto& e : lt1){cout << e << " ";}cout << endl;// 预期输出: 1 2 3 4 5 6// 测试拷贝构造函数jqj::list<int> lt2(lt1);for (auto& e : lt2){cout << e << " ";}cout << endl;// 预期输出: 1 2 3 4 5 6// 测试赋值运算符jqj::list<int> lt3 = { 10,20,30,40 };lt1 = lt3; // 赋值操作:lt1现在变成lt3的内容for (auto& e : lt1){cout << e << " ";}cout << endl;// 预期输出: 10 20 30 40// 使用Print函数再次输出Print(lt1);// 预期输出: 10 20 30 40
}
运行一次,成功了——
4.2 一口气讲完list底层基础框架实现:结合注释(list.h文件)
我们将从以下几点介绍list底层基础框架实现——
4.2.1 链表节点结构
4.2.2 链表迭代器
4.2.3 链表主体类
4.2.4 迭代器访问接口
4.2.5 链表初始化相关
4.2.6 析构函数
4.2.7 拷贝控制函数
4.2.8 容量操作
4.2.9 元素访问操作
4.2.10 指定位置操作
4.2.11 容量信息
4.3 链表 && 迭代器机制图解
4.3.1 链表的节点管理:内存分配和对象构造/销毁
4.3.2 双向链表的插入和删除操作
4.3.3 双向链表的结构和插入操作
4.3.4 迭代器(iterator)的设计理念和实现
"封装隐藏底层的结构差异,提供统一的方式访问容器"。
4.3.5 链表迭代器的具体实现
"通过运算符重载隐藏底层链表结构的复杂性"。
这就是迭代器模式的精髓:提供一致的访问接口,隐藏不同容器的实现差异。
4.3.6 链表迭代器的完整使用和实现对应关系
封装底层指针操作,提供统一简洁的访问接口。
4.3.7 链表迭代器的工作原理和遍历过程
4.3.8 迭代器实现的两种设计思路
本文代码完整展示
list.h:
#pragma oncenamespace jqj
{// 链表节点结构template<class T>struct list_node{list_node<T>* _next; // 指向下一个节点的指针list_node<T>* _prev; // 指向前一个节点的指针T _data; // 节点存储的数据// 节点构造函数list_node(const T& x = T()):_next(nullptr), _prev(nullptr), _data(x) // x 节点数据,默认为T类型的默认值{}};// 链表迭代器// 实现双向迭代器功能,支持前向和后向遍历template<class T, class Ref> // T 数据类型// Ref 引用类型(T& 或 const T&)struct list_iterator{// using还具有tepedef没有的功能// 使用类型别名(C++11新特性)using Self = list_iterator<T, Ref>; // 自身类型using Node = list_node<T>; // 节点类型Node* _node; // 当前指向的节点指针// 迭代器构造函数list_iterator(Node* node):_node(node){}// 迭代器解引用操作// *it = 1// Ref 返回节点数据的引用(可读或可写)Ref operator*() // 解引用,Ref就是reference,引用的意思{return _node->_data;}// ++it // 前向迭代操作Self& operator++() // Self& 返回递增后的迭代器引用{_node = _node->_next;return *this;}Self operator++(int) // Self 返回递增前的迭代器副本{Self tmp(*this);_node = _node->_next;return tmp;}// --it// 后向迭代操作Self& operator--() // Self& 返回递减后的迭代器引用{_node = _node->_prev;return *this;}Self operator--(int) // Self 返回递减前的迭代器副本{Self tmp(*this);_node = _node->_prev;return tmp;}// 迭代器比较操作bool operator!=(const Self& s) const // bool 两个迭代器是否不指向同一节点{return _node != s._node;}bool operator==(const Self& s) const // bool 两个迭代器是否不指向同一节点{return _node == s._node;}};//template<class T>//struct list_const_literator//{// using Self = list_const_literator<T>;// using Node = list_node<T>; Node* _node;// Node* _node;// list_const_iterator(Node* node)// :_node(node)// { }// // *it// const T& operator*()// {// return _node->_data;;// }// // ++it// Self& operator++()// {// _node = _node->_next;// return *this;// }// Self operator++(int)// {// Self tmp(*this);// _node = _node->_next;// return *this;// }// // --it// Self& operator--()// {// _node = _node->_prev;// return *this;// }// Self operator--(int)// {// Self tmp(*this);// _node = _node->_prev;// return tmp;// }// bool operator!=(const Self& s) const// {// return _node != s._node;// }// bool operator==(const Self& s) const// {// return _node == s._node;// }//};// 链表主体类template<class T>class list{using Node = list_node<T>; // 节点类型别名public:// 迭代器类型定义 using iterator = list_iterator<T, T&>; // 普通迭代器using const_iterator = list_iterator<T, const T&>; // 常量迭代器// 迭代器访问接口//using iterator = list_iterator<T>;//using const_iterator = list_const_iterator<T>;// 迭代器访问接口// 获取指向第一个元素的迭代器// iterator 指向首元素的迭代器iterator begin(){return iterator(_head->_next);}// iterator 指向哨兵节点的迭代器iterator end(){return iterator(_head);}// 获取指向第一个元素的常量迭代器// const_iterator 指向首元素的常量迭代器const_iterator begin() const{return const_iterator(_head->_next);}// const_iterator 指向哨兵节点的常量迭代器const_iterator end() const{return const_iterator(_head);}// 链表初始化相关 void empty_init() // 初始化空链表(创建哨兵节点){_head = new Node;_head->_next = _head;_head->_prev = _head;}// 默认构造函数list(){empty_init();}// 初始化列表构造函数// il 初始化列表list(initializer_list<T> il){empty_init();for (auto& e : il){push_back(e);}}// 范围构造函数// InputIterator 输入迭代器类型template <class InputIterator> list(InputIterator first, InputIterator last) // first 范围起始迭代器 // last 范围结束迭代器{empty_init();while (first != last){push_back(*first);++first;}}// 数量构造函数(size_t版本)list(size_t n, T val = T()) // val 元素值,默认为T的默认值{empty_init();for (size_t i = 0; i < n; ++i){push_back(val);}}// 数量构造函数(int版本)list(int n, T val = T()) // val 元素值,默认为T的默认值{empty_init();for (size_t i = 0; i < n; ++i){push_back(val);}}// 析构函数// 清理所有节点并释放哨兵节点~list(){clear();delete _head;_head = nullptr;_size = 0;}// 拷贝控制函数// lt 要拷贝的源链表// lt2(lt1)list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}// 拷贝赋值运算符// lt 要拷贝的源链表// list<T>& 返回当前链表的引用// lt1 = lt3list<T>& operator=(const list<T>& lt){if (this != <){clear();for (auto& e : lt){push_back(e);}}return *this;}//////list(list<T>& lt)// list(const list& lt)//{// empty_init();// list tmp(lt.begin(), lt.end());// swap(tmp);//}//// lt1 = lt3////list<T>& operator=(list<T> tmp)//list& operator=(list tmp)//{// swap(tmp);//}// 容量操作// 交换两个链表的内容void swap(list<T>& lt) // lt 要交换的另一个链表{std::swap(_head, lt._head);std::swap(_size, lt._size);}// 清空链表中的所有元素// 保留哨兵节点,删除所有数据节点void clear(){iterator it = begin();while (it != end()){it = erase(it);}}//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->_prev = newnode;//}// -----------------元素访问操作--------------------// 复用void push_back(const T& x) // 在链表尾部插入元素,x:要插入的元素值{insert(end(), x);}// 在链表头部插入元素,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& x) // pos:插入位置的迭代器,x:要插入的元素值{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);// // 连接新节点:prev -> newnode -> cur// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;}// 删除指定位置的元素iterator erase(iterator pos) // iterator 返回指向被删除元素后一个元素的迭代器{Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;// 跳过被删除节点:prev -> nextprev->_next = next;next->_prev = prev;delete cur;--_size;/*return iterator(next);*/return next; /// 返回下一个节点的迭代器//两种写法都可以}// -------------- 容量信息 ------------------size_t size() const{//size_t n = 0;//for (auch e : *this)//{// ++n;//}//return n;return _size;}private:Node* _head; // 哨兵头节点指针size_t _size = 0; // 链表元素个数计数器};
}
Test.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<vector>
#include<list>
using namespace std;void Test_op1()
{srand(time(0));const int N = 1000000000;list<int> lt1;vector<int> v;for (int i = 0; i < N; ++i){auto e = rand() + i;lt1.push_back(e);v.push_back(e);}int begin1 = clock();//sort(v.begin(), v.end());int end1 = clock();int begin2 = clock();lt1.sort();int end2 = clock();printf("vector sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}void Test_op2()
{srand(time(0));const int N = 1000000;list<int> lt1;list<int> lt2;for (int i = 0; i < N; ++i){auto e = rand() + i;lt1.push_back(e);lt2.push_back(e);}int begin1 = clock();// vectorvector<int> v(lt2.begin(), lt2.end());// sort(v.begin(), v.end());// lt2lt2.assign(v.begin(), v.end());int end1 = clock();int begin2 = clock();lt1.sort();int end2 = clock();printf("list copy vector sort copy list sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}//--------------------------------------------------------------------
#include"list.h"// 测试链表基础功能和迭代器的函数
// 目的:验证链表的基本插入操作和两种遍历方式
void Test_list1()
{// 创建一个整型双向链表对象jqj::list<int> lt;// 向链表尾部依次添加元素 1, 2, 3, 4lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);// 方式1:使用显式迭代器遍历链表// 获取指向链表第一个元素的迭代器jqj::list<int>::iterator it = lt.begin();// 传统while循环遍历,从begin()到end()// end()返回的是哨兵节点,表示链表末尾while (it != lt.end()){cout << *it << " ";// 解引用迭代器,获取当前元素值++it; // 迭代器递增,移动到下一个节点}cout << endl;// 预期输出:1 2 3 4// 方式2:使用C++11范围for循环遍历链表// 这是更现代的遍历方式,编译器会自动转换为迭代器操作// auto& e:自动类型推导,使用引用避免拷贝for (auto& e : lt){cout << e << " "; // 直接访问元素值}cout << endl;// 预期输出:1 2 3 4
}// 测试链表基本操作的函数
// 目的:验证push_back、push_front、pop_back、pop_front等操作的正确性
void Test_list2()
{jqj::list<int> lt; // 创建一个整型双向链表对象// 向链表尾部依次添加元素 1, 2, 3, 4// 链表变化过程:1 → 1→2 → 1→2→3 → 1→2→3→4lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);// 向链表头部依次添加元素 -1, -2, -3// 链表变化过程:1→2→3→4 → -1→1→2→3→4 → -2→-1→1→2→3→4 → -3→-2→-1→1→2→3→4lt.push_front(-1);lt.push_front(-2);lt.push_front(-3);// 第一次遍历输出:显示所有插入后的链表元素// 使用范围for循环遍历链表,auto&避免拷贝,提高效率// 预期输出:-3 -2 -1 1 2 3 4for (auto& e : lt){cout << e << " "; // 输出当前元素值加空格}cout << endl;// 删除操作:从尾部删除2个元素,从头部删除2个元素// 链表变化过程:-3→-2→-1→1→2→3→4 → -3→-2→-1→1→2→3 → -3→-2→-1→1→2 → -2→-1→1→2 → -1→1→2lt.pop_back();lt.pop_back();lt.pop_front();lt.pop_front();// 第二次遍历输出:显示删除操作后的剩余元素// 预期输出:-1 1 2for (auto& e : lt){cout << e << " "; // 输出当前元素值加空格}cout << endl;// 输出链表当前包含的元素个数// 预期输出:3(因为初始7个元素,删除了4个,剩余3个)cout << lt.size() << endl;
}// 打印链表函数 - 常量版本
// 功能:遍历并输出链表中所有元素,不修改原链表
// 参数:lt —— 常量链表引用,确保函数内不会修改链表内容
void Print(const jqj::list<int>& lt)
{// 使用常量迭代器开始遍历,从链表头部开始// const_iterator确保在遍历过程中不会修改链表元素jqj::list<int>::const_iterator it = lt.begin();// 循环遍历直到到达链表末尾// end()返回的是哨兵节点,表示链表的结束位置while (it != lt.end()){// 这行代码被注释掉了,因为它试图修改常量迭代器指向的值// 在const_iterator中,operator*()返回const T&,所以不能赋值//*it = 1; // 错误:尝试修改常量数据// 输出当前元素的值,后跟空格分隔符cout << *it << " ";// 移动到下一个节点// ++it调用的是迭代器的前置递增运算符++it;}// 输出换行,使输出结果更清晰cout << endl;
}void Test_list3()
{// 使用初始化列表构造链表jqj::list<int> lt1 = { 1,2,3,4,5,6 };for (auto& e : lt1){cout << e << " ";}cout << endl;// 预期输出: 1 2 3 4 5 6// 测试拷贝构造函数jqj::list<int> lt2(lt1);for (auto& e : lt2){cout << e << " ";}cout << endl;// 预期输出: 1 2 3 4 5 6// 测试赋值运算符jqj::list<int> lt3 = { 10,20,30,40 };lt1 = lt3; // 赋值操作:lt1现在变成lt3的内容for (auto& e : lt1){cout << e << " ";}cout << endl;// 预期输出: 10 20 30 40// 使用Print函数再次输出Print(lt1);// 预期输出: 10 20 30 40
}int main()
{//Test_op1();//Test_op2();//Test_list1();//Test_list2();Test_list3();return 0;
}
结尾
往期回顾:
【C++STL :list类 (一) 】C++98 完全指南:std::list 详解与源码剖析
结语:都看到这里啦!那请大佬不要忘记给博主来个“一键四连”哦!
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡
૮₍ ˶ ˊ ᴥ ˋ˶₎ა