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

【C++】list容器实现

目录

一、list和vector区别

二、list模拟实现过程

1. 构造函数

2. 尾插 push_back

3. 迭代器

4. 任意位置插入 insert

5.删除指定位置的节点 erase

6. 复用

7. 链表节点个数

8. 析构函数

9. 拷贝构造函数

10. 赋值运算符重载

11.多参数构造


正文:

本次list详解主要放在迭代器和类封装部分,特别是迭代器。

一、list和vector区别

(1)在两者的底层数据结构中:

vector:动态数组,元素在内存中连续存储。

list:双向链表,元素通过指针非连续存储(每个节点包含前驱和后继指针)。

(2)迭代器支持:

vector:支持随机访问迭代器(可跳跃访问)。

list:仅支持双向迭代器(只能逐元素移动)。

(3)内存占用:

vector:内存紧凑,仅存储元素,缓存友好(预取效率高)。

list:每个元素额外占用两个指针空间(前驱/后继),内存碎片可能更多。

(4)使用场景:

选择vector:(1)需要频繁随机访问(2) 内存连续性对性能关键(如缓存优化)(3)尾部操作为主,中间插入较少。

选择list:(1)避免扩容导致的开销或迭代器失效(2)不需要随机访问(3)频繁在任意位置插入/删除。

二、list模拟实现过程

list:带头双向循环链表,底层是由一个一个节点构成。

可以理解为:有多个节点,再把节点串起来就形成链表。list 类的成员变量通常是一个节点类型的头指针(_head)指向链表的首节点,通过不断插入节点和更改指针之间指向形成一个链表结构,而每个节点是一个独立单元,通常包含:数据部分(存储元素值)、前驱指针、后继指针,在 C++ 实现中,节点用类实现。

list类模版如下:

节点的类模板用struct可以直接访问内部数据,模版一般声明定义不分离。外部包了一层zss命名空间,防止在大项目中因命名冲突引发报错!

1. 构造函数

目的是对list进行初始化,由于后面可能出现单独调用初始化功能,所以将初始化的步骤单独提出写成一个函数,让构造函数内部调用实现初始化。

目前实现的链表是带头链表,创建链表第一步是开一个Node大小的空间作为哨兵节点,当没有节点插入时,它的前驱和后继都是指向自己(已通过typedef把节点模版命名为Node)

接下来实现的方法除非有特别声明位置,不然都在public下

2. 尾插 push_back

链表的底层是节点,尾插一个数得创建一个节点把这个数存到节点中才能插入。

 

3. 迭代器

迭代器本质:不暴露容器底层结构,用户不需要关心底层结构,让使用变得简单

在vector中迭代器的底层结构是原生指针int* 但是list不能直接用node*

因为node*解引用后是节点不是数据,达不到我们要的效果。需要用一个类去封装节点指针,然后进行运算符的重载按我们要求实现。

迭代器的类型分为:普通迭代器、const迭代器、反向迭代器

本次以普通迭代器和const迭代器为主,const迭代器并不是在函数名前面加个const就行,这样const修饰的是函数本身不能修改,我们需要函数的内容不修改本身可以改变。所以要同时实现两个迭代器类模版(如下图)

两个迭代器模版只有名字不同,其他重复度太高因此可合成一个模版。通过参数不同来判断调什么类型迭代器。

   

使用同一个模版后:

先定造一个迭代器模版:迭代器本质是封装了节点指针的类,通过运算符重载模拟指针行为。

使用 Ref(引用)和 Ptr(指针)模板参数,统一普通迭代器和 const 迭代器的实现。

list 类通过 typedef 定义迭代器类型,并暴露 begin()/end() 接口。

 

为什么 operator-> 返回 Ptr(指针)?
为了支持 it->method() 的语法,编译器会隐式转换为 Ptr->method()。
 

4. 任意位置插入 insert

只要插入就要创建节点,改变指向逻辑。

    

5.删除指定位置的节点 erase

由于编译器的强制机制,erase后的迭代器可能失效所以要更新一下迭代器,erase后返回pos位置的下一个迭代器。

删除节点之前要先把指针指向调整好,为了防止调整过程中的思维混乱通过定义变量记录下每个节点。

6. 复用

头插、尾插、头删、尾删的复用:插入、删除和迭代器功能都已经实现,这几个就可巧妙复用。

 

7. 链表节点个数

由于有成员变量_size,插入删除中都有对应++/--,当要求链表节点个数时直接返回就行。

 

8. 析构函数

在对整个链表析构前要先清空数据(链表的数据是节点),清空数据的方法可能被单独调用所以提取出来写成一个clean()函数。clean后再删除头结点,将其置空。

clean内部直接复用erase进行链表中节点数据的清理,由于上面提到erase后迭代器会失效所以然it重新赋值一下erase的返回值。

9. 拷贝构造函数

此处就体现了文章开头为什么要把初始化方法单独写成一个函数而不是在构造函数中实现的重要性,因为在拷贝构造中就出现被单独调用了!

拷贝构造:用一个已存在的对象list<T>& lt 构造一个新对象。可以直接巧妙用范围for遍历 lt 对象然后尾插形成新对象。

10. 赋值运算符重载

此处建议用现代写法:通过浅拷贝传参,lt里面就是我想要的数据,利用swap直接将数据交换给自己,函数运行结束后lt会自动销毁。

11.多参数构造

这个是C++11中的一种构造方式。

initializer_list:允许用花括号 {} 初始化对象(如容器、自定义类)。

头文件#include <initializer_list>

以上十几点就是list的一些比较常用的方法实现,小伙伴们要是有兴趣可以对应去看一下list的原码和一些更详细的方法。下面把整个list实现代码附上:

#pragma once
#include<iostream>
#include<assert.h>
#include<algorithm>
#include<vector>
using namespace std;namespace zss
{template<class T>struct list_Node{list_Node<T>* _next;list_Node<T>* _prev;T _date;//拷贝构造list_Node(const T& x = T()):_next(nullptr), _prev(nullptr), _date(x){}};//3.迭代器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->_date;}Ptr operator->(){return &_node->_date;}//迭代器++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& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}};template <class T>class list{typedef list_Node<T> Node;public:/*typedef list_iterator<T> iterator;typedef list_const_iterator<T> const_iterator;*/typedef list_iterator<T,T&,T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;//普通迭代器iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}//const迭代器const_iterator begin()const{return const_iterator(_head->_next);}const_iterator end()const{return const_iterator(_head);}//1.构造函数void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}//19.拷贝构造:lt2(lt1)list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}//10.赋值重载:lt1 = lt3void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}list<T>& operator=(list<T> lt){//超绝现代写法swap(lt);return *this;}//11.多参数构造list(initializer_list<T> lt){//凡是链表的构造先想是否可以范围for+尾插实现empty_init();for (auto& e : lt){push_back(e);}}//8.析构~list(){//先清数据再删节点clear();delete _head;_head = nullptr;}//清理数据函数,可能单独调用void clear(){iterator it = begin();while (it != end()){//迭代器失效更新it = erase(it);}}//2.push_backvoid push_back(const T& x){//先建个节点/*Node* newnode = new Node(x);Node* tail = _head->_prev;tail->_next = newnode;newnode->_next = _head;_head->_prev = newnode;*///复用大法insert(end(), x);}//4.增insertvoid insert(iterator pos,const T& x){//先创个节点出来Node* newnode = new Node(x);//改变指向逻辑Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;}//5.删eraseiterator erase(iterator pos){//断言防止把头删了assert(pos != end());Node* cur = pos._node;//改变指向逻辑Node* prev = cur->_prev;Node* newpos = cur->_next;prev->_next = newpos;newpos->_prev = prev;--_size;delete cur;//迭代器失效问题,所以返回pos的下一个位置return iterator(newpos);}//6.复用void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}void pop_back(){erase(--end());}//7.sizesize_t size(){return _size;}private:Node* _head;size_t _size;};
}

文章中有理解错误、文字错误欢迎指出

list的方法实现1-11并不是固定的顺序~


完结~感谢观看

http://www.xdnf.cn/news/14592.html

相关文章:

  • Lighthouse与首屏优化
  • 【看到哪里写到哪里】如何在C中使用多进程设计(1)
  • STM32 开发 - STM32CubeMX 下载芯片支持包、创建 HAL 库工程
  • 牙科医疗设备EMC电磁兼容技术讨论
  • 数列的极限
  • 推荐标注数据标注
  • 【精选】计算机毕业设计基于SpringBoot高校社团管理系统 社团信息维护 活动发布报名 成员审核与公告发布平台源码+论文+PPT+讲解
  • Git(三) Git 分支工作流管理模型探究与实践
  • 电容篇---常见作用
  • Apache Iceberg与Hive集成:分区表篇
  • StarRocks Community Monthly Newsletter (May)
  • JavaScript中Date对象用法详解
  • 深入实践Caffeine+Redis两级缓存架构:从原理到高可用设计
  • 「Linux文件及目录管理」文件及目录操作类命令
  • Grdle版本与Android Gradle Plugin版本, Android Studio对应关系
  • OpenWrt:交叉编译openssl
  • redis缓存的基础知识
  • DBSCAN(Density-Based Spatial Clustering of Applications with Noise)基于密度的聚类方法介绍
  • 移动应用开发实验室web组大一下期末考核题解
  • 【arXiv2024】时间序列|TimesFM-ICF:即插即用!时间序列预测新王者!吊打微调!
  • 如何用ai设计测试
  • WebStorm编辑器侧边栏
  • NodeJS的fs模块的readFile和createReadStream区别以及常见方法
  • Nacos 实战指南:服务注册、分级与环境隔离
  • 第二十六周:序列化和反序列化
  • 变幻莫测:CoreData 中 Transformable 类型面面俱到(三)
  • 【Git】代码托管服务
  • 【一天一个知识点】RAG 是“问答脑”,智能体是“有行动力的大脑”
  • AndroidStudio下载的SDK没有tool目录,或者想要使用uiautomatorviewer工具
  • 二.TvSettings从Android.bp解析成build.gradle