C++相关概念和易错语法(16)(list)

news2024/12/23 16:05:14

1.list易错点

(1)慎用list的sort,list的排序比vector慢得多,尽管两者时间复杂度一样,甚至不如先把list转为vector,用vector排完序后再转为list

(2)splice是剪切链表,将x的部分剪切到pos后,也可以自己剪自己的

(3)list迭代器不支持[]运算符,但相比较vector多支持了push_front和pop_front,原因在于[]效率不高,push_front和pop_front效率高

2.list模拟实现

list实现中我们会用到多个模板,注意每个template定义的模板参数都只能供当前类或函数使用,不存在一个文件中所有T都是一个意思

(1)结点

为了适应不同数据的存储,我们采用模板的方式来定义结点。为了能够体现封装特性,我们将创建结点并赋值作为一个构造函数写入类ListNode中。因为在接下来的class list中会频繁调用lt->next,即在外部访问LIstNode的成员变量,所以我们的ListNode使用struct,默认访问限定符是public

(2)无参构造和析构

我们的list是带头双向循环链表,所以所有的构造都需要定义一个哨兵位

还有很多带参的构造,虽然这里我们可以去实现,但是它们需要使用到的功能和后面的函数相符合,所以我会先实现后续的函数。

析构只需要遍历所有节点,将它们释放即可

(3)迭代器

list的迭代器和指针就拉开了差距,list的数据是不连续存储的,因此无法使用指针的加减来遍历list。我们需要定义一个iterator的类,在这个类里重载运算符++、*等操作,使它们在使用过程中和指针一致但能够正常访问数据。

我们使用ListNode*作为结点的标识,当++时就调用它的_next,--就调用它的_prev,*就返回对应_data的值。有了这个思路,迭代器的大部分功能我们都可以顺利实现了。


	template<class T, class T1 = T>
	struct List_iterator
	{
		typedef ListNode<T> Node;
		typedef List_iterator<T, T1> iterator;

		List_iterator(Node* node)
			:_curnode(node)
		{}

		iterator operator++()
		{
			_curnode = _curnode->_next;
			return _curnode;
		}
		
		iterator operator--()
		{
			_curnode = _curnode->_prev;
			return _curnode;
		}		
		
		iterator operator++(int)
		{
			_curnode = _curnode->_next;
			return _curnode->_prev;
		}		
		
		iterator operator--(int)
		{
			_curnode = _curnode->_prev;
			return _curnode->_next;
		}

		T1& operator*()//对于const T无法进行修改
		{
			return _curnode->_data;
		}

		T1* operator->()//对于const T无法进行修改
		{
			return &(_curnode->_data);
		}

		bool operator!=(const List_iterator lt) const
		{
			return _curnode != lt._curnode;
		}

		bool operator==(const List_iterator lt) const
		{
			return _curnode == lt._curnode;
		}


		Node* _curnode;
	};

但是这里面还有一些代码需要解释

a.operator->()

要理解这里,我们要思考迭代器本身想要模仿的是指向数据的指针,如果存储类型是int,对应iterator和int*的使用方式一模一样,类似地,像char、double、int*等内置类型是这样,那么自定义类型呢?operator->()就是为了模仿自定义类型的指针而专门设计的。

我们先将T1就看成T,T是一个结构体,里面有自己的成员变量,当我们使用结构体指针想要访问它们时,我们首先要拿到这个结构体的地址,再用->访问,这里的iterator也是如此,_data是结构体类型,我们直接先指向这个结构体内容本身,即_curnode->_data,这个表达式的返回值是一个结构体,也就是我们想要访问的结构体,但是我们是要模拟指针的操作,要使用->而不是.,所以我们要再对它取地址,即&(_curnode->_data),这样返回的就是一个指向_data的指针了,当我们调用it.operator->()->(成员变量)时就能访问结构体内部的成员变量了,简化为it->(成员变量),省略了一个->为了易读性。

b.T1和T的区分

在我们想要创建一个const_iterator时,T1的出现就很重要了。

当不传第二个模板参数时,默认和第一个一样,如果传了const T,那就使用传的参数

对于这两个要返回指针或引用的函数,加上const修饰T可以防止T被修改。那为什么不直接用const T一个参数呢?要注意const int和int是两个类型,对于一些自定义类型来说有很大区别,所以要分成两块来写。

有了上面的基础,反向迭代器也可以很快的完成,思路就是复用,用刚刚实现的正向迭代器稍加修改得到。


	template<class T, class T1 = T>
	struct List_reverse_iterator
	{
		typedef ListNode<T> Node;
		typedef List_iterator<T, T1> iterator;
		typedef List_reverse_iterator<T, T1> reverse_iterator;

		List_reverse_iterator(Node* node)
			:_it(node)
		{}

		List_reverse_iterator(iterator it)
			:_it(it)
		{}

		reverse_iterator operator++()
		{
			return --_it;
		}

		reverse_iterator operator--()
		{
			return ++_it;
		}

		reverse_iterator operator++(int)
		{
			return _it--;
		}

		reverse_iterator operator--(int)
		{
			return _it++;
		}

		T1& operator*()//对于const T无法进行修改
		{
			return *_it;
		}

		T1* operator->()//对于const T无法进行修改
		{
			return &(*_it);
		}

		bool operator!=(const List_reverse_iterator lt) const
		{
			return _it != lt._it;
		}

		bool operator==(const List_reverse_iterator lt) const
		{
			return _it == lt._it;
		}


		iterator _it;
	};

(4)push_back

push_back是非常关键的一个函数,在构造函数中,我们经常用到它

先用val创建一个newnode,找到尾节点并修改尾节点和哨兵位的_prev和_next使得newnode插入链表中

(5)insert

我们实现insert的方式是先实现一个可复用性最强的函数(迭代器版本的insert),再用它实现其它函数

后续的函数只需要调整参数复用即可

iterator insert(iterator pos, int n, int val)//特殊处理,防止定位错误
{
	return insert(pos, (size_t)n, (T)val);
}

iterator insert(iterator pos, size_t n, const T& val)
{
	list<T> tmp;
	for (size_t count = 0; count < n; count++)
	{
		tmp.push_back(val);
	}

	return insert(pos, tmp.begin(), tmp.end());
}		

iterator insert(iterator pos, const T& val)
{
	return insert(pos, 1, val);
}

其中有一个函数需要解释

当我们调用insert(lt.begin(), 1, 3)时,是想在首位置插入1个3,但是编译器会将1,3识别成迭代器(注意看我们之前已经实现了模板函数,1和3同类型是会匹配的),size_t和const int&虽然都能作为1和3的类型,但它们都不叫完美匹配,不完美匹配就会去找模板函数。

注意各大整型家族的区别,10u是unsigned int类型。

因此我们只有针对这一种情况进行单独处理,将它们强转后就可以匹配正确的函数了。

(6)erase

我们的思路也是先实现一个可复用性最强的,再对其它函数复用。这些函数逻辑都很简单,不做过多讲解。

(7)带参构造

这里我们就可以发现,只要有了push_back,insert,我们实现这些带参构造就很快了,我们要学会复用,即实现最关键的几个函数,其它的函数用这几个核心的函数套用就可以了,这不仅节省了我们的时间,还使得代码易于维护,失误率降低。

list(size_t n, const T& val)
{
	_head = new Node;
	_head->_next = _head->_prev = _head;

	for (size_t count = 0; count < n; count++)
	{
		push_back(val);
	}
}

list(const list<T>& lt)
{
	_head = new Node;
	_head->_next = _head->_prev = _head;

	for (const auto& e : lt)
	{
		push_back(e);
	}
}


template<class Init>
list(Init first, Init last)
{
	_head = new Node;
	_head->_next = _head->_prev = _head;

	insert(begin(), first, last);
}
		

(8)全部代码(包含一些边缘性的函数)

注意模板函数是按需实例化,只检查调用的实例化的函数,不调用就不实例化

#pragma once

#include <iostream>
#include <algorithm>
#include <assert.h>

using namespace std;


namespace my_list
{
	template<class T>
	struct ListNode
	{
		ListNode(const T& data = T())
			:_data(data)
			, _next(nullptr)
			, _prev(nullptr)
		{}

		T _data;
		ListNode<T>* _next;
		ListNode<T>* _prev;
	};



	template<class T, class T1 = T>
	struct List_iterator
	{
		typedef ListNode<T> Node;
		typedef List_iterator<T, T1> iterator;

		List_iterator(Node* node)
			:_curnode(node)
		{}

		iterator operator++()
		{
			_curnode = _curnode->_next;
			return _curnode;
		}
		
		iterator operator--()
		{
			_curnode = _curnode->_prev;
			return _curnode;
		}		
		
		iterator operator++(int)
		{
			_curnode = _curnode->_next;
			return _curnode->_prev;
		}		
		
		iterator operator--(int)
		{
			_curnode = _curnode->_prev;
			return _curnode->_next;
		}

		T1& operator*()//对于const T无法进行修改
		{
			return _curnode->_data;
		}

		T1* operator->()//对于const T无法进行修改
		{
			return &(_curnode->_data);
		}

		bool operator!=(const List_iterator lt) const
		{
			return _curnode != lt._curnode;
		}

		bool operator==(const List_iterator lt) const
		{
			return _curnode == lt._curnode;
		}


		Node* _curnode;
	};


	template<class T, class T1 = T>
	struct List_reverse_iterator
	{
		typedef ListNode<T> Node;
		typedef List_iterator<T, T1> iterator;
		typedef List_reverse_iterator<T, T1> reverse_iterator;

		List_reverse_iterator(Node* node)
			:_it(node)
		{}

		List_reverse_iterator(iterator it)
			:_it(it)
		{}

		reverse_iterator operator++()
		{
			return --_it;
		}

		reverse_iterator operator--()
		{
			return ++_it;
		}

		reverse_iterator operator++(int)
		{
			return _it--;
		}

		reverse_iterator operator--(int)
		{
			return _it++;
		}

		T1& operator*()//对于const T无法进行修改
		{
			return *_it;
		}

		T1* operator->()//对于const T无法进行修改
		{
			return &(*_it);
		}

		bool operator!=(const List_reverse_iterator lt) const
		{
			return _it != lt._it;
		}

		bool operator==(const List_reverse_iterator lt) const
		{
			return _it == lt._it;
		}


		iterator _it;
	};

	template<class T>
	class list
	{
	public:

		typedef ListNode<T> Node;

		typedef List_iterator<T> iterator;
		typedef List_iterator<T, const T> const_iterator;

		typedef List_reverse_iterator<T> reverse_iterator;
		typedef List_reverse_iterator<T, const T> const_reverse_iterator;



		list()
		{
			_head = new Node;
			_head->_next = _head->_prev = _head;
		}


		list(size_t n, const T& val)
		{
			_head = new Node;
			_head->_next = _head->_prev = _head;

			for (size_t count = 0; count < n; count++)
			{
				push_back(val);
			}
		}

		list(const list<T>& lt)
		{
			_head = new Node;
			_head->_next = _head->_prev = _head;

			for (const auto& e : lt)
			{
				push_back(e);
			}
		}


		template<class Init>
		list(Init first, Init last)
		{
			_head = new Node;
			_head->_next = _head->_prev = _head;

			insert(begin(), first, last);
		}
		
		~list()
		{
			Node* cur = _head->_next, * next = _head->_next->_next;

			while (cur != _head)
			{
				delete cur;
				cur = next, next = next->_next;
			}

			delete _head;

			_head = nullptr;
		}

		size_t size()
		{
			size_t count = 0;

			for (const auto& e : *this)
			{
				count++;
			}

			return count;
		}

		bool empty()
		{
			return _head->_next == _head;
		}


		list<T>& operator=(const list<T>& lt)
		{
			if (lt._head != _head)
			{
				clear();

				for (const auto& e : lt)
				{
					push_back(e);
				}
			}

			return *this;
		}

		T& front()
		{
			return *begin();
		}		
		
		T& back()
		{
			return *(--end());
		}		
		
		const T& front() const
		{
			return *begin();
		}		
		
		const T& back() const
		{
			return *(--end());
		}

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

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


		reverse_iterator rbegin()
		{
			return reverse_iterator(_head->_prev);
		}

		reverse_iterator rend()
		{
			return reverse_iterator(_head);
		}

		const_reverse_iterator rbegin() const
		{
			return const_reverse_iterator(_head->_prev);
		}

		const_reverse_iterator rend() const
		{
			return const_reverse_iterator(_head);
		}

		void push_back(const T& val)
		{
			Node* newnode = new Node(val);
			Node* tail = _head->_prev;

			tail->_next = newnode;
			newnode->_prev = tail;

			newnode->_next = _head;
			_head->_prev = newnode;
		}		
		
		void push_front(const T& val)
		{
			insert(begin(), 1, val);
		}

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

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

		template<typename Input>
		iterator insert(iterator pos, Input first, Input last)
		{
			Node* cur = pos._curnode->_prev, * next = cur->_next;
			Node* ret = cur;

			while (first != last)
			{
				Node* newnode = new Node((T)(*first));

				cur->_next = newnode, newnode->_prev = cur;
				newnode->_next = next, next->_prev = newnode;

				cur = cur->_next;
				first++;
			}

			return ret->_next;//新插入的第一个结点对应的迭代器,有隐式类型转换
		}

		iterator insert(iterator pos, int n, int val)//特殊处理,防止定位错误
		{
			return insert(pos, (size_t)n, (T)val);
		}

		iterator insert(iterator pos, size_t n, const T& val)
		{
			list<T> tmp;
			for (size_t count = 0; count < n; count++)
			{
				tmp.push_back(val);
			}

			return insert(pos, tmp.begin(), tmp.end());
		}		
		
		iterator insert(iterator pos, const T& val)
		{
			return insert(pos, 1, val);
		}

		iterator erase(iterator first, iterator last)
		{

			Node* cur = first._curnode->_prev, * next = last._curnode;
			
			iterator nextit = first;

			while (first != last)
			{
				nextit++;

				delete first._curnode, first = nextit;
			}

			cur->_next = next, next->_prev = cur;

			return cur->_next;
		}


		iterator erase(iterator pos)
		{
			iterator cur = pos;

			return erase(pos, ++cur);
		}


		void clear()
		{
			erase(begin(), end());
		}

	private:
		Node* _head;
	};


}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AGAST (角点检测)

AGAST检测原理 AGAST(Adaptive and Generic Accelerated Segment Test)算法是Elmar于2010年提出的特征检测算法,改进了FAST(Features from Accelerated Segment Test)特征检测方法,使其具有更快的速度和更好的鲁棒性。AGAST算法提供了比FAST算法更详细的特征标记方式和判断依…

读人工智能全传07智能体

1. 布鲁克斯革命 1.1. 随着科学认知的发展&#xff0c;有时候旧有科学体系会面临全盘崩溃的危机&#xff0c;新的科学体系诞生&#xff0c;取代传统的、既定的科学体系&#xff0c;这就意味着科学的范式将发生变化 1.2. 澳大利亚机器人学家罗德尼布鲁克斯(Rodney Brooks)&…

vue3+ el-tree 展开和折叠,默认展开第一项

默认第一项展开: 展开所有项&#xff1a; 折叠所有项&#xff1a; <template><el-treestyle"max-width: 600px":data"treeData"node-key"id":default-expanded-keys"defaultExpandedKey":props"defaultProps"…

Qt creator 控件转到槽 报错 The class containing “Ui:Dialog“ could not be found in

今天调试程序&#xff0c;发现主界面控件转到槽&#xff0c;报错如下图&#xff1a; 问题表现为&#xff1a;只有主窗口控件有这个错误&#xff0c;其他子窗口正常。 解决&#xff1a; 在网上搜这个报错信息&#xff0c;都没有一个很好的解决办法。 最后发现是我在子窗口中要…

004-基于Sklearn的机器学习入门:回归分析(下)

本节及后续章节将介绍机器学习中的几种经典回归算法&#xff0c;包括线性回归&#xff0c;多项式回归&#xff0c;以及正则项的岭回归等&#xff0c;所选方法都在Sklearn库中聚类模块有具体实现。本节为下篇&#xff0c;将介绍多项式回归和岭回归等。 2.3 多项式回归 在一般的…

Visual Studio Code 教程 VsCode安装Live Server以服务形式打开html

搜索Live Server 插件,然后安装 选一个html文件&#xff0c;右键点击 Open with live server,然后就自动弹出来了

怎样优化 PostgreSQL 中对日期时间范围的模糊查询?

文章目录 一、问题分析&#xff08;一&#xff09;索引未有效利用&#xff08;二&#xff09;日期时间格式不统一&#xff08;三&#xff09;复杂的查询条件 二、优化策略&#xff08;一&#xff09;使用合适的索引&#xff08;二&#xff09;规范日期时间格式&#xff08;三&a…

北森锐途人才竞聘盘点管理测评:高管领导力六大评判标准深度解析万达商管中国绿发等

北森锐途人才管理测评&#xff1a;高管领导力评判标准深度解析 在企业高管的盘点与竞聘测评领域&#xff0c;众多管理人才面临评估自身领导力的挑战。面对能力卓越、职级显赫的同僚&#xff0c;许多管理者感到缺乏一套权威且专业的评价体系。然而&#xff0c;无论是天赋异禀的领…

Html5前端基本知识整理与回顾上篇

今天我们结合之前上传的知识资源来回顾学习的Html5前端知识&#xff0c;与大家共勉&#xff0c;一起学习。 目录 介绍 了解 注释 标签结构 排版标签 标题标签 ​编辑 段落标签 ​编辑 换⾏标签 ​编辑 ⽔平分割线 ⽂本格式化标签 媒体标签 绝对路径 相对路径 …

Chromium编译指南2024 Linux篇-安装官方工具depot_tools(二)

1.引言 在上一节中&#xff0c;我们已经完成了 Git 的安装&#xff0c;并了解了其在 Chromium 编译过程中的重要性。接下来&#xff0c;我们将继续进行环境的配置&#xff0c;首先是安装和配置 Chromium 编译所需的重要工具——depot_tools。 depot_tools 是一组用于获取、管…

怎样优化 PostgreSQL 中对布尔类型数据的查询?

文章目录 一、索引的合理使用1. 常规 B-tree 索引2. 部分索引 二、查询编写技巧1. 避免不必要的类型转换2. 逻辑表达式的优化 三、表结构设计1. 避免过度细分的布尔列2. 规范化与反规范化 四、数据分布与分区1. 数据分布的考虑2. 表分区 五、数据库参数调整1. 相关配置参数2. 定…

linux工具应用_GVIM

gvim 1. introduction1.1 **gvim的功能(选择用gvim的原因)**1.2 模式及切换1.2.1 normal1.2.2 insert1.2.3 visual1.2.4 command2. gvim配置-vimrc2.1 什么是vimrc2.2 配置修改及理解2.2.1 基本修改2.2.2 UI 相关配置2.2.3 编码相关配置2.3.4 文件相关配置2.3.5 编辑器相关配…

用Conda配置 Stable Diffusion WebUI 1.9.4

用Conda配置 Stable Diffusion WebUI 1.9.4 本文主要讲解: 如何用Conda搭建Stable Diffusion WebUI 1.9.4环境&#xff0c;用Conda的方式安装&#xff0c;不需要单独去安装Cuda了。 1. 安装miniconda https://docs.anaconda.com/free/miniconda/index.html 2. 搭建虚拟环境…

Java设计模式---(创建型模式)工厂、单例、建造者、原型

目录 前言一、工厂模式&#xff08;Factory&#xff09;1.1 工厂方法模式&#xff08;Factory Method&#xff09;1.1.1 普通工厂方法模式1.1.2 多个工厂方法模式1.1.3 静态工厂方法模式 1.2 抽象工厂模式&#xff08;Abstract Factory&#xff09; 二、单例模式&#xff08;Si…

快速掌握AI的最佳途径实践

科技时代&#xff0c;人工智能&#xff08;AI&#xff09;已经成为许多人希望掌握的重要技能。对于普通人来说&#xff0c;如何快速有效地学习AI仍然是一个挑战。本文将详细介绍几种快速掌握AI的途径&#xff0c;并提供具体的操作步骤和资源建议。 前言 AI的普及和应用已经深…

逻辑回归模型(非回归问题,而是分类问题)

目录&#xff1a; 一、Sigmoid函数&#xff1a;二、逻辑回归介绍&#xff1a;三、决策边界四、逻辑回归模型训练过程&#xff1a;1.训练目标&#xff1a;2.梯度下降调整参数&#xff1a; 一、Sigmoid函数&#xff1a; Sigmoid函数是构建逻辑回归模型的重要函数&#xff0c;如下…

【Word】快速对齐目录

目录标题 1. 全选要操作的内容 → 右键 → 段落2. 选则制表位3. 配置制表符4. Tab键即可 1. 全选要操作的内容 → 右键 → 段落 2. 选则制表位 3. 配置制表符 4. Tab键即可

js+spring boot实现简单前后端文件下载功能

jsboot项目实现自定义下载 一、前端页面 1、先导入axios的js包 2、注意axios响应的格式&#xff1a;result.data.真实的数据内容 3、这里请求的url就是你boot项目的getMapping的url&#xff0c;保持一致即可 4、如果想在后端设置文件名&#xff0c;那么后端生成后&#xf…

HackTheBox--BoardLight

BoardLight 测试过程 1 信息收集 NMAP端口扫描 端口扫描开放 22、80 端口 80端口测试 # 添加 boardLight.htb 到hosts文件 echo "10.10.11.11 boardLight.htb" | sudo tee -a /etc/hosts检查网页源代码&#xff0c;发现 board.htb # 添加 board.htb 到 hosts 文…

安卓应用开发学习:腾讯地图SDK应用改进,实现定位、搜索、路线规划功能集成

一、引言 我的上一篇学习日志《安卓应用开发学习&#xff1a;通过腾讯地图SDK实现定位功能》记录了利用腾讯地图SDK实现手机定位功能&#xff0c;并能获取地图中心点的经纬度信息。这之后的几天里&#xff0c;我对《Android App 开发进阶与项目实战》一书第九章的内容深入解读…