【C++】手把手教你模拟实现 list

目录

                                                                   前提:list 的基本介绍

 一、构造/析构/拷贝/赋值

1、构造函数

2、析构函数

3、拷贝构造函数

4、赋值

二、修改操作

1、push_back

2、insert

3、erase

4、clear

三、list iterator 的使用

1、operator *

2、operator ++

3、operator --

 4、operator !=

5、operator ==

 6、begin( ) / end( )

四、迭代器模板参数

  1、const_iterator (重要)

 2、operator->(重要)

 五、完整代码


前提:list 的基本介绍

【list的文本介绍】

⭕list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
⭕ list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
⭕ list与 forward_list 非常相似:最主要的不同在于 forward_list 是单链表,只能朝前迭代,已让其更简单高效。
⭕与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
⭕ 与其他序列式容器相比,list 和 forward_list 最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

 一、构造/析构/拷贝/赋值

我们已经知道 list 的底层逻辑是双向链表结构,所以 list 定义模版参数为T,并且节点结构体中有两个指针(_next、_prev)。使用 struct 类来定义节点,是为了便于 list 类访问,因为 struct 不对成员的访问进行限制

1、构造函数

链表是由一个个的结点连接而成,所以第一步先构造出一个结点类。这个结点包含前后指针和对应的数据。对这个节点也需要进行初始化。

2、析构函数

可以调用 clear 函数,然后把 head 删除

3、拷贝构造函数

 4、赋值

二、修改操作

1、push_back

功能:尾插一个节点。

步骤:利用 head 将最后一个节点记为 tail;

创建一个新的节点 newnode 

更新节点的 next 和 prev

2、insert

功能:在 pos 处插入一个新的节点。

步骤:记录 pos 和 pos 前一个位置的值。

新建一个新的节点。

更新节点的 prev 和 next。

insert 也可以帮助尾插和头插

3、erase

 功能:删除 pos 处的节点。

要注意迭代器失效的问题。具体内容可以参考【迭代器失效】

erase 也可以帮助尾删和头删

4、clear

功能:清除所有节点,但是保留头结点。

三、list iterator 的使用

list 容器的迭代器不再是像 string 或者 vector 那样的原生指针了,首先因为 list 的各个节点在物理空间上不是连续的,我们不能直接对节点的地址进行 ++ / - - 得到其前、后位置的迭代器;并且我们的数据是保存在节点之中的,不能把节点的指针解引用直接得到里面数据。这些操作我们可以通过封装节点的地址形成一个迭代器类,然后重载这个类的 operator* 和 operator++ 等运算符及其他一系列方法,最终可以向操作指针一样去操作迭代器。

1、operator *

功能:获取该迭代器指向位置的值

2、operator ++

功能:迭代器向后移动一位。返回值依旧是迭代器

3、operator --

功能:迭代器向前移动一位

 4、operator !=

 功能:判断节点的指针是否不等。

5、operator ==

 功能:判断节点的指针是否相等。

 6、begin( ) / end( )

begin () 和 end() 的 const 版本需要借助 const 迭代器。 

四、迭代器模板参数

  1、const_iterator (重要)

在上述 begin 和 end 的函数中,const 修饰的是迭代器本身,并不是迭代器指向的内容,导致迭代器所指向的内容依旧可以修改。但是我们使用 const 修饰时,希望达成的目标是不能改变迭代器所指向的内容,迭代器自己可以修改。所以重定义一个 const 迭代器模板。

 虽然重新定义一个 const 版本的迭代器可以使代码正常运行,但是代码显得很冗余,只有解引用处会不一样。为了解决这个问题,我们可以在定义迭代器模板时增加一个参数,模板参数不同,就可以实例化为两个不同的类,这样当传 const 修饰的值时就会调用 const 迭代器。

 2、operator->(重要)

功能:迭代器是模拟指针的行为,通过->来访问迭代器所指向的内容。

这里我们可以像 const 迭代器一样,增加一个类参数来应对const迭代器而设计的,只不过这个模板参数控制的是箭头运算符重载函数的返回值类型,因为const迭代器使用 ->运算符后,返回的应该是const T类型,而正常迭代器使用 ->运算符后,返回的应该是T。 

 【补充】为什么要重载 ->

我们知道想调用成员可以使用 "." 和 "->"。下面箭头运算符其实是省略了一个->,it->_data 首先调用重载函数 it.operator->(). 返回的T* ,那 T* 是怎么访问的 _data 的呢?这里明显少了一个箭头,正确的写法应该是这样it->->_data ,但编译器为了简洁好看将一个箭头省略了。

 五、完整代码

#define  _CRT_SECURE_NO_WARNINGS
#pragma once
#include<assert.h>
#include<iostream>namespace zhou
{template<class T>struct List_node{//List_node类名;List_node<T>类型List_node<T>* _next;//指向后一个节点List_node<T>* _prev;//指向前一个节点T _data;//存储的数据//构造函数//T()List_node(const T& x = T()):_next(nullptr), _prev(nullptr),_data(x){}};//迭代器的类template<class T,class Ref,class Ptr>struct _list_iterator{typedef List_node<T> node;typedef _list_iterator<T,Ref,Ptr> self;node* _node;//构造函数_list_iterator(node* n):_node(n){}//解引用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& s){return _node != s._node;}bool operator==(const self& s){return _node == s._node;}};/*template<class T>struct __list_const_iterator{typedef List_node<T> node;typedef __list_const_iterator<T> self;node* _node;__list_const_iterator(node* n):_node(n){}const T& 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& s){return _node != s._node;}bool operator==(const self& s){return _node == s._node;}};*/template<class T>class list{typedef List_node<T> node;public:typedef _list_iterator<T,T&,T*> iterator;typedef _list_iterator<T,const T&,const T*> const_iterator;//typedef __list_const_iterator<T> const_iterator;void empty_init(){_head = new node;//给头结点创造出一个空间_head->_next = _head;_head->_prev = _head;//让其头和尾指向自己,形成一个循环列表}list(){empty_init();}~list(){clear();delete _head;_head = nullptr;}//方法一//list(const list<T>& it)//{//	//必须要初始化//	empty_init();//将初始化写成一个函数//	for (auto e : it)//	{//		push_back(e);//	}//}//方法二void swap(list<T>& tmp){std::swap(_head, tmp._head);}list(const list<T>& lt){empty_init();list<T> tmp(lt.begin(), lt.end());swap(tmp);}//赋值list<T>& operator=(list<T> lt){swap(lt);return *this;}iterator begin(){/*iterator it(_head->_next);return it;*/return (_head->_next);}iterator end(){return (_head);}const_iterator  begin() const{return const_iterator(_head->_next);}const_iterator  end() const{return const_iterator(_head);}void push_back(const T& x){/*node* tail = _head->_prev;node* newnode = new node(x);newnode->_next = _head;_head->_prev = newnode;tail->_next = newnode;newnode->_prev = tail;*/insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}void insert(iterator pos, const T& x){node* cur = pos._node;node* prev = cur->_prev;node* new_node = new node(x);prev->_next = new_node;new_node->_prev = prev;new_node->_next = cur;cur->_prev = new_node;}iterator erase(iterator pos){assert(pos != end());node* prev = pos._node->_prev;node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;return iterator(next);}void clear(){iterator it = begin();while (it != end()){//it = erase(it);erase(it++);//erase迭代器返回删除的后一个}}private:node* _head;};void test_list1(){list<int> It;It.push_back(1);It.push_back(2);It.push_back(3);It.push_back(4);/*list<int>::iterator It = it.begin();while (It != it.end()){cout << (*It) << " ";It++;}*/list<int>::iterator it = It.begin();while (it != It.end()){(*it) *= 2;//修改数据cout << *it << " ";//读取数据++it;}cout << endl;}struct AA{int _a1;int _a2;AA(int a1 = 0, int a2 = 0):_a1(a1),_a2(a2){}};void test_AA(){list<AA> lt;lt.push_back(AA(1, 1));lt.push_back(AA(2, 2));lt.push_back(AA(3, 3));lt.push_back(AA(4, 4));list<AA>::iterator it = lt.begin();while (it != lt.end()){cout << (*it)._a1 << ":" << (*it)._a2 << endl;//不习惯使用先解引用后.对象调用成员cout << it->_a1 << ":" << it->_a2 << endl;//->调用迭代器所指向的内容,但是需要重载->cout << it.operator->()->_a1 << ":" << it.operator->()->_a1 << endl;//但是编译器会自动省略一个->it++;}}}

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

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

相关文章

MATLAB中的矩阵的重构和重新排列

师从清风 矩阵的重构和重新排列 reshape函数 reshape函数可以改变矩阵的形状&#xff0c;其常用语法为reshape(A,m,n)或者reshape(A,[m,n]),这可以将矩阵A的形状更改为m行n列&#xff0c;前提是转化前后的两个矩阵的元素总数要相同。例如有一个矩阵A&#xff0c;它原来的大小是…

字节跳动也启动春季校园招聘了(含二面算法原题)

字节跳动 - 春招启动 随着各个大厂陆续打响春招的响头炮&#xff0c;字节跳动也官宣了春季校园招聘的正式开始。 还是那句话&#xff1a;连互联网大厂启动校招计划尚且争先恐后&#xff0c;你还有什么理由不马上行动&#xff1f;&#xff01; 先来扫一眼「春招流程」和「面向群…

掌握Redis,看完这篇文章就够了

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Redis是什么&#xff1f;二、Redis安装三、Redis相关数据类型 四、基础操作&#xff08;使用了python连接redis&#xff09;1.字符串2.键对应操作3.哈希&am…

2024.3.12

1. 要求&#xff1a;自己封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height)&#xff0c; 定义公有成员函数: 初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度的函数:set_h(int h) 输出该矩形的周长和面积函数:void sho…

[Unity3D]--更换天空盒子

我们原来的天空盒子是这样的。 感觉不是特别满意&#xff0c;想换一个更好看的。 去资源商店找个好看的 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 例如这个 然后在Window>Rendering>Lighting里的环境选项里更换材质 更换&#xff1a; ​ …

考察1学生学籍系统winform .net6 sqlserver

考察1学生学籍系统winform .net6 sqlserver 下载地址: 考察1学生学籍系统winform .net6 sqlserver winform(.net6)sqlserver数据库 只有数据库的表结构需要自己建表 启动程序 登录失败 进入主界面 项目获取&#xff1a; 项目获取&#xff1a;typora: typora/img (gitee.com…

【C语言程序设计】C语言求圆周率π(三种方法)

题目一&#xff1a; 利用公式①计求π的近似值&#xff0c;要求累加到最后一项小于10^(-6)为止。 程序代码&#xff1a; #include <stdio.h> #include <stdlib.h> #include <math.h> int main(){float s1;float pi0;float i1.0;float n1.0;while(fabs(i)&…

Java中的参数传递

程序设计语言将实参传递给方法&#xff08;或函数&#xff09;的方式分为两种&#xff1a; 值传递&#xff1a;方法接收的是实参值的拷贝&#xff0c;会创建副本。引用传递&#xff1a;方法接收的直接是实参所引用的对象在堆中的地址&#xff0c;不会创建副本&#xff0c;对形…

小家电显示驱动芯片SM1616特点与相关型号推荐

电饭煲、电磁炉、空调和机顶盒等等小家电通常需要使用显示驱动芯片来控制和驱动显示屏。这些显示驱动芯片的主要功能是将处理器的信号转换成显示屏能够理解的信号&#xff0c;从而显示出相应的文字和图像。 具体来说&#xff0c;电饭煲、电磁炉、空调等家等小家电通常会有一个或…

C语言 —— 图形打印

题目1&#xff1a; 思路&#xff1a; 如果我们要打印一个实心正方形&#xff0c;其实就是一个二维数组&#xff0c;i控制行&#xff0c;j控制列&#xff0c;行列不需要控制&#xff0c;arr[i][j]直接打印星号即可。 对于空心正方形&#xff0c;我们只需要控制行和列的条件&…

Igraph入门指南 6

3、make_系列&#xff1a;igraph的建图工具 按照定义&#xff0c;正则图是指各顶点的度均相同的无向简单图&#xff0c;因为我目前没有找到描述度相等的有向&#xff08;或自环图&#xff09;的标准名称&#xff0c;所以在本文中借用一下这个概念&#xff0c;并加上定语有向无…

3.1_3 连续分配管理方式

3.1_3 连续分配管理方式 连续分配&#xff1a;指为用户进程分配的必须是一个连续的内存空间。 &#xff08;一&#xff09;单一连续分配 在单一连续分配方式中&#xff0c;内存被分为系统区和用户区。 系统区通常位于内存的低地址部分&#xff0c;用于存放操作系统相关数据&am…

原生高性能抓包工具Proxyman,送给爱学习的你

现在的抓包工具可谓是五花八门&#xff0c;比如Fiddler&#xff0c;Charles&#xff0c;LightProxy等&#xff0c;各有各的优缺点&#xff0c;最近又看到一个新的抓包工具&#xff0c;像我这样一个有强烈好奇心的人&#xff0c;怎么能错过&#xff0c;我们一起来学习下吧&#…

Python 导入Excel三维坐标数据 生成三维曲面地形图(面) 4-2、线条平滑曲面(原始颜色)但不去除无效点

环境和包: 环境 python:python-3.12.0-amd64包: matplotlib 3.8.2 pandas 2.1.4 openpyxl 3.1.2 scipy 1.12.0 代码: import pandas as pd import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from scipy.interpolate import griddata fro…

3.8_理解代码(3)

fliplr函数 其中fliplr函数为flip array left to right&#xff0c;此处fliplr(i)的输出结果为[4 3 2 1] 我的代码实验 area1 fill(i,u_up(i),cyan,FaceAlpha,0.3);把我都弄得无语了&#xff0c;就实现fill怎么这么难 真是不知道向量长度哪里不同&#xff0c;知道了哈哈 终于…

[Java安全入门]三.CC1链

1.前言 Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库&#xff0c;它提供了很多强大的数据结构类型和实现了各种集合工具类。Commons Collections触发反序列化漏洞构造的链叫做cc链&#xff0c;构造方式多种&#xff0c;这里先学习cc1链…

知识文档管理系统平台:企业管理的王炸

无论是企业内部的文件共享&#xff0c;还是团队之间的协作编辑&#xff0c;知识文档管理系统都能发挥巨大的作用。它帮助企业整理、存储和查找各种文档资料&#xff0c;这不仅能提高企业的工作效率&#xff0c;还能增强企业的竞争力。今天就跟着LookLook同学一起来深入了解知识…

仿牛客项目Day6:账号设置——检查登录状态

账号设置 这个功能主要就是上传头像 在账户设置页可以点击上传头像&#xff0c;然后在首页可以改变头像&#xff08;获取头像&#xff09; 访问账号设置页面 controller getSettingPage的方法就是返回html页面 前端 改一下setting页面&#xff0c;index账号设置的url就可…

ARMv8架构特殊寄存器介绍-1

1&#xff0c;ELR寄存器&#xff08;Exception Link Register &#xff09; The Exception Link Register holds the exception return address。 异常链接寄存器保存异常返回地址。最常用也很重要。 2&#xff0c;SPSR&#xff08;Saved Process Status Register&#xff09;…

TypeScript 哲学 - Generic 和 ts表示构造函数

ts中 类的二义性 &#xff1a;做类型 和作为构造函数