C++第二十四弹---从零开始模拟STL中的list(上)

news2024/12/28 20:55:55

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

1、基本结构

2、基本函数实现

2.1、默认构造函数

2.2、尾插数据

3、迭代器的封装

3.1、迭代器的基本结构

3.2、迭代器重载函数的实现

4、迭代器与list进行关联

4.1、使用迭代器打印链表数据

4.2、其他相关函数

总结


1、基本结构

namespace lin
{
	template<class T>
	struct ListNode//双向循环链表的基本结构
	{
		ListNode<T>* _prev;//前驱指针
		ListNode<T>* _next;//后继指针
		T _data;//数据值
        
        //不传值时使用T()默认值构造,传值则传值构造
		ListNode(const T& val = T())//默认构造 + 传值构造
			:_prev(nullptr)
			,_next(nullptr)
			,_data(val)
		{}
	};

    template<class T>
    struct ListIterator//迭代器封装类,成员都会被调用,因此使用struct
    {
	    typedef ListNode<T> Node;
        Node* _node;//结点指针
    }

    template<class T>
    class list//链表模板类,成员变量定义及函数封装
    {
	    typedef ListNode<T> Node;//将链表结构取别名,简化代码
    public:
        typedef ListIterator<T> iterator;//迭代器重命名
    private:
	    Node* _head;//链表头指针
        size_t size;//链表长度
    }
}

上述代码实现了双向循环链表的基本结构,其中包含了四个部分:

1.namespace lin,命令空间 lin 是用于封装代码,避免同名类型和函数冲突

2.在命名空间中,定义了模板类ListNode(双向循环链表基本结构),该类包含三个成员变量:

  • _prev : 存储指向前一个结点的指针
  • _next : 存储指向后一个结点的指针
  • _data : 存储数据

ListNode类还实现一个有缺省值(T())的构造函数,如果构造函数没有提供参数,则使用T类型的默认构造来初始化_data,如果传值则使用该值来初始化_data,该构造函数也会将_prev和_next指针指向nullptr。

3.模板类ListIterator(迭代器封装),该类包含一个成员变量,即链表的结点指针:

为什么链表需要封装一个迭代器的类呢???

  1. 链表的物理空间是不连续的,是通过结点的指针依次链接。
  2. 不能像string和vector一样直接解引用去访问其数据
  3. 结点的指针解引用还是结点结点指针++还是结点指针。
  4. 在string和vector的物理空间是连续的,所以这俩不需要实现迭代器类,可以直接使用。

4.模板类list(链表的基本成员变量及其函数接口),该类包含两个成员变量:

  • _head : 链表的头结点指针
  • _size : 链表的长度

2、基本函数实现

注意:我们实现的是带头双向循环链表。

2.1、默认构造函数

list()

默认构造的函数功能是构造一个没有元素的空容器。

思路:我们实现的是带头双向循环链表,因此默认构造时我们需要创建(new)一个头结点,并将链表长度初始化为0。

//构造头结点函数
void empty_init()
{
	_head = new Node;//创建新结点
	_head->_next = _head;
	_head->_prev = _head;
    _size = 0;
}

//默认构造 构造一个头结点
list()
{
	empty_init();
}

为了后序使用方便,我们将构造头结点封装成了一个函数。 

 2.2、尾插数据

为什么在第二个函数就写尾插呢?因为后面的函数会大量用到尾插函数。

push_back()

思想:

  • 先找到尾结点,即头结点的前一个结点。
  • 然后将尾结点,新结点以及头结点进行链接。
void push_back(const T& val)
{
	//tail _head->_prev
	Node* tail = _head->_prev;

	Node* newnode = new Node(val);//创建一个值为val的新结点
	//tail newnode _head //链接关系的顺序
	tail->_next = newnode;
	newnode->_prev = tail;
	newnode->_next = _head;
	_head->_prev = newnode;

	_size++;//尾插之后长度要++
}

我们尾插完数据之后,想要遍历整个链表怎么遍历呢???

我们在使用链表的时候是通过迭代器进行遍历,如下代码:

void test_list1()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	cout << lt.size() << endl;
}

此时我们就需要对链表的迭代器进行封装!!!

3、迭代器的封装

此时的迭代器是一个结点指针(Node*)。

3.1、迭代器的基本结构

template<class T>
struct ListIterator
{
    typedef ListNode<T> Node;//类型起别名
    Node* _node;//成员变量
    ListIterator(Node* node)//构造函数
        :_node(node)
    {}
};

但是我们使用迭代器时,是在list内部进行使用的,且类型名称为iterator,因此需要在list内部重命名迭代器类型(公有的,因为我们需要在类外访问)。

template<class T>
class list 
{
public:  
    typedef ListIterator<T> iterator;//迭代器重命名
};

迭代器实质是一个结点指针,因此类的成员是_node(结点指针),此处我们使用一个结点的指针对其初始化。

typedef ListNode<T> Node;//类型起别名
    Node* _node;//成员变量
    ListIterator(Node* node)//构造函数
        :_node(node)
    {}

3.2、迭代器重载函数的实现

前置++

先++,再使用返回的是++后的结点,用引用返回。

//前置++
typedef ListIterator<T> Self//对返回迭代器类型重命名,因为原类型较长
Self& operator++()
{
	_node = _node->_next;
	return *this;
}

后置++

先使用,再++返回的是++前的结点,传值返回。

typedef ListIterator<T> Self
Self operator++(int)
{
	Self tmp(*this);//构造一个临时变量存储之前的结点
	_node = _node->_next;
	return tmp;//返回临时对象
}

注意:前置和后置的区别是,后置需要在形参中传一个占位符,一般使用int类型。

 前置--

先--,再使用返回的是--后的结点,用引用返回。

Self& operator--()
{
	_node = _node->_prev;
	return *this;
}

 后置--

先使用,再++返回的是++前的结点,传值返回。

Self operator--(int)
{
	Self tmp(*this);
	_node = _node->_prev;
	return tmp;
}

为什么前置++返回的是类对象引用,而后置++返回的是结点类型呢???

因为在前置++中,我们返回的是类本身,而后置++,我们返回的是一个局部的类对象,局部的类对象出了函数会自动销毁。 

operator*

 对该迭代器位置的数据进行解引用类似与指针解引用。

T& operator*()//遍历及修改
{
	return _node->_data;//访问链表的data数据
}

 operator!=

重载两个迭代器不相等指针不相等则返回true,相等则返回false。

bool operator!=(const Self& lt)
{
	return _node != lt._node;//两个迭代器不相等即指针不相等
}	

operator==

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

 注意:比较迭代器是否相等比较的是的地址。

4、迭代器与list进行关联

4.1、使用迭代器打印链表数据

void test_list1()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	cout << lt.size() << endl;
}

根据打印的测试函数我们可以知道,我们需要获取链表的第一个结点的迭代器(即第一个结点的地址),但是这个地址只有在list类中有,因此我们需要在list类中封装一个获取第一个结点的迭代器(begin),获取end()也是同理。

begin() 

第一个结点的迭代器,即头结点的下一个位置(_head->next)。

iterator begin() 
{
	return iterator(_head->_next);//调用迭代器类的构造函数
    //return _head->next;//单参数的隐式类型转换
}

 end()

最后一个结点的下一个位置,即_head位置。

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

封装完迭代器之后我们就可以进行打印了。 

list类代码:

template<class T>
class list
{
	typedef ListNode<T> Node;
public:
	typedef ListIterator<T> iterator;
	iterator begin() //打印链表时,只能访问数据,不能修改内容及指向的内容
	{
		return iterator(_head->_next);
	}
	iterator end()
	{
		return iterator(_head);
	}
private:
	Node* _head;//链表头指针
	size_t _size;//链表大小
};

 测试结果:

4.2、其他相关函数

insert()

在pos位置之前插入val。

思路:

  • 先获取当前结点的地址
  • 然后通过前驱指针找到前面一个结点的地址
  • 再创建一个新的结点
  • 最后将前驱结点,新结点,当前结点构成链接关系
void insert(iterator pos, const T& val)//在pos位置前面插入val
{
	Node* cur = pos._node;//当前结点指针
	Node* prev = cur->_prev;

	Node* newnode = new Node(val);
	//prev newnode cur
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;

	_size++;
}

erase()

删除pos位置的值,并返回删除前的下一个结点的地址。

思路:

  • 先获取当前结点的地址
  • 然后通过前驱指针找到前一个结点地址,通过后继指针找到后一个结点的地址
  • 将prev 前驱指针与后继指针建立链接关系
  • 释放当前结点
  • 返回next结点
iterator erase(iterator pos)//删除pos位置值,迭代器失效问题
{
	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);//返回迭代器中结点指针
}

头插尾插头删尾删

复用insert()函数和erase()函数实现。

void push_back(const T& val)
{
	insert(end(), val);//end()之前插入
}
void push_front(const T& val)
{
    insert(begin(),val);//begin()之前插入
}
void pop_back()
{
	erase(--end());//删除end前面一个结点
}
void pop_front()
{
    erase(begin());//删除begin位置结点
}

总结


本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!

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

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

相关文章

代码随想录——二叉搜索树中的插入操作(Leetcode701)

题目链接 递归 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {* …

git: 批量删除分支

环境&#xff1a; window11git version 2.42.0git-bash.exe window环境下&#xff1a; 1. 批量删除本地 git branch |grep xxx |xargs git branch -D比如&#xff1a; 想批量删除本地含有 release 关键字的分支&#xff1a; 2. 批量删除远程 git branch -r | grep xxxx | …

华硕NUC 14 Pro+ :科技与艺术相得益彰

什么样的迷你主机可以称之为“艺术品”&#xff1f;让我们一起认识NUC 14 Pro&#xff0c;看科技与艺术可以交汇出怎样的独特韵味&#xff1f; 科技与美学的邂逅 华硕NUC 14 Pro不仅是一台性能强劲的电脑主机&#xff0c;更像是一件可以在桌面“展出”的艺术品。精致小巧的体积…

基于JAVA+SpringBoot+Vue前后端分离的医院在线挂号预约问诊平台

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 链接点击直达&#xff1a;下载链接 前言 哈喽兄弟…

CSS真题合集(二)

CSS真题合集&#xff08;二&#xff09; 11. css3新增特性12. css3动画12.1 关键帧动画 (keyframes)12.2 animation12.3 transition12.4 transform 13. grid网格布局13.1 使用display: grid或display: inline-grid的HTML元素。13.2 定义网格13.3 13.4 自动填充和自动放置13.4 对…

训练营第二十七天 | 491.递增子序列46.全排列47.全排列 II332.重新安排行程51. N皇后

491.递增子序列 力扣题目链接(opens new window) 给定一个整型数组, 你的任务是找到所有该数组的递增子序列&#xff0c;递增子序列的长度至少是2。 示例: 输入: [4, 6, 7, 7]输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]] 说明: …

新手学习编程网站一站式合集

LTPP在线开发平台 探索编程世界的新天地&#xff0c;为学生和开发者精心打造的编程平台&#xff0c;现已盛大开启&#xff01;这个平台汇集了近4000道精心设计的编程题目&#xff0c;覆盖了C、C、JavaScript、TypeScript、Go、Rust、PHP、Java、Ruby、Python3以及C#等众多编程语…

【Java】---- SpringBoot 统一数据返回格式

目录 1. 统一数据返回格式介绍2. 实际应用2.1 添加前后的返回结果区别2.2 存在问题 3. 统一数据返回格式的优点 1. 统一数据返回格式介绍 通过使用ControllerAdvice和引用ResponseBodyAdvice接口来进行实现。 ResponseBodyAdvice这个接口里面有两个方法&#xff0c;分别是: s…

B端数据看板,其实数据可以更美的。

B端数据看板可以通过设计来提升其美观度。 色彩和配色方案&#xff1a; 选择适合品牌和数据类型的色彩搭配方案。使用渐变色、明亮的色调和对比度来突出重要的数据指标。 数据可视化&#xff1a; 使用图表、图形和数据图像来呈现数据&#xff0c;使其更易于理解和解读。选择…

【算法】合并两个有序链表(easy)——递归算法

题解&#xff1a;合并两个有序链表(easy)——递归求解 目录 1.题目2.题解3.参考代码4.总结 1.题目 题目链接&#xff1a;LINK 2.题解 本题有两种解法&#xff0c; 一是用循环去处理 链接&#xff1a;【刷题记录】合并两个有序数组、移除元素二是用递归去处理 将在下面中说…

买视觉检测设备需要多少钱?

随着工业自动化的发展&#xff0c;其应用范围逐步提高&#xff0c;其中母子图像传感器、CMOS和CCD摄像机、DSP、ARM嵌入式技术、图像处理和模式识别技术的快速发展&#xff0c;有效地推动了视觉检测设备的发展。在机器视觉领域中&#xff0c;常见的就是视觉检测、视觉识别、视觉…

统计信号处理基础 习题解答10-6

题目 在例10.1中&#xff0c;把数据模型修正为&#xff1a; 其中是WGN&#xff0c;如果&#xff0c;那么方差&#xff0c;如果&#xff0c;那么方差。求PDF 。把它与经典情况PDF 进行比较&#xff0c;在经典的情况下A是确定性的&#xff0c;是WGN&#xff0c;它的方差为&#…

蒙层(css)

如何在 Vue 中实现一个包含图像和蒙层效果的组件&#xff1f;这个组件根据某个条件显示或隐藏蒙层&#xff0c;用于表示图像是否已读。 1. 创建基础模板 首先&#xff0c;我们在模板中使用 div 包裹我们的图像组件 GraphImage&#xff0c;并为最外层 div 设置 position: relat…

WordPress plugin MStore API SQL注入漏洞复现(CVE-2023-3077)

0x01 产品简介 WordPress和WordPress plugin都是WordPress基金会的产品。WordPress是一套使用PHP语言开发的博客平台。该平台支持在PHP和MySQL的服务器上架设个人博客网站。WordPress plugin是一个应用插件。 0x02 漏洞概述 WordPress plugin MStore API 3.9.8 版本之前存在S…

【逻辑回归】Logistic Regression逻辑回归模型学习笔记

文章目录 序言1. 线性回归2. 逻辑回归2.1 引入逻辑回归的原因2.2 逻辑回归2.3 逻辑回归的应用 3. 逻辑函数3.1 sigmoid函数3.2 sigmoid函数的性质3.3 决策边界3.4 对数几率 4. 损失函数4.1 为什么说逻辑回归时概率类模型4.2 为什么要进行极大似然估计4.3 利用MLE如何推导出损失…

XML解析库tinyxml2库使用详解

XML语法规则介绍及总结-CSDN博客 TinyXML-2 是一个简单轻量级的 C XML 解析库,它提供了一种快速、高效地解析 XML 文档的方式。 1. 下载地址 Gitee 极速下载/tinyxml2 2. 基本用法 下面将详细介绍 TinyXML-2 的主要使用方法: 2.1. 引入头文件和命名空间 #i…

Linux操作系统:Redis在虚拟环境下的安装与部署

Redis下载方法 最近部署项目的时候用到了Redis&#xff0c;自己在安装的时候也碰到了一些列问题最终安装成功&#xff0c;记录一下自己的安装历程。前期准备&#xff1a; 服务器Linux版本&#xff1a;Centos8.4 64位&#xff08;http://isoredirect.centos.org/centos/8/isos/…

Django 部署指南

部署 Django 应用程序涉及将我们的应用程序从开发环境部署到生产环境&#xff0c;并确保它可以在生产服务器上安全运行和扩展。其实了解几种部署方案&#xff0c;相信你对将来的项目更得心应手。 1、问题背景 Django 是一款流行的 Python Web 框架&#xff0c;但对于新手来说&…

Parallels Desktop 优化大学 IT 环境的八大最佳实践

在不断变化的大学 IT 环境中&#xff0c;优化资源和确保无缝运营至关重要。Parallels Desktop 是学术用户弥合 Mac 和 Windows 环境之间差距的强大工具。 大学 IT 部门可以在很多方面受益于 Parallels Desktop&#xff0c;主要原因是它使 IT 管理员能够提供让 Mac 用户无缝运行…

Aigtek功率放大器由什么组成

功率放大器是一种电子设备&#xff0c;用于将输入信号的功率增加到更高的水平&#xff0c;以驱动负载或输出设备。它由多个组件和电路构成&#xff0c;以实现信号放大和传输。 下面是功率放大器的主要组成部分&#xff1a; 输入级&#xff1a;输入级是功率放大器的第一个阶段&a…