STL forward_list 模拟实现

news2025/1/19 3:24:13

forward_list 概述

forward_list 是 C++ 11 新增的容器,它的实现为单链表。

forward_list 是支持从容器中的任何位置快速插入和移除元素的容器,不支持快速随机访问。forward_list 和 list 的主要区别在于,前者的迭代器属于单向的 Forward Iterator,后者的迭代器属于双向的 Bidirectional Iterator。

下面实现的单链表为单向带头循环链表,实现为循环链表只是因为这样实现比较简单,更容易处理 end 的指向。

文章完整代码:ForwardList · 秦1230/STL study

接口总览

namespace qgw {
	/// @brief forward_list 中每个节点
	/// @tparam T 节点存储的数据类型
	template <class T>
	struct _forward_list_node {
		_forward_list_node(const T& data = T());	// 节点类的构造函数
			
		_forward_list_node<T>* _next;				// 指向后一节点
		T _data;									// 存储节点数据
	};

	/// @brief forward_list 的迭代器
	/// @tparam T forward_list 数据的类型
	/// @tparam Ref 数据的引用类型
	/// @tparam Ptr 数据的指针类型
	template <class T, class Ref, class Ptr>
	struct _forward_list_iterator {
		typedef _forward_list_iterator<T, T&, T*>		iterator;
		typedef _forward_list_iterator<T, Ref, Ptr>		self;

		typedef Ptr pointer;
		typedef Ref reference;
		typedef _forward_list_node<T> forward_list_node;

		// 构造函数
		_forward_list_iterator(forward_list_node* node = nullptr);

		// 各种运算符重载
		bool operator==(const self& x) const;
		bool operator!=(const self& x) const;
		reference operator*() const;
		pointer operator->() const;
		self& operator++();
		self operator++(int);

		forward_list_node* _node;	// 指向对应的 forward_list 节点
	};

	template <class T>
	class forward_list {
	public:
		typedef T* pointer;
		typedef const T* const_pointer;
		typedef T& reference;
		typedef const T& const_reference;
		typedef _forward_list_node<T>	forward_list_node;
		typedef _forward_list_iterator<T, T&, T*>             iterator;
		typedef _forward_list_iterator<T, const T&, const T*> const_iterator;

	public:
		// 默认成员函数
		forward_list();
		forward_list(const forward_list<T>& other);
		forward_list<T>& operator=(const forward_list<T>& other);
		~forward_list();

		// 元素访问
		reference front();
		const_reference front() const;

		// 迭代器
		iterator begin();
		const_iterator begin() const;
		iterator end();
		const_iterator end() const;

		// 容量
		bool empty() const;

		// 修改器
		void clear();
		iterator insert_after(iterator pos, const_reference val);
		void push_front(const_reference val);
		iterator erase_after(iterator pos);
		void pop_front();
		void swap(forward_list& other);

	private:
		forward_list_node* _node;
	};
}

forward_list 的节点

forward_list 节点的设计与 list 的节点类似,只需两个成员变量:一个节点指针和数据。

构造函数将数据初始化为给定数据,再将 _next 指针初始化为空。

/// @brief 节点类的构造函数
/// @param data 用来构造节点的初值
_forward_list_node(const T& data = T())
    : _data(data) {
        _next = nullptr;
    }

forward node

默认成员函数

默认构造函数

因为实现的是的单向带头循环链表,所以要在构造函数创建一个头节点,并将 _next 指针指向自己。

/// @brief 构造一个空链表
forward_list() {
    _node = new forward_list_node;	// 创建一个头节点
    _node->_next = _node;			// 后面指向自己
}

empty forward

析构函数

forward_list 的析构函数,先调用 clear 释放数据资源,再 delete 掉头节点即可。

/// @brief 释放资源
~forward_list () {
    clear();
    delete _node;
    _node = nullptr;
}

forward_list 的迭代器

forward_list 的节点在内存中不是连续存储的,因此不能使用原生指针作为 forward_list 的迭代器。

由于 forward_list 是一个单向链表,迭代器必须具备后移的能力,所以 forward_list 提供的是 Forward Iterator。

forward iter

构造函数

forward_list 的迭代器中成员变量只有一个节点指针,将其初始化为给定的节点指针。

/// @brief 迭代器的构造函数
/// @param node 用来构造的节点
_forward_list_iterator(forward_list_node* node = nullptr) {
    _node = node;
}

operator==

两个 forward_list 迭代器的比较,实际上比较的是迭代器所指向的节点,指向同一节点即为两迭代器相同。

/// @brief 判断两迭代器指向的节点是否相同
/// @param x 用来比较的迭代器
/// @return 相同返回 true,不同返回 false
bool operator==(const self& x) const {
    return _node == x._node;
}

operator!=

operator!= 的实现可以借助 operator==,直接调用判断是否相等的函数,然后返回相反的结果。

/// @brief 判断两迭代器指向的节点是否不同
/// @param x 用来比较的迭代器
/// @return 不同返回 true,相同返回 false
bool operator!=(const self& x) const {
    return !operator==(x);
}

operator*

当我们对一个指针进行解引用时会发生什么,我们会得到指针指向的数据。同理,我们对迭代器进行解引用,得到的是迭代器中节点指针所指向变量的值。

list operator1

/// @brief 获取指向节点中的数据值
/// @return 返回指向节点数据的引用
reference operator*() const {
    return (*_node)._data;
}

operator->

假如我们有如下数据类:

// 有一个 Person 类,里面有身高和体重两个成员
struct Person {
	double height;
	double weight;
};

此时,我们的数据不再是单一的变量了,而是一个结构体变量。我们想读取其中的数据,该怎么操作呢?

Person p1 = { 165, 105 };
Person* p = &p1;
cout << (*p).height << endl;	// 获取身高数据
cout << p->weight << endl;		// 获取体重数据

我们可以先对直接解引用得到一个 Person 对象,再用 . 操作符访问其中的变量。

当然我们也可以选择对 Person* 使用 -> 操作符访问结构体内的变量。

于是乎,operator-> 的功能也就很清晰了。当我们对迭代器使用 -> 时我们可以直接访问节点中的变量。也就是说,我们有一迭代器 iter,其中迭代器中存储的数据类型为 Person,当我们使用 iter->height 时,可以直接获取到身高。

/// @brief 获取节点中数据的地址
/// @return 返回节点指向的数据的地址
pointer operator->() const {
    return &(operator*());
}

看了实现你可能会疑惑 iter-> 获得的明明是结构体的指针,后面应该再跟一个 -> 箭头才对。是的没错,确实应该是这样,不过 iter->->height 的可读性比较差,所以编译器会在编译时自动为我们添加一个箭头。

operator++

operator++ 运算符的作用是让迭代器指向 forward_list 中下一节点。因为 forward_list 是单链表不能向前移动,所以不用实现 operator--。

前置实现的思路是:通过迭代器中的节点指针找到下一节点,然后赋值给迭代器中的节点指针。

后置实现的思路是:先拷贝一份当前位置的迭代器,然后调用前置 ++,最后返回临时变量。

需要注意的是:前置 ++ 返回的是前进后迭代器的引用,后置 ++ 返回的是一个临时变量。

/// @brief 前置 ++
/// @return 返回前进一步后的迭代器
self& operator++() {
    _node = _node->_next;
    return *this;
}

/// @brief 后置 ++
/// @param  无作用,只是为了与前置 ++ 进行区分,形成重载
/// @return 返回当前的迭代器
self operator++(int) {
    self tmp = *this;
    ++(*this);
    return tmp;
}

元素访问

front

front 获取容器首元素的引用,调用 begin 得到首元素的迭代器,再解引用即可。

因为 forward_list 的迭代器只能单向移动,故不能快速获得链表中最后一个节点。

/// @brief 返回容器首元素的引用
/// @return 首元素的引用
reference front() {
    return *begin();
}

// 与上面唯一不同就是用于 const 容器
const_reference front() const {
    return *begin();
}

迭代器

forward iter2

begin

begin 获取的是首元素的迭代器,根据上图,直接返回头节点的下一位置即可。

/// @brief 返回指向 forward_list 首元素的迭代器
/// @return 指向首元素的迭代器
iterator begin() {
    // 根据节点指针构造迭代器
    return iterator(_node->_next);
}

// const 版本供 const 容器使用
const_iterator begin() const {
    return const_iterator(_node->_next);
}

end

end 获取的是最后一个元素下一个位置的迭代器,根据上图就是 _node 所指向的节点。

/// @brief 返回指向 forward_list 末元素后一元素的迭代器
/// @return 指向最后元素下一位置的迭代器
iterator end() {
    // 调用 iterator 构造函数
    return iterator(_node);
}

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

容量

empty

begin 和 end 指向相同,说明链表此时只有一个头节点,链表为空。

/// @brief 检查容器是否无元素
/// @return 若容器为空则为 true,否则为 false
bool empty() const {
    return begin() == end();
}

修改器

insert_after

根据 STL 的习惯,插入操作会将新元素插入于指定位置之前,而非之后。然而 forward_list 是一个单向链表,它没有任何方便的方法可以定位出前一个位置,它必须从头找。因此,forward_list 中提供的插入操作,是插入在指定位置之后。

下图为:只有 0、1 两个元素的单链表,在 0 之后插入元素值为 2 的节点的示意图。

forward insert

插入的过程非常简单:

  1. 创建一个要插入的节点
  2. 插入节点的 _next 指向 pos 后一位置的节点
  3. pos 的 _next 指向要插入的节点
/// @brief 在容器中的指定位置后插入元素
/// @param pos 内容将插入到其后的迭代器
/// @param val 要插入的元素值
/// @return 指向被插入元素的迭代器
iterator insert_after(iterator pos, const_reference val) {
    forward_list_node* tmp = new forward_list_node(val);	// 创建要插入的节点
    tmp->_next = pos._node->_next;							// (1)
    pos._node->_next = tmp;									// (2)
    return tmp;
}

push_front

push_front 的作用是在容器起始处插入元素。

直接调用 insert_after 插入就行,需要注意的是,insert_after 是在给定位置之后插入,所以应传入头节点对应的迭代器位置。

/// @brief 头插给定元素 val 到容器起始
/// @param val 要头插的元素值
void push_front(const_reference val) {
    // end() 返回头节点位置的迭代器,在这之后插入是头插
	insert_after(end(), val);
}

erase_after

下图为:有三个元素 0、1、2 的链表,删除 pos 指向节点之后节点(值为 1)的示意图。

forward erase

删除的过程非常简单:

  1. 记录 pos 的下一节点 nextNode
  2. 将 pos 的 _next 指向 nextNode 的下一个节点
  3. delete 释放掉 nextNode 所指向的节点
/// @brief 从容器移除 pos 后面一个元素
/// @param pos 指向要被移除元素前一个元素的迭代器
/// @return 最后移除元素之后的迭代器
iterator erase_after(iterator pos) {
    forward_list_node* nextNode = pos._node->_next;		// 记录 pos 指向节点的下一节点
    pos._node->_next = nextNode->_next;					// (1)
    delete (nextNode);
    return (iterator)(pos._node->_next);
}

pop_front

pop_front 移除容器的首元素,也就是 end 指向的下一节点。

/// @brief 移除容器首元素
void pop_front() {
    erase_after(end());
}

clear

clear 用于清空容器所有数据,不清理头节点。

要注意 erase_after 删除给定位置下一个节点,end 的下一个是第一个元素,这样以次删除直到容器为空,即只剩一个头节点。

/// @brief 从容器擦除所有元素
void clear() {
    while (!empty()) {
        erase_after(end());
    }
}

swap

swap 用来交换两个 forward_list容器,不用交换 forward_list 中每个元素的值,直接交换 _node 指针即可。

/// @brief 将内容与 other 的交换
/// @param other 要与之交换内容的容器
void swap(forward_list& other) {
    std::swap(_node, other._node);
}

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

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

相关文章

二分法讲解

目录 一、前言 二、二分法理论 1、引导&#xff1a;猜数游戏 2、理论背景&#xff1a;非线性方程的求根问题 1&#xff09;非线性方程的近似解 2&#xff09;搜索法和二分法 3、用二分的两个条件 4、二分法复杂度 三、整数二分 1、在单调递增序列中找 x 或者 x 的后继…

使用python-pptx创建PPT演示文档功能实践

python对PPT演示文档读写&#xff0c;是通过第三方库python-pptx实现的&#xff0c;python-pptx是用于创建和更新 PowerPoint&#xff08;.pptx&#xff09;文件的 Python 库。 关于PPT演示文档与幻灯片模板的内容不是本文的重点&#xff0c;在此略过。 1. PPT基本结构在pyth…

Sophus降维、升维与欧拉角、旋转向量的爱恨情仇

0. 简介 在面对二维与三维之间的转换时&#xff0c;我们常常会困惑该如何去转换&#xff0c;在G2O中存在有理想的坐标转换工具&#xff0c;但是在Sophus中却缺乏这样的手段。之前在Sophus处简要的介绍了一下SE(2)与SE(3)的转换&#xff0c;最近发现之前的文章这部分需要拿出来…

Leetcode:654. 最大二叉树(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 直接模拟&#xff08;递归)&#xff1a; 原理思路&#xff1a; 索引版本&#xff1a; 问题描述&#xff1a; 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&a…

Thymeleaf从入门到清晰使用

文章目录什么是thymeleaf&#xff1f;第一个Thymeleaf程序Thymeleaf详解配置常用标签最后什么是thymeleaf&#xff1f; 模板引擎&#xff1a; 前端交给我们的页面&#xff0c;是html页面&#xff0c;如果是以前开发&#xff0c;我们需要把他们转成jsp页面&#xff0c;jsp的好处…

ABB AC500 系列 PLC 与上位机iFix 的通讯配置

ABB PLC IP 及 MODBUS TCP/IP 协议设置 通过 IP config tool 配置设备 IP 在 软件中&#xff0c;有 3 种方式可以进入 IP config tool 的配置界面  双击 IP_settings&#xff0c;点击 IP config tool&#xff0c;即可进入 IP config tool 界面 点击菜单栏的 Tool→IP confi…

k8s 微服务spring boot JVM 监控

目录 1.前言 1.1 JVM监控方案 1.2 接入操作步骤 2. 服务开启actuator prometheus监控 2.1 示例参考添加依赖 2.2 配置prometheus监控 3 配置 prometheus 监控 4 配置jvm grafana dashboard 1.前言 服务 部署环境 监控 spring-demo k8s v1.22 Prometheus Operator …

进程相关概念

1、什么是程序&#xff0c;什么是进程&#xff0c;有什么区别 程序是静态的概念。例如&#xff1a;gcc xxx.c -o pro&#xff0c;磁盘中生成pro文件&#xff0c;叫做程序&#xff08;还未运行起来&#xff09; 进程是程序的一次运行活动&#xff0c;也就是程序跑起来了&#xf…

【Linux】信号机制(非实时信号)

目录 前言 一.信号的概念以及产生 1.什么是信号 2.信号分为两类 3.查看信号的命令 4.信号如何产生 1).通过软件产生 2).通过硬件产生 3).通过键盘组合键产生 二.信号的发送以及保存 1.信号如何发送 2.信号如何保存 1).概念 2).底层实现结构&&内核中的实现…

WampServer服务器设置控件

WampServer服务器设置控件 WampServer是一个web开发环境&#xff0c;是一个用于Windows操作系统的类似服务器的软件&#xff0c;由Romain Bourdon构建。该工具允许您开始构建web应用程序&#xff0c;并在Windows上使用Apache服务器2、PHP编程语言和MySQL数据库在本地网络上启动…

java 实现 springboot项目 使用socket推送消息,前端实时进行接收后端推送的消息

1 后端 1.1 添加依赖 在我们的springboot项目里面&#xff0c;添加依赖&#xff1b; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.7.0</version>…

企业微信在职继承功能如何理解?怎么使用?

企业微信的在职继承功能就是企业管理员将在职员工的客户分配给其他在职员工跟进的一种方式&#xff0c;很方便&#xff0c;可以在工作出现变更时使用。 前言 企业微信之前的离职继承功能受到很多企业的青睐&#xff0c;企业能够将离职员工的客户分配给其他员工接手&#xff0c…

云天励飞在科创板获准注册:计划募资30亿元,陈宁为实际控制人

2023年1月10日&#xff0c;证监会发布公告&#xff0c;同意深圳云天励飞技术股份有限公司&#xff08;下称“云天励飞”&#xff09;首次公开发行股票注册。据贝多财经了解&#xff0c;云天励飞于2020年12月8日在科创板递交上市申请&#xff0c;2021年8月6日过会。 本次冲刺上市…

蓝队攻击的四个阶段(一)

蓝队的攻击是一项系统的工作&#xff0c;整个攻击过程是有章可循、科学合理的涵盖了从前期准备、攻击实施到靶标控制的各个步骤和环节。按照任务进度划分&#xff0c;一般可以将蓝队的工作分为4个阶段:准备工作、目标网情搜集、外网络向突破和内网横向拓展。第一阶段准备工作&a…

App开发提效法宝之插件技术

近年来技术革新频率越来越高&#xff0c;最近工作中经常有小伙伴问到插件技术的相关内容&#xff0c;今天就来跟大家系统的说清楚什么是插件技术以及它的好处。欢迎评论区交流哦&#xff01; 什么是插件技术&#xff1f; 插件技术指的是一种应用程序&#xff0c;遵循程序接口…

高温热水解预处理对厌氧消化期间污泥腐殖化的调控机制

期刊&#xff1a;Water Research 影响因子&#xff1a;9.13 发表时间&#xff1a;2022样本类型&#xff1a;污泥客户单位&#xff1a;同济大学凌恩生物客户同济大学发表在《Water Research》上的文章“The neglected effects of polysaccharide transformation on sludge humif…

振弦采集模块参数配置工具VMTool 的使用

振弦采集模块参数配置工具VMTool 的使用 准备工作 &#xff08; 1&#xff09; 将 VMXXX 模块的 UART_TTL、 RS232&#xff08; 或 RS485&#xff09; 接口与计算机的 COM 端口连接&#xff1b; &#xff08; 2&#xff09; 连接振弦传感器及温度传感器到 VMXXX 的对应接口&…

三菱FX3U系列PLC运动控制_伺服回原点的3种方法示例

三菱FX3U系列PLC运动控制_伺服回原点的3种方法示例 方法1: 运动的方向为圆形、环形、电机往一个方向转动; 只有一个原点开关,没有极限开关 如下图所示, 原点回归的方式为:启动回原点后,电机开始寻找原点开关,在找到原点开关的瞬间,开始减速;在离开原点开关的瞬间,电…

23. 反爬案例:不登录不给,要数据请先登录我的站点

登录之后&#xff0c;可以查看数据&#xff0c;是部分站点常用规则&#xff0c;本篇博客将在爬虫训练场中实现该需求。 文章目录安装必备模块建立 models建立 login_form 表单文件flask_wtf 中 FlaskForm 类建立登录视图函数配置 login.html 页面安装必备模块 实现 Python Fla…

Qt基于CTK Plugin Framework搭建插件框架--插件通信【事件监听】

文章目录一、前言二、事件三、类通信3.1、新建接收插件3.2、新建发送插件3.3、启用插件四、信号槽通信4.1、新建接收插件4.2、新建发送插件4.3、启用插件五、类通信和信号槽通信的区别六、插件依赖七、获取元数据一、前言 CTK框架中的事件监听&#xff0c;其实就是观察者模式&…