【C++STL :list类 (二) 】list vs vector:终极对决与迭代器深度解析 && 揭秘list迭代器的陷阱与精髓

🔥艾莉丝努力练剑:个人主页

专栏传送门:《C语言》《数据结构与算法》C/C++干货分享&学习过程记录Linux操作系统编程详解笔试/面试常见算法:从基础到进阶

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬艾莉丝的简介:


🎬艾莉丝的C++专栏简介:


目录

C++的两个参考文档

[3 ~> list VS vector](#3 ~> list VS vector)

[3.1 核心缺陷](#3.1 核心缺陷)

[3.1.1 list](#3.1.1 list)

[3.1.2 vector](#3.1.2 vector)

[3.2 两个容器的区别](#3.2 两个容器的区别)

[3.2.1 总结](#3.2.1 总结)

[3.3 sort对比](#3.3 sort对比)

[3.3.2 对比vector和list中的sort排序](#3.3.2 对比vector和list中的sort排序)

[3.3.3 vector和list的sort在不同数据量下的性能测试对比](#3.3.3 vector和list的sort在不同数据量下的性能测试对比)

[3.3.4 不同数据类型下vector与list的sort性能补充表](#3.3.4 不同数据类型下vector与list的sort性能补充表)

[3.3.5 string类型下vector与list的sort性能补充表](#3.3.5 string类型下vector与list的sort性能补充表)

[3.3.6 结论](#3.3.6 结论)

[4 ~> 深入理解 list 迭代器:双向遍历的底层原理与源码实现](#4 ~> 深入理解 list 迭代器:双向遍历的底层原理与源码实现)

[4.1 深入 list 实现:核心代码解读与 Test.cpp 测试全流程分析](#4.1 深入 list 实现:核心代码解读与 Test.cpp 测试全流程分析)

[4.1.1 测试链表基础功能和迭代器 && 迭代器:begin() / end()](#4.1.1 测试链表基础功能和迭代器 && 迭代器:begin() / end())

[4.1.2 测试尾插 / 头插 / 尾删 / 头删](#4.1.2 测试尾插 / 头插 / 尾删 / 头删)

[4.1.3 打印链表:Print](#4.1.3 打印链表:Print)

[4.1.4 测试链表拷贝构造和赋值操作](#4.1.4 测试链表拷贝构造和赋值操作)

[4.2 一口气讲完list底层基础框架实现:结合注释(list.h文件)](#4.2 一口气讲完list底层基础框架实现:结合注释(list.h文件))

[4.2.1 链表节点结构](#4.2.1 链表节点结构)

[4.2.2 链表迭代器](#4.2.2 链表迭代器)

[4.2.3 链表主体类](#4.2.3 链表主体类)

[4.2.4 迭代器访问接口](#4.2.4 迭代器访问接口)

[4.2.5 链表初始化相关](#4.2.5 链表初始化相关)

[4.2.6 析构函数](#4.2.6 析构函数)

[4.2.7 拷贝控制函数](#4.2.7 拷贝控制函数)

[4.2.8 容量操作](#4.2.8 容量操作)

[4.2.9 元素访问操作](#4.2.9 元素访问操作)

[4.2.10 指定位置操作](#4.2.10 指定位置操作)

[4.2.11 容量信息](#4.2.11 容量信息)

[4.3 链表 && 迭代器机制图解](#4.3 链表 && 迭代器机制图解)

[4.3.1 链表的节点管理:内存分配和对象构造/销毁](#4.3.1 链表的节点管理:内存分配和对象构造/销毁)

[4.3.2 双向链表的插入和删除操作](#4.3.2 双向链表的插入和删除操作)

[4.3.3 双向链表的结构和插入操作](#4.3.3 双向链表的结构和插入操作)

[4.3.4 迭代器(iterator)的设计理念和实现](#4.3.4 迭代器(iterator)的设计理念和实现)

[4.3.5 链表迭代器的具体实现](#4.3.5 链表迭代器的具体实现)

[4.3.6 链表迭代器的完整使用和实现对应关系](#4.3.6 链表迭代器的完整使用和实现对应关系)

[4.3.7 链表迭代器的工作原理和遍历过程](#4.3.7 链表迭代器的工作原理和遍历过程)

[4.3.8 迭代器实现的两种设计思路](#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()

cpp 复制代码
// 测试链表基础功能和迭代器的函数
// 目的:验证链表的基本插入操作和两种遍历方式
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 测试尾插 / 头插 / 尾删 / 头删

这个代码运行会报错------

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

问题出在这里------

再运行一次,成功了------

代码演示如下------

cpp 复制代码
// 测试链表基本操作的函数
// 目的:验证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

cpp 复制代码
// 打印链表函数 - 常量版本
// 功能:遍历并输出链表中所有元素,不修改原链表
// 参数: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 测试链表拷贝构造和赋值操作

cpp 复制代码
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:

cpp 复制代码
#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> // 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 = 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:

cpp 复制代码
#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
}

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

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

	return 0;
}

结尾

往期回顾:

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

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

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

૮₍ ˶ ˊ ᴥ ˋ˶₎ა

相关推荐
游坦之2 小时前
基于Java Swing的智能数据结构可视化系统 | 支持自然语言交互的AI算法助手
java·数据结构·交互
寒冬没有雪3 小时前
矩阵的翻转与旋转
c++·算法·矩阵
ʚ希希ɞ ྀ3 小时前
二叉树的层序遍历
数据结构·算法
努力也学不会java3 小时前
【Java并发】深入理解synchronized
java·开发语言·人工智能·juc
星竹晨L3 小时前
【C++】深入理解list底层:list的模拟实现
开发语言·c++
Tiny番茄3 小时前
leetcode 3. 无重复字符的最长子串
数据结构·python·算法·leetcode
---学无止境---3 小时前
Linux中页面分配alloc_pages相关函数
linux
豐儀麟阁贵3 小时前
Java知识点储备
java·开发语言
豐儀麟阁贵3 小时前
2.3变量与常量
java·开发语言