[STL]详解list模拟实现

[STL]list模拟实现

文章目录

  • [STL]list模拟实现
    • 1. 整体结构总览
    • 2. 成员变量解析
    • 3. 默认成员函数
      • 构造函数1
      • 迭代器区间构造函数
      • 拷贝构造函数
      • 赋值运算符重载
      • 析构函数
    • 4. 迭代器及相关函数
      • 迭代器整体结构总览
      • 迭代器的模拟实现
      • begin函数和end函数
      • begin函数和end函数const版本
    • 5. 数据修改函数
      • push_back函数
      • insert函数
      • push_front函数
      • erase函数
      • pop_back函数
      • pop_front函数
      • clear函数
    • 6. 完整代码链接

1. 整体结构总览

template<class T>
	struct list_node //结点结构 --ListNode为类名,加上模板参数后为类型
	{
		list_node* _prev;
		list_node* _next;
		T _data;

		list_node(const T& val = T()) //结点的构造函数
		{
			_data = val;
			_prev = nullptr;
			_next = nullptr;
		}
	};

template<class T, class Ref, class Ptr> //迭代器类实现
	struct __list_iterator
	{
		typedef list_node<T> node; //对结点重命名
		typedef __list_iterator<T, Ref, Ptr> self; //对迭代器重命名
		//...
		node* _node;
	};


template<class T>
	class list
	{
		typedef list_node<T> node; //对结点重命名
	public:
		typedef __list_iterator<T, T&, T*> iterator; //普通迭代器
		typedef __list_iterator<T, const T&, const T*> const_iterator; //const迭代器

	public:
		void empty_init();

		list(); //默认构造函数
		template<class Iteartor>
		list(Iteartor begin, Iteartor end);
		void swap(list<T>& tmp);
		list(const list<T>& l);
		list<T>& operator=(list<T> l);
		~list();

		iterator begin();
		iterator end();

		const_iterator begin()const;
		const_iterator end()const;

		void push_back(const T& x);
		void insert(iterator pos, const T x);
		void push_front(const T& x);
		iterator erase(iterator pos);
		void pop_back();
		void pop_front();
		void clear();
	private:
		node* _head;
	};

2. 成员变量解析

成员变量相关代码结构如下:

template<class T>
struct list_node //结点结构 --ListNode为类名,加上模板参数后为类型
{
	list_node* _prev; //指向上一个结点的指针
	list_node* _next; //指向下一个结点的指针
	T _val;	//结点存储的数据
	// ...
};
template<class T>
class list
{
	typedef list_node<T> node;
	// ...
private:
	node* _head; //指向哨兵位的头结点
};

image-20230723183036391

由于list是由双向循环链表实现的,因此只需要一个指向哨兵位的头结点的指针_head作成员变量,通过_head和指向上一个结点和下一个结点的指针能够很快的找到头结点和尾结点。

3. 默认成员函数

构造函数1

无参的默认构造函数,由于实现的双向循环链表,因此需要在创建链表时创建哨兵位的头结点,由于创建哨兵位的过程在后续实现中还需使用,因此将其封装成一个单独的empty_init函数。

void empty_init() //便于后续复用
{
	_head = new node;
	_head->_prev = _head;
	_head->_next = _head;
}

list() //-const list也可以调用此构造函数,因为在初始化后才加的const属性
{
	empty_init();
}

迭代器区间构造函数

迭代器区间构造就是将传入的容器按其迭代器范围内的数据作为要构造的容器的数据进行构造,只需要将传入迭代器内的数据尾插即可。

template<class Iteartor>
list(Iteartor begin, Iteartor end)
{
	empty_init();
	while (begin != end)
	{
		push_back(*begin);
		++begin;
	}
}

拷贝构造函数

拷贝构造函数的实现分为传统写法和现代写法。

传统写法是将要拷贝的list的数据依次进行尾插:

list(const list<T>& l)
{
	//传统写法
	empty_init();
	const_iterator it = l.begin();
	while (it != l.end())
	{
		push_back(*it);
		it++;
	}
}

现代写法是创建一个临时的list进行数据拷贝,然后将临时的list内的结点交换过来:

void swap(list<T>& tmp)
{
	std::swap(_head, tmp._head);
}

list(const list<T>& l)
{
	//现代写法
	empty_init();  // -- 不申请结点会因为tmp变量析构野指针而报错
	list<T> tmp(l.begin().l.end());
	swap(tmp);
}

赋值运算符重载

赋值运算符重载的实现利用参数会拷贝构造的特性,然后交换参数的数据。

list<T>& operator=(list<T> l)
{
	swap(l);
	return *this;
}

析构函数

析构函数的实现可以复用能够将除了哨兵位结点外的所有结点删除的clear函数,然后删除哨兵位结点。(clear函数的实现在文末。)

qxm::list<T>::~list()
{
	clear();
	delete _head;
	_head = nullptr;
}

4. 迭代器及相关函数

迭代器整体结构总览

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* n){};//构造函数
        __list_iterator(const __list_iterator<T, T&, T*>& it){}; //普通迭代器构造const迭代器
		__list_iterator(const __list_iterator<T, const T&, const T*>& it){};//cosnt迭代器构造普通迭代器
		self& operator++(){};//前置++
		self operator++(int){};//后置++
		self& operator--(){};//前置--
		self operator--(int){};//后置--
		Ref operator*() {};//*运算符重载
        Ptr operator->() {};//->运算符重载
		bool operator!=(const self& s){};//!=运算符重载
		bool operator==(const self& s){};//==运算符重载
	};

迭代器的模拟实现

由于迭代器需要的功能很多,因此需要给迭代器单独封装一个类,成员变量是结点指针。迭代器成员变量有关的代码如下:

template<class T>
    struct __list_iterator
    {
        typedef list_node<T> node; //对结点重命名
        // ...
        node* _node; //指向结点的指针
    };

迭代器构造函数

迭代器只有一个指向结点的指针变量,迭代器构造函数只要将其初始化即可。

__list_iterator(node* n)
    :_node(n)
    {}

迭代器构造函数

为了实现普通迭代器和const迭代器的转换需要实现如下构造函数:

__list_iterator(const __list_iterator<T, T&, T*>& it) //普通迭代器构造const迭代器
    :_node(it._node)
    {}

__list_iterator(const __list_iterator<T, const T&, const T*>& it)//cosnt迭代器构造普通迭代器
    :_node(it._node)
    {}

迭代器前置++运算符重载

迭代器实现++操作只需要将指针指向下一个结点即可。

template<class T>    
self& operator++()//前置++
{
	_node = _node->_next;
	return _node;
}

迭代器后置++运算符重载

实现后置++和前置++相比只需要将++前的迭代器保存并且返回即可。

self operator++(int)//后置++
{
    self tmp(_node);
    _node = _node->_next;
    return tmp;
}

迭代器前置–运算符重载

迭代器实现–操作只需要将指针指向上一个结点即可。

self& operator--()//前置--
{
    _node = _node->_prev;
    return *this;
}

迭代器后置–运算符重载

实现后置–和前置–相比只需要将–前的迭代器保存并且返回即可。

self operator--(int)//后置--
{
	self tmp(_node);
	_node = _node->_prev;
	return tmp;
}

迭代器*运算符重载

迭代器进行*操作是要获取迭代器指向的数据,因此只需要将结点指向的数据返回即可。

T& operator*()
{
    return _node->_val;
}

迭代器->运算符重载

迭代器进行->操作是因为list存储的是自定义数据类型,->运算符的重载只需要返回数据的地址即可。

T* operator->()
{
	return &_node->_data;
}

为了理解->运算符重载的实现,我们看下面的例子:

struct AA
{
	AA(int a1 = 1, int a2 = 2)
		:_a1(a1),
		_a2(a2)
	{}
	int _a1;
	int _a2;
};

void test_list2()
{
	qxm::list<AA> l; //qxm作用域是模拟实现时设置的命名空间
	l.push_back(AA(1, 1));
	l.push_back(AA(2, 2));
	l.push_back(AA(3, 3));

	for (qxm::list<AA>::iterator it = l.begin(); it != l.end(); it++)
	{
		cout << it->_a1 << ":" << it->_a2 << endl;
	}
}

在这个场景中如果调用迭代器的->会返回AA类型的指针,it->_a1相当于是&AA_a1将数据地址和变量写到一块,应该是访问不到数据的错误代码,但是编译器会自动做优化,此时一个->运算符当两个->使用,也就是说本来需要(it.operator->())->_a1访问数据的,但是编译器把其中一个->优化掉了,因此现在只要it->_a1就可以访问成功了。

迭代器!=运算符重载

迭代器的!=是指向的数据不同,因此只需要判断迭代器内的指针是否相同。

bool operator!=(const self& it)
{
    return this->_node != it->_node;
}

迭代器!=运算符重载

迭代器的==是指向的数据相同,因此只需要判断迭代器内的指针是否相同。

bool operator==(const self& s)
{
    return _node == s._node;
}

const迭代器实现

const迭代器和普通迭代器的实现只有在*运算符重载和->运算符的重载的返回值上有所不同。

const迭代器的*运算符重载函数返回的是const的数据,这样const迭代器就不能修改数据了。

const T& operator*() 
{
    return _node->_data;
}

const迭代器的->运算符重载函数返回的是const的指针,这样const迭代器就不能修改数据了。

const T* operator->()
{
    return &_node->_data;
}

迭代器模拟实现整体代码:

template<class T, class Ref, class Ptr> // -- Ref/Ptr控制是普通迭代器还是const迭代器
    struct __list_iterator
    {
        typedef list_node<T> node; //对结点重命名
        typedef __list_iterator<T, Ref, Ptr> self; //对迭代器重命名

        node* _node;

        __list_iterator(node* n)
            :_node(n)
            {}
        
        __list_iterator(const __list_iterator<T, T&, T*>& it)
			:_node(it._node)
		{}

		__list_iterator(const __list_iterator<T, const T&, const T*>& it)
			:_node(it._node)
		{}

        self& operator++()//前置++
        {
            _node = _node->_next;
            return *this;
        }

        self operator++(int)//后置++
        {
            self tmp(_node);
            _node = _node->_next;
            return tmp;
        }

        self& operator--()//前置--
        {
            _node = _node->_prev;
            return *this;
        }

        self operator--(int)//后置--
        {
            self tmp(_node);
            _node = _node->_prev;
            return tmp;
        }

        Ref operator*() //传引用返回可以修改结点内的数据
        {
            return _node->_data;
        }
        
        Ptr operator->()
		{
			return &_node->_data;
		}

        bool operator!=(const self& s) //!=运算符重载
        {
            return _node != s._node;
        }

        bool operator==(const self& s) //==运算符重载
        {
            return _node == s._node;
        }
    };

注意: 模板参数Ref控制的是*运算符重载的返回值类型,模板参数Ptr控制的是->运算符重载的返回值类型,进而控制了迭代器是普通迭代器还是const迭代器。

begin函数和end函数

注: 文中此位置往下是迭代器相关函数实现,实现在list类内。

begin函数:

begin函数只需要返回拥有有效数据的头结点即可。

iterator begin()
{
	return iterator(_head->_next);
}

end函数:

end函数只需要返回哨兵位就可,因为哨兵位没有有效数据。

iterator end()
{
	return iterator(_head);
}

begin函数和end函数const版本

const版本begin函数:

begin函数只需要返回拥有有效数据的头结点即可。

const_iterator begin()const
{
	return const_iterator(_head->_next);
}

const版本end函数:

end函数只需要返回哨兵位就可,因为哨兵位没有有效数据。

const_iterator end()const
{
	return const_iterator(_head);
}

5. 数据修改函数

push_back函数

push_back函数为尾插函数,尾插示意图如下:

image-20230723165738795

实现尾插函数时创建一个临时变量tail,记录插入数据前的尾结点,方便进行指针指向的改动,避免因为指针指向改动而找不到正确的结点。

void push_back(const T& val)
{
	node* tail = _head->_prev; //记录插入数据前的尾部结点
	node* newnode = new node(x);

	tail->_next = newnode;//指针改变的顺序不影响结果
	newnode->_prev = tail;
	newnode->_next = _head;
	_head->_prev = newnode;
}

insert函数

insert函数的功能是通过迭代器,在迭代器指向的结点前插入结点,insert函数的实现只需要将传入的迭代器的前一个结点和迭代器指向的结点记录,然后将要插入的新节点进行链接。

insert函数插入结点示意图:

image-20230727131020076

void insert(iterator pos, const T x)
{
	node* cur = pos._node;
	node* prev = cur->_prev;
	node* newnode = new node(x);

	prev->_next = newnode;
	cur->_prev = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
}

说明: insert函数不会使迭代器失效,因为迭代器指向的结点不会随着插入而改变。

有了insert函数后,push_back函数可以改写为复用insert函数的版本:

void push_back(const T& x)
{
	insert(end(), x);
}

end函数返回的是有_head封装的迭代器,指向哨兵位,哨兵位的前一个结点就是尾结点,因此复用insert函数和end函数能实现尾插。

push_front函数

push_front函数的功能是头插结点,有了insert函数实现头插只需要在insert函数中传入头结点就可以实现。

void push_front(const T& x)
{
	insert(begin(), x);
}

erase函数

erase函数的功能是删除指定结点,并返回在删除之前删除结点的下一个结点,只需要将要删除的前一个结点和后一个结点记录,进行链接即可,但要注意不能删除哨兵位结点。

iterator erase(iterator pos)
{
	assert(pos != end());

	node* prev = pos._node->_prev;
	node* next = pos._node->_next;

	prev->_next = next;
	next->_prev = prev;
	delete pos._node;

	return iterator(next);
}

pop_back函数

pop_back函数的功能是尾删,只需要复用erase函数和end函数即可,end函数返回的是哨兵位的迭代器,–得到的是尾结点的迭代器。

void pop_back()
{
	erase(--end());
}

pop_front函数

pop_front函数的功能是尾删,只需要复用erase函数和begin函数即可,begin函数返回的头结点的迭代器。

void pop_front()
{
	erase(begin());
}

clear函数

clear函数的功能是将除了哨兵位结点外的所有结点都删除,只需要复用erase函数循环删除结点。(erase函数的实现需上翻本文)

void clear()
{
	iterator it = begin();
	while (it != end)
	{
		it = erase(it); //--erase后需要重新对迭代器赋值,不然迭代器会失效。
		//erase(it++);  --后置++会返回++前的值,因此迭代器不会失效
	}
}

6. 完整代码链接

STL/List/List/List.h · 钱雪明/日常代码 - 码云 - 开源中国 (gitee.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/52756.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DevOps-Git

DevOps-Git 版本控制软件提供完备的版本管理功能&#xff0c;用于存储&#xff0c;追踪目录&#xff08;文件夹&#xff09;和文件的修改历史。版本控制软件的最高目标是支持公司的配置管理活动&#xff0c;最终多个版本的开发和维护活动&#xff0c;即使发布软件。 git安装 h…

Eureka注册中心 与 OpenFeign调用接口

需求 一个应用通过接口&#xff0c;调用另一个应用的接口。使用OpenFeign来实现接口调用。 说明 通过OpenFeign&#xff08;本文接下来简称Feign&#xff09;调用远程接口&#xff0c;需要Eureka注册中心的支持。 OpenFeign调用接口的逻辑如下&#xff1a; 提供接口的应用…

笔记本触摸板没反应怎么办?只需要4个方法!快速解决!

“大家知道为什么笔记本触摸板没反应吗&#xff1f;我的鼠标不见了现在触摸板也没反应&#xff0c;根本就用不了电脑了&#xff0c;有什么方法可以解决吗&#xff1f;” 触摸板是笔记本电脑上最重要的输入设备之一&#xff0c;它可以提供便捷的操作方式。对于很多朋友来说&…

原型模式——对象的克隆

1、简介 1.1、概述 可以通过一个原型对象克隆出多个一模一样的对象&#xff0c;该模式被称为原型模式。 在使用原型模式时&#xff0c;需要首先创建一个原型对象&#xff0c;再通过复制这个原型对象来创建更多同类型的对象。 1.2、定义 原型模式&#xff08;Prototype Patt…

前端试用期工作总结范文5篇

前端试用期工作总结 &#xff08;篇1&#xff09; 时间飞逝&#xff0c;转眼间&#xff0c;做为一名Web前端开发的正式员工已经有两个月之久。在这个难忘而又夸姣的 日子里&#xff0c;我深入体会到了公司的积极氛围和各个部门的巨大魅力&#xff0c;目睹了公司一步步走向成熟…

(202307)wonderful-sql:复杂一点的查询(task3)

教程链接&#xff1a;Datawhale - 一个热爱学习的社区 知识学习 1 视图 视图是一张虚拟的表。《sql基础教程第2版》用一句话非常凝练的概括了视图与表的区别---“是否保存了实际的数据”。 通过定义视图可以将频繁使用的SELECT语句保存以提高效率。通过定义视图可以使用户看…

Games101学习笔记 - 变换矩阵基础

二维空间下的变换 缩放矩阵 缩放变换: 假如一个点&#xff08;X,Y&#xff09;。x经过n倍缩放&#xff0c;y经过m倍缩放&#xff0c;得到的新点&#xff08;X1&#xff0c;Y1&#xff09;&#xff1b;那么新点和远点有如下关系&#xff0c;X1 n*X, Y1 m*Y写成矩阵就是如下…

C++ 自定义数据类型

C自定义数据类型有&#xff1a;枚举类型、结构类型、联合类型、数组类型、类类型 1. typedef 声明 在编写程序时&#xff0c;除了可以使用内置的基本数据类型名和自定义的数据类型名以外&#xff0c;还可以为一个已有的数据类型另外命名。这样&#xff0c;就可以根据不同的应…

word怎么转换成pdf?分享几种转换方法

word怎么转换成pdf&#xff1f;将Word文档转换成PDF文件有几个好处。首先&#xff0c;PDF文件通常比Word文档更容易在不同设备和操作系统上查看和共享。其次&#xff0c;PDF文件通常比Word文档更难以修改&#xff0c;这使得它们在需要保护文件内容的情况下更加安全可靠。最后&a…

创建维基WIKI百科和建立百度百科有何不同?

很多企业有出口业务&#xff0c;想在互联网上开展全球性网络营销&#xff0c;维基百科往往被认为是开展海外营销的第一站。其作用相当于开展国内网络营销的百度百科&#xff0c;经常有些企业给小马识途营销顾问提供的词条内容就是百度百科的内容&#xff0c;可事实上两个平台的…

1227. 分巧克力(简单,易懂)

输入样例&#xff1a; 2 10 6 5 5 6输出样例&#xff1a; 2 这个题就是基础的二分问题&#xff0c;做题思路&#xff1a; 找到一个数&#xff0c;让其满足&#xff0c;所有小块的边值&#xff0c;且最终的总和要大于等于我们的K 第一次做错了&#xff01;&#xff01; #in…

云计算——虚拟化层架构

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​ 前言 本章将会讲解云计算的虚拟化层架构&#xff0c;了解云计算虚拟化层都有哪些架构模式…

Zotero ubuntu2023安装 关联 ubuntu文献翻译

一、准备下载的软件&#xff1a; Zotero | Downloads 1. Zotero-6.0.26_linux-x86_64.tar.bz2 下面是插件 zotfile-5.1.2-fx.xpi zotero-pdf-translate.xpi jasminum-v0.2.6.xpi 2.2.5 Tampermonkey 4.11.crx 所准备的文件&#xff0c;都已经在这个链接的压缩包下面 …

25.10 matlab里面的10中优化方法介绍—— 函数fmincon(matlab程序)

1.简述 关于非线性规划 非线性规划问题是指目标函数或者约束条件中包含非线性函数的规划问题。 前面我们学到的线性规划更多的是理想状况或者说只有在习题中&#xff0c;为了便于我们理解&#xff0c;引导我们进入规划模型的一种情况。相比之下&#xff0c;非线性规划会更加贴近…

代码随想录算法训练营第三十天 | 单调栈系列复习

单调栈系列复习 每日温度未看解答自己编写的青春版重点题解的代码日后再次复习重新写 下一个更大元素 I未看解答自己编写的青春版重点题解的代码日后再次复习重新写 下一个更大元素II未看解答自己编写的青春版重点题解的代码日后再次复习重新写 接雨水未看解答自己编写的青春版…

VUE使用docxtemplater导出word(带图片) 踩坑 表格循环空格 ,canvas.toDataURL图片失真模糊问题

参考&#xff1a;https://www.codetd.com/article/15219743 安装 // 安装 docxtemplater npm install docxtemplater pizzip --save // 安装 jszip-utils npm install jszip-utils --save // 安装 jszip npm install jszip --save // 安装 FileSaver npm install file-save…

深度学习实践——循环神经网络实践

系列实验 深度学习实践——卷积神经网络实践&#xff1a;裂缝识别 深度学习实践——循环神经网络实践 深度学习实践——模型部署优化实践 深度学习实践——模型推理优化练习 代码可见于&#xff1a; 深度学习实践——循环神经网络实践 0 概况1 架构实现1.1 RNN架构1.1.1 RNN架…

mac版窗口管理 Magnet for mac中文最新

magnet mac版是一款运行在苹果电脑上的一款优秀的窗口大小控制工具&#xff0c;拖拽窗口到屏幕边缘可以自动半屏&#xff0c;全屏或者四分之一屏幕&#xff0c;还可以设定快捷键完成分屏。这款专业的窗口管理工具当您每次将内容从一个应用移动到另一应用时&#xff0c;当您需要…

调整数组顺序使奇数位于偶数前面——剑指 Offer 21

文章目录 题目描述法一 两次遍历法二 双指针一次遍历法三 原地交换 题目描述 法一 两次遍历 class Solution{ public:vectro<int> exchange(vector<int>& nums){vector<int> res;for(auto & num : nums){if(num%21){res.push_back(num);}}for(auto &…

【宝藏系列】STM32之C语言基础知识

【宝藏系列】STM32之C语言基础知识 文章目录 【宝藏系列】STM32之C语言基础知识1️⃣位操作2️⃣define宏定义3️⃣ifdef条件编译4️⃣extern变量声明5️⃣typedef类型别名 C语言是单片机开发中的必备基础知识&#xff0c;本文列举了部分 STM32 学习中比较常见的一些C语言基础知…
最新文章