当前位置: 首页 > news >正文

C++STL的list模拟实现

文章目录

    • 前言
  • list实现
    • push_back
    • 迭代器(重点)
      • 普通迭代器
      • const迭代器
    • insert
    • erase
    • 析构函数
    • 构造函数
    • 拷贝构造
    • 赋值
  • vector和list的区别

前言

要实现STL的list, 首先我们还得看一下list的源码。
在这里插入图片描述
我们看到这么一个东西,我们知道C++兼容C,可以用struct来创建一个类。但是我们习惯用class。

那什么时候会用struct呢?
这个类所有成员都想开放出去,比如结点的指针,它一般开放出来。所以我们用struct.。

继续看源码比较重要的东西,成员变量的结构。
在这里插入图片描述

这个东西是啥?
在这里插入图片描述
在这里插入图片描述
这样就很清晰了。

知道它是一个结点的指针,下一步 应该看什么?
成员看了,就看接口。
看接口第一步,看构造函数,看构造函数就知道它怎样初始化,就知道它的初始结构是怎样的。
初始结构摸清楚了,就对它的大概形态摸清楚了。

接着看它的核心方法,当然我们本身对list有一定的了解。
头插头删,尾插尾删就是核心方法。

看它的构造函数
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
后面就先不接着往下看了。

list实现

先把最基本的东西写出来。

namespace but
{template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _data;};template<class T>class list{list(){_head = new list_node;_head->_next = _head;_head->_prev = _head;}private:list_node* _head;};
}

push_back

在这里插入图片描述
在这里插入图片描述

为什么报错?
在这里插入图片描述
前面我们说过像构造函数,参数可以不加模板参数,但是声明类型还是得加上。

list_node是类名,list_node才是类型。

更新一下前面的代码。

namespace but
{template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _data;};template<class T>class list{typedef list_node<T> node;list(){_head = newnode;_head->_next = _head;_head->_prev = _head;}private:node _head;};
}

push_back怎么搞?
找到尾,然后new 一个新节点,最后链接。
在这里插入图片描述

void push_back(const T& x)
{node* tail = _head->_prev;node* new_node = new node(x);tail->_next = new_node;new_node->_prev = tail;new_node->_next = _head;_head->_prev = new_node;
}

写个list_node的构造函数。

list_node(const T& x ):_next(nullptr), _prev(nullptr), _data(x)
{}

紧接着报错。
在这里插入图片描述
没有默认构造怎么办?
最好还是提供一个全缺省的构造函数。

//list_node(const T& x =0)不能给0
list_node(const T& x =T()):_next(nullptr), _prev(nullptr), _data(x)

迭代器(重点)

普通迭代器

首先我们肯定会遇到一个问题,之前的vector的数据是连续存放的,而链表每个结点是不连续的。
++不能指向下一个结点。
在这里插入图片描述
怎么解决这个问题?
能不能给node提供一个重载,不行,因为是node*而不是node;

我们可以看一下STL的源码。
在这里插入图片描述
++还可以解引用
在这里插入图片描述

现在我们根据自己的理解,写一个简单的迭代器,让它运行起来。
在这里插入图片描述

接着我们再在list这个对象里写上begin()和end()就可以正常访问了。
在这里插入图片描述

最后测试一下
在这里插入图片描述
在这里插入图片描述
大家仔细看,数组和链表的结构千差万别,但是用起来是如此的相似。
这源自于封装,屏蔽掉了我们看不到的细节。

今天最重要的并不是链表的实现,迭代器的实现才是最最重要的。

总结一下,node*不支持解引用,不支持++,但是我可以用一个自定义类型对你封装,然后去重载运算符,我可以控制我想要的解引用的行为,想要的++的行为,这是自定义类型达到的意义。

在这里插入图片描述
注意看这里有个隐藏的点,发生了拷贝构造,我们自己没有写拷贝构造,编译器自动生成的不会 出问题吗?
在这里插入图片描述
程序运行没有报错,什么原因呢?这里没有写析构函数,不需要释放结点。

为什么不需要释放结点?
虽然有结点的指针,但是这结点的指针并不属于迭代器。
结点的指针给迭代器,只是为了遍历链表,++,解引用,修改链表。
释放是链表的事情,链表的析构函数会释放,不需要你释放。
这个结点不是迭代器new出来的,你只有使用权,没有归属权。

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

const迭代器

假设我们传了const的链表,编译不通过。
在这里插入图片描述
为什么编译不通过?
还是我们之前讲了很多次的权限放大。
我们提供一个支持const对象的迭代器就可以了。

但是看这里,为什么const对象还可以调用构造迭代器?
在这里插入图片描述
首先const修饰的*this具体是_head;所以_head不能被改变,而不是_head指向的内容不能被改变。
这个结点指针本身不能改变,但是它可以拷贝给别人。

但是这样写不符合我们的预期,可以修改了。为什么能修改呢?就是因为它构造出了普通迭代器。但是普通迭代器是不可写的。

我们要写一个const迭代器

首先我们先想一下普通迭代器和const迭代器的区别是什么?

先看一个问题,能不能这样定义const迭代器?
在这里插入图片描述
绝对不可以。
首先迭代器对标的是指针。
在这里插入图片描述

写成上面这样,是保护迭代器本身不能修改,而我们想要的是,迭代器指向的内容不能修改,也就是 const T*;

那怎么实现呢,我们要实现的内容不能修改。
我们可以像之前实现普通迭代器一样,再写一个const迭代器对象,只是名字改一下,然后解引用的时候不能修改。
在这里插入图片描述

在这里插入图片描述

两个对象除了那个返回值不一样,其他都一样怎么简化一下呢?
控制返回值不一样就可以了。增加一个模板参数。
还能这么玩。
在这里插入图片描述
在这里插入图片描述

// 1、迭代器要么就是原生指针
// 2、迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为
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;}};

我们看一下库里面的模板参数
在这里插入图片描述

为什么还有一个Ptr呢?
它还提供了一个重载operator->;
在这里插入图片描述
什么时候会用->?
大家注意,上面的迭代器模拟的是int*;
自定义的类型是不是得用->
在这里插入图片描述
在这里插入图片描述
大家看报错了。报的啥错。
it返回的是AA,AA没有返回流插入。

第一种方式可以使用重载一个流插入,这里因为AA里面的成员都不是私有,所以我们可以这样。
在这里插入图片描述
这样写很别扭我们可以这样。
在这里插入图片描述
我们可以在迭代器里面重载一个->
在这里插入图片描述

总感觉有点怪怪,其实是这样的。
在这里插入图片描述
在这里插入图片描述

好,接下来const的迭代器的->重载需要返回const T*,所以这里再增加一个模板参数。

insert

链表其实已经实现的差不多了,我们现在自己再把功能完善一下。其实我们没必要实现头插头删尾插尾删,我们只需要实现insert.和 erase, insert和erase实现了,其他都可以实现。
在这里插入图片描述

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

链表的insert会不会导致迭代器失效?
不会
因为pos始终指向这个结点,并且这个位置关系也不会变。

接着我们其实自己不用写push_back和push_front了

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

erase

在这里插入图片描述

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

链表的erase会不会导致迭代器失效?
铁铁的失效,迭代器指向的结点的指针都被干掉了

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

在这里插入图片描述
大家看下面这两行代码的差异在哪里?
本质上没有差异。它们的差异点在于pnode是一个内置类型,it是一个自定义类型。

从物理空间上看,它们的代码是一摸一样的,都是4个字节,并且都是同一个地址。
在这里插入图片描述
但是这两个的行为天差地别

在这里插入图片描述
这就是C语言和C++的差异。

析构函数

clear可以帮我们把数据清掉,但是它不清头结点。

void clear()
{iterator it = begin();while (it != end()){it = erase(it);//防止迭代器失效erase(it++);}
}

在这里插入图片描述
这样写行不行?
可以。it不是失效了吗?为什么还可以it++; 我们之前说过it失效有个现象就是野指针,那这里怎么没事呢?
这就是后置++的价值,它会返回++之前的值。
在这里插入图片描述
也就是说erase的并不是it,而是返回的迭代器。

析构和clear的区别就是头节点要不要清楚掉,析构是彻底不用了。

~list()
{clear();delete _head;_head = nullptr;
}void clear()
{iterator it = begin();while (it != end()){//it = erase(it);erase(it++);}
}

构造函数

我们再提供一下迭代器区间的构造。
在这里插入图片描述
这样写可不可以,不可以,你要push_back,你得有一个哨兵卫的头节点。

void empty_init()
{_head = new node;_head->_next = _head;_head->_prev = _head;
}template <class Iterator>
list(Iterator first, Iterator last)
{empty_init();while (first != last){push_back(*first);++first;}
}

const对象可不可以调用构造函数。可以。
在这里插入图片描述

拷贝构造

传统写法
在这里插入图片描述

现代写法

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

在这里插入图片描述
this跟tmp交换,但是this是随机值,会报错,所以要初始化。

赋值

在这里插入图片描述
为什么不用引用传参?
用引用会导致一个非常恶劣的后果。
大家看,传引用的话,lt就是lt3,交换就变成lt1和lt3的交换了。


// lt1 = lt3
list<T>& operator=(list<T> lt)
{swap(lt);return *this;
}

vector和list的区别

其实就是顺序表和链表的区别。

相关文章:

C++STL的list模拟实现

文章目录 前言 list实现push_back迭代器(重点)普通迭代器const迭代器 inserterase析构函数构造函数拷贝构造赋值 vector和list的区别 前言 要实现STL的list, 首先我们还得看一下list的源码。 我们看到这么一个东西&#xff0c;我们知道C兼容C&#xff0c;可以用struct来创建一…...

django--分页功能

Django 提供了强大的分页功能&#xff0c;可以轻松地在视图中实现分页。 在视图中使用分页&#xff1a; # views.py from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.shortcuts import render from .models import YourModeldef your…...

centOS安装bochsXshell连接centos启动可视化界面

centOS安装bochs 参考&#xff1a;https://blog.csdn.net/muzi_since/article/details/102559187 首先安装依赖环境&#xff1a; yum install gtk2 gtk2-devel yum install libXt libXt-devel yum install libXpm libXpm-devel yum install SDL SDL-devel yum install libXr…...

mac m2芯片 安装nginx + php + mysql

1.安装homebrew&#xff1a; 系统本身就有&#xff08;命令brew -v查看下&#xff09;&#xff0c;如果没有安装一下 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 2.安装nginx brew install nginx 3.安装php bre…...

vue axios 使用

使用Vue中的Axios需要先安装axios库&#xff0c;可以通过yarn或npm安装&#xff1a; yarn add axios # 或者 npm install axios --save然后在Vue组件中导入axios并使用&#xff1a; import axios from axios;export default {data() {return {responseData: null,error: null…...

使用docker实现logstash同步mysql到es

准备工作&#xff1a; 1.有mysql的连接方式&#xff0c;并且可以连接成功 2.有es的连接方式&#xff0c;并且可以连接成功 3.安装了docker 环境是Ubuntu中安装了docker 一、创建配置文件&#xff0c;用于容器卷挂载 # 切换目录&#xff0c;可自定义 cd /home/test/ # 创建lo…...

hive数据仓库工具

1、hive是一套操作数据仓库的应用工具&#xff0c;通过这个工具可实现mapreduce的功能 2、hive的语言是hql[hive query language] 3、官网hive.apache.org 下载hive软件包地址 Welcome! - The Apache Software Foundationhttps://archive.apache.org/ 4、hive在管理数据时分为元…...

C语言 联合体验证 主机字节序 +枚举

联合体应用&#xff1a;验证当前主机的大小端&#xff08;字节序&#xff09; //验证当前主机的大小端 #include <stdio.h>union MyData {unsigned int data;struct{unsigned char byte0;unsigned char byte1;unsigned char byte2;unsigned char byte3;}byte; };int main…...

python和pygame实现烟花特效

python和pygame实现烟花特效 新年来临之际&#xff0c;来一个欢庆新年烟花祝贺&#xff0c;需要安装使用第三方库pygame&#xff0c;关于Python中pygame游戏模块的安装使用可见 https://blog.csdn.net/cnds123/article/details/119514520 效果图及源码 先看效果图&#xff1a…...

gRPC-Gateway:高效转换 RESTful 接口 | 开源日报 No.105

grpc-ecosystem/grpc-gateway Stars: 16.4k License: BSD-3-Clause gRPC-Gateway 是一个遵循 gRPC HTTP 规范的 gRPC 到 JSON 代理生成器。它是 Google 协议缓冲编译器 protoc 的插件&#xff0c;可以读取 protobuf 服务定义并生成反向代理服务器&#xff0c;将 RESTful HTTP…...

非专业的建模人员如何给模型设置材质纹理贴图?

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 1、材质和纹理的区别于关联 材质&#xff08;Material&#xff09;是…...

自动化测试、压力测试、持续集成

因为项目的原因&#xff0c;前段时间研究并使用了 SoapUI 测试工具进行自测开发的 api。下面将研究的成果展示给大家&#xff0c;希望对需要的人有所帮助。 SoapUI 是什么&#xff1f; SoapUI 是一个开源测试工具&#xff0c;通过 soap/http 来检查、调用、实现 Web Service …...

FFmpeg之HWContextType

HWContextType算是ffmpeg中为硬解码第三方接口的一个辅助类&#xff0c;它自己有两个辅助子类 AVHWDeviceContext和AVHWFramesContext。 AVHWDeviceContext主要表示硬件上下文 AVHWFramesContext主要表示硬件Frame的一些参数&#xff0c;比如你解码后的YUV数据还在硬件上&#…...

Python面向对象之类和对象(Python系列16)

前言&#xff1a;面向对象是什么&#xff0c;为什么要学面向对象&#xff1f;面向对象是一种思想&#xff0c;让我们的程序变得更加的贴切我们的生活&#xff0c;更加的形象&#xff0c;让代码的可读性和扩展性变得更高。 面向对象&#xff1a;可以使用类将变量和函数组成新的…...

电商对传统零售业的影响:销售渠道、价格竞争与服务质量挑战

随着互联网的普及和电商行业的飞速发展&#xff0c;传统零售业面临着前所未有的挑战。电商不仅改变了消费者的购物方式和消费习惯&#xff0c;还对传统零售业的销售渠道、价格竞争和服务质量等方面产生了深远的影响。本文将详细分析电商对传统零售业的影响&#xff0c;以期为传…...

DENet:用于可见水印去除的Disentangled Embedding网络笔记

1 Title DENet: Disentangled Embedding Network for Visible Watermark Removal&#xff08;Ruizhou Sun、Yukun Su、Qingyao Wu&#xff09;[AAAI2023 Oral] 2 Conclusion This paper propose a novel contrastive learning mechanism to disentangle the high-level embedd…...

C++初阶(十五)Stack和Queue

文章目录 一、Stack的模拟实现二、Queue的模拟实现三、容器适配器1、什么是容器适配器2、STL标准库中stack和queue的底层结构3、 deque的简单介绍(了解)1、deque的原理介绍2、deque的缺陷 4、为什么选择deque作为stack和queue的底层默认容器 一、Stack的模拟实现 #include<…...

C#面试题

基本概念 装箱和拆箱 装箱的过程&#xff0c;是将 值类型 转换为 引用类型 的过程&#xff1b; 拆箱则是将引用类型转换为值类型。 int val 100; object obj val; //装箱 int num (int) obj; //拆箱 委托(delegate) 委托&#xff08;Delegate&#xff09; 是存有对某个…...

python源码,在线读取传奇列表,并解析为需要的JSON格式

python源码&#xff0c;在线读取传奇列表&#xff0c;并解析为需要的JSON格式 [Server] ; 使用“/”字符分开颜色&#xff0c;也可以不使用颜色&#xff0c;支持以前的旧格式&#xff0c;只有标题和服务器标题支持颜色 ; 标题/颜色代码(0-255)|服务器标题/颜色代码(0-255)|服务…...

二叉排序树的判断(二叉树的顺序存储):2022年408算法题

对于采用顺序存储方式保存的二叉树&#xff0c;根结点保存在SqBiTNode[0]中&#xff1b;当某结点保存SqBiTNode[i]中时&#xff0c;若有左孩子&#xff0c;则其值保存在SqBiTNode [2i1]中&#xff1b;若有右孩子&#xff0c;则其值保存在SqBiTNode[2i2]中&#xff1b;若有双亲结…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

2024年赣州旅游投资集团社会招聘笔试真

2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍&#xff1a; img 属性指定分区存放的 image 名称&#xff0c;指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件&#xff0c;则以 proj_name:binary_name 格式指定文件名&#xff0c; proj_name 为工程 名&…...

宇树科技,改名了!

提到国内具身智能和机器人领域的代表企业&#xff0c;那宇树科技&#xff08;Unitree&#xff09;必须名列其榜。 最近&#xff0c;宇树科技的一项新变动消息在业界引发了不少关注和讨论&#xff0c;即&#xff1a; 宇树向其合作伙伴发布了一封公司名称变更函称&#xff0c;因…...

淘宝扭蛋机小程序系统开发:打造互动性强的购物平台

淘宝扭蛋机小程序系统的开发&#xff0c;旨在打造一个互动性强的购物平台&#xff0c;让用户在购物的同时&#xff0c;能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机&#xff0c;实现旋转、抽拉等动作&#xff0c;增…...