【C++笔记】C++ list类模拟实现

【C++笔记】C++ list类模拟实现

  • 一、初始化和各种构造
    • 1.1、准备工作
    • 1.2、各种构造和析构
  • 二、插入和删除
    • 2.1、插入
    • 2.2、删除
  • 三、迭代器
    • 3.1、正向迭代器
    • 3.2、反向迭代器
    • 3.3、提供迭代器位置
  • 四、其他一些接口
    • 4.1、链表的长度和判空
    • 4.2、返回链表的头尾结点

一、初始化和各种构造

C++的list类使用的模型是带头双向循环链表:
在这里插入图片描述
所以今天我们也是基于这个模型来实现一个list。

1.1、准备工作

在定义链表类之前,我们先要定义链表的节点类:

// list节点类
template <class T>
struct ListNode {// 构造函数ListNode(const T& val = T()) {_val = val;_pre = nullptr;_next = nullptr;}T _val;ListNode<T>* _pre; // 指向前一个节点ListNode<T>* _next; // 指向后一个节点
};

然后就是list类了:

template <class T>
class list {typedef ListNode<T> Node;
public :
private :Node* _head;size_t _size;
};

因为我们所写的是带头链表,所以在list类里就只需要顶一个头节点和size即可。

1.2、各种构造和析构

有了链表类,我们就要先来实现各种构造和析构了。
空初始化
但在开始实现构造之前,我们必须先得实现一个“空初始化”,因为我们设计的链表是带头结点的,并且这个头结点是不算入有效数据的。
所以我们要先将这个头结点申请出来,这也就是空初始化要做的事情:

// 空初始化
void emptyInit() {// 开辟一个头节点Node* newHead = new Node();_head = newHead;_head->_next = _head;_head->_pre = _head;_size = 0;
}

无参构造
无参构造的作用就是只创建一个链表对象,其他什么都不做,所以我们直接调用空初始化即可:

// 无参构造函数
list() {emptyInit();
}

以n个相同值构造
这个其实我们可以直接复用后面写的尾插,直接一直追加即可。
在正式插入之前,我们还是先要处理头节点,即调用空初始化:

// 以n个相同值构造
list(int n, const T& val = T()) {// 先空初始化emptyInit();//  插入for (int i = 0; i < n; i++) {push_back(val);}
}

以一段迭代器区间初构造
这个其实也和其他容器是一样的:

// 以一段迭代器区间初始化
template <class Iterator>
list(Iterator first, Iterator last) {// 先空初始化emptyInit();while (first != last) {push_back(*first);first++;}
}

拷贝构造
对于其他容器而言,链表的拷贝构造我想是最简单的了,因为它的空间本身就不连续,也就不必申请新的一段连续的空间再拷贝数据了。
其实我们直接将两个链表的哨兵位节点交换即可。
所以我们可以先实现一个交换函数:

// 交换
void Swap(list<T>& lt) {swap(_head, lt._head);swap(_size, lt._size);
}

然后我们在构造函数中创建一个临时对象,再将这个对象与我们要构造的对象交换即可:

// 拷贝构造
list(const list<T>& lt) {// 先空初始化emptyInit();// 创建一个临时对象list<T> temp(lt.begin(), lt.end());// 交换即可Swap(temp);
}

析构函数
析构函数,我这里并不想使用以前的方法依次删除节点,我这里打算复用其他的函数。
我们先得要实现一个清数据的函数——只清理有效数据,不清理哨兵头节点:

// 清除数据
void clear() {iterator it = begin();while (it != end()) {it = erase(it);}
}

这里用到的迭代器后面后讲到。

然后在析构函数中,我们只需要调用一次clear然后再将哨兵头节点释放掉即可:

// 析构函数
~list() {clear();delete _head;
}

二、插入和删除

2.1、插入

尾插
尾插其实就和我们以前写的带头双向循环链表一模一样了:

	// 尾插
void push_back(const T& val) {Node* tail = _head->_pre;// 申请一个新节点Node* newNode = new Node(val);tail->_next = newNode;newNode->_pre = tail;newNode->_next = _head;_head->_pre = newNode;_size++;
}

头插

	// 头插void push_front(const T& val) {// 申请新的头节点Node* newHead = new Node(val);// 原来的头节点Node* head = _head->_next;_head->_next = newHead;newHead->_pre = _head;newHead->_next = head;head->_pre = newHead;_size++;}

随机插入
在pos位置之前插入一个新节点,并返回新节点的位置:

iterator insert(iterator pos, const T& val) {// 申请一个新节点Node* newNode = new Node(val);Node* Pre = pos._node->_pre;Node* Next = pos._node;Pre->_next = newNode;newNode->_pre = Pre;newNode->_next = Next;Next->_pre = newNode;_size++;return newNode;
}

2.2、删除

尾删

// 尾删
void pop_back() {assert(!empty());Node* tail = _head->_pre;Node* Pre = tail->_pre;// 释放尾节点delete tail;Pre->_next = _head;_head->_pre = Pre;_size--;
}

头删

// 头删
void pop_front() {assert(!empty());// 旧的头节点Node* head = _head->_next;// 新的头节点Node* newHead = head->_next;// 释放旧的头节点delete head;_head->_next = newHead;newHead->_pre = _head;_size--;
}

随机删除
删除pos位置的节点,并返回被删除节点的下一个节点的位置:

iterator erase(iterator pos) {assert(!empty());Node* cur = pos._node;Node* Pre = cur->_pre;Node* Next = cur->_next;// 释放原来的节点delete cur;Pre->_next = Next;Next->_pre = Pre;_size--;return Next;
}

三、迭代器

因为链表的哨兵头节点是私有的,并且链表也不支持随机访问(不能实现方括号重载),所以我们遍历链表的方式就只剩一种——迭代器。

3.1、正向迭代器

因为链表的空间不是连续的,所以我们就不能直接用原生指针来模拟了。我们需要对原生指针再进行封装。
并且迭代器的++和–也要通过运算符重载的方式达到:

// list迭代器类
template <class T, class ref, class ptr>
struct ListIterator {typedef ListNode<T> Node;typedef ListIterator<T, ref, ptr> iterator;
public :// 构造函数ListIterator(Node* node = nullptr) {_node = node;}// 拷贝构造ListIterator(const iterator& it) {_node = it._node;}// 星号解引用运算符重载ref operator*() {return _node->_val;}// 箭头解引用运算符重载ptr operator->() {return &_node->_val;}// 前置++运算符重载iterator& operator++() {_node = _node->_next;return *this;}// 后置++运算符重载iterator& operator++(int) {iterator* temp = new iterator(this->_node);++(*this);return *temp;}// 前置--运算符重载iterator& operator--() {_node = _node->_pre;return *this;}// 后置--运算符重载iterator& operator--(int) {iterator* temp = new iterator(this->_node);--(*this);return *temp;}// 等于运算符重载bool operator==(const iterator& it) {return _node == it._node;}// 不等于运算符重载bool operator!=(const iterator& it) {return !((*this) == it);}
public :Node* _node;
};

3.2、反向迭代器

实现反向迭代器我们其实只需要适配正向迭代器即可,因为反向迭代器的逻辑几乎和正向迭代器是一样的,只是在对反向迭代器进行++和–的时候需要和正向迭代器相反:

// list反向迭代器类
template <class Iterator, class Ref, class Ptr>
struct Reverse_iterator
{Iterator _it;typedef Reverse_iterator<Itertor, Ref, Ptr> Self;Reverse_iterator(Iterator it): _it(it){}Ref operator*(){Iterator tmp = _it;return *(--tmp);}Ptr operator->(){return &(operator*());}Self& operator++(){--_it;return *this;}Self& operator--(){++_it;return *this;}bool operator!=(const Self& s){return _it != s._it;}
};

3.3、提供迭代器位置

// 正向const迭代器起始位置
const_iterator begin() const
{return const_iterator(_head->_next);
}
// 正向const迭代器结束位置
const_iterator end() const
{return const_iterator(_head);
}// 正向迭代器起始位置
iterator begin()
{return iterator(_head->_next);
}// 正向迭代器结束位置
iterator end()
{return iterator(_head);
}// 反向const迭代器起始位置
const_reverse_iterator rbegin() const
{return const_reverse_iterator(end());
}// 反向const迭代器结束位置
const_reverse_iterator rend() const
{return const_reverse_iterator(begin());
}// 反向迭代器起始位置
reverse_iterator rbegin()
{return reverse_iterator(end());
}// 反向迭代器结束位置
reverse_iterator rend()
{return reverse_iterator(begin());
}

四、其他一些接口

4.1、链表的长度和判空

// 返回长度size_t size() {return _size;}// 判断是否为空bool empty() {return _size == 0;}

4.2、返回链表的头尾结点

// 返回链表的头一个节点(第一个有效节点的值)T& front() {assert(!empty());return _head->_next->_val;}// const版本的头节点const T& front() const {assert(!empty());return _head->_next->_val;}// 返回链表的尾节点(最后一个有效节点的值)T& back() {assert(!empty());return _head->_pre->_val;}// const版本尾节点const T& back() const {assert(!empty());return _head->_pre->_val;}

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

李宏毅hw-10 ——adversarial attack

一、查漏补缺&#xff1a; 1.关于glob.glob的用法&#xff0c;返回一个文件路径的 列表&#xff1a; 当然&#xff0c;再套用1个sort&#xff0c;就是将所有的文件路径按照字母进行排序了 2.relpath relative_path返回相对于基准路径的相对路径的函数 二、代码剖析&#xff…

【红帽】跟着学习如何使用桌面访问命令行

今天我们分享一些红帽Linux的知识&#xff0c;记得关注&#xff0c;会一直更新~ ▶1、以student用户身份并使用student作为密码登录workstation 1.1.在workstation上&#xff0c;从GNOME登录屏幕中单击student用户帐户。系统提示输入密码时&#xff0c;请输入student。 1.2.…

JavaScript系列从入门到精通系列第九篇:JavaScript中赋值运算符和关系运算符以及Unicode编码介绍

一&#xff1a;赋值运算符 1&#xff1a; 右侧的值可以赋值给左侧的变量。 var a 123; console.log(a);//123 2&#xff1a; var a 10; a a 5; a 5; 上边这两个写法是一样的。 3&#xff1a;- var a 10; a a-5; a - 5; 上边这两个写法是一样的。 4&#xff1a;* …

数据备份文件生成--根据表名生成对应的sql语句文件

最近客户有个需求&#xff0c;希望在后台增加手动备份功能&#xff0c;将数据导出下载保存。 当然&#xff0c;此方法不适用于海量数据的备份&#xff0c;这只适用于少量数据的sql备份。 这是我生成的sql文件&#xff0c;以及sql文件里的insert语句&#xff0c;已亲测&#x…

Java抽象类、接口

1.抽象类 1.abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类那么该类就是抽象类。2.抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类3.抽象类,不能使用new关键字来创建对象,它是用来让子类继承的4.抽象方法,只有…

接口测试入门

1. 什么是接口测试 顾名思义&#xff0c;接口测试是对系统或组件之间的接口进行测试&#xff0c;主要是校验数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及相互逻辑依赖关系。其中接口协议分为HTTP,WebService,Dubbo,Thrift,Socket等类型&#xff0c;测试类型又主…

nodejs 如何在npm发布自己的包 <记录>

一、包结构 必要结构&#xff1a; 一个包对应一个文件夹&#xff08;文件夹名不是包名&#xff0c;但最好与包名保持一致&#xff0c;包名以package.json中的name为主&#xff09;包的入口文件index.js包的配置文件package.json包的说明文档README.md 二、需要说明的文件 1.配…

【密码学补充知识】

&#x1f511;密码学&#x1f512;概述 &#x1f4d5; 1.基本概念 明文 &#xff1a; 要交换的信息 密文 &#xff1a; 明文经过一组规则变换成看似没有意义的随机消息。 加密 &#xff1a; 明文经过一组规则变换成密文的过程 解密 &#xff1a; 密文恢复出明文的过程 加…

基于 MATLAB 的电力系统动态分析研究【IEEE9、IEEE68系节点】

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

linux-如何用起来ubuntu

1 Oracle VM VirtualBox安装ubuntu20.04虚拟机 【工具】->【新建】 1.1 虚拟电脑名称和系统类型 【名称】&#xff1a;自定义名称即可 【文件夹】&#xff1a;虚拟机文件将要存储的路径 【虚拟光盘】&#xff1a;将要安装的虚拟机iso文件 1.2 自动安装 【用户名】&…

uniapp:tabBar点击后设置动画效果

APP端不支持dom操作&#xff0c;也不支持active伪类&#xff0c;绞尽脑汁也没办法给uniapp原生的tabBar点击加动画效果&#xff0c;所以最终只能舍弃原生tabBar&#xff0c;改用自定义tabBar。 自定义tabBar的原理是&#xff0c;页面的上部分分别是tabBar对应的页面组件&#…

什么是Selenium?使用Selenium进行自动化测试!

你知道什么是 Selenium 吗&#xff1f;你知道为什么要使用它吗&#xff1f;答案就在本文中&#xff0c;很高兴能够与你共飧。 自动化测试正席卷全球&#xff0c;Selenium 认证是业界最抢手的技能之一。 什么是 Selenium&#xff1f; Selenium 是一种开源工具&#xff0c;用于…

400电话申请流程详解,助您快速办理联通、移动、电信400电话

导语&#xff1a;随着企业业务的发展&#xff0c;越来越多的企业开始关注400电话的申请与办理。本文将为您详细介绍联通、移动、电信400电话的申请流程&#xff0c;帮助您快速办理400电话&#xff0c;提升企业形象和客户服务质量。 一、联通400电话申请流程 咨询与选择&#x…

孤网双机并联逆变器下垂控制策略MATLAB仿真模型

微❤关注“电气仔推送”获得资料 主体模块&#xff1a; 建议使用MATLAB2021b及以上版本打开&#xff01; 功率计算模块、下垂控制模块、电压电流双环控制模块 系统输出有功功率: 系统输出无功功率&#xff1a; 系统频率变化曲线: 参考文献&#xff1a; 微电网并网运行模式下…

arcgis搭建离线地图服务WMTS

Arcgis搭建离线地图服务WMTS 发布时间&#xff1a;2021-03-04 版权&#xff1a; ARCGIS搭建离线地图服务器&#xff0c;进行离线地图二次开发 2. 离线地图服务发布&#xff08;WMTS服务&#xff09; &#xff08;详细教程&#xff1a;卫星地图_高清卫星地图_地图编辑_离线地…

数据结构上机1

1、题目&#xff1a; 将1~10存入数组a[10]&#xff0c;并将其逆序输出 #define _CRT_SECURE_NO_WARNINGS 1 //(1) 将1~10存入数组a[10]&#xff0c;并将其逆序输出#include <stdio.h>int main() {int a[10];// 将1到10存入数组a[10]for (int i 0; i < 10; i){a[i] i…

Day 02 python学习笔记

python运算符 算术运算符 混合运算的优先级&#xff1a; () 高于 ** * / // % 高于 - 赋值运算符 - * / ** a 1 > a 3 > a a 3 其余同理 注意&#xff1a; python没有自增自减 &#xff08;a a a-- --a&#xff09;…

ad18学习笔记十一:显示和隐藏网络、铺铜

如何显示和隐藏网络&#xff1f; Altium Designer--如何快速查看PCB网络布线_ad原理图查看某一网络的走线_辉_0527的博客-CSDN博客 AD19(Altium Designer)如何显示和隐藏网络 如何显示和隐藏铺铜&#xff1f; Altium Designer 20在PCB中显示或隐藏每层铺铜-百度经验 AD打开与…

404. 左叶子之和

给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 示例 1&#xff1a; 输入: root [3,9,20,null,null,15,7] 输出: 24 解释: 在这个二叉树中&#xff0c;有两个左叶子&#xff0c;分别是 9 和 15&#xff0c;所以返回 24示例 2: 输入: root [1] 输出: 0 //bfs …

4年从外包到外企,一个测试老鸟的自述

4年前&#xff0c;我拖着行李箱来到北京&#xff0c;成为了一名北漂&#xff0c;离开了校园的庇护&#xff0c;只身一人&#xff0c;想要在这片陌生的地方闯出一番名堂&#xff0c;可最后却不得人意&#xff0c;面临着所有北漂群体的共同困局&#xff0c;没有归属感&#xff0c…