C++之list类及模拟实现
目录
list的介绍
list的模拟实现
定义节点
有关遍历的重载运算符
list的操作实现
(1)构造函数
(2)拷贝构造函数
(3)赋值运算符重载函数=
(4)析构函数和clear成员函数
(5)尾插/头插和尾删/头删
(6)size成员函数
(7)在任意位置插入 (insert)
(8)任意位置删除(erase)
(9)迭代器
完整代码展示
vector和list的比较
1.排序
(1)list和vector排序
(2)list copy vector sort copy list sort和list
2.总结
list的介绍
(1)list类其实就是链表,但是它是双向链表。在数据结构中我们了解过双向链表的特点。下面我们回忆一下。
1.节点中具有两个指针。一个指针指向该节点的前一个节点,另一个指针指向该节点的下一个节点。
2.存在哨兵位。初始化的时候节点里的下一个节点和上一个节点都指向自己。
(2)STL中list的底层结构

list的模拟实现
定义节点
我们先定义双向链表的节点并初始化。
template <class T>struct list_node{list_node* _next;list_node* _prev;T _data;list_node(const T& x = T()):_next(nullptr), _prev(nullptr), _data(x){}};
有关遍历的重载运算符
list容器有迭代器,那么就可以进行遍历,因此我们要可以++,--等运算符重载。而且在插入删除操作中我们常常需要 ‘.’ '->'对链表进行遍历。因为普通迭代器和const迭代器中只有operator*和operator->的返回值有区别,所以我们就在模板上多增加了两个模板参数。

代码如下:
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* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}//C++规定后缀调用需要有一个int型参数作为区分前缀与后缀调用的区别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& lt){return _node == lt._node;//结构体变量用「.」来访问成员,而结构体指针用「->」来访问。}bool operator!=(const Self& lt){return _node != lt._node;}};
list的操作实现
(1)构造函数
创建节点并把节点中的指针全部指向自己。
list()
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
}
(2)拷贝构造函数
先构造a1,再把lt中的资源尾插给a1。
void empty_Init(){_head = new node;_head->_next = _head;_head->_prev = _head;} //a1(a2),a1是新建的list(const list<T>& lt){empty_Init();for (auto& e : lt){push_back(e);}}//initializer_list<T>list(std::initializer_list<T> lt){empty_Init();for (auto& e : lt){push_back(e);}}
两种拷贝构造的区别:

(3)赋值运算符重载函数=
void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}//lt1=ltlist<T>& operator=(list<T>& lt){swap(lt);return *this;}
看到这个代码我们就会想为什么运算符重载中的形参不加const呢?如果加了const就像 void swap(*this,const list<T>& y)一样,这样是会报错的,两边类型不同,swap函数是一个函数模板,只有一个模板参数,那么有人会说把这个改成类型相同的不就行了。但是我们知道const list<T>& lt中const修饰list<T> 类型,则lt 引用的对象(即 list<T>)是常量对象,不能通过 lt 修改它的内容。它的值在初始化后就不能改变,而在swap函数中需要交换它们的资源,那么lt就需要改变。

(4)析构函数和clear成员函数
clear的作用只是清理链表的节点,只剩下哨兵位,并不会释放空间。
~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}
(5)尾插/头插和尾删/头删
void push_back(const T&x){insert(end(), x);}void push_front(const T&x){insert(begin(), x);}void pop_back(){erase(--end());//end()是哨兵位}void pop_front(){erase(begin());}
(6)size成员函数
size_t size()const{return _size;}
(7)在任意位置插入 (insert)

typedef list_iterator<T,T&,T*> iterator; void insert(iterator pos, const T& x){node* newnode = new node(x);node*pre = pos._node;node* prev = pre->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = pre;pre->_prev = newnode;_size++;}
注意:pos的类型是list_iterator<T,T&,T*>,这个类中的成员变量只有_node,而_node的类型才是list_node,类型为list_node才有节点的成员变量。所以我们要node*pre = pos._node,而不能直接使用pos。
(8)任意位置删除(erase)
list中的erase也会有迭代器失效,所以我们需要返回下一个迭代器。

typedef list_iterator<T,T&,T*> iterator; iterator erase(iterator pos){assert(pos != end());node* pre = pos._node;node* prev = pre->_prev;node* next = pre->_next;delete pre;prev->_next = next;next->_prev = prev;_size--;return iterator(next);}
(9)迭代器
iterator begin(){iterator it(_head->_next);//调用了list_iterator类模板的构造函数return it;}iterator end(){iterator it(_head);return it;}const_iterator begin()const{const_iterator it(_head->_next);return it;}const_iterator end()const{const_iterator it(_head);return it;}
完整代码展示
#include<assert.h>
namespace slm
{//创建节点template <class T>struct list_node{list_node* _next;list_node* _prev;T _data;list_node(const T& x = T()):_next(nullptr), _prev(nullptr), _data(x){}};//实现运算符重载template<class T,class Ref,class Ptr>//template<class T,class T&,class T*>struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;Node* _node;list_iterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}//C++规定后缀调用需要有一个int型参数作为区分前缀与后缀调用的区别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& lt){return _node == lt._node;//结构体变量用「.」来访问成员,而结构体指针用「->」来访问。}bool operator!=(const Self& lt){return _node != lt._node;}};template<class T>class list{typedef list_node<T> node;typedef list_iterator<T,T&,T*> iterator;typedef list_iterator<T,const T&,const T*> const_iterator;public:iterator begin(){iterator it(_head->_next);return it;}iterator end(){iterator it(_head);return it;}const_iterator begin()const{const_iterator it(_head->_next);return it;}const_iterator end()const{const_iterator it(_head);return it;}void empty_Init(){_head = new node;_head->_next = _head;_head->_prev = _head;}list(){empty_Init();}//a1(a2)list(const list<T>& lt){empty_Init();for (auto& e : lt){push_back(e);}}list(std::initializer_list<T> lt){empty_Init();for (auto& e : lt){push_back(e);}}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}//lt1=ltlist<T>& operator=(list<T>& lt){swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}size_t size()const{return _size;}void push_back(const T&x){insert(end(), x);}//在pos前插入void insert(iterator pos, const T& x){node* newnode = new node(x);node*pre = pos._node;node* prev = pre->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = pre;pre->_prev = newnode;_size++;}iterator erase(iterator pos){assert(pos != end());node* pre = pos._node;node* prev = pre->_prev;node* next = pre->_next;delete pre;prev->_next = next;next->_prev = prev;_size--;return iterator(next);}void push_front(const T&x){insert(begin(), x);}void pop_back(){erase(--end());//end()是哨兵位}void pop_front(){erase(begin());}private:node* _head;size_t _size;};
}
vector和list的比较
1.排序
(1)list和vector排序
#include<iostream>
#include<list>
#include<vector>
#include<algorithm>
using namespace std;
void test_op1()
{srand(time(0));const int N = 1000000;list<int> lt1;vector<int> v;for (int i = 0; i < N; ++i){auto e = rand() + i;lt1.push_back(e);v.push_back(e);}int begin1 = clock();// vector排序sort(v.begin(), v.end());int end1 = clock();//list排序int begin2 = clock();lt1.sort();int end2 = clock();printf("vector sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}
int main()
{
test_op1();
return 0;
}

从结果中我们发现list排序比vector排序快了两倍多。
(2)list copy vector sort copy list sort和list
那如果我们先把list类资源拷贝构造给vector排序,排完序后又拷贝回list类,那结果会是如何呢?
#include<iostream>
#include<list>
#include<vector>
#include<algorithm>
using namespace std;
void test_op2()
{srand(time(0));const int N = 10000;list<int> lt1;list<int> lt2;for (int i = 0; i < N; ++i){auto e = rand() + i;lt1.push_back(e);lt2.push_back(e);}int begin1 = clock();// 拷贝vectorvector<int> v(lt2.begin(), lt2.end());// 排序sort(v.begin(), v.end());// 拷贝回lt2lt2.assign(v.begin(), v.end());int end1 = clock();int begin2 = clock();lt1.sort();int end2 = clock();printf("list copy vector sort copy list sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}int main()
{test_op2();return 0;
}

我们发现上面list先拷贝构造成vector类在排序,排完序后再拷贝回list的效率还是比直接list排序慢。
2.总结
| vector | list | |
| 底 层 结 构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
| 随 机 访 问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元 素效率O(N) |
| 插 入 和 删 除 | 任意位置插入和删除效率低,需要搬移元素,时间 复杂度为O(N),插入时有可能需要增容,增容: 开辟新空间,拷贝元素,释放旧空间,导致效率更 低 | 任意位置插入和删除效率高, 不需要搬移元素,时间复杂度 为O(1) |
| 空 间 利 用 率 | 底层为连续空间,不容易造成内存碎片,空间利用 率高,缓存利用率高 | 底层节点动态开辟,小节点容 易造成内存碎片,空间利用率 低,缓存利用率低 |
| 迭 代 器 | 原生态指针 | 对原生态指针(节点指针)进行 封装 |
| 迭 代 器 失 效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效. 删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失 效,删除元素时,只会导致当 前迭代器失效,其他迭代器不 受影响 |
| 使 用 场 景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心 随机访问 |
相关文章:
C++之list类及模拟实现
目录 list的介绍 list的模拟实现 定义节点 有关遍历的重载运算符 list的操作实现 (1)构造函数 (2)拷贝构造函数 (3)赋值运算符重载函数 (4)析构函数和clear成员函数 (5)尾…...
SwinTransformer 改进:添加DoubleAttention模块提升上下文语义提取能力
目录 1. DoubleAttention模块 2. SwinTransformer + DoubleAttention 3. 完整代码 Tips:融入模块后的网络经过测试,可以直接使用,设置好输入和输出的图片维度即可 1. DoubleAttention模块 DoubleAttention 是一种用于计算机视觉任务的注意力机制,旨在通过双重注意力机制…...
在Electron中实现实时下载进度显示的完整指南
在开发Electron应用时,提供良好的用户体验至关重要,尤其是在下载大文件时。用户需要知道下载进度、预计完成时间以及当前下载速度。本文将详细介绍如何在Electron应用中实现实时下载进度显示功能,从主进程到渲染进程的完整流程。 技术栈是ele…...
java生成一个可以下载的word文件
在 Java 里,你能够借助 Apache POI 库来生成 Word 文件,并且实现文件下载功能。下面为你详细介绍实现步骤和示例代码。 1. 添加依赖 若使用 Maven 项目,需在 pom.xml 里添加 Apache POI 的依赖: <dependencies><depen…...
MacBook部署达梦V8手记
背景 使用Java SpringBootDM开发Web应用,框架有License,OSX加载dll失败,安装了Windows 11,只有一个C盘,达梦安装后因为C盘权限问题,创建数据库失败,遂采用Docker容器方式部署。 下载介质 官网在…...
外贸 B2B 平台没落?多语言批发系统正在崛起
近年来,全球外贸行业正在发生快速变化,传统的 B2B 平台正面临越来越多的挑战,尤其是在面对新兴的多语言批发系统时。这种变化不仅影响了供应商和买家之间的交易方式,也正在推动外贸行业的数字化升级和转型。今天,让我们…...
[spring] Spring JPA - Hibernate 多表联查 1
[spring] Spring JPA - Hibernate 多表联查 之前在 [spring] spring jpa - hibernate 名词解释&配置 和 [spring] spring jpa - hibernate CRUD 简单的学习了一下怎么使用 Hibernate 实现 CRUD 操作,不过涉及到的部分都是逻辑上比较简单的实现——只在一张表上…...
鸿蒙Next开发实战教程—电影app
最近忙忙活活写了不少教程,但是总感觉千篇一律,没什么意思,大家如果有感兴趣的项目可以私信给幽蓝君写一写。 今天分享一个电影App。 这个项目也比较简单,主要是一些简单页面的开发和本地视频的播放以及横竖屏切换。 页面搭建以…...
共享栈 线程局部存储 线程互斥 线程同步 消费者生产者模型
共享栈 第一个主线程会在栈区 而当其他线程创建时实在共享区动态申请的栈区 线程局部存储 __thread 关键字 与编译有关 全局变量是被线程共享的 每个线程都能看到 修改 但是如果对该全局变量加上__thread关键字后 该全局变量就不会被共享 将变量在库中的每一个线程的属…...
停车场停车位数据集,标注停车位上是否有车,平均正确识别率99.5%,支持yolov5-11, coco json,darknet,xml格式标注
停车场停车位数据集,标注停车位上是否有车,平均正确识别率98.0%,支持yolov5-11, coco json,darknet,xml格式标注 数据集-识别停车场所有车辆的数据集 数据集分割 一共184张图片 训练组 89&am…...
【Go】运算符笔记
基本数学运算 Go 语言支持常见的 算术运算符,用于执行数学计算。 运算符说明加法-减法*乘法/除法%取余自增--自减 整数运算只能得到整数部分 package mainimport ("fmt""math" )func main() {go_math() }func go_math() {x, y : 8, 5fmt.Pr…...
ssm框架之mybatis框架讲解
1,Mybatis 1.1 Mybatis概述 1.1.1 Mybatis概念 MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发 MyBatis 本是 Apache 的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2…...
CEF 多进程模式时,注入函数,获得交互信息
CEF 控制台添加一函数,枚举 注册的供前端使用的CPP交互函数有哪些-CSDN博客 上篇文章,是在模拟环境,单进程中设置的,这篇文章,将其改到正常多进程环境中设置。 对应于工程中的 CEF_RENDER项目 一、多进程模式中,改写 修改步骤 1、注入函数 client_app_render.cpp 在…...
Androidstudio出现警告warning:意外的元素
这些警告信息通常与 Android SDK 或系统镜像的配置文件有关,可能是由于 SDK 工具或系统镜像的版本不兼容或配置文件格式发生了变化。以下是解决这些警告的步骤: 1. 更新 Android SDK 工具 确保你使用的是最新版本的 Android SDK 工具: 打开…...
深入了解Linux —— git三板斧
版本控制器git 为了我们方便管理不同版本的文件,就有了版本控制器; 所谓的版本控制器,就是能够了解到一个文件的历史记录(修改记录);简单来说就是记录每一次的改动和版本迭代的一个管理系统,同…...
Vala编程语言教程-运算符
运算符 赋值操作。左操作数必须为标识符,右操作数必须为适当的值或引用。 , -, /, *, % 基础算术运算,作用于左右操作数。 运算符也可用于字符串拼接。 , -, /, *, % 左右操作数间算术运算,左操作数必须为标识符,运…...
C#本地将labelme数据集转换为机器视觉yolo数据集格式
C#本地,将labelme数据集转换为机器视觉yolo数据集格式 using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.Encodings.Web; using System.Text.RegularExpressions; using System.Text.U…...
【软件系统架构】单体架构
一、引言 在软件开发的漫长历程中,架构的选择一直是至关重要的决策。单体架构作为一种经典的架构模式,曾经在许多项目中发挥着不可替代的作用。虽然如今微服务等架构逐渐流行,但理解单体架构对于深入掌握软件架构体系仍然有着重要意义。 二、…...
【求助】【建议放弃】【谷粒商城版】Kubernetes
本文作者: slience_me 文章目录 Kubernetes【谷粒商城版】【建议放弃】1. docker安装2. kubernetes安装前3. kubeadm,kubelet,kubectl3.1 简介kubeadmkubeletkubectl常用指令 3.2 安装3.3 kubeadm初始化3.4 加入从节点(工作节点)3.5 安装Pod网络插件(CNI…...
uniapp 实现微信小程序电影选座功能
拖动代码 /*** 获取点击或触摸事件对应的座位位置* 通过事件对象获取座位的行列信息* param {Event|TouchEvent} event - 点击或触摸事件对象* returns {Object} 返回座位位置对象,包含行(row)和列(col)信息,若未找到有效位置则返回 {row: -1, col: -1}*…...
python+flask实现360全景图和stl等多种格式模型浏览
1. 安装依赖 pip install flask 2. 创建Flask应用 创建一个基本的Flask应用,并设置路由来处理不同的文件类型。 from flask import Flask, render_template, send_from_directory app Flask(__name__) # 设置静态文件路径 app.static_folder static app.r…...
IntelliJ 配置文件plugin.xml
在 IntelliJ IDEA 插件开发中,plugin.xml 是插件的配置文件,它包含了关于插件的所有基本信息、扩展点、依赖关系等。该文件使用 XML 格式进行定义。以下是 plugin.xml 中常见的元素及其用途: <idea-plugin><!-- 插件的基本信息 --&…...
C# Unity 唐老狮 No.10 模拟面试题
本文章不作任何商业用途 仅作学习与交流 安利唐老狮与其他老师合作的网站,内有大量免费资源和优质付费资源,我入门就是看唐老师的课程 打好坚实的基础非常非常重要: Unity课程 - 游习堂 - 唐老狮创立的游戏开发在线学习平台 - Powered By EduSoho C# 1. 内存中,堆和…...
数据库系统——规范化1NF~BCNF
数据库规范化完全指南:从零到BCNF,中学生也能秒懂!📚✨ 一、什么是数据库规范化? 科学定义 🔍 数据库规范化是通过一系列规则(范式)将数据库表结构分解为更小、更高效、无冗余的表…...
第十五届蓝桥杯2024JavaB组省赛试题A:报数游戏
简单的找规律题目。题目给得数列,第奇数项是20的倍数,第偶数项时24的倍数。题目要求第n 202420242024 项是多少。这一项是偶数,所以答案一定是24的倍数,并且偶数项的个数和奇数项的个数各占一半,所以最终的答案ans( n…...
Matlab 汽车二自由度转弯模型
1、内容简介 Matlab 187-汽车二自由度转弯模型 可以交流、咨询、答疑 2、内容说明 略 摘 要 本文前一部分提出了侧偏角和横摆角速度作为参数。描述了车辆运动的运动状态,其中文中使用的参考模型是二自由度汽车模型。汽车速度被认为是建立基于H.B.Pacejka的轮胎模…...
关于 2>/dev/null 的作用以及机理
每个进程都有三个标准文件描述符:stdin(标准输入)、stdout(标准输出)和stderr(标准错误)。默认情况下,stderr会输出到终端。使用2>可以将stderr重定向到其他地方,比如…...
学c++的人可以几天速通python?
学了俩天啊,文章写纸上了 还是蛮有趣的...
HTML,CSS,JavaScript
HTML:负责网页的结构(页面元素和内容)。 CSS:负责网页的表现(页面元素的外观、位置等页面样式,如:颜色、大小等)。 Javascript:负责网页的行为(交互效果)。 MDN前端开发文档(MDN Web Docs) HTML HTML(HyperText Markup Language):超文本标记语言超文本:超越了文本的…...
微信小程序面试内容整理-图片优化
在微信小程序中,图片优化是提升加载速度、节省网络带宽和提高用户体验的重要步骤。图片通常是小程序页面中的主要资源,合理的图片优化能显著提高小程序的性能,尤其是在用户网络状况较差的情况下。 1. 选择合适的图片格式 不同的图片格式有不同的特点,选择合适的格式能够有效…...

