当前位置: 首页 > news >正文

【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中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同艾莉丝会从如下七个角度进行对比——

vectorlist
底层结构动态顺序表,一段连续空间带头结点的双向循环链表
随机访问支持随机访问,访问某个元素效率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~110ms5~5.5倍
int(4字节)1000万约220~280ms 约1300~1600ms5.9~6.8倍
自定义结构体(12字节)100万约25~30ms 约110~140ms4.4~5.6倍
自定义结构体(12字节)1000万约280~350ms约1500~1900ms5.4~6.8倍

我们从表格可以看到,无论数据类型是基础int还是自定义结构体,数据量越大,list的sort耗时劣势越明显,且结构体因数据体积更大,list的内存离散访问问题会进一步放大耗时差距。

3.3.5  string类型下vector与list的sort性能补充表

string平均长度设为20字符,模拟日常文本数据场景,耗时受字符串比较逻辑影响更大。

数据类型数据量vector(快速排序)耗时list(归并排序)耗时耗时差距倍数
string(约20字节)100万约80~110ms 约350~450ms4.4~4.5倍
string(约20字节)1000万约900~1200ms 约4000~5000ms4.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 != &lt){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 详解与源码剖析

结语:看到这里啦!那请大佬不要忘记给博主来个“一键四连”哦!

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡

૮₍ ˶ ˊ ᴥ ˋ˶₎ა

http://www.dtcms.com/a/473468.html

相关文章:

  • 虚幻引擎入门教程:虚幻引擎的安装
  • FastbuildAI后端服务启动流程分析
  • AI×Cursor 零基础前端学习路径:避误区学HTML/CSS/JS
  • 新手小白——Oracle数据库.索引与数据完整性
  • 免费注册网站软件网站制作 东莞
  • Redis 的璀璨明珠:深入剖析有序集合 (ZSET) 的奥秘与艺术
  • 【Linux网络编程】多路转接reactor——ET模式的epoll
  • 深入理解线程池:核心处理流程与工作原理
  • 关于unity一个场景中存在多个相机时Game视图的画面问题
  • 中国室内设计网站排名太原建设银行网站
  • 手写MyBatis第104弹:SqlSession从工厂构建到执行器选择的深度剖析
  • 【力扣 SQL 50】连接
  • 手机的网站有哪些女装网站建设规划书
  • 《领码 SPARK 融合平台》投资研究报告(最终完整版)
  • 【Linux】操作系统上的进程状态及其转换
  • (done) 矩阵分块计算和分块转置
  • linux复习速通面试版
  • 大数据Spark(六十八):Transformation转换算子所有Join操作和union
  • HTTP初识
  • 【Linux网络】Socket编程:TCP网络编程
  • 离线docker安装jupyter(python网页版编辑器)
  • 自己怎么做彩票网站吗网站建设招标2017
  • 达梦守护集群部署安装
  • 农村电子商务网站建设wordpress不能安装插件
  • 每天五分钟深度学习:两个角度解释正则化解决网络过拟合的原理
  • 【Android Gradle学习笔记】第二天:Gradle工程目录结构
  • 【知识拓展Trip Six】宿主OS是什么,传统虚拟机和容器又有什么区别?
  • AI眼镜:作为人机交互新范式的感知延伸与智能融合终端
  • 开发网站 语言卡片式网站
  • 长乐市住房和城乡建设局网站在线购物商城网站建设