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;
};
相关文章:
list简单模拟实现
成员变量迭代器(重点)ListIterator运算符重载begin、end 插入、删除inserterase头插、尾插、头删、尾删 operator->const_iterator拷贝构造operator析构函数完整代码 由于前面已经模拟实现了vector,所以这里关于一些函数实现就不会讲的过于…...

QT6 源(101)阅读与注释 QPlainTextEdit,其继承于QAbstractScrollArea,属性学习与测试
(1) (2) (3)属性学习与测试 : (4) (5) 谢谢...

Coze 实战教程 | 10 分钟打造你的AI 助手
> 文章中的 xxx 自行替换,文章被屏蔽了。 📱 想让你的xxx具备 AI 对话能力?本篇将手把手教你,如何用 Coze 平台快速构建一个能与用户自然交流、自动回复提问的 xxx助手,零代码、超高效! 📌…...
Spring Boot中Redis序列化配置详解
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 引言 在使用Spring Boot集成Redis时,序列化方式的选择直接影响数据存储的效率和系统兼容性。默认的JDK序列化存在可读性差、存储空间大等问题&am…...
【spring】spring源码系列之九:spring事务管理(上)
系列文章目录 前言 在开始spring事务管理的源码分析之前,我们先自己尝试简单实现一下事务管理,实现事务的传递 一、事务的使用 有了spring之后,事务的使用变得简单,但是封装得也更深,功能也更复杂,也更…...

牛客网 NC22167: 多组数据a+b
牛客网 NC22167: 多组数据ab 题目分析 这道题目来自牛客网(题号:NC22167),要求我们计算两个整数a和b的和。乍看简单,但有以下特殊点需要注意: 输入包含多组测试数据每组输入两个整数当两个整数都为0时表示…...

K8S Ingress、IngressController 快速开始
假设有如下三个节点的 K8S 集群: k8s31master 是控制节点 k8s31node1、k8s31node2 是工作节点 容器运行时是 containerd 一、理论介绍 1)什么是 Ingress 定义:Ingress 是 Kubernetes 中的一种资源对象,它定义了外部访问集群内…...
GitHub 趋势日报 (2025年05月14日)
本日报由 TrendForge 系统生成 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日整体趋势 Top 10 排名项目名称项目描述今日获星总星数语言1xming521/WeClone🚀从聊天记录创造数字分身的一站式解决方案&…...

快消零售AI转型:R²AIN SUITE如何破解效率困局
引言 快消零售行业正经历从“规模扩张”到“精益运营”的转型阵痛,消费者需求迭代加速、供应链复杂度攀升、人力成本持续走高,倒逼企业通过技术升级实现业务重塑[1]。RAIN SUITE以AI应用中台为核心,针对快消零售场景打造全链路提效方案&…...

电路中零极点的含义
模拟电路中的零极点设计非常重要,涉及到系统的稳定。零点是开环传输函数分子为0时对应的频率。极点就是开环传递函数分子为0时对应的频率。 零点表征电路中能量输出路径的抵消效应,当不同支路的信号大小相等、方向相反时,导致特定频率下响应…...

解读RTOS 第八篇 · 内核源码解读:以 FreeRTOS 为例
1. 引言 FreeRTOS 作为最流行的嵌入式实时操作系统之一,其内核源码简洁且功能完善。通过剖析其关键模块(任务管理、调度器、队列、内存管理和移植层),不仅能够更深入地理解 RTOS 的运行机制,还能掌握根据项目需求进行内核定制与优化的能力。本章将带你以 FreeRTOS 10.x 版…...

2025年长三角+山东省赛+ 认证杯二阶段资料助攻说明
长三角高校数模B题 完整论文代码已经在售后群 网盘链接 发布 长三角更新时间轴 5.15 23:00 B站发布 完整论文讲解视频 5.16 18:00 j降重说明 5.17 22:00 无水印版本可视化无水印代码 其余时间 写手老师 售后群在线答疑 山东省助攻C道 认证杯二阶段助攻C题 山东省认证杯…...
平滑过滤值策略
该策略是一种基于技术分析的交易策略,主要通过计算一系列指标来判断市场趋势,并根据这些指标生成交易信号。 策略概述 该策略的核心在于利用多个技术指标来分析市场动态,并据此制定交易决策。它结合了价格动量、波动性和趋势跟踪等多种因素,旨在提高交易的准确性和效率。…...
MATLAB安装全攻略:常见问题与解决方案
MATLAB安装常见问题与解决方案 一、系统兼容性验证 安装前需确认操作系统满足MATLAB版本要求: Windows 10版本1903及以上(64位)macOS Monterey 12.6及以上Ubuntu 22.04 LTS及以上 验证命令示例: # Linux系统验证 lsb_release…...
Apache HttpClient 5 用法-Java调用http服务
Apache HttpClient 5 核心用法详解 Apache HttpClient 5 是 Apache 基金会推出的新一代 HTTP 客户端库,相比 4.x 版本在性能、模块化和易用性上有显著提升。以下是其核心用法及最佳实践: 一、添加依赖 Maven 项目: <dependency><…...

鸿蒙电脑:五年铸剑开新篇,国产操作系统新引擎
出品 | 何玺 排版 | 叶媛 前不久,玺哥发布的《鸿蒙电脑,刺向垄断的利刃,将重塑全球PC市场格局》发布后,获得了读者朋友的积极反馈,不少都期望鸿蒙电脑早日发布。 如今,它真来了! 5月8日&…...
AI大模型:(二)2.5 人类对齐训练自己的模型
目录 1.人类对齐原理 1.1. 偏好学习(人类反馈,RLHF/DPO) 1.2. 奖励模型(AI的“打分老师”) 1.3. 价值观约束(如宪法AI) 2.如何人类对齐训练 2.1.对比学习(人类反馈 RLHF/DPO) 2.2.考试评分(奖励模型训练) 2.3.底线教育(安全防护) 2.4.持续优化(在线学习…...
算法图表总结:查找、排序与递归(含 Mermaid 图示)
算法图表总结:查找、排序与递归(含 Mermaid 图示) 分类标签:算法、数据结构、Mermaid、技术图表 关键词: 算法可视化、Mermaid 图表、数据结构、二分查找、快速排序、递归树 摘要: 本文通过 Mermaid 图表…...
【redis】jedis客户端的使用
Jedis是Redis官方推荐的Java客户端库,提供了对Redis数据库的全面支持,适用于单机、哨兵及集群模式。作为最老牌的Java Redis客户端,其API设计直观,与Redis命令高度对应,例如set、get等方法与原生命令一致,降…...

SQLMesh信号机制详解:如何精准控制模型评估时机
SQLMesh的信号机制为数据工程师提供了更精细的模型评估控制能力。本文深入解析信号机制的工作原理,通过简单和高级示例展示如何自定义信号,并提供实用的使用技巧和测试方法,帮助读者优化数据管道的调度效率。 一、为什么需要信号机制…...
TCP(传输控制协议)建立连接的过程
TCP(传输控制协议)建立连接的过程称为 三次握手(Three-Way Handshake)。这是为了确保通信双方能够可靠地建立连接,并同步初始序列号。以下是详细步骤: 三次握手过程(通俗比喻:打电话…...

通义千问-langchain使用构建(二)
目录 序言xinference应用构建构建过程简单概述成效 chatchat应用构建过程成效 总结 序言 在昨天的使用langchain的基础上。又尝试了构建智能问答应用。 使用langchain chatchat这个开源包,构建了一下智能问答系统。 前置项,是使用了一下xinference框架&…...

[IMX] 02.GPIO 寄存器
目录 手册对应章节 1.GPIO 复用(引脚功能选择)- IOMUXC_SW_MUX_CTL_PAD_xxx 2.GPIO 电气特性 - IOMUXC_SW_PAD_CTL_PAD_xxx 3.GPIO 数据与控制寄存器 3.1.数据 - DR 3.2.输入/输出选择 - GDIR 3.3.状态 - PSR 3.4.中断触发控制 - ICR 3.5.中断使…...

【电子通识】热敏纸的静态发色性能和动态发色性能测试方法
静态发色性能的测定 测定治具 测定静态发色曲线需要使用三个仪器,包括静态发色仪、秒表(分辨力为0.01 s)、反射光密度计(符合 GB/T23649)。 静态发色曲线使用的测试仪为静态发色仪。其结构如下图所示:包括了保湿压板、金属加热板、温度显示器、控制面板。温度能在50℃到…...
Nginx 返回 504 状态码表示 网关超时(Gateway Timeout)原因排查
Nginx 返回 504 状态码表示 网关超时(Gateway Timeout),这意味着 Nginx 作为反向代理服务器,在等待上游服务器(如后端应用服务器、数据库服务器等)响应时,超过了预设的时间限制,最终…...

AIbase推出全球MCP Server集合平台 收录超12万个MCP服务器客户端
2025年,AI领域迎来了一项重要的技术进展——MCP(Model Context Protocol,模型上下文协议)的广泛应用。全球MCP Server集合平台AIbase(https://mcp.aibase.cn/)应运而生,为AI开发者提供了一站式的MCP服务器和客户端整合…...

使用CMake中的configure_file命令自动生成项目版本信息
1 背景 随着实际项目的完善,可维护变的更加重要。在日志中保存项目的版本或是构建信息是一个非常有用的方法。 CMake提供了configure_file()命令,可以帮助开发者在构建项目时,自动生成版本或是构建信息,便于开发者在代码中直接引…...

Linux的进程管理和用户管理
gcc与g的区别 比如有两个文件:main.c mainc.cpp(分别是用C语言和C语言写的)如果要用gcc编译: gcc -o mainc main.c gcc -o mainc mainc.cpp -lstdc表明使用C标准库; 区别一: gcc默认只链接C库&#x…...

【springcloud学习(dalston.sr1)】Eureka服务端集群的搭建(含源代码)(二)
该系列项目整体介绍及源代码请参照前面写的一篇文章【springcloud学习(dalston.sr1)】项目整体介绍(含源代码)(一) 这篇文章主要介绍多个eureka服务端的集群环境是如何搭建的。 (一)eureka的简要说明 Eu…...
【匹配】Needleman–Wunsch
Needleman-Wunsch 文章目录 Needleman-Wunsch1. 算法介绍2. 公式及原理3. 伪代码 1. 算法介绍 背景与目标 Needleman–Wunsch 算法由 Saul B. Needleman 和 Christian D. Wunsch 于1970年提出,是用于生物序列(如蛋白质或 DNA)全局比对&#x…...