首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++STL :list类 (二) 】序列容器性能的本质:深度剖析 list 与 vector 的迭代器核心奥秘

【C++STL :list类 (二) 】序列容器性能的本质:深度剖析 list 与 vector 的迭代器核心奥秘

作者头像
艾莉丝努力练剑
发布2025-11-17 09:31:32
发布2025-11-17 09:31:32
950
举报
文章被收录于专栏:C / C++C / 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()
代码语言:javascript
复制
// 测试链表基础功能和迭代器的函数
// 目的:验证链表的基本插入操作和两种遍历方式
void Test_list1()
{
	// 创建一个整型双向链表对象
	jqj::list<int> lt;
	// 向链表尾部依次添加元素 1, 2, 3, 4
	lt.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 测试尾插 / 头插 / 尾删 / 头删

这个代码运行会报错——

前面的尾插 / 头插打印出来了,但是尾删 / 头删报错了。

问题出在这里——

再运行一次,成功了——

代码演示如下——

代码语言:javascript
复制
// 测试链表基本操作的函数
// 目的:验证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→4
	lt.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→4
	lt.push_front(-1);
	lt.push_front(-2);
	lt.push_front(-3);

	// 第一次遍历输出:显示所有插入后的链表元素
	// 使用范围for循环遍历链表,auto&避免拷贝,提高效率
	// 预期输出:-3 -2 -1 1 2 3 4
	for (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→2
	lt.pop_back();
	lt.pop_back();
	lt.pop_front();
	lt.pop_front();

	// 第二次遍历输出:显示删除操作后的剩余元素
	// 预期输出:-1 1 2
	for (auto& e : lt)
	{
		cout << e << " "; // 输出当前元素值加空格
	}
	cout << endl;

	// 输出链表当前包含的元素个数
	// 预期输出:3(因为初始7个元素,删除了4个,剩余3个)
	cout << lt.size() << endl;
}
4.1.3 打印链表:Print
代码语言:javascript
复制
// 打印链表函数 - 常量版本
// 功能:遍历并输出链表中所有元素,不修改原链表
// 参数: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 测试链表拷贝构造和赋值操作
代码语言:javascript
复制
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 迭代器实现的两种设计思路

4.4 operator* && operator->

4.4.1 两个箭头
4.4.2 Ptr

本文代码完整展示

list.h:

代码语言:javascript
复制
#pragma once

namespace 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,class Ptr> // T 数据类型
	// Ref 引用类型(T& 或 const T&)
	struct list_iterator
	{
		// using还具有tepedef没有的功能
		// 使用类型别名(C++11新特性)
		using Self = list_iterator<T, Ref, Ptr>; // 自身类型
		using Node = list_node<T>; // 节点类型
		Node* _node; // 当前指向的节点指针

		// 迭代器构造函数
		list_iterator(Node* node)
			:_node(node)
		{}

		// 迭代器解引用操作
		// *it = 1
		// Ref 返回节点数据的引用(可读或可写)
		Ref operator*() // 解引用,Ref就是reference,引用的意思
		{
			return _node->_data;
		}
		// operator*()返回对应数据类型的引用

		Ptr operator->() // 返回对应数据类型的指针
		{
			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&, T*>; // 普通迭代器
		using const_iterator = list_iterator<T, const T&, const 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 = lt3
		list<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 cur
			prev->_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 -> next
			prev->_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:

代码语言:javascript
复制
#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();
	// vector
	vector<int> v(lt2.begin(), lt2.end());
	// 
	sort(v.begin(), v.end());

	// lt2
	lt2.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, 4
	lt.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→4
	lt.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→4
	lt.push_front(-1);
	lt.push_front(-2);
	lt.push_front(-3);

	// 第一次遍历输出:显示所有插入后的链表元素
	// 使用范围for循环遍历链表,auto&避免拷贝,提高效率
	// 预期输出:-3 -2 -1 1 2 3 4
	for (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→2
	lt.pop_back();
	lt.pop_back();
	lt.pop_front();
	lt.pop_front();

	// 第二次遍历输出:显示删除操作后的剩余元素
	// 预期输出:-1 1 2
	for (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
}

struct A
{
	int _a1;
	int _a2;

	A(int a1 = 0,int a2 = 0)
		:_a1(a1)
		,_a2(a2)
	{ }
};

void Test_list4()
{
	jqj::list<A> lt;
	lt.push_back({ 1,1 }); // 多参数走隐式类型转换
	lt.push_back({ 2,2 });
	lt.push_back({ 3,3 });

	jqj::list<A>::iterator it = lt.begin();
	while (it != lt.end())
	{
		//cout << *it << " "; // 解引用,数据是A类型,这里A类型没有重载流插入
		//cout << (*it) << " "; // 不行
		//cout << (*it)._a1 << ":" << (*it)._a2 << endl; // 迭代器模拟指针行为,这样就可以访问了

		// 为了可读性,省略了一个->
		cout << it->_a1 << ":" << it->_a2 << endl; // 要重载一个operator->() 
		// 迭代器还有一种重载,因为迭代器要模拟指针的行为,指针有两种解引用的方式,一种是“*”,一种是“->”
		cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl; // 两个箭头
		// 第一个箭头是运算符重载,第二个箭头是运算符重载后返回一个A*的数据,A*的数据再去访问A类型指针指向的数组成员
		++it;
	}
	cout << endl;
}

int main()
{
	//Test_op1();
	//Test_op2();

	//Test_list1();
	//Test_list2();

	//Test_list3();
	Test_list4();

	return 0;
}

结尾

往期回顾:

【C++STL :list类 (一) 】C++98 完全指南:std::list 详解与源码剖析

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

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 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 迭代器实现的两种设计思路
    • 4.4 operator* && operator->
      • 4.4.1 两个箭头
      • 4.4.2 Ptr
  • 本文代码完整展示
    • list.h:
    • Test.c:
  • 结尾
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档