【C++修炼之路】list 模拟实现

news/2024/5/16 13:57:22/文章来源:https://blog.csdn.net/m0_67867172/article/details/131790149

👑作者主页:@安 度 因
🏠学习社区:StackFrame
📖专栏链接:C++修炼之路

文章目录

  • 一、读源码
  • 二、成员
  • 三、默认成员函数
    • 1、构造
    • 2、析构
    • 3、拷贝构造
    • 4、赋值重载
  • 四、迭代器
  • 五、其他接口

如果无聊的话,就来逛逛 我的博客栈 吧! 🌹

一、读源码

list 是双向带头循环链表,不了解这个结构可以去看我的双向链表。

list 不支持 [ ] ,因为地址不连续,list 的访问通过迭代器。

迭代器:

template<class T, class Ref, class Ptr>
struct __list_iterator {typedef __list_iterator<T, T&, T*>             iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;typedef __list_iterator<T, Ref, Ptr>           self;typedef bidirectional_iterator_tag iterator_category;typedef T value_type;typedef Ptr pointer;typedef Ref reference;typedef __list_node<T>* link_type;typedef size_t size_type;typedef ptrdiff_t difference_type;link_type node;// ...
};

迭代器是自定义类型。

成员变量:

template <class T, class Alloc = alloc>
class list {// ...
protected:link_type node;  
}

查看 link_type 本质:

typedef list_node* link_type;
typedef __list_node<T> list_node;
// 链表节点
template <class T>
struct __list_node {typedef void* void_pointer;void_pointer next;void_pointer prev;T data;
};

实质上就是节点类型的指针。

创建节点:

link_type create_node(const T& x) {link_type p = get_node();__STL_TRY {construct(&p->data, x);}__STL_UNWIND(put_node(p));return p;
}

get_node:

link_type get_node() { return list_node_allocator::allocate(); } // 空间配置器开辟空间

construct 实际上就是定位 new 。

list 构造:

list() { empty_initialize(); }
void empty_initialize() { node = get_node();node->next = node;node->prev = node;
}

list 构造没有调用定位 new ,因为哨兵位上面并不需要存放有效数据,所以只需要开辟空间,确定链接关系;普通节点上,存放自定义类型数据,若赋值空间上存在随机值,对空间进行释放可能会导致崩溃的现象,所以对于普通节点需要调用定位 new 进行构造。

二、成员

list_node 为节点,需要经常访问,开 struct :

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

list:

template<class T>class list{typedef list_node<T> Node; // 给类内部用的// ...private:Node* _head; // 哨兵位size_t _size;};

三、默认成员函数

1、构造

给哨兵位完成初始化:

void empty_init()
{_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;
}list()
{empty_init();
}

2、析构

~list()
{clear();delete _head;_head = nullptr;
}// 清除除哨兵位的所有节点
void clear()
{iterator it = begin();while (it != end()){it = erase(it);}_size = 0;
}

3、拷贝构造

// list(const list& lt) // 类中类名可以充当类型
list(const list<T>& lt)
{// new(this)list; // 定位 newempty_init();// & 提高效率for (auto& e : lt){push_back(e);}
}

可以使用定位 new 直接调用构造函数,也可以复用 empty_init 。赋值通过 push_back ,push_back 中会完成对自定义类型成员的深拷贝。

4、赋值重载

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

调用拷贝构造,进行交换。

四、迭代器

list 的迭代器是自定义类型,不是原生指针Node*.

若:

typedef Node* iterator;list<int>::iterator it = lt.begin();while (it != lt.end())
{(*it) += 1;cout << *it << " ";++it;
}

那么 *it 获得的是节点类,并不是节点中存储的值。

所以,迭代器为自定义类型,其中 *, ++ 等都是通过运算符重载来完成的。

通过封装自定义类型为迭代器,利用节点指针构造迭代器,用类封装类型,统一迭代器的使用方法,来获得统一的结果。

思考,普通迭代器的模板类型构成:

我们需要如下重载符号:*, 前置++,后置++,前置--,后置--,->, !=, ==,返回类型分别为:T&, 迭代器引用,迭代器拷贝,迭代器引用,迭代器拷贝,T*, 布尔值,布尔值

-> 模拟的行为:

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; // 自定义类型成员访问成员变量cout << it->_a1 << " " << it->_a2 << endl; // 打印自定义类型的成员变量++it;}cout << endl;
}

类比 const 迭代器,const 迭代器与普通迭代器的区别无非是 * 返回的值不能修改,-> 返回的 T 类型指针指向的成员变量不能修改

所以,我们完全可以将类模板写为:

template<class T, class Ref, class Ptr> // T 为任意类型,Ref 为 T& 为数据的引用,Ptr 为 T* 为返回的 T 类型的指针

迭代器根据节点来构造,通过运算符重载来完成统一行为,写出迭代器:

template<class T, class Ref, class Ptr>struct _list_iterator{typedef list_node<T> Node;typedef _list_iterator<T, Ref, Ptr> self; // 重命名避免繁琐,selft 就是迭代器本身Node* _node;_list_iterator(Node* node):_node(node){}// 返回引用的值Ref operator*(){return _node->_val;}// 编译器做了优化// Ptr 返回的是 T*// 调用时 it->_member// 那么此刻为 T*_member,原本为 T*->_member// 所以其实调用方应该写为 it->->_member,为了符合逻辑,编译器做了优化,只要写 it->_memberPtr operator->(){return &_node->_val;}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;}// it != it.end()// it.end() 返回的是临时对象,为常量,所以要加 const 修饰bool operator!=(const self& it) const{return _node != it._node;}bool operator==(const self& it) const{return _node == it._node;}};

迭代器的拷贝只要浅拷贝,期望拿到的就是对应节点的 begin;迭代器没有析构,析构工作交给 list 统一处理,迭代器只需要完成它的本质工作:对节点的访问。

普通迭代器和 const 迭代器只需要在 list 中 typedef :

typedef _list_iterator< T, T&, T*> iterator;
typedef _list_iterator< T, const T&, const T*> const_iterator;

注:类中的自定义类型有两方面 - 1. typedef 的内嵌类型 2. 内部类。

iterator begin()
{return _head->_next; // 隐式类型转换
}iterator end()
{return _head;
}const_iterator begin() const
{return _head->_next;
}const_iterator end() const
{return _head;
}

返回时,隐式类型转换,例如 begin,实际上是 iterator(_head->_next),单参数的构造函数支持隐式类型转换。

五、其他接口

void push_back(const T& val)
{/*Node* newnode = new Node(val);Node* tail = _head->_prev;newnode->_prev = tail;tail->_next = newnode;newnode->_next = _head;_head->_prev = newnode;*/insert(end(), val);
}void push_front(const T& val)
{insert(begin(), val);
}void pop_back()
{erase(--end());
}void pop_front()
{erase(begin());
}// pos 位置之前插入
iterator insert(iterator pos, const T& x)
{Node* newnode = new Node(x);Node* cur = pos._node; // 迭代器访问成员变量,获取 Node*Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return newnode; // 插入的位置
}// 删除 pos 位置
iterator erase(iterator pos)
{assert(pos != end()); // 删除的不为头结点Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete[] cur;--_size;return next; // 删除后的位置
}size_t size()
{return _size;
}

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

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

相关文章

RocketMQ环境搭建

环境搭建 环境准备 下载地址: https://downloads.apache.org/rocketmq/4.9.5/安装 上传至服务器 mkdir /usr/soft #上传至此目录/usr/softmkdir /usr/soft 解压 cd /usr/soft unzip rocketmq-all-4.9.5-bin-release.zip移动 mkdir /usr/local/rocketmq cd /usr/soft mv r…

Kubernetes - kubeadm部署

Kubernetes - kubeadm部署 1 环境准备1.1 在各个节点上配置主机名&#xff0c;并配置 Hosts 文件1.2 关闭防护墙&#xff0c;禁用selinux&#xff0c;关闭swap1.3 配置免密登录1.4 配置内核参数1.5 配置br_netfilter 2. 安装K8s2.1 安装docker(各节点)2.2 安装K8s组件(各节点)2…

安达发|如何选择更适合我们的APS高级排程软件

如何选择aps高级排程公司更适合我们?在选购aps高级排程的时候&#xff0c;一些朋友由于不清楚其中的选购技巧&#xff0c;许多时候会掉入些许选择误区&#xff0c;导致我们买不了合适我们选择的aps高级排程。因此选择适合我们的aps高级排程就变得十分重要&#xff0c;唯有明白…

使用typora+PicGo+Gitee简单实现图片上传功能

本文通过配置PicGoGitee来实现typora图片上传功能&#xff0c;系统是window 注意下载的清单有&#xff1a;PicGo&#xff0c;node.js&#xff0c;配置有&#xff1a;PicGo&#xff0c;node.js&#xff0c;gitee&#xff0c;typora 看着复杂实际上并不难&#xff0c;只是繁琐&am…

Django项目开发快速入门

Django项目开发快速入门 生成Django项目编写module后台管理系统admin自定义管理页面视图函数使用Django模板 生成Django项目 现在cmd中使用命令安装Django框架 pip install django3.2使用命令生成项目 django-admin startproject DjStore使用命令生成应用 python .\manage.…

学生成绩管理系统|Python小应用练习

题目要求 实现学生成绩管理系统 输入学生成绩信息序列&#xff0c;获得成绩从高到低、从低到高、按某一门成绩的排列,相同成绩都按先录入排列在前的规则处理。 数据如下&#xff1a;(数据规则&#xff1a;学生姓名 高数成绩 英语成绩 大物成绩) SanZhang 70 80 61 SiLi 86 77 …

CS 144 Lab Zero -- 可靠的内存字节流

CS 144 Lab Zero -- 可靠的内存字节流 环境搭建使用socket写一个网络程序In-memory reliable byte stream 对应课程视频: 【计算机网络】 斯坦福大学CS144课程 Lab 0 对应的PDF: Lab Checkpoint 0: networking warmup Lab 0 会省去Telnet部分内容。 环境搭建 Run Ubuntu ver…

golang 日志库logrus实践

logrus完全兼容标准的log库&#xff0c;还支持文本、JSON 两种日志输出格式。很多知名的开源项目都使用了这个库&#xff0c;如大名鼎鼎的 docker。 快速使用 第三方库需要先安装&#xff1a; $ go get github.com/sirupsen/logrus 后使用&#xff1a; package mainimport (&qu…

go语言终端交叉编译的事项windows编译其它平台软件包

交叉编译的终极版本[以此为准]&#xff1a; windows编译窗口目前分为cmd窗口&#xff0c;powershell窗口&#xff0c;这两个里面运行的命令不一样。 1.cmd窗口编译&#xff1b; 在windows10之前的系统版本上使用cmd命令行可以使用命令 CMD命令行中 在CMD命令行中编译&#…

HarmonyOS/OpenHarmony应用开发-程序包多HAP机制(下)

三、多HAP的开发调试与发布部署流程 &#xff08;一&#xff09;多HAP的开发调试与发布部署流程如下图所示。 图1 多HAP的开发调试与发布部署流程 &#xff08;二&#xff09;开发 开发者通过DevEco Studio工具按照业务的需要创建多个Module&#xff0c;在相应的Module中完成…

抖音seo源码搭建,抖音矩阵系统源码分发,抖音矩阵账号管理

前言&#xff1a; 抖音seo源码&#xff0c;抖音矩阵系统源码搭建&#xff0c;抖音矩阵同步分发。抖音seo源码部署是需要对接到这些正规接口再来做开发的&#xff0c;目前账号矩阵程序开发的功能&#xff0c;围绕一键管理多个账号&#xff0c;做到定时投放&#xff0c;关键词自动…

spring复习:(40)全注解的spring AOP

零、需要的依赖&#xff1a; <dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.9</version></dependency><dependency><groupId>org.aspectj</groupId><arti…

OpenCv之图像形态学

目录 一、形态学 二、图像全局二值化 三、自适应阈值二值化 四、腐蚀操作 五、获取形态学卷积核 六、膨胀操作 七、开运算 八、闭运算 一、形态学 定义: 指一系列处理图像形状特征的图像处理技术形态学的基本思想是利用一种特殊的结构元(本质上就是卷积核)来测量或提取输…

11.Ceph 对象存储系统 RGW 接口

文章目录 Ceph 对象存储系统 RGW 接口概念逻辑单位创建RGW接口开启httphttps创建RadosGW账户S3接口访问测试 Ceph 对象存储系统 RGW 接口 概念 对象存储&#xff08;object storage&#xff09;是非结构数据的存储方法&#xff0c;对象存储中每一条数据都作为单独的对象存储&…

【Ajax】笔记-取消请求

在进行AJAX(Asynchronous JavaScript and XML) 请求时&#xff0c;有时候我们需要取消正在进行的请求。取消请求可以帮助我们提高用户体验&#xff0c;病减少不必要的网络流量和服务器负载。 取消请求的方法 在AJAX请求中&#xff0c;我们可以使用以下方法来取消正在进行的请求…

润和软件与华秋达成生态共创合作,共同推动物联网硬件创新

7月11日&#xff0c;在2023慕尼黑上海电子展现场&#xff0c;江苏润开鸿数字科技有限公司(以下简称“润开鸿”)与深圳华秋电子有限公司(以下简称“华秋”)签署了生态共创战略合作协议&#xff0c;共同推动物联网硬件生态繁荣发展。当前双方主要基于润开鸿的硬件产品及解决方案开…

qgis以某个字段(属性)的类目值来分类显示不同的颜色和调整每个类别的绘制顺序(一个类别在另一个类别上面不被覆盖)和将某字段(属性)的值当成图层标签进行显示

前言 我这里的样例以北京景区的点shp数据为例,属性表如下所示: 我们将以景区的等级划分成五类,分别显示不同的颜色,并且5A景区绘制在4A景区上方不被遮挡…,并且将景区的名称显示在景区点的上方。 一、分类不同颜色显示 1、自动分配颜色 首先,我们双击图层文件,打开…

51单片机学习--LED流水灯

#include <REGX52.H> #include <INTRINS.H>void Delay1ms(int t) //11.0592MHz {while(t --) {unsigned char i, j;_nop_(); //需要添加头文件i 2;j 199;do{while (--j);} while (--i);} }//延时1ms执行t次void main() {while(1){P2 0xFE; //1111 1110Delay1ms…

优维科技通过TMMi3级认证,软件测试能力迈上新台阶

近日&#xff0c;优维科技正式通过国际软件测试成熟度模型集成&#xff08;TMMi&#xff09;3级认证&#xff0c;标志着优维科技的软件测试能力、风险应对水平、产品质量管理水平、测试技术创新能力迈上新台阶&#xff0c;获得国际权威组织认可。 TMMi全称为Test Maturity Mode…

在Windows Server2016上搭建Active Directory域控服务

搭建服务端 使用Windows2016数据中心版完成 1. 配置服务器角色 2. 选择服务器角色 3. 选择当前服务器4. 选择Active Directory和DNS角色5. 确认安装 6. 提升为Domain Controller域控服务器 7. 设置根域 8. 配置保护密码 9. DNS 10. NetBIOS配置 11. 指定数据文件位置 12. 确…