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
类的构建, 为了方便使用,使用 typedef
对 ListNode<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
的重点就是迭代器的实现。
之前的 vector
和 string
由于是顺序存储结构,所有迭代器是原生指针,通过 ++
等操作可以直接访问到对应元素。
但是,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;
}
本章完。
相关文章:

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

抖音视频批量下载软件|视频评论采集工具
抖音视频评论采集软件是一款基于C#开发的高效、便捷的工具,旨在为用户提供全面的数据采集和分析服务。用户可以通过关键词搜索抓取视频数据,也可以通过分享链接进行单个视频的抓取和下载,从而轻松获取抖音视频评论数据。 批量视频提取模块&a…...
Oracle RMAN 备份恢复
Oracle RMAN 备份恢复 1.什么是RMAN RMAN在数据库服务器的帮助下实现数据库文件、控制文件、数据库文件和控制文件的映像副本,以及归档日志文件,数据库服务器参数文件的备份。RMAN也允许使用脚本文件实现数据的备份与恢复,而且这些脚本保存…...

【MySQL】学习和总结联合查询
🌈个人主页: Aileen_0v0 🔥热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 💫个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-OPj5g6evbkm5ol0U {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…...

Flink应用场景
1、介绍 (1) Apache Flink 功能强大,支持开发和运行多种不同种类的应用程序。它的主要特性包括:批流一体化、精密的状态管理、事件时间支持以及精确一次的状态一致性保障等。Flink 不仅可以运行在包括 YARN、 Mesos、Kubernetes 在内的多种资源管理框架…...

产品渲染3D效果图一张多少钱,哪个平台更有性价比?
产品渲染3D效果图的价格受到多方面因素的影响,包括但不限于产品类型、渲染难度以及输出尺寸等。如果效果图需要后期处理,还有可能增加其他费用。接下来,我们来了解一下产品渲染效果图的费用情况。 1.产品渲染3D效果图一张多少钱? …...

云原生之容器编排实践-ruoyi-cloud项目部署到K8S:MySQL8
背景 前面搭建好了 Kubernetes 集群与私有镜像仓库,终于要进入服务编排的实践环节了。本系列拿 ruoyi-cloud 项目进行练手,按照 MySQL , Nacos , Redis , Nginx , Gateway , Auth ,…...

go interface{} 和string的转换问题
1.遇到的问题 问题来源于,我sql模版拼接遇到的问题。 首先,这样是没有问题的。 var qhx interface{} "qhx"s : qhx.(string)fmt.Println(s) 但是当我在这段代码里用:1.类型断言 var sqlStr "select * from tx_user where username %s" join…...

【Git教程】(三)提交详解 —— add、commit、status、stach命令的说明,提交散列值与历史,多次提交及忽略 ~
Git教程 提交详解 1️⃣ 访问权限与时间戳2️⃣ add命令与 commit 命令3️⃣ 提交散列值4️⃣ 提交历史5️⃣ 一种特别的提交查看方法6️⃣ 同一项目的多部不同历史6.1 部分输出:-n6.2 格式化输出:--format、--oneline6.3 统计修改信息:--st…...

vue3个人网站电子宠物
预览 具体代码 Attack.gif Attacked.gif Static.gif Walk.gif Attack.gif Static.gif Attacked.gif Walk.gif <template><div class"pet-container" ref"petContainer"><p class"pet-msg">{{ pet.msg }}</p><img re…...

2.22 作业
顺序表 运行结果 fun.c #include "fun.h" seq_p create_seq_list() {seq_p L (seq_p)malloc(sizeof(seq_list));if(LNULL){printf("空间申请失败\n");return NULL;}L->len 0; bzero(L,sizeof(L->data)); return L; } int seq_empty(seq_p L) {i…...

office word保存pdf高质量设置
1 采用第三方pdf功能生成 分辨率越大质量越好...

微服务设计模式
微服务在过去十年中已经发展到现在非常成熟的水平。许多模式被演变以适应不同的需求。 架构模式 分层图案 2层三层n层客户端服务器 一个服务器和多个客户端大多数在线应用程序,例如电子邮件、银行应用程序等。分开演示 模型-视图-控制器 (MVC) 模型——包含核心功能和数据查看…...

10.网络游戏逆向分析与漏洞攻防-游戏网络架构逆向分析-接管游戏发送数据的操作
内容参考于:易道云信息技术研究院VIP课 上一个内容:接管游戏连接服务器的操作 码云地址(master 分支):染指/titan 码云版本号:00820853d5492fa7b6e32407d46b5f9c01930ec6 代码下载地址,在 ti…...

将SU模型导入ARCGIS,并获取高度信息,多面体转SHP文件(ARCMAP)
问题:将Sketchup中导出的su模型,导入arcgis并得到面shp文件,进而获取各建筑的高度、面积等信息。 思路: (1)导入arcgis得到多面体 (2)转为面shp文件 (3)计算高度/面积等 1、【3D Analyst工具】【转换】【由文件转出】【导入3D文件】(在此步骤之间,建议先建立一个…...

【电子通识】为什么单片机芯片上会有多组VDD电源?
在单片机芯片规格书中,我们经常能看到多个组VDD的设计,如下红框所示管脚都是VDD管脚。 为什么需要这样设计?只设置一个VDD管脚,把其他的VDD管脚让出来多做几个IO或是其他复用功能不好吗?接下来我们从单片机内部的电路结…...
跟我学C++中级篇——单实例和静态化
一、单实例模式 在设计模式中,单实例模式几乎是所有语言中都非常常用的一种设计模式。它在实际的应用中也非常广泛,在很多的开源框架中,都可以看到单实例的影子。单实例,简单的就可以看做在整个应用周期中,只有一个对…...

下载 axios.js 文件到本地【linux】
方式一 npm install axios在$NODE_PATH/node_modules/axios/dist路径下即可找到axios.js。 方式二 1、百度搜索 GitHub 官网:https://github.com/ 2、搜索 axios 3、点击 axios/axios 4、下载到本地 5、解压,进入到 dist 文件夹** 参考&#x…...
一些matlab的常用用法。在MATLAB中,如何实现数据的导入和导出?
一些matlab的常用用法。 MATLAB(Matrix Laboratory)是一款广泛使用的数值计算环境和编程语言,主要用于算法开发、数据可视化、数据分析以及数值计算等。以下是一些MATLAB的常用用法: 创建矩阵: 使用方括号 [] 创建矩阵…...

数学建模【插值与拟合】
一、插值与拟合简介 在数学建模过程中,通常要处理由试验、测量得到的大量数据或一些过于复杂而不便于计算的函数表达式,针对此情况,很自然的想法就是,构造一个简单的函数作为要考察数据或复杂函数的近似。插值和拟合就可以解决这…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...

MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

五子棋测试用例
一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏,有着深厚的文化底蕴。通过将五子棋制作成网页游戏,可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家,都可以通过网页五子棋感受到东方棋类…...
【HarmonyOS 5】鸿蒙中Stage模型与FA模型详解
一、前言 在HarmonyOS 5的应用开发模型中,featureAbility是旧版FA模型(Feature Ability)的用法,Stage模型已采用全新的应用架构,推荐使用组件化的上下文获取方式,而非依赖featureAbility。 FA大概是API7之…...
Python爬虫实战:研究Restkit库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的有价值数据。如何高效地采集这些数据并将其应用于实际业务中,成为了许多企业和开发者关注的焦点。网络爬虫技术作为一种自动化的数据采集工具,可以帮助我们从网页中提取所需的信息。而 RESTful API …...
算法刷题-回溯
今天给大家分享的还是一道关于dfs回溯的问题,对于这类问题大家还是要多刷和总结,总体难度还是偏大。 对于回溯问题有几个关键点: 1.首先对于这类回溯可以节点可以随机选择的问题,要做mian函数中循环调用dfs(i&#x…...

Pandas 可视化集成:数据科学家的高效绘图指南
为什么选择 Pandas 进行数据可视化? 在数据科学和分析领域,可视化是理解数据、发现模式和传达见解的关键步骤。Python 生态系统提供了多种可视化工具,如 Matplotlib、Seaborn、Plotly 等,但 Pandas 内置的可视化功能因其与数据结…...