list简单模拟实现

  • 成员变量
  • 迭代器(重点)
    • ListIterator
    • 运算符重载
    • begin、end
  • 插入、删除
    • insert
    • erase
    • 头插、尾插、头删、尾删
  • operator->
  • const_iterator
  • 拷贝构造
  • operator=
  • 析构函数
  • 完整代码

由于前面已经模拟实现了vector,所以这里关于一些函数实现就不会讲的过于细致。

成员变量

template<class T>
struct ListNode
{ListNode<T>* _next;ListNode<T>* _prev;T _data;ListNode(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x){}
};template<class T>
class list
{typedef ListNode<T> Node;
public:list(){_head = new Node;_head->_next = _head->_prev = _head;_size = 0;}size_t size()const{return _size;}
private:Node* _head;size_t _size;
};

迭代器(重点)

我们想要迭代器能实现以下的功能:

void test_1()
{list<int>lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << ' ';++it;}cout << endl;
}

实际上迭代器模拟的是指针的功能,然而链表的指针++并不会得到下一个元素的指针。但我们却希望迭代器++能得到下一个元素的迭代器。

那么我们是不是需要对++进行一个修改,也就是运算符重载。

再仔细想想,重载后的运算符是对这一个类生效的,但我们只需要对迭代器生效。那是不是意味着我们的迭代器需要是一个独立的类:

ListIterator

template<class T>
struct ListIterator
{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Node* _node;//内置类型指针ListIterator(Node* node):_node(node){}
}

注意不需要析构函数,实际上我们的迭代器就是原数组结点指针的一个类,怎么可以用过一次后就析构掉呢?

运算符重载

前置++:

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

后置++:

Self operator++(int)//后置++
{Self tmp(*this);//浅拷贝_node = _node->_next;return tmp; 
}

值得注意的是,前后++的重载函数名都是operator++,那么如何区分他们呢?

C++语法规定:后置++的函数参数里面需要有一个int

此外,我这的tmp是一个浅拷贝,这样做有没有问题呢?
我们需要的是指针,那么对于指针自然应该采用浅拷贝。
事实上,我们一般有一个规律:如果类需要重载析构函数,那么一般就要深拷贝,反之一般就需要浅拷贝。

剩余运算符:

Self& operator*()
{return _node->_data;
}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;
}

begin、end

注意到我们的begin是返回首元素迭代器,而end是返回最后一个元素的下一个位置的迭代器:

typedef ListIterator<T> iterator;
iterator begin() 
{return _head->_next;
}iterator end()
{return iterator(_head);
}

注意到,begin这里我返回的是_head->_next,因为单参数会隐式类型转换:自动调用单参数的构造函数,也就是等价于iterator(_head->_next).

插入、删除

insert

void insert(iterator pos, const T& val)
{Node* cur = pos._node;Node* newnode = new Node(val);Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;_size++;
}

erase

iterator erase(iterator pos)//返回iterator防止迭代器失效
{Node* cur = pos->_node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;_size--;return iterator(next);
}

值得注意的是,为了防止迭代器失效,我们这里要返回最后一个删除的元素的下一个元素的迭代。

头插、尾插、头删、尾删

void push_back(const T& x)
{insert(end(), x);
}void push_front(const T& x)
{insert(begin(), x);
}void pop_back()
{erase(end()--);//没有重载-1,所以要用--
}void pop_front()
{erase(begin());
}

尾删这里需要注意一定要是end()–,而不能是end()-1.理由很简单,因为我们没有重载-。

operator->

对于非内置类型如下:

struct A
{int _a1;int _a2;A(int a1 = 0, int a2 = 0):_a1(a1),_a2(a2){}
};void test_2()
{list<A>lt;A aa1(1, 1);lt.push_back(aa1);lt.push_back(aa1);lt.push_back(A(2, 2));lt.push_back({ 3,3 });lt.push_back({ 4,4 });list<A>::iterator it = lt.begin();while (it != lt.end()){cout << *it << ' ';//不支持流插入++it;}}

不难发现cout << *it << ’ ';这里会报错,因为自定义类型没有重载<<。
因此我们需要改写为:

cout << (*it)._a1 << ' ' << (*it)._a2 << endl;

不难发现这有些许繁琐,正常来说对于类指针功能我们更希望用->这个操作符,也就是:

cout << it->_a1 << ' ' << it->_a2 << endl;

那就需要重载->:

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

这时候就有人发出疑问了,怎么返回值是一个指针,那要调用_a1,不应该是it->->_a1吗?
没错,这是原生写法,但是我们C++语法给他优化掉了,现在it->_a1等价于it.operator->()->_a1

const_iterator

const_iterator为什么不是const iterator?
这个问题其实前面学习类的时候已经解答过了,事实上我们的const_iterator是不能改变迭代器呢,还是不能改变迭代器指向的元素的值呢?
事实上const_iterator也是可以++的,否则怎么遍历数组元素。
那也就是说const_iterator只是不能改变指向的元素的值,而不是不能改变迭代器本身。

经过上述分析我们得知,我们只需要对*和->重载的返回值作出修改即可:

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

但是,只有返回值不同是无法重载函数的!!
那么我们是不是需要再多写一个const_iterator类呢?
可以,但有更好的方法。
注意到我们只有这两个函数不同,并且只有返回值不同,那是不是可以写一个模板将返回值不同分成不同的类呢?

template<class T,class Ref,class Ptr>
struct ListIterator
{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}}
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;const_iterator begin() const
{return _head->_next;
}const_iterator end() const
{return _head;
}

这样我们就省去了CV再写一个类的功夫。

拷贝构造

一样是复用push_back

void empty_init()
{_head = new Node;_head->_next = _head->_prev = _head;_size = 0;
}
list(const T& lt)// 复用push_back来深拷贝
{empty_init();for (auto& e : lt){push_back(e);}
}

operator=

一样是复用拷贝构造:

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;
}

析构函数

同样复用erase

void clear()//没清除头结点
{iterator it = begin();while (it != end()){it = erase(it);}}~list()
{clear();delete _head;_head = nullptr;}

完整代码

template<class T>
struct ListNode
{ListNode<T>* _next;ListNode<T>* _prev;T _data;ListNode(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x){}
};template<class T,class Ref,class Ptr>
struct ListIterator
{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Node* _node;//内置类型指针ListIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}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 ListNode<T> Node;
public:typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T*> const_iterator;iterator begin() //const{return iterator(_head->_next);}iterator end(){return iterator(_head);}//const_iterator而不是const iterator,因为是指向内容不能被修改,而非迭代器本身不能被修改。如果是const iterator就不能it++const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}void empty_init(){_head = new Node;_head->_next = _head->_prev = _head;_size = 0;}list(){empty_init();}list(const T& lt)// 复用push_back来深拷贝{empty_init();for (auto& e : lt){push_back(e);}}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;}void clear()//没清除头结点{iterator it = begin();while (it != end()){it = erase(it);}}~list(){clear();delete _head;_head = nullptr;}//void push_back(const T& x)//{//	Node* newnode = new Node(x);//	Node* tail = _head->_prev;//	tail->_next = newnode;//	newnode->_prev = tail;//	newnode->_next = _head;//	_head->_prev = newnode;//}void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void pop_back(){erase(end()--);//没有重载-1,所以要用--}void pop_front(){erase(begin());}void insert(iterator pos, const T& val){Node* cur = pos._node;Node* newnode = new Node(val);Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;_size++;}iterator erase(iterator pos)//返回iterator防止迭代器失效{Node* cur = pos->_node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;_size--;return iterator(next);}size_t size()const{return _size;}bool empty()const{return _size == 0;}private:Node* _head;size_t _size;
};

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

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

相关文章

【计算机视觉】基于Python的相机标定项目Camera-Calibration深度解析

基于Python的相机标定项目Camera-Calibration深度解析 1. 项目概述技术核心 2. 技术原理与数学模型2.1 相机模型2.2 畸变模型 3. 实战指南&#xff1a;项目运行与标定流程3.1 环境配置3.2 数据准备3.3 执行步骤3.4 结果验证 4. 常见问题与解决方案4.1 角点检测失败4.2 标定结果…

多光谱影像:解锁遥感奥秘的 “彩色钥匙”

在遥感领域&#xff0c;多光谱影像犹如一把神奇的 “彩色钥匙”&#xff0c;为我们开启洞察地球表面与大气层的全新视角。 图片来源于星图云开放平台 多光谱影像&#xff0c;顾名思义&#xff0c;就是利用遥感平台上的多光谱传感器&#xff0c;同时对地球目标地物在多个不同光谱…

【ROS2】ROS节点启动崩溃:rclcpp::exceptions::RCLInvalidArgument

1、问题描述 启动ROS节点时,直接崩溃,打印信息如下: terminate called after throwing an instance of rclcpp::exceptions::RCLInvalidArgumentwhat(): failed to create guard condition: context argument is null, at ./src/rcl/guard_condition.c:65 [ros2run]: Abo…

MinerU安装(pdf转markdown、json)

在Windows上安装MinerU&#xff0c;参考以下几个文章&#xff0c;可以成功安装&#xff0c;并使用GPU解析。 整体安装教程&#xff1a; MinerU本地化部署教程——一款AI知识库建站的必备工具 其中安装conda的教程&#xff1a; 一步步教你在 Windows 上轻松安装 Anaconda以及使…

aws 实践创建policy + Role

今天Cyber 通过image 来创建EC2 的时候,要添加policy, 虽然是administrator 的role, 参考Cyber 提供的link: Imageshttps://docs.cyberark.com/pam-self-hosted/14.2/en/content/pas%20cloud/images.htm#Bring 1 Step1:

【ROS2】编译Qt实现的库,然后链接该库时,报错:/usr/bin/ld: XXX undefined reference to `vtable for

1、问题描述 在ROS2工程中,编译使用Qt实现的库,在其它ROS2包链接该库时,报错: /usr/bin/ld: XXX undefined reference to `vtable for2、原因分析 查看链接失败的几个函数接口都是,信号函数(signals 标记的函数)。因为信号函数都只有定义,没有实现,在执行ROS2 colc…

数据库--处理模型(Processing Model)(二)

执行查询的方法有很多,接下来将介绍以更高效和更有效率的方式执行分析工作负载(在OLAP系统中)的不同技术,包括以下内容: 执行并行性(Execution Parallelism)执行引擎(Execution Engines)执行操作符输出(Execution Operator Output)中间数据表示(Intermediate Data …

PostgreSQL pgrowlocks 扩展详解

一、简介 pgrowlocks 是 PostgreSQL 官方提供的扩展模块&#xff0c;用于查看指定表中每一行当前的行级锁&#xff08;Row Lock&#xff09;信息。它非常适用于&#xff1a; 并发冲突排查行级锁等待分析死锁前兆探测热点数据行分析 二、安装与启用 1. 安装前提&#xff08;…

关于xammp数据库打开不了,但是日志没错误的问题解决以及其数据库的备份

这里参考了两篇文章 解决Xampp中mysql无法启动的问题_xampp里面mysql的stop启动不起来-CSDN博客 mysqli_real_connect(): (HY000/1045): Access denied for user ‘root‘‘localhost‘ (using password: YES-CSDN博客 相信很多和我一样&#xff0c;很久没登xammp突然数据库…

在UI 原型设计中,交互规则有哪些核心要素?

在UI 原型设计中&#xff0c;交互规则主要有三个核心要素&#xff0c;分别为重要性、原则与实践&#xff0c;具体表现在&#xff1a; 一、交互规则在 UI 原型设计中的重要性 明确交互逻辑&#xff1a;设计阶段制定交互规则&#xff0c;清晰定义界面元素操作响应。 如社交应用…

BFD与VRRP联动

一、概述 在前面的文章我们学习了VRRP与BFD协议,VRRP(虚拟路由冗余协议)的主要特点是当Master(主)设备出现故障时,Backup(备用)设备能够快速接替Master的转发工作,尽量缩短数据流的中断时间。 在没有采用BFD与VRRP联动机制前,当Master出现故障时,VRRP依靠Backup设置的超时时间来…

Protobuf3协议关键字详解与应用实例

一、核心语法与基础关键字 syntax 声明协议版本&#xff0c;必须为文件的第一行非空、非注释内容。 syntax "proto3"; // 显式指定proto3语法&#xff0c;否则编译器默认使用proto2message 定义消息类型&#xff0c;包含一组结构化字段。支持嵌套消息定义&#xff…

如何在线免费压缩PDF文档?

PDF文件太大&#xff0c;通常是因为内部嵌入字体和图片。怎么才能将文件大小减减肥呢&#xff0c;主要有降低图片清晰度和去除相关字体两个方向来实现文档效果。接下来介绍三个免费压缩PDF实用工具。 &#xff08;一&#xff09;iLoveOFD在线转换工具 iLoveOFD在线转换工具&a…

NSSCTF [GFCTF 2021]where_is_shell

889.[GFCTF 2021]where_is_shell(system($0)64位) [GFCTF 2021]where_is_shell (1) 1.准备 motalymotaly-VMware-Virtual-Platform:~$ file shell shell: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.s…

深度学习中的提示词优化:梯度下降全解析

深度学习中的提示词优化:梯度下降全解析 在您的代码中,提示词的更新方向是通过梯度下降算法确定的,这是深度学习中最基本的优化方法。 一、梯度下降与更新方向 1. 核心公式 对于可训练参数 θ \theta θ(这里是提示词嵌入向量),梯度下降的更新公式为:

win10电脑无法访问局域网内其他共享电脑文件的问题

一、启用本地计算机guest来宾账户 操作步骤 点击桌面上的“此电脑”图标&#xff0c;再点击“管理” 在“计算机管理”界面依次点击“系统工具”“本地用户和组”“用户” 再点击右侧的“Guest”&#xff0c;在弹出的对话框中点击“属性” 在“Guest属性”界面取消勾选“账户已…

会计要素+借贷分录+会计科目+账户,几个银行会计的重要概念

1.借贷分录还是借贷分路 正确表述是“借贷分录”。 “分录”即会计分录&#xff0c;它是指预先确定每笔经济业务所涉及的账户名称&#xff0c;以及计入账户的方向和金额的一种记录&#xff0c;简称分录。 在借贷记账法下&#xff0c;会计分录通过“借”和“贷”来表示记账方向…

AI日报 · 2025年5月15日|GPT-4.1 登陆 ChatGPT

AI日报 2025年5月15日&#xff5c;GPT-4.1 登陆 ChatGPT 1、OpenAI 在 ChatGPT 全面开放 GPT-4.1 与 GPT-4.1 mini 北京时间 5 月 14 日晚&#xff0c;OpenAI 在官方 Release Notes 中宣布&#xff1a;专为复杂代码与精细指令场景打造的 GPT-4.1 正式加入 ChatGPT&#xff0…

π0: A Vision-Language-Action Flow Model for General Robot Control

TL;DR 2024 年 Physical Intelligence 发布的 VLA 模型 π0&#xff0c;基于 transformer 流匹配&#xff08;flow matching&#xff09;架构&#xff0c;当前开源领域最强的 VLA 模型之一。 Paper name π0: A Vision-Language-Action Flow Model for General Robot Contr…

Java详解LeetCode 热题 100(17):LeetCode 41. 缺失的第一个正数(First Missing Positive)详解

文章目录 1. 题目描述2. 理解题目3. 解法一&#xff1a;排序法&#xff08;不满足题目要求&#xff09;3.1 思路3.2 Java代码实现3.3 代码详解3.4 复杂度分析3.5 不足之处 4. 解法二&#xff1a;哈希表法4.1 思路4.2 Java代码实现4.3 代码详解4.4 复杂度分析4.5 不足之处 5. 解…