【C++】list容器实现

开发者福利!热门AI工具限时免费用 购周边即赠Coding Plan Lite,Claude Code、Cursor等20+工具畅享,效率翻倍! 阅读详情

目录

一、list和vector区别

二、list模拟实现过程

1. 构造函数

2. 尾插 push_back

3. 迭代器

4. 任意位置插入 insert

5.删除指定位置的节点 erase

6. 复用

7. 链表节点个数

8. 析构函数

9. 拷贝构造函数

10. 赋值运算符重载

11.多参数构造


正文:

本次list详解主要放在迭代器和类封装部分,特别是迭代器。

一、list和vector区别

(1)在两者的底层数据结构中:

vector:动态数组,元素在内存中连续存储。

list:双向链表,元素通过指针非连续存储(每个节点包含前驱和后继指针)。

(2)迭代器支持:

vector:支持随机访问迭代器(可跳跃访问)。

list:仅支持双向迭代器(只能逐元素移动)。

(3)内存占用:

vector:内存紧凑,仅存储元素,缓存友好(预取效率高)。

list:每个元素额外占用两个指针空间(前驱/后继),内存碎片可能更多。

(4)使用场景:

选择vector:(1)需要频繁随机访问(2) 内存连续性对性能关键(如缓存优化)(3)尾部操作为主,中间插入较少。

选择list:(1)避免扩容导致的开销或迭代器失效(2)不需要随机访问(3)频繁在任意位置插入/删除。

二、list模拟实现过程

list:带头双向循环链表,底层是由一个一个节点构成。

可以理解为:有多个节点,再把节点串起来就形成链表。list 类的成员变量通常是一个节点类型的头指针(_head)指向链表的首节点,通过不断插入节点和更改指针之间指向形成一个链表结构,而每个节点是一个独立单元,通常包含:数据部分(存储元素值)、前驱指针、后继指针,在 C++ 实现中,节点用类实现。

list类模版如下:

节点的类模板用struct可以直接访问内部数据,模版一般声明定义不分离。外部包了一层zss命名空间,防止在大项目中因命名冲突引发报错!

1. 构造函数

目的是对list进行初始化,由于后面可能出现单独调用初始化功能,所以将初始化的步骤单独提出写成一个函数,让构造函数内部调用实现初始化。

目前实现的链表是带头链表,创建链表第一步是开一个Node大小的空间作为哨兵节点,当没有节点插入时,它的前驱和后继都是指向自己(已通过typedef把节点模版命名为Node)

接下来实现的方法除非有特别声明位置,不然都在public下

2. 尾插 push_back

链表的底层是节点,尾插一个数得创建一个节点把这个数存到节点中才能插入。

 

3. 迭代器

迭代器本质:不暴露容器底层结构,用户不需要关心底层结构,让使用变得简单

在vector中迭代器的底层结构是原生指针int* 但是list不能直接用node*

因为node*解引用后是节点不是数据,达不到我们要的效果。需要用一个类去封装节点指针,然后进行运算符的重载按我们要求实现。

迭代器的类型分为:普通迭代器、const迭代器、反向迭代器

本次以普通迭代器和const迭代器为主,const迭代器并不是在函数名前面加个const就行,这样const修饰的是函数本身不能修改,我们需要函数的内容不修改本身可以改变。所以要同时实现两个迭代器类模版(如下图)

两个迭代器模版只有名字不同,其他重复度太高因此可合成一个模版。通过参数不同来判断调什么类型迭代器。

   

使用同一个模版后:

先定造一个迭代器模版:迭代器本质是封装了节点指针的类,通过运算符重载模拟指针行为。

使用 Ref(引用)和 Ptr(指针)模板参数,统一普通迭代器和 const 迭代器的实现。

list 类通过 typedef 定义迭代器类型,并暴露 begin()/end() 接口。

 

为什么 operator-> 返回 Ptr(指针)?
为了支持 it->method() 的语法,编译器会隐式转换为 Ptr->method()。
 

4. 任意位置插入 insert

只要插入就要创建节点,改变指向逻辑。

    

5.删除指定位置的节点 erase

由于编译器的强制机制,erase后的迭代器可能失效所以要更新一下迭代器,erase后返回pos位置的下一个迭代器。

删除节点之前要先把指针指向调整好,为了防止调整过程中的思维混乱通过定义变量记录下每个节点。

6. 复用

头插、尾插、头删、尾删的复用:插入、删除和迭代器功能都已经实现,这几个就可巧妙复用。

 

7. 链表节点个数

由于有成员变量_size,插入删除中都有对应++/--,当要求链表节点个数时直接返回就行。

 

8. 析构函数

在对整个链表析构前要先清空数据(链表的数据是节点),清空数据的方法可能被单独调用所以提取出来写成一个clean()函数。clean后再删除头结点,将其置空。

clean内部直接复用erase进行链表中节点数据的清理,由于上面提到erase后迭代器会失效所以然it重新赋值一下erase的返回值。

9. 拷贝构造函数

此处就体现了文章开头为什么要把初始化方法单独写成一个函数而不是在构造函数中实现的重要性,因为在拷贝构造中就出现被单独调用了!

拷贝构造:用一个已存在的对象list<T>& lt 构造一个新对象。可以直接巧妙用范围for遍历 lt 对象然后尾插形成新对象。

10. 赋值运算符重载

此处建议用现代写法:通过浅拷贝传参,lt里面就是我想要的数据,利用swap直接将数据交换给自己,函数运行结束后lt会自动销毁。

11.多参数构造

这个是C++11中的一种构造方式。

initializer_list:允许用花括号 {} 初始化对象(如容器、自定义类)。

头文件#include <initializer_list>

以上十几点就是list的一些比较常用的方法实现,小伙伴们要是有兴趣可以对应去看一下list的原码和一些更详细的方法。下面把整个list实现代码附上:

#pragma once
#include<iostream>
#include<assert.h>
#include<algorithm>
#include<vector>
using namespace std;

namespace zss
{
	template<class T>
	struct list_Node
	{
		list_Node<T>* _next;
		list_Node<T>* _prev;
		T _date;

		//拷贝构造
		list_Node(const T& x = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _date(x)
		{
		}
	};
	//3.迭代器
	template<class T,class Ref,class Ptr>
	struct list_iterator
	{
		//迭代器本质还是节点
		typedef list_Node<T> Node;
		typedef list_iterator<T, Ref, Ptr> Self;
		Node* _node;

		list_iterator(Node* node)
			:_node(node)
		{
		}
		//解引用
		Ref operator*()
		{
			return _node->_date;
		}
		Ptr operator->()
		{
			return &_node->_date;
		}
		//迭代器++
		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		//后置++
		Self operator++(int)
		{
			Self tmp(*this);
			_node = _node->_next;
			return tmp;
		}
		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		//后置--
		Self operator--(int)
		{
			Self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

		bool operator!=(const Self& it)
		{
			return _node != it._node;
		}
		bool operator==(const Self& it)
		{
			return _node == it._node;
		}
	};

	template <class T>
	class list
	{
		typedef list_Node<T> Node;
	public:
		/*typedef list_iterator<T> iterator;
		typedef list_const_iterator<T> const_iterator;*/
		typedef list_iterator<T,T&,T*> iterator;
		typedef list_iterator<T, const T&, const T*> const_iterator;

		//普通迭代器
		iterator begin()
		{
			return iterator(_head->_next);
		}
		iterator end()
		{
			return iterator(_head);
		}
		//const迭代器
		const_iterator begin()const
		{
			return const_iterator(_head->_next);
		}
		const_iterator end()const
		{
			return const_iterator(_head);
		}

		//1.构造函数
		void empty_init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}
		list()
		{
			empty_init();
		}

		//19.拷贝构造:lt2(lt1)
		list(const list<T>& lt)
		{
			empty_init();
			for (auto& e : lt)
			{
				push_back(e);
			}
		}
		//10.赋值重载:lt1 = lt3
		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);

		}
		list<T>& operator=(list<T> lt)
		{
			//超绝现代写法
			swap(lt);
			return *this;
		}
		//11.多参数构造
		list(initializer_list<T> lt)
		{
			//凡是链表的构造先想是否可以范围for+尾插实现
			empty_init();
			for (auto& e : lt)
			{
				push_back(e);
			}
		}

		//8.析构
		~list()
		{
			//先清数据再删节点
			clear();
			delete _head;
			_head = nullptr;
		}

		//清理数据函数,可能单独调用
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				//迭代器失效更新
				it = erase(it);
			}
		}

		//2.push_back
		void push_back(const T& x)
		{
			//先建个节点
			/*Node* newnode = new Node(x);
			Node* tail = _head->_prev;
			tail->_next = newnode;
			newnode->_next = _head;
			_head->_prev = newnode;*/

			//复用大法
			insert(end(), x);

		}
		//4.增insert
		void insert(iterator pos,const T& x)
		{
			//先创个节点出来
			Node* newnode = new Node(x);
			//改变指向逻辑
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			++_size;
		}
		//5.删erase
		iterator erase(iterator pos)
		{
			//断言防止把头删了
			assert(pos != end());
			Node* cur = pos._node;
			//改变指向逻辑
			Node* prev = cur->_prev;
			Node* newpos = cur->_next;
			prev->_next = newpos;
			newpos->_prev = prev;
			--_size;

			delete cur;
			//迭代器失效问题,所以返回pos的下一个位置
			return iterator(newpos);
		}
		//6.复用
		void push_front(const T& x)
		{
			insert(begin(), x);
		}
		void pop_front()
		{
			erase(begin());
		}
		void pop_back()
		{
			erase(--end());
		}

		//7.size
		size_t size()
		{
			return _size;
		}
		
	private:
		Node* _head;
		size_t _size;
	};
}

文章中有理解错误、文字错误欢迎指出

list的方法实现1-11并不是固定的顺序~


完结~感谢观看

C++——list和string C++容器的学习开始啦!大家先来学习list!紧接着string和vector也会一一呈现!大家继续加油吧!这次的模拟实现就到这里啦这些只是建议的模拟,目的是让大家更充分的了解容器的使用如果对于里面一些知识不了解的话,可以看看前面的博客哟大家继续加油吧! 阅读详情

相关推荐

深入解析C++ STL list容器实现原理

STL(Standard Template Library)是C++标准库中的一个重要组成部分,它提供了丰富的数据结构和算法。在STL中,list是一个双向链表容器,它允许在序列中任何位置快速地插入和删除元素。list容器是一种典型的标准模板库容器,适用于需要频繁在序列中添加或删除元素,而不关心随机访问的场景。list容器的这些特性使得它在处理动态数据集时表现得非常高效,尤其在插入和删除操作频繁的场合,list往往比其他容器如vector或deque有更佳的性能表现。

weixin_29138345的博客 1087

C++中的list

1.Cpp中的list双向链表 list是顺序容器的一种,list是一个双向链表。使用list需要包含头文件list。双向链表的每个元素中都有一个指针指向后一个元素,也有一个指针指向前一个元素,如图1所示。 在list容器中,在已经定位到要增删元素的位置的情况下,增删元素能在常数时间内完成。如图2所示,在ai和ai+1之间插入一个元素,只需要修改ai和ai+1中的指针即可。 list容器不支...

CurryCoder的个人博客 773

C++deque容器

1、deque容器基本概念 功能: 双端数组,可以对头端进行插入删除操作 deque与vector区别: vector对于头部的插入删除效率低,数据量越大,效率越低 deque相对而言,对头部的插入删除速度回比vector快 vector访问元素时的速度会比deque快,这和两者内部实现有关 deque内部工作原理: deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据...

weixin_43867777的博客 316

STL详解(五)—— list的介绍及使用

文章目录list的介绍list的使用list的定义方式list的插入和删除push_front和pop_frontpush_back和pop_backinserteraselist的迭代器使用begin和endrbegin和rendlist的元素获取front和backlist的大小控制sizeresizeemptyclearlist的操作函数sortsplicremoveremove_ifuniquemergereverseswap list的介绍 list是一种可以在常数范围内在任意位置进行插入和删除

2021dragon的博客 4768

深入浅出C++ ——list深度剖析

本文介绍了C++ STL中的list容器,主要包括:1. list是双向链表结构,支持高效插入删除但不支持随机访问;2. 详细讲解了list的构造、迭代器、容量操作、元素访问和修改等常用接口;3. 分析了list迭代器失效问题;4. 展示了list类的模拟实现list适用于频繁插入删除的场景,但随机访问效率较低。

weixin_55051736的博客 1743

Cpp || list学习

底层是通过循环双链表实现的奥

Template Code 974

C++ STL】list容器的简单模拟实现

在vector容器的简单模拟实现中,我们构造了一个简单的vector容器,那相比于其它的容器,vector有哪些优势呢?vector的元素是连续存储的,所以vector的遍历速度非常快。访问下一个元素,不需要利用指针间接寻址,这使得vector元素的线性扫描(find()和copy())接近最优。vector支持简单且高效的随机访问,这使得vector上的很多算法(如sort()和)非常高效。vector在oj题目里有一个比较常用的用法:二维数组。

luoshashou_123的博客 1298

C++实现list容器

这一节和上一节一样,实现只是为了更好的理解底层。

2301_79875009的博客 828

C++list容器模拟实现

list模拟实现主要包括四个类:节点类、迭代器类、反向迭代器类、list类。list底层结构:因为list的底层空间不连续,所以迭代器不能使用原生态的指针,将节点类型的指针封装成类,重载解引用及自增等常用操作。list可以保存多种数据类型,所以这些类都写成类模板。这里对listr容器中的基本方法进行模拟实现

qq_44631587的博客 1364

C++STL容器List的模拟实现

list实现,还是比较简单的,大家只要想着土家楼的形状,画出图来就好了,不需要过多担心。本次的博客会发出一个完整的实现ListList.hpp,以后也会这样,主要是分段发被说孩子分段生。

John1214的博客 1260

C++】——list 容器的解析与极致实现

人的一切痛苦,本质上都是对自己的无能的愤怒。—— 王小波是C++(STL)中的一个成员,其。不同于连续的、紧密排列的数组容器List容器的内部是由双向链表构成的,使得它在插入和删除操作上,就如同行云流水一般顺畅,不需移动其它元素。环状链表的尾是一个空节点,符合“左闭右开”区间。需要频繁进行插入和删除操作的场合。更多地需要随机访问元素。

2402_82668782的博客 3925

C++ stl容器list的底层模拟实现

list的底层模拟实现

2301_79698419的博客 2236

C++初阶之STL》【list容器:详解 + 实现

这篇文章详细介绍了C++ STL中list容器的使用方法和实现原理。主要内容包括: list容器的标准接口介绍,包括常用构造函数和成员函数 list容器的底层双向链表结构实现分析、list核心功能的具体实现代码,包括节点操作、插入删除等 实现过程中遇到的典型问题及解决方案 文章采用理论讲解与代码实现相结合的方式,通过大量注释和示例代码帮助读者理解list容器的内部工作机制。

序属秋秋秋的博客 2604

C++ list 容器类的模拟实现

文章内容包括 模板节点类的实现、模板迭代器的实现、模板list类的实现,以及三者相互作用所模拟实现的lsit容器的思路介绍

2302_80475369的博客 1262

C++ STL list 容器详解:使用与模拟实现

本文详细介绍了C++ STL中的list容器及其模拟实现list是带头结点的双向循环链表,支持O(1)时间复杂度的插入删除操作,但不支持随机访问。文章从list的基本使用入手,讲解了构造、迭代器、容量操作等接口,重点分析了迭代器失效问题。在模拟实现部分,详细展示了节点类、迭代器类的设计,以及list容器类的核心实现,包括类型定义、迭代器转换机制、构造函数和插入操作等关键功能。通过模板参数实现了const和非const迭代器的统一,支持前后遍历和箭头操作符访问成员。

凤年徐的博客 2101

C++list 容器模拟实现解析

本文详细介绍了list容器实现原理,主要包括三个核心部分:1. 链表节点采用双向链表设计,包含数据存储和前后指针;2. 迭代器通过模板适配const/非const场景,重载运算符实现双向遍历;3. list类封装双向链表,使用哨兵节点简化边界处理,提供插入删除等操作。

2402_83411382的博客 1049

C++list容器及其模拟实现

list是可以在常数范围内在进行插入和删除的序列式容器,并且该容器可以list的底层机构是,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指向前一个元素和后一个元素。list的最大缺点就是,即,要访问list中的元素就需要从前开始迭代,产生了线性时间的开销;另外list,以保存每个节点的的相关联信息。

Augety_fly的博客 898

C++】模拟实现STL容器list

list的普通迭代器通过类进行封装+运算符重载实现list的const通过类模板实现。 operator*返回节点的数据。 operator->返回节点数据的指针。

学了就忘是正常现象 1972
上一篇: 【Linux】进程创建、终止、等待、替换
下一篇: 【C++】deque和优先级队列
永远向阳而生
博客等级 码龄4年 394粉丝 42原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值