C++list的模拟实现

文章目录

  • 一、观察源码
  • 二、模拟实现
    • 1. 节点结构体
    • 2. list类
    • 3. 迭代器的定义与实现
      • (1) 前置++--后置++--模拟实现
      • (2) *和->重载模拟实现
      • (3) ==和!=重载实现
    • 4. list成员函数模拟实现。
      • (1) begin和end模拟实现
      • (2)insert模拟实现
      • (3) erase模拟实现
      • (4) push_back和push_front
      • (5) pop_back和pop_front
      • (6) size和clear
      • (7) 析构函数
      • (8) 拷贝构造
      • (9) swap和赋值重载
  • 三、完整代码实现与测试用例


一、观察源码

我们可以看到源码中定义了三个自定义对象,分别是节点,迭代器和list类。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、模拟实现

1. 节点结构体

因为list底层是一个带头节点的双向循环链表嘛,所以我们先使用结构体定义一个节点,里面有三个成员变量,分别是指向下一个节点的指针、指向上一个节点的指针和数据。因为数据的类型是不确定的,所以我们这里使用模板来实现。
然后再写一个构造函数,初始化一下成员变量。(C语言中结构体里是不能写成员函数的,C++中可以,其实C++中的结构体被封装成了类,区别只是默认的访问级别不同罢了。struct默认是public的,class默认是private的。)

template <class T>
struct list_node
{T _val;list_node<T>* _next;list_node<T>* _prev;list_node(const T& val = T()):_val(val), _next(nullptr), _prev(nullptr){}
};

2. list类

之后我们再定义一个list类,该类中保存一个list_node节点的指针,将来通过这个指针进行链表的增删查改,还有一个size_t 的变量,用来记录元素个数。

list_node 太长了,先给他typedef一下,变成Node。然后写个构造函数,构造函数中调用了empty_init(),所以实际的初始化逻辑是在empty_init()函数中实现的。

一开始的时候只有一个头节点,我们直接把next指针和prev指针都指向自己就好了。

template<class T>
class list
{
public:typedef list_node<T> Node;list(){empty_init();}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}private:Node* _head;size_t _size;
};

这里我们先写一个尾插,让程序跑起来再说。
尾插的逻辑比较简单,先用new创建一个新的节点,然后按照带头节点的双向循环队列处理好节点之间的指向关系就好了。
在这里插入图片描述

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

3. 迭代器的定义与实现

之后我们建立一个迭代器的结构体,该结构体中有一个成员变量是一个指向list_node类型的指针_node。值得注意的是我们这里使用了模板,而且模板参数有三个,除了T之外还多了个Ref和Ptr,分别代表引用类型和指针类型。
因为迭代器分为两种,一种是普通版本,另一种是const版本,不同版本在实现前置++,*等运算符重载的时候,返回值就会不同。如果不用模板的话,我们就得实现两套迭代器,并且这两套迭代器也仅仅只是在返回值上不相同而已,这样就显得不是很划算,因此我们这里使用模板来控制返回值的类型是const版本还是非const版本。

template<class T, class Ref, class Ptr>struct _list_iterator{typedef list_node<T> Node;typedef _list_iterator<T, Ref, Ptr> self;Node* _node;//这里为什么不能加const? //加了const const类型的Node*指针不能赋值给非const的_node_list_iterator(Node* node) :_node(node){}};

(1) 前置+±-后置+±-模拟实现

先利用typedef_list_iterator<T, Ref, Ptr>取个别名 self;
这里我们先简单的实现前置++和后置++

这里要注意的是后置++和前置++的区别:

  1. 后置++有一个int类型的参数,而前置++没有。这个参数并没有实际作用,仅仅只是用来区分是前置++还是后置++罢了。
  2. 前置++返回的是该类型的一个引用即self&,而后置++返回的只是该类型的一个拷贝而已。
self operator++(int)//后置++
{self tmp(*this);_node = _node->_next;return tmp;
}
self& operator++()//前置++
{_node = _node->_next;return *this;
}

同理我们可以实现前置–和后置–

前后置返回值是啥,有没有参数这些都是有说法的。self operator--(int)//后置--{self tmp(_node);_node = _node->_prev;return tmp;}self& operator--()//前置--{_node = _node->_prev;return *this;}

(2) *和->重载模拟实现

之后我们来实现一下解引用*和->重载。
解引用返回的当然就是节点中的数据了,所以直接return _node->_val即可,至于返回值的类型则由模板参数Res来决定。

->的话则是返回_node->_val的地址。

Ref operator*()
{return _node->_val;
}
Ptr operator->()
{return&_node->_val;
}

(3) ==和!=重载实现

最后就是实现一下==和!=的重载了。比较简单,不在赘述。

bool operator==(const self& it) const
{return it._node == _node;
}bool operator!=(const self& it) const
{return it._node != _node;
}

4. list成员函数模拟实现。

(1) begin和end模拟实现

begin就是返回指向第一个元素的迭代器(第一个元素不是头节点而是头节点的下一个节点),所以直接返回iterator(_head->next)就可以,这里我们是直接返回了一个匿名对象回去。
end返回的是指向最后一个元素的下一个位置的迭代器,那最后一个元素的下一个位置在双向循环链表中不就是头节点吗?所以这里直接返回iterator(_head)就好了,但是我们这里直接返回了一个_head,不知道大家还记不记得有这么一个知识点**构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。**这里就是利用了这个特性

typedef _list_iterator<T, const T&, const T*> const_iterator;
typedef _list_iterator<T, T&, T*> iterator;
iterator begin()
{return iterator(_head->_next);
}
iterator end()
{return _head;
}

对于const_iterator来说与iterator同理,不再赘述

const_iterator begin() const
{return const_iterator(_head->_next);
}
const_iterator end() const
{return _head;
}

(2)insert模拟实现

由于实现insert和erase之后可以在后面的多个函数中进行复用,因此我们先模拟实现insert和erase。
insert有两个参数,一个是要插入位置的迭代器,另一个是要插入的元素。
该函数的功能是在迭代器指向的元素之前插入一个新的元素,返回指向新元素的迭代器。
在这里插入图片描述

iterator insert(iterator pos, const T& x)
{Node* cur = pos._node;//通过迭代器获得当前元素的指针Node* pre = cur->_prev;//获得当前元素的上一个元素的指针Node* newnode = new Node(x);//建立一个新节点pre->_next = newnode;newnode->_prev = pre;newnode->_next = cur;cur->_prev = newnode;++_size;return newnode;
}

(3) erase模拟实现

erase有一个参数,就是要删除元素的迭代器,最后会返回下一个元素的迭代器。

iterator erase(iterator pos)
{assert(pos != end());Node* cur = pos._node;Node* next = cur->_next;Node* pre = cur->_prev;next->_prev = pre;pre->_next = next;--_size;return next;
}

(4) push_back和push_front

比较简单直接复用insert就好

void push_back(const T& x)
{insert(end(), x);
}
void push_front(const T& x)
{insert(begin(), x);
}

(5) pop_back和pop_front

比较简单直接复用erase就好

void pop_back()
{erase(--end());
}
void pop_front()
{erase(begin());
}

(6) size和clear

size函数直接return _size即可
clear函数的话可以利用迭代器进行遍历,依次调用erase函数,达到删除所有节点的目的

size_t size()
{return _size;
}
void clear()
{iterator it = begin();while (it != end()){erase(it);++it;}_size = 0;
}

(7) 析构函数

析构函数中先直接调用clear函数,释放掉所有节点,然后在释放头节点的空间即可。

~list()
{clear();delete _head;_head = nullptr;
}

(8) 拷贝构造

先调用empty_init()进行初始化,然后可以利用迭代器,将元素依次push_back即可。

list(const list<T>& it)
{empty_init();for (auto& e : it)//有了迭代器利用范围for简单解决{push_back(e);}
}

(9) swap和赋值重载

swap函数中直接调用库里的swap将头节点的指针互换就可以了。
赋值重载中直接利用swap进行交换就可以,注意参数list it只是一个临时变量而已,出了作用域就会销毁,所以我们直接将it的数据和自己的数据互换,不仅拿到了它的数据,还把自己的数据交给了它,然后一出函数,临时变量销毁,资源也会释放,都不用我们手动释放。

void swap(list<T>& it)
{std::swap(_head, it._head);std::swap(_size, it._size);
}
list<T>& operator=(list<T> it)
{swap(it);return *this;
}

三、完整代码实现与测试用例

#pragma once
#include<assert.h>
#include<iostream>namespace hao
{template <class T>struct list_node{T _val;list_node<T>* _next;list_node<T>* _prev;list_node(const T& val = T()):_val(val), _next(nullptr), _prev(nullptr){}};template<class T, class Ref, class Ptr>struct _list_iterator{typedef list_node<T> Node;typedef _list_iterator<T, Ref, Ptr> self;Node* _node;//explicit 这里为什么不能加const? //加了const const类型的Node*指针不能赋值给非const的//-node_list_iterator(Node* node) :_node(node){}bool operator==(const self& it) const{return it._node == _node;}bool operator!=(const self& it) const{return it._node != _node;}self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}self& operator++(){_node = _node->_next;return *this;}前后置返回值是啥,有没有参数这些都是有说法的。self operator--(int)//后置--{self tmp(_node);_node = _node->_prev;return tmp;}self& operator--()//前置--{_node = _node->_prev;return *this;}Ref operator*(){return _node->_val;}Ptr operator->(){return&_node->_val;}};template<class T>class list{public:typedef list_node<T> Node;typedef _list_iterator<T, const T&, const T*> const_iterator;typedef _list_iterator<T, T&, T*> iterator;list(){empty_init();}list(const list<T>& it){empty_init();for (auto& e : it)//有了迭代器利用范围for简单解决{push_back(e);}}list<T>& operator=(list<T> it){swap(it);return *this;}void swap(list<T>& it){std::swap(_head, it._head);std::swap(_size, it._size);}~list(){clear();delete _head;_head = nullptr;}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}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;insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator begin(){return iterator(_head->_next);}iterator end(){return _head;}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return _head;}size_t size(){return _size;}void clear(){iterator it = begin();while (it != end()){erase(it);++it;}_size = 0;}//pos位置之前插入iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* pre = cur->_prev;Node* newnode = new Node(x);pre->_next = newnode;newnode->_prev = pre;newnode->_next = cur;cur->_prev = newnode;++_size;return newnode;}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* next = cur->_next;Node* pre = cur->_prev;next->_prev = pre;pre->_next = next;--_size;return next;}private:Node* _head;size_t _size;};void print(const list<int>& lt){for (list<int>::const_iterator it = lt.begin(); it != lt.end(); it++){std::cout << *it << " ";}}void test_list1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();while (it != lt.end()){(*it) += 1;std::cout << *it << " ";++it;}std::cout << std::endl;for (auto e : lt){std::cout << e << " ";}std::cout << std::endl;print(lt);}struct A{A(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}int _a1;int _a2;};void test_list2(){list<A> lt;lt.push_back(A(1, 1));lt.push_back(A(2, 2));lt.push_back(A(3, 3));lt.push_back(A(4, 4));list<A>::iterator it = lt.begin();while (it != lt.end()){//cout << (*it)._a1 << " " << (*it)._a2 << endl;std::cout << it->_a1 << " " << it->_a2 << std::endl;++it;}std::cout << std::endl;}void test_list3(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_front(5);lt.push_front(6);lt.push_front(7);lt.push_front(8);for (auto e : lt){std::cout << e << " ";}std::cout << std::endl;lt.pop_front();lt.pop_back();for (auto e : lt){std::cout << e << " ";}std::cout << std::endl;lt.clear();lt.push_back(10);lt.push_back(20);lt.push_back(30);lt.push_back(40);for (auto e : lt){std::cout << e << " ";}std::cout << std::endl;std::cout << lt.size() << std::endl;}void test_list4(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);for (auto e : lt){std::cout << e << " ";}std::cout << std::endl;list<int> lt1(lt);for (auto e : lt1){std::cout << e << " ";}std::cout << std::endl;list<int> lt2;lt2.push_back(10);lt2.push_back(20);lt2.push_back(30);lt2.push_back(40);for (auto e : lt2){std::cout << e << " ";}std::cout << std::endl;lt1 = lt2;for (auto e : lt1){std::cout << e << " ";}std::cout << std::endl;}
}
#include"list.h"
using namespace std;int main()
{//hao::test_list1();//hao::test_list2();//hao::test_list3();hao::test_list4();return 0;
}

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

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

相关文章

Unity Audio Filter 入门

概述&#xff1a; 如果你在你项目中需要一些特殊的声音效果&#xff0c;那这部分声音过滤器的部分一定不要错过喔&#xff0c;让我们来学习这部分的内容吧&#xff01; 这部分理论性比较强&#xff0c;认真看我的注解哈&#xff0c;我尽量解释的易懂一点。 Audio Chorus Filter…

翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习六

合集 ChatGPT 通过图形化的方式来理解 Transformer 架构 翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习一翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习二翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深…

基于Springboot+Vue的Java项目-火车票订票系统开发实战(附演示视频+源码+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &am…

html--剑雨

<!doctype html> <html> <head> <meta charset"utf-8"> <title>css3剑雨-jq22.com</title> <script src"http://www.jq22.com/jquery/jquery-1.10.2.js"></script> <style> .sword:before, .sword:…

Docker基础学习(5.Docker镜像命令)

⭐ 作者简介&#xff1a;码上言 ⭐ 代表教程&#xff1a;Spring Boot vue-element 开发个人博客项目实战教程 ⭐专栏内容&#xff1a;个人博客系统 ⭐我的文档网站&#xff1a;http://xyhwh-nav.cn/ ⭐微信公众号&#xff1a;码上言 文章目录 Docker run流程镜像是什么&a…

云计算技术概述_1.云计算相关概念

1.关于IBM“蓝云&#xff08;Blue Cloud&#xff09;”计划 IBM 推出的“蓝云&#xff08;Blue Cloud&#xff09;”计划为客户带来即可使用的云计算(Cloud Computing)。它包括一系列的云计算产品&#xff0c;使计算不仅仅局限在本地机器或远程Server Farms&#…

树莓派点亮LED灯

简介 使用GPIO Zero library 的 Python库实现点亮LED灯。接线 树莓派引脚参考图如下&#xff1a; LED正极 接GPIO17 LED负极 接GND 权限 将你的用户加到gpio组中&#xff0c; 否则无法控制GPIO sudo usermod -a -G gpio 代码 from gpiozero import LED from time impor…

MouseBoost PRO for Mac激活版:强大的 鼠标增强软件

在追求高效工作的今天&#xff0c;MouseBoost PRO for Mac成为了许多Mac用户的得力助手。这款功能强大的鼠标增强软件&#xff0c;以其独特的智能化功能和丰富的实用工具&#xff0c;让您的电脑操作更加便捷、高效。 MouseBoost PRO for Macv3.4.0中文激活版下载 MouseBoost PR…

【Mac】Photoshop 2024 for mac最新安装教程

软件介绍 Photoshop 2024是Adobe公司推出的一款图像处理软件&#xff0c;它支持Windows和Mac OS系统。Adobe Photoshop是业界领先的图像编辑和处理软件之一&#xff0c;广泛用于设计、摄影、数字绘画等领域。 Photoshop 2024的功能包括&#xff1a; 1.图像编辑&#xff1a;提…

图片壁纸社区app前后端开源小程序源码

图片壁纸社区APP前后端开源小程序源码&#xff0c;修改了开源版的前端样式&#xff0c;变成图片社区&#xff0c;也可以用来作为壁纸。 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/89122506 更多资源下载&#xff1a;关注我。

【Unity】修改模型透明度

在 Unity 中修改模型透明度主要有两种方法&#xff1a;通过材质和通过着色器。以下是两种方法的步骤和解释&#xff1a; 方法 1&#xff1a;通过材质 在 Unity 编辑器中&#xff0c;选择你想要修改透明度的模型。在 Inspector 窗口中&#xff0c;找到模型的 Renderer 组件&am…

简约大气的全屏背景壁纸导航网源码(免费)

简约大气的全屏背景壁纸导航网模板 效果图部分代码领取源码下期更新预报 效果图 部分代码 <!DOCTYPE html> <html lang"zh-CN"> <!--版权归孤独 --> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible…

【b站前端-小鑫】Vue Router(路由)快速掌握(入门到精通5节课)

课程地址&#xff1a;【Vue Router(路由)快速掌握&#xff08;入门到精通5节课&#xff09;】 https://www.bilibili.com/video/BV1aP4y1W7Uz/?share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 目录 1 Vue Router 1.1 Vue Router的安装 1.2 创建路由…

提高 RAG 效果示例配置

提高 RAG 效果示例配置 最近在调整一个学习赛&#xff0c;针对所有问题&#xff0c;为了尽可能的获得答案&#xff0c;尝试了各种配置。 20240501时点&#xff0c;下面配置暂时能够获得测试的所有十几个问题的答案。后续测试再更新更优化的配置。 未完待续&#xff01;

在UI界面中播放视频_unity基础开发教程

在UI界面中播放视频_unity基础开发教程 前言操作步骤结语 前言 之前我写过一篇在场景中播放视频的文章&#xff0c;但是在开发中有时候也会在UI的界面中播放视频&#xff0c;这期我们做一下在UI的界面中播放视频。 操作步骤 首先在场景中创建一个Raw Image&#xff0c;UI->…

手撕spring框架(3)

手撕spring框架&#xff08;3&#xff09; 相关系列 手撕spring框架&#xff08;1&#xff09; 手撕spring框架&#xff08;2&#xff09; InitializingBean 接口详解 什么是 InitializingBean 接口&#xff1f; InitializingBean 接口是 Spring 框架中的一个接口&#xff0c…

与Apollo共创生态:探索自动驾驶的未来蓝图

目录 引言Apollo开放平台Apollo开放平台企业生态计划Apollo X 企业自动驾驶解决方案&#xff1a;加速企业场景应用落地Apollo开放平台携手伙伴共创生态生态共创会员权益 个人心得与展望技术的多元化应用数据驱动的智能化安全与可靠性的重视 结语 引言 就在2024年4月19日&#x…

Golang | Leetcode Golang题解之第60题排列序列

题目&#xff1a; 题解&#xff1a; func getPermutation(n int, k int) string {factorial : make([]int, n)factorial[0] 1for i : 1; i < n; i {factorial[i] factorial[i - 1] * i}k--ans : ""valid : make([]int, n 1)for i : 0; i < len(valid); i {…

C++系列-输入输出

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” C输入和输出 我们都知道C语言的输出是用printf函数来实现的&#xff0c;那么C呢&#xff0c;它的实现逻辑是什么呢&#xff0c;让我们一起来看一下&#xff0c; #include<i…

多家企业机密数据遭Lockbit3.0窃取,亚信安全发布《勒索家族和勒索事件监控报告》

本周态势快速感知 本周全球共监测到勒索事件87起&#xff0c;与上周相比勒索事件大幅下降。美国依旧为受勒索攻击最严重的国家&#xff0c;占比45%。 本周Cactus是影响最严重的勒索家族&#xff0c;Lockbit3.0和Bianlian恶意家族紧随其后&#xff0c;从整体上看Lockbit3.0依旧…