C++:list容器(非原生指针迭代器的实现)

本章是STL容器 list 的模拟实现。
之前已经使用 C语言 对带头双向循环链表 进行实现,详见数据结构: 线性表(带头双向循环链表实现), 相较于之前的实现,C++ 下多了对迭代器以及模板等相关语法特性。下面将着重讲解这些新知识。

文章目录

  • 一. list 的基本框架
    • 1. 结点的结构
    • 2. 链表初始化
    • 3. push_back 尾插
  • 二. list 迭代器的实现
    • 1. 迭代器的结构
    • 2. ++,--,*,==,!=
    • 3. ->
    • 4. 利用模板实现const迭代器
  • 三. 完整代码
    • list.h
    • test.cpp

一. list 的基本框架

1. 结点的结构

n个结点链接成一个链表,首先要构造结点的结构,C语言中结点是这样定义的:
在这里插入图片描述

虽然可以用 typedef 使得该结点可以存放不同的数据类型,但是如果在一个程序中有两个不同数据类型的链表,就需要再重新创建新的结点结构体,与此同时,链表的相关操作也需要进行重新创建。这样,一个程序中就有两个近乎相同的一长串代码,C++ 的模板此时就给了完美的解决方案:

// ListNode
template <typename T>
struct ListNode
{ListNode<T> *_next; // 指向后继结点的指针ListNode<T> *_prev; // 指向前驱结点的指针T _data;            // 存放结点的数据
};

通过类模板即可以在创建链表的时候指定结点的类型,以此推导出 T 的类型。

由于 C++ 中的关键字 struct 升级成了一个类, 这样就可以通过创建结点类的默认构造函数来实现结点的默认初始化。
STL 中 list 是一个带头双向循环链表,那么结点初始化的时候,可以使其的前驱后继都指向空指针, 同时数据域的初始化调用结点类型的默认构造函数。

// ListNode
template <typename T>
struct ListNode
{ListNode<T> *_next; // 指向后继结点的指针ListNode<T> *_prev; // 指向前驱结点的指针T _data;            // 存放结点的数据ListNode(const T &val = T()) // 全缺省构造: _next(nullptr), _prev(nullptr), _data(val){}
};

2. 链表初始化

设计完结点的结构,接下来就是 List 类的构建, 为了方便使用,使用 typedefListNode<T> 进行重命名。
List 只有一个成员,就是指向头结点即哨兵位的指针。
构造函数也可以写出来了,创建一个新结点,该结点的前驱和后继指向自己,同时 _head 的值为该结点的地址。为了方便拷贝构造以及其他构造函数复用,这里将这个操作封装成一个私有函数。

namespace wr
{template <typename T>class list{typedef ListNode<T> Node;public:list(){empty_init():}private:void empty_init(){_head = new Node;_head->_prev = _head;_head->_next = _head;}Node* _head;};
}

3. push_back 尾插

此时完成尾插操作的实现,就可以把一个链表的最初框架完成了,尾插的实现就不过多赘述了。

push_back(const T &val = T())
{Node* newNode = new Node(val);Node* tail = _head->_prev;// tail newNode _headtail->_next = newNode;newNode->_prev = tail;newNode->_next = _head;_head->_prev = newNode;
}

这时候通过调试,就可以确认链表创建并尾插成功:
在这里插入图片描述

二. list 迭代器的实现

list 的重点就是迭代器的实现。
之前的 vectorstring 由于是顺序存储结构,所有迭代器是原生指针,通过 ++ 等操作可以直接访问到对应元素。
但是,list 是链式存储结构,在底层各结点的位置不是连续的,单纯使用原生指针的加减是访问不到结点数据的

那么,怎么样才可以通过 iterator++ 等操作来实现访问结点的效果呢?
得益于C++自定义类型可以进行运算符重载,但Node* 是内置类型,不可以进行运算符重载, 可以将Node*进行封装,再重载 ++ 等操作

1. 迭代器的结构

template<class T>
struct __list_iterator{typedef ListNode<T> Node; // 重命名Node* _node;  // 迭代器指向的结点指针// construct__list_iterator(Node* node = nullptr): _node(node){}
};

2. ++,–,*,==,!=

接着是实现 ++,--,* 操作符的重载
++ 使迭代器指向当前结点的后驱
-- 使迭代器指向当前结点的前驱
* 得到结点的数据

typedef __list_iterator<T> self;  // 重命名
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;
}T& operator*()
{return _node->_data;
}bool operator!=(const self &s)
{return _node != s._node;
}bool operator==(const self &s)
{return _node == s._node;
}

list 类中添加 end, begin

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

随后进行测试,迭代器构建成功:
在这里插入图片描述

3. ->

若结点的数据域的类型是自定义类型,例如下面的自定义类型 AA

struct AA{int _a1;int _a2;
};

当然可以先对迭代器进行解引用, 再访问成员:(*it)._a1
或者直接使用箭头进行访问: it->_a1

这里直接给出 operator->()的实现

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

这样的实现方式会令人感到奇怪,为什么是直接返回结点的数据域地址呢?

这里类似于运算符重载中的后置++——将int放入参数括号内,也是一种特殊处理。
当出现迭代器后面跟了一个->时,C++语法规定省略了一个->, 实际上为 it.operator->()->_a1,这样就可以理解为什么返回的是结点的数据域地址了。

为了实现逻辑自恰,对此进行特殊处理。

4. 利用模板实现const迭代器

const迭代器和普通迭代器的区别是能否对迭代器指向的数据进行修改,不是直接简单粗暴的 cosnt iterator ,迭代器本身是需要改变的。

那么两者有区别的就是 operator*()operator->() 的返回类型。
普通迭代器是:T& operator*(),T* operator->()
const迭代器:const T& operator*(),const T* operator->()

既然两者十分相似,就没有必要在重新创建一个 __const_list_iterator 这样的类,导致代码冗余,重复。
模板这个时候又发挥了作用
可以直接在迭代器的类模板再添加两个类型,在重命名迭代器的时候只要放入对应的类型,让编译器进行类型推演即可

template<class T, class Ref, class Ptr>
class __list_iterator{//...
};template<class T>
class list{
public:// 重命名typedef __list_iterator<T, T&, T*> iterator;  typedef __list_iterator<T, const T&, const T*> const_iterator;//...
};

三. 完整代码

list 的其他接口实现就不过多赘述,这里直接贴上模拟实现 list 的完整代码

list.h

#ifndef __LIST_H__
#define __LIST_H__
#include <assert.h>namespace wr
{// ListNodetemplate <typename T>struct ListNode{ListNode<T> *_next; // 指向后继结点的指针ListNode<T> *_prev; // 指向前驱结点的指针T _data;            // 存放结点的数据ListNode(const T &val = T()) // 全缺省构造: _next(nullptr), _prev(nullptr), _data(val){}};// __list_iteratortemplate <typename T, typename Ref, typename Ptr>struct __list_iterator{typedef ListNode<T> Node;typedef __list_iterator<T, Ref, Ptr> self; // 重命名为selfNode *_node; // 迭代器指向的结点指针__list_iterator(Node *node = nullptr): _node(node){}__list_iterator(const self &s): _node(s._node){}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;}Ref operator*(){return _node->_data;}Ptr operator->(){return &(operator*());}bool operator!=(const self &s){return _node != s._node;}bool operator==(const self &s){return _node == s._node;}};// listtemplate <typename T>class list{typedef ListNode<T> Node;public:typedef __list_iterator<T, T &, T *> iterator;typedef __list_iterator<T, const T &, const T *> const_iterator;list(){empty_init();}list(int n, const T &val = T()){empty_init();for (int i = 0; i < n; ++i){push_back(val);}}template<typename InputIterator>list(InputIterator first, InputIterator last){empty_init();while (first != last){push_back(*first);++first;}}list(const list<T> & l){empty_init();for (const auto &e : l){push_back(e);}}list<T>& operator=(const list<T> l){swap(l);return *this;}~list(){clear();delete _head;_head = nullptr;}// List Iteratoriterator begin(){return _head->_next;}iterator end(){return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}// List Capacitysize_t size() const{size_t size = 0;const_iterator it = begin();while (it != end()){++size;++it;}return size;}bool empty() const{return !size();}// List AccessT &front(){return *(begin());}const T &front() const{return *(begin());}T &back(){return *(--end());}const T &back() const{return *(--end());}// List Modifyvoid push_back(const T &val = T()){// Node *newNode = new Node(val);// Node *tail = _head->_prev;// tail->_next = newNode;// newNode->_prev = tail;// newNode->_next = _head;// _head->_prev = newNode;insert(end(), val);}void pop_back(){erase(--end());}void push_front(const T &val = T()){insert(begin(), val);}void pop_front(){erase(begin());}iterator insert(iterator pos, const T &val){ // 在pos位置前插入值为val的节点Node *cur = pos._node;Node *prev = cur->_prev;Node *newNode = new Node(val);prev->_next = newNode;newNode->_prev = prev;newNode->_next = cur;cur->_prev = newNode;return newNode;}iterator erase(iterator pos){ // 删除pos位置的节点,返回该节点的下一个位置assert(pos != end());Node *cur = pos._node;Node *prev = cur->_prev;Node *next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;return next;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}void swap(list<T> &l){std::swap(_head, l._head);}private:void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;}Node *_head;};
}#endif // __LIST_H__

test.cpp

#include <iostream>
#include <utility>
#include <string>
#include "list.h"using namespace std;
using namespace wr;#define SHOW(x)       \for (auto e : x)    \cout << e << " "; \cout << endl;       \

void Test1()
{list<int> l;l.push_back(1);l.push_back(2);l.push_back(3);l.push_back(4);l.push_back(5);list<int>::iterator lt = l.begin();while (lt != l.end()){cout << *lt << " ";lt++;}cout << endl;
}void Test2()
{list<int> l;l.push_back(1);l.push_back(2);l.push_back(3);l.push_back(4);l.push_back(5);l.push_back(6);SHOW(l);l.push_front(0);SHOW(l);l.pop_back();SHOW(l);l.pop_front();SHOW(l);l.clear();SHOW(l);
}void Test3()
{list<string> l1;l1.push_back("1111");l1.push_back("2222");l1.push_back("3333");l1.push_back("4444");l1.push_back("5555");l1.push_back("6666");SHOW(l1);list<string> l2;l2.push_back("aaaa");l2.push_back("bbbb");l2.push_back("cccc");l2.push_back("dddd");l2.push_back("eeee");SHOW(l2);l1.swap(l2);SHOW(l1);l1.front() = "1111";l1.back() = "9999";cout << l1.front() << endl;cout << l1.back() << endl;SHOW(l1);
}void Test4()
{list<int> l;cout << l.empty() << endl;cout << l.size() << endl;l.push_back(1);l.push_back(1);l.push_back(1);l.push_back(1);l.push_back(1);l.push_back(1);l.push_back(1);cout << l.empty() << endl;cout << l.size() << endl;
}void Test5()
{char a[] = "abcdeftg";list<char> l1(a, a + sizeof(a) / sizeof(char));SHOW(l1);cout << l1.size() << endl;list<char> l2(l1);SHOW(l2);list<char> l3;l3.push_back('1');l2.swap(l3);SHOW(l2);SHOW(l3);
}int main()
{Test1();//Test2();//Test3();//Test4();//Test5();return 0;
}

本章完。

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

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

相关文章

数据结构学习——跳表

假设我们有一个有序链表A&#xff0c;其中元素为1,3,4,5,7,8,9,10,13,16,17,18 我们在找寻其中的元素的时候&#xff0c;需要我们从头开始向下找寻。因此时间复杂度为O(n)。为了减少时间复杂度&#xff0c;我们提出了跳表的概念 原始链表 跳表 可以看到&#xff0c;我们实际…

HTML-基础标签

1. HTML初识 1.1 什么是HTML HTML&#xff08;英文Hyper Text Markup Language的缩写&#xff09;中文译为“超文本标签语言”&#xff0c;是用来描述网页的一种语言。所谓超文本&#xff0c;因为它可以加入图片、声音、动画、多媒体等内容&#xff0c;不仅如此&#xff0c;它还…

[Mac软件]Adobe Substance 3D Stager 2.1.4 3D场景搭建工具

应用介绍 Adobe Substance 3D Stager&#xff0c;您设备齐全的虚拟工作室。在这个直观的舞台工具中构建和组装 3D 场景。设置资产、材质、灯光和相机。导出和共享媒体&#xff0c;从图像到 Web 和 AR 体验。 处理您的最终图像 Substance 3D Stager 可让您在上下文中做出创造性…

论文阅读:SOLOv2: Dynamic, Faster and Stronger

目录 概要 Motivation 整体架构流程 技术细节 小结 论文地址&#xff1a;[2003.10152] SOLOv2: Dynamic and Fast Instance Segmentation (arxiv.org) 代码地址&#xff1a;GitHub - WXinlong/SOLO: SOLO and SOLOv2 for instance segmentation, ECCV 2020 & NeurIPS…

【OpenCV C++】Mat img.total() 和img.cols * img.rows 意思一样吗?二者完全相等吗?

文章目录 1 结论及区别2 Mat img的属性 介绍1 结论及区别 在大多数情况下,img.total() 和 img.cols * img.rows 是相等的,但并不总是完全相等的。下面是它们的含义和一些区别: 1.img.total() 表示图像中像素的总数,即图像的总像素数量。2.img.cols * img.rows 也表示图像中…

【机器人最短路径规划问题(栅格地图)】基于遗传算法求解

基于遗传算法求解机器人最短路径规划问题&#xff08;栅格地图&#xff09;的仿真结果 仿真结果&#xff1a; 路径长度的变化曲线&#xff1a; 遗传算法优化后的机器人避障路径&#xff1a;

ky10-server docker 离线安装包、离线安装

离线安装脚本 # ---------------离线安装docker------------------- rpm -Uvh --force --nodeps *.rpm# 修改docker拉取源为国内 rm -rf /etc/docker mkdir -p /etc/docker touch /etc/docker/daemon.json cat >/etc/docker/daemon.json<<EOF{"registry-mirro…

yolov9 瑞芯微芯片rknn部署、地平线芯片Horizon部署、TensorRT部署

特别说明&#xff1a;参考官方开源的yolov9代码、瑞芯微官方文档、地平线的官方文档&#xff0c;如有侵权告知删&#xff0c;谢谢。 模型和完整仿真测试代码&#xff0c;放在github上参考链接 模型和代码。 之前写过yolov8检测、分割、关键点模型的部署的多篇博文&#xff0c;y…

flutter 加密安全

前言&#xff1a;数据安全 数据的加密解密操作在 日常网络交互中经常会用到&#xff0c;现在密码的安全主要在于 秘钥的安全&#xff0c;如论 DES 3DES AES 还是 RSA, 秘钥的算法&#xff08;计算秘钥不固定&#xff09; 和 保存&#xff0c;都决定了你的数据安全&#xff1b;…

电子电器架构新趋势 —— 最佳着力点:域控制器

电子电器架构新趋势 —— 最佳着力点&#xff1a;域控制器 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师&#xff08;Wechat&#xff1a;gongkenan2013&#xff09;。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师…

wpf 数据绑定 数据转换

1.概要 数据绑定&#xff0c;有时候绑定的数据源和目标的数据类型不同&#xff0c;这时候就需要转换。 2.代码 2.1 xaml(eXtensible Application Markup Language) 可扩展应用程序标记语言 <Window x:Class"WpfApp6.MainWindow"xmlns"http://schemas.mi…

小白水平理解面试经典题目LeetCode 655. Print Binary Tree【Tree】

655 打印二叉树 一、小白翻译 给定二叉树的 root &#xff0c;构造一个 0 索引的 m x n 字符串矩阵 res 来表示树的格式化布局。格式化布局矩阵应使用以下规则构建&#xff1a; 树的高度为 height &#xff0c;行数 m 应等于 height 1 。 列数 n 应等于​​xheight1​​ - …

ROS-Ubuntu 版本相关

ROS-Ubuntu 版本相关&#xff1a;安装指引 年代ROS1版本Ubuntu 版本2014Indigo14.042016Kinetic16.042018Melodic18.042020Noetic20.04 & 22.04 ROS2兼顾了工业使用上的问题。 年代ROS2版本Ubuntu 版本2022Humble20.04 & 22.042023Iron16.04 相关参考&#xff1a; […

Linux/Spectra

Enumeration nmap 第一次扫描发现系统对外开放了22&#xff0c;80和3306端口&#xff0c;端口详细信息如下 22端口运行着ssh&#xff0c;80端口还是http&#xff0c;不过不同的是打开了mysql的3306端口 TCP/80 进入首页&#xff0c;点击链接时&#xff0c;提示域名不能解析&…

【深度学习目标检测】二十一、基于深度学习的葡萄检测系统-含数据集、GUI和源码(python,yolov8)

葡萄检测在农业中具有多方面的意义&#xff0c;具体来说如下&#xff1a; 首先&#xff0c;葡萄检测有助于保障农产品质量安全。通过对葡萄进行质量安全专项监测&#xff0c;可以确保葡萄中的农药残留、重金属等有害物质含量符合标准&#xff0c;从而保障消费者的健康。同时&am…

Ubuntu Mysql Innodb cluster集群搭建+MaxScale负载均衡(读写分离)

Ubuntu系统版本 20.04.3 LTS (Focal Fossa) 、64位系统。 cat /etc/os-release查看Ubuntu系统是32位还是64位 uname -m如果显示“i686”,则表示安装了32位操作系统。如果显示“x86_64”,则表示安装了64位操作系统。 一、安装MySql 参考: https://blog.csdn.net/qq_3712…

Idea安装gideabrowser插件

Idea安装gideabrowser插件 一、安装二、设置教程 一、安装 gideabrowser链接地址 二、设置教程 在人生的舞台上&#xff0c;奋力拼搏&#xff0c;才能演绎出最精彩的人生之歌。面对挑战和困难&#xff0c;不妥协、不气馁&#xff0c;只争朝夕&#xff0c;方显坚韧与智慧。努…

10.题号:编号3227 找到最多的数

题目&#xff1a; ###本题考察map和枚举 #include<bits/stdc.h> using namespace std; map<int,int> mp; int main(){int n,m;cin>>n>>m;for(int i1;i<m*n;i){int x;cin>>x;mp[x];}for(const auto & [x,y] : mp){if(2*y>n*m/2){cout…

【MATLAB】 LMD信号分解+FFT傅里叶频谱变换组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 展示出图效果 1 LMD分解算法 LMD (Local Mean Decomposition) 分解算法是一种信号分解算法&#xff0c;它可以将一个信号分解成多个局部平滑的成分&#xff0c;并且可以将高频噪声和低频信号有效地分离出来。LMD 分解算…

osi模型,tcp/ip模型(名字由来+各层介绍+中间设备介绍)

目录 网络协议如何分层 引入 osi模型 tcp/ip模型 引入 命名由来 介绍 物理层 数据链路层 网络层 传输层 应用层 中间设备 网络协议如何分层 引入 我们已经知道了网络协议是层状结构,接下来就来了解了解下网络协议如何分层 常见的网络协议分层模型是OSI模型 和 …