【C++】一条双向链的诞生:手写 list 模拟实现

🏆浮世尘弦:个人主页

⭐个人专栏:《C语言》、《数据结构与算法》、《C++》

💎:非淡薄无以明志,非宁静无以致远


文章目录

前言

C++参考文档

一,List的底层原理

1.1,List的结构

1.2,List和vector的对比

1.3,sort排序算法对比

二,List各个模块的实现(List.h)

2.1,链表节点和成员变量

2.2,迭代器模块⭐

2.3,链表list类的初始化和释放

2.4,list的基本操作函数

2.4.1迭代器失效

三,测试用例(test.cpp)

3.1,测试用例1:构造,遍历与访问(push_back+迭代器+operator->以及print_container)

3.2,测试用例2:迭代器失效测试(insert,erase)

3.3,测试用例3:赋值重载和拷贝构造(list()、operator=)

3.4,测试用例4:initializer_list函数(C++11)

四,本文完整代码

4.1,头文件list.h

4.2,测试源文件test.cpp

结尾:


前言

在上一篇C++专题当中,已经简单介绍了List类和他的函数的使用。接下来我们要学习一下List是如何模拟实现的。本文的重****难点在于讲解List迭代器和之前的vector和string的迭代器的不同。接下来开始本篇的学习吧!

C++参考文档

cplusplus(非官方文档):list

官方文档:C++ reference

一,List的底层原理

1.1,List的结构

List的结构是一个 双向循环的带头链表。

通过这种结构使得list在任意位置插入元素的效率极高。

1.2,List和vector的对比

接下来从7个维度进行对比:

特性 std::vector std::list
底层结构 动态数组(连续内存) 双向链表(离散内存)
随机访问 支持,O(1) 不支持,O(n)
尾部插入/删除 均摊 O(1) O(1)
中间插入/删除 O(n)(需移动元素) O(1)(已知位置时)
迭代器失效 扩容、插入或删除时极易失效 插入不失效,删除仅当前失效
CPU缓存 极度友好 不友好(指针跳跃)
内存开销 小(紧凑) 大(每个节点多两个指针)

std:vector:内存连续 ,对 CPU 缓存极其友好。CPU 预取机制能发挥最大作用,遍历速度极快。

std::list: 内存碎片化,节点分散。遍历时指针跳跃会导致频繁的缓存未命中(Cache Miss) ,因此实际遍历速度往往比**vector** 慢很多(即使时间复杂度都是 O(n))。

空间开销 :vector 几乎没有额外开销;**list**每个节点都要额外存两个指针(64位上是 16 字节),内存开销较大。

总结一下:

vector和list可以进行互补,具体的应用的场景如下:

如果需要频繁插入和删除,并且元素量非常大(拷贝成本高)时,优先选择list

如果数据量不大,同时需要进行随机访问,只会在尾部插入元素或删除元素时,选择vector

1.3,sort排序算法对比

算法库当中的sort需要随机迭代器,而list的迭代器是双向迭代器,所以只能使用自己实现的sort算法(归并排序)。

而算法库当中sort使用的是快速排序。速度要比归并排序的速度快。

即使list先转化为vector进行排序后再拷贝回来的速度也比list直接使用自己的sort进行排序快。

具体可以参照下面代码:

cpp 复制代码
void test_time3()
{
	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);
}

Debug下运行结果:

这样看起来速度差不多,而且为什么list自己的归并排序反而速度更快。这是为什么呢?

在Debug下:

第一种:lt2.assign(v.begin(), v.end()):它会清空原有的lt2(释放100万个节点),并重新分配100万个节点(100万次 new 和 delete),再将数据逐个拷贝回去

100万次 new 和 delete 的代价极其惨重。Windows的Debug堆管理器会为每次分配添加额外的调试信息(如内存填充、内存块头、泄漏跟踪等),导致分配速度极慢

接下来看一下release下的结果:

release下结果发生了反转,先转化为vector进行排序再拷贝回去的速度比直接使用list的归并排序的速度快了1倍多。

在release下:vector 排序和复制操作大幅加速,vector 和 list 遍历都更快,vector 排序远快于 list 指针跳转。

结论:比较速度最好在release下进行比较,Debug下的结果可能不准确。

二,List各个模块的实现(List.h)

在list的模拟实现中我们可以通过类模版来实现,为了方便展示,这里list的函数声明和定义就不分开了,我们只需要创建两个文件:List.h和test.cpp,前面的用来实现List的各种功能,后面的用来对我们实现的函数进行测试。同时为了防止和std命名空间当中的List发生冲突,需要将模拟实现的List放入到新的命名空间att当中。

list可以分为:

2.1,链表节点和成员变量

在之前C语言中数据结构当中节点只是一个结构体,而在这里是一个类,可以自动进行初始化。

节点除了元素本身还要包含分别指向上一个节点和下一个节点的指针。

cpp 复制代码
namespace att
{
	template <class T>
	//节点为一个单独的类
	struct list_node
	{
		T _data; //使用struct定义类,里面的成员默认为public
		list_node<T>* _next;
		list_node<T>* _prev;
		//构造函数
		list_node(const T& data = T())//这里使用默认值T()进行初始化
			:_data(data)
			, _next(nullptr)
			, _prev(nullptr)
		{}
	};
    class list
    {
    public:
	    typedef list_node<T> Node;
        //成员函数
        //............
	private:
		Node* _head;
		size_t _size;
	};
}

1,模板参数T主要控制节点内部的元素类型。

2,参数中data = T()保证了默认初始化为0

3,对节点的前驱指针和后继指针进行初始化为nullptr,防止野指针。

list的成员只需要两个变量既可以进行控制:1,_head 头节点 2,_size 元素个数

2.2,迭代器模块⭐

list的迭代器不是原生指针,而是一个进行封装的类。

在list的模拟实现中:

1,list的模拟实现中为什么要对迭代器进行封装?

2,为什么重载函数要实现在迭代器的内部?而不是在类list当中?

首先回答问题1:因为 list 的底层空间不连续,不能直接用普通指针模拟迭代器,用户需要的是类似指针的统一操作,所以需要设计一个迭代器类,内部保存当前节点指针。

问题2:因为这些运算符操作的是"迭代器当前位置",而不是整个 list 对象。

cpp 复制代码
	//list迭代器
	//模版化--包含const和普通iterator
    //T,T&,T*
	template <class T,class T1,class T2>
	struct list_iterator
	{
		typedef list_node<T> Node;
		typedef list_iterator<T,T1,T2> _iterator;
		Node* node;
		list_iterator(Node* _node)
			:node(_node)
		{}
		T1 operator*()
		{
			return node->_data;
		}
		//处理元素类型T是结构体类型,通过指针访问内部成员变量
		T2 operator->()
		{
			return &node->_data;
		}
		_iterator& operator++()
		{
			node = node->_next;
			return *this;
		}
		_iterator& operator--()
		{
			node = node->_prev;
			return *this;
		}
		//后置++,--
		_iterator operator++(int)
		{
			_iterator tmp(*this);
			node = node->_next;
			return tmp;
		}
		_iterator operator--(int)
		{
			_iterator tmp(*this);
			node = node->_prev;
			return tmp;
		}
		bool operator!=(const _iterator& it2) const
		{
			return node != it2.node;
		}
		bool operator==(const _iterator& it2)const
		{
			return node == it2.node;
		}
	};

成员只有一个node节点,设置三个模板参数满足不同操作的返回值类型。

通过类模板,完成对普通迭代器和const迭代器的适配

封装迭代器主要有这些作用:

  • 隐藏 Node、next、prev 等底层实现,用户不需要知道链表的节点结构。
  • 为所有 STL 算法提供统一接口,例如 find、count、范围 for。
  • 支持普通迭代器和 const_iterator,控制是否允许修改数据。
  • 保存独立的遍历位置,多个迭代器可以同时遍历同一个链表。

2.3,链表list类的初始化和释放

链表的初始化主要分为:默认构造,拷贝构造 。接着加上一个赋值重载 。最后补充析构函数。

cpp 复制代码
	template <class T> //每次创建类,需要重新写模板类
	class list
	{
	public:
		typedef list_node<T> Node;
		//对两种迭代器进行重命名
		typedef list_iterator<T,T&,T*> iterator;
		typedef list_iterator<T, const T&, const T*>const_iterator;

		//默认构造
		list()
		{
			empty_Init();
		}
		//数组直接构造(拷贝构造)--C++11
		list(initializer_list<T> lt)
		{
			empty_Init();
			for (auto& ch : lt)
			{
				push_back(ch);
			}
		}
		//拷贝构造t1(t2)
		//先创建一个头结点哨兵位,后续构造直接进行尾插
		//否则不知道往哪里进行尾插
		void empty_Init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}
		//接着往哨兵位后面插入数据即可
		list(const list<T>& v1)
		{
			empty_Init();
			for (auto& ch : v1)
			{
				push_back(ch);
			}
		}

		//赋值重载
		//v2 = v1
		void swap(list<T>& v1)
		{
			std::swap(_head, v1._head);
			std::swap(_size, v1._size);
		}
		//现代写法--直接交换
		list<T>& operator=(list<T> v1)
		{
			swap(v1);
			return *this;
		}
		void clear()
		{
			iterator cur = begin();
			while (cur != end())
			{
				cur = erase(cur);//更新一下防止野指针
			}
		}
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

核心解读:

1,空初始化empty_Init();,初始化一个哨兵位,方便拷贝构造等构造直接进行使用insert直接在后面进行插入元素即可。

2,接口复用:empty_Init(),push_back()都被进行了复用。减少了代码量更加简洁。 //数组直接构造(拷贝构造)

3,析构函数删除所有节点,再释放头节点,保证无内存泄漏。clear只清空数据,保留头节点,方便下次使用。

4,--C++11 list(initializer_list<T> lt)部分的构造见下面的测试用例

2.4,list的基本操作函数

list的基本操作包括:begin()、end()、insert()、erase()、push_back()、push_front()、pop_back()、pop_front()、size()、empty()

cpp 复制代码
		//迭代器初始位置和终止位置
		iterator begin()
		{
			return _head->_next;
		}
		iterator end()
		{
			return _head;
		}
		const_iterator begin()const
		{
			return _head->_next;
		}
		const_iterator end()const
		{
			return _head;
		}
		//由于空间不连续同时不开辟新的空间,所以不会出现野指针的问题
		//同时如果不用返回值更新迭代器,依旧迭代器依旧指向原来的位置没有改变
		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos.node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);
			//进行插入
			newnode->_next = cur;
			prev->_next = newnode;
			newnode->_prev = prev;
			cur->_prev = newnode;
			_size++;
			return newnode;
		}
		//直接复用insert
		void push_back(const T& x)
		{
			insert(end(), x);
		}
		void push_front(const T& x)
		{
			insert(begin(), x);
		}
		//erase如果不进行接收返回值更新,就会出现野指针的情况,因为迭代器所指对象已经析构
		iterator erase(iterator pos)
		{
			//删除的元素不能是哨兵位
			assert(pos != end());
			Node* next = pos.node->_next;
			Node* prev = pos.node->_prev;
			prev->_next = next;
			next->_prev = prev;
			delete pos.node;
			_size--;
			return next;
		}
		void pop_back()
		{
			erase(--end());
		}
		void pop_front()
		{
			erase(begin());
		}
		size_t size() const
		{
			return _size;
		}
		bool empty() const
		{
			return _size == 0;
		}
	private:
		Node* _head;
		size_t _size;

核心解读:

1,接口复用:insert和erase被多次直接进行复用

2.4.1迭代器失效

先说结论:在list当中insert不会导致迭代器失效,erase会导致迭代器失效

insert原因:在list当中insert不会导致开辟新空间,因此不会出现野指针的情况。同时不会出现数据的挪动,因此迭代器还是指向原来的位置。因此不会出现迭代器失效
erase原因:erase会删除元素导致迭代器指向失效,需要重新更新指向的节点。

我们对insert和erase都设置了迭代器返回值方便进行更新,insert是指向插入元素的迭代器,而erase是指向当前删除位置的下一个位置的元素。

三,测试用例(test.cpp)

头文件:

头文件写在test.cpp当中即可,List.h当中不用写任何的头文件,List.h在test.cpp中才会进行使用,头文件当中的全部内容在#include "List.h"处进行展开。只需要在#include "List.h"前进行包含即可。

打印模板 :

cpp 复制代码
	//T* const ptr 表示指向不能进行修改
	//const T* ptr 表示指向的内容不能修改
	template <class container>
	void print_Container(const container& lt)
	{
		//const iterator:iterator自身不能改变,即指向不能进行改变
		//const_iterator:iterator指向的内容不能进行修改
		typename container::const_iterator it = lt.begin();
		//此时可以使用auto进行简写
		//auto it = lt.begin();
		while (it != lt.end())
		{
			//*it += 10;   //按需实例化,未检查出来
			cout << *it << " ";
			it++;
		}
		cout << endl;
		for (auto& ch : lt)
		{
			cout << ch << " ";
		}
		cout << endl;
	}

核心解读:

1,由于所有的容器都有迭代器,所以我们可以通过模板和迭代器实现对所有容器的打印。

container::const_iterator it = lt.begin();为什么前面要加上typename?

原因:此时 container 还是未知的模板参数,只有当模板被实例化时才知道它具体是 **list<int>、vector<int>**还是其他类型。因此编译器不能确定类型:可能是一个类型,一个静态变量;或者是一个静态函数;因此需要加上typename明确告诉编译器这是一个类型。

可以使用auto进行简化。

按需实例化:

print_container中有一行指令为*it += 10,但是在const参数当中it是不能进行改变的。但是实际运行时如果不执行print_container这一函数的话,即使有这一行指令,也不会进行报错。

上面这种现象就是按需实例化导致的隐藏错误。

按需实例化只会对具体运行使用到的部分进行细致的检查,对没有用到的部分只会大致搜查明显的写法错误,因此不执行print_container这一函数时就不会进行报错。

3.1,测试用例1:构造,遍历与访问(push_back+迭代器+operator->以及print_container)

cpp 复制代码
	struct B
	{
		int b1 = 6;
		int b2 = 3;
	};
	void test1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

		list<int>::iterator cur = lt.begin();
		while (cur != lt.end())
		{
			cout << *cur << " ";
			cur++;
		}
		cout << endl;
		for (auto& ch : lt)
		{
			cout << ch << " ";
		}
		cout << endl;
		print_Container(lt);

		list<B> lt2;
		lt2.push_back(B());//匿名对象构造
		lt2.push_back(B(2,3));
		lt2.push_back(B(4,5));
		lt2.push_back(B(6,7));
		list<B>::iterator t1 = lt2.begin();
		while (t1 != lt2.end())
		{
			cout << t1->b1 << ":" << t1->b2 << endl;
			//上面的写法是一种简写,下面才是实际的写法
			cout << t1.operator->()->b1 << ":" << t1.operator->()->b2 << endl;
			t1++;
		}
		cout << endl;
 	}

打印结果:

结果解读:

1,当节点当中的对象T是一个结构体时,可以使用迭代器当中的operator->进行访问。

2,push_back ,迭代器,operator->的功能均正常。

3.2,测试用例2:迭代器失效测试(insert,erase)

cpp 复制代码
	//测试迭代器失效
	void test2()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		//insert
		list<int>::iterator it = lt.begin();
		lt.insert(it, 25);
		*it += 200;
		for (auto v1 : lt)
		{
			cout << v1 << " ";
		}
		cout << endl;
		//erase--删除所有的偶数
		list<int>::iterator it2 = lt.begin();
		while(it2 != lt.end())
		{
			//判断是否为偶数
			if (*it2 % 2 == 0)
			{
				it2 = lt.erase(it2);
			}
			else
			{
				it2++;
			}
		}
		for (auto v3 : lt)
		{
			cout << v3 << " ";
		}
		cout << endl;
	}

运行结果:

结果分析:

1,insert没有进行更新迭代器,就在原来的位置处进行+200

2,而erase必须进行更新,否则会出现野指针导致的迭代器失效的问题

3.3,测试用例3:赋值重载和拷贝构造(list()、operator=)

cpp 复制代码
	void test3()
	{
		list<int> lt1;
		lt1.push_back(1);
		lt1.push_back(2);
		lt1.push_back(3);
		lt1.push_back(4);
		list<int> lt2(lt1);
		print_Container(lt1);
		print_Container(lt2);
		list<int> lt3;
		lt3.push_back(10);
		lt3.push_back(20);
		lt3.push_back(30);
		lt3.push_back(40);
		print_Container(lt3);
	}

运行结果:

结果分析:拷贝构造和赋值重载功能正常

3.4,测试用例4:initializer_list<int>函数(C++11)

cpp 复制代码
	//测试initializer_list<T>
	void func(const list<int>& lt)
	{
		print_Container(lt);
	}
	void test4()
	{
		//直接进行构造
		list<int> t1({ 1,2,3,4,5,6 });
		//隐式类型转化
		list<int> t2 = { 4,5,6,7,8,9 };
		const list<int>& t3 = { 2,3,4,5,6,7,8 };
		func(t2);
		func({ 12,33,45,6,44 });
		print_Container(t1);
		initializer_list<int> t4 = { 4,5,4,3,2,1 };
		cout << typeid(t4).name() << endl;
		//64位下是16字节
		cout << sizeof(t4) << endl;//本质上是两个指针32位下是8字节
	}

32位(x86)下运行结果:

结果分析:

1,initializer_list可以通过数组直接进行构造,也可以先隐式类型转化为list再进行拷贝

2,initializer_list是通过一个begin()迭代器和一个end()迭代器进行控制的所以32位下大小为8个字节

四,本文完整代码

gitee链接:https://gitee.com/codelsj-w/test9.20.git

4.1,头文件list.h

cpp 复制代码
#pragma once

namespace att
{
	template <class T>
	//节点为一个单独的类
	struct list_node
	{
		T _data; //使用struct定义类,里面的成员默认为public
		list_node<T>* _next;
		list_node<T>* _prev;
		//构造函数
		list_node(const T& data = T())//这里使用默认值T()进行初始化
			:_data(data)
			, _next(nullptr)
			, _prev(nullptr)
		{}
	};

	//list迭代器
	//模版化--包含const和普通iterator
    //T,T&,T*
	template <class T,class T1,class T2>
	struct list_iterator
	{
		typedef list_node<T> Node;
		typedef list_iterator<T,T1,T2> _iterator;
		Node* node;
		list_iterator(Node* _node)
			:node(_node)
		{}
		T1 operator*()
		{
			return node->_data;
		}
		//处理元素类型T是结构体类型,通过指针访问内部成员变量
		T2 operator->()
		{
			return &node->_data;
		}
		_iterator& operator++()
		{
			node = node->_next;
			return *this;
		}
		_iterator& operator--()
		{
			node = node->_prev;
			return *this;
		}
		//后置++,--
		_iterator operator++(int)
		{
			_iterator tmp(*this);
			node = node->_next;
			return tmp;
		}
		_iterator operator--(int)
		{
			_iterator tmp(*this);
			node = node->_prev;
			return tmp;
		}
		bool operator!=(const _iterator& it2) const
		{
			return node != it2.node;
		}
		bool operator==(const _iterator& it2)const
		{
			return node == it2.node;
		}
	};
	template <class T> //每次创建类,需要重新写模板类
	class list
	{
	public:
		typedef list_node<T> Node;
		//对两种迭代器进行重命名
		typedef list_iterator<T,T&,T*> iterator;
		typedef list_iterator<T, const T&, const T*>const_iterator;

		//默认构造
		list()
		{
			empty_Init();
		}
		//数组直接构造(拷贝构造)--C++11
		list(initializer_list<T> lt)
		{
			empty_Init();
			for (auto& ch : lt)
			{
				push_back(ch);
			}
		}
		//拷贝构造t1(t2)
		//先创建一个头结点哨兵位,后续构造直接进行尾插
		//否则不知道往哪里进行尾插
		void empty_Init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}
		//接着往哨兵位后面插入数据即可
		list(const list<T>& v1)
		{
			empty_Init();
			for (auto& ch : v1)
			{
				push_back(ch);
			}
		}

		//赋值重载
		//v2 = v1
		void swap(list<T>& v1)
		{
			std::swap(_head, v1._head);
			std::swap(_size, v1._size);
		}
		//现代写法--直接交换
		list<T>& operator=(list<T> v1)
		{
			swap(v1);
			return *this;
		}
		void clear()
		{
			iterator cur = begin();
			while (cur != end())
			{
				cur = erase(cur);//更新一下防止野指针
			}
		}
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		//迭代器初始位置和终止位置
		iterator begin()
		{
			return _head->_next;
		}
		iterator end()
		{
			return _head;
		}
		const_iterator begin()const
		{
			return _head->_next;
		}
		const_iterator end()const
		{
			return _head;
		}
		//由于空间不连续同时不开辟新的空间,所以不会出现野指针的问题
		//同时如果不用返回值更新迭代器,依旧迭代器依旧指向原来的位置没有改变
		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos.node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);
			//进行插入
			newnode->_next = cur;
			prev->_next = newnode;
			newnode->_prev = prev;
			cur->_prev = newnode;
			_size++;
			return newnode;
		}
		//直接复用insert
		void push_back(const T& x)
		{
			insert(end(), x);
		}
		void push_front(const T& x)
		{
			insert(begin(), x);
		}
		//erase如果不进行接收返回值更新,就会出现野指针的情况,因为迭代器所指对象已经析构
		iterator erase(iterator pos)
		{
			//删除的元素不能是哨兵位
			assert(pos != end());
			Node* next = pos.node->_next;
			Node* prev = pos.node->_prev;
			prev->_next = next;
			next->_prev = prev;
			delete pos.node;
			_size--;
			return next;
		}
		void pop_back()
		{
			erase(--end());
		}
		void pop_front()
		{
			erase(begin());
		}
		size_t size() const
		{
			return _size;
		}
		bool empty() const
		{
			return _size == 0;
		}
	private:
		Node* _head;
		size_t _size;
	};


	//T* const ptr 表示指向不能进行修改
	//const T* ptr 表示指向的内容不能修改
	template <class container>
	void print_Container(const container& lt)
	{
		//const iterator:iterator自身不能改变,即指向不能进行改变
		//const_iterator:iterator指向的内容不能进行修改
		typename container::const_iterator it = lt.begin();
		//此时可以使用auto进行简写
		//auto it = lt.begin();
		while (it != lt.end())
		{
			//*it += 10;   //按需实例化,未检查出来
			cout << *it << " ";
			it++;
		}
		cout << endl;
		for (auto& ch : lt)
		{
			cout << ch << " ";
		}
		cout << endl;
	}


	struct B
	{
		int b1 = 6;
		int b2 = 3;
	};
	void test1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

		list<int>::iterator cur = lt.begin();
		while (cur != lt.end())
		{
			cout << *cur << " ";
			cur++;
		}
		cout << endl;
		for (auto& ch : lt)
		{
			cout << ch << " ";
		}
		cout << endl;
		print_Container(lt);

		list<B> lt2;
		lt2.push_back(B());//匿名对象构造
		lt2.push_back(B(2,3));
		lt2.push_back(B(4,5));
		lt2.push_back(B(6,7));
		list<B>::iterator t1 = lt2.begin();
		while (t1 != lt2.end())
		{
			cout << t1->b1 << ":" << t1->b2 << endl;
			//上面的写法是一种简写,下面才是实际的写法
			cout << t1.operator->()->b1 << ":" << t1.operator->()->b2 << endl;
			t1++;
		}
		cout << endl;
 	}
	//测试迭代器失效
	void test2()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		//insert
		list<int>::iterator it = lt.begin();
		lt.insert(it, 25);
		*it += 200;
		for (auto v1 : lt)
		{
			cout << v1 << " ";
		}
		cout << endl;
		//erase--删除所有的偶数
		list<int>::iterator it2 = lt.begin();
		while(it2 != lt.end())
		{
			//判断是否为偶数
			if (*it2 % 2 == 0)
			{
				it2 = lt.erase(it2);
			}
			else
			{
				it2++;
			}
		}
		for (auto v3 : lt)
		{
			cout << v3 << " ";
		}
		cout << endl;
	}
	//测试赋值重载和拷贝构造
	void test3()
	{
		list<int> lt1;
		lt1.push_back(1);
		lt1.push_back(2);
		lt1.push_back(3);
		lt1.push_back(4);
		list<int> lt2(lt1);
		print_Container(lt1);
		print_Container(lt2);
		list<int> lt3;
		lt3.push_back(10);
		lt3.push_back(20);
		lt3.push_back(30);
		lt3.push_back(40);
		lt1 = lt3;
		print_Container(lt1);
	}
	//测试initializer_list<T>
	void func(const list<int>& lt)
	{
		print_Container(lt);
	}
	void test4()
	{
		//直接进行构造
		list<int> t1({ 1,2,3,4,5,6 });
		//隐式类型转化
		list<int> t2 = { 4,5,6,7,8,9 };
		const list<int>& t3 = { 2,3,4,5,6,7,8 };
		func(t2);
		func({ 12,33,45,6,44 });
		print_Container(t1);
		initializer_list<int> t4 = { 4,5,4,3,2,1 };
		cout << typeid(t4).name() << endl;
		//64位下是16字节
		cout << sizeof(t4) << endl;//本质上是两个指针32位下是8字节
	}

}

4.2,测试源文件test.cpp

cpp 复制代码
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <initializer_list>
#include <assert.h>
using namespace std;

void test_time1()
{
	srand(time(0));
	const int N = 1000000;

	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_time3()
{
	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);
}
//int main()
//{
//	test_time3();
//	return 0;
//}

//List的模拟实现测试
#include "List.h"
int main()
{
	//att::test1();
	//att::test2();
	//att::test3();
	att::test4();
	return 0;
}

以上就是本文《list模拟实现》的全部内容了,如果对你有所帮助的话,请大佬不妨给博主来个"一键三连",这对我是大大的支持,也能让博主产出更优质的内容。

结尾:

往期回顾:

双向奔赴的链表:list 使用全指南

总结:C++ std::list 是双向链表容器,头文件 <list>。内存不连续,不支持随机访问,只能双向迭代。任意位置插入、删除为 O(1)(已知位置),且不会使其他元素迭代器失效。常用操作:push_front/back、pop_front/back、insert、erase、clear、size、empty。内置 sort、unique、remove、merge、splice、reverse 等成员函数。适合频繁中间增删、无需随机访问的场景;但缓存不友好,遍历和查找较慢。

⚡把上面的内容吃透就休息一下吧⚡

相关推荐
沫璃染墨1 小时前
《从零入门Linux系统篇(五十六):线程篇·九——生产者消费者模型:从条件变量到BlockingQueue实现》
linux·服务器·c++·驱动开发·设计模式·架构·系统架构
张小姐的猫1 小时前
【Linux】网络编程 —— 五种IO模型
linux·运维·服务器·网络·c++·人工智能·php
重生之小比特1 小时前
【C++进阶】02 Linux基本指令
java·数据库·c++
(Charon)1 小时前
【C++面试】手写智能指针(一):从unique_ptr理解独占所有权与移动语义
开发语言·c++
李游Leo2 小时前
HarmonyOS 7 + Spatial Recon Kit-C++:3DGS 输入帧内参与位姿的版本一致性门禁【鸿蒙心迹】
c++·3d·harmonyos
(Charon)2 小时前
【C++面试】手写智能指针(二):从shared_ptr理解引用计数与共享所有权
c++·算法
无名猿2 小时前
构造函数详解:默认、委托、转换与 explicit 到底写在哪
c++·现代c++·语法基础
纪念 2292 小时前
C++ string(一)
android·开发语言·c++
纪念 2292 小时前
C++算法(二)
开发语言·c++·算法