list部分接口模拟实现(c++)

List

  • list简介
  • list基本框架
    • list构造函数
      • list_node结构体的默认构造
      • list类的默认构造
    • push_back()
    • iteartor迭代器
    • 迭代器里面的其他接口
      • const迭代器
      • 通过模板参数实现复用
      • operator->()
    • insert()
    • erase()
    • clear()
    • 析构函数
    • 迭代器区间构造
    • 拷贝构造
    • operator=()

list简介

- list可以在常数范围内在任意位置进行插入和删除的序列式容器,并且容器可以双向迭代。
- list底层是一个带头双向链表结构,通过指针连接前一个和后一个元素。
- list支持在任意位置进行插入、删除元素,效率更好。
- list不支持随机访问

list基本框架

namespace xty
{//带头双向循环链表template<class T>struct list_node{list_node<T>* _prev;list_node<T>* _next;T _data;};template<class T>class list{typedef list_node<T> node;private:node* _head;//头指针};
}

list构造函数

我们实现一个无参构造函数,但是在这之前我们需要做一些准备工作,先实现一个申请list_node的构造函数,在struct类里面实现。

list_node结构体的默认构造

	//带头双向循环链表template<class T>struct list_node{list_node<T>* _prev;list_node<T>* _next;T _data;//在创建list_node变量时,自动调用构造list_node(const T& val = T()):_prev(nullptr),_next(nullptr),_data(val){}};

为什么不使用class,而使用struct呢?
因为我们想达到这样一个目的:想要让别人自由的访问自己的成员函数,不做任何限制,就用struct。而list_node,list是要经常使用的,因此使用struct修饰list_node比较方便。

list类的默认构造

仅仅申请一个空的头结点,next都指向头

在这里插入图片描述

		list(){//两种申请方法都可以//_head = new list_node<T>_head = new node;_head->next = _head;_head->prev = _head;//_head->_data已经在new的时候调用构造了}

push_back()

先记录一下尾结点,插入更简单。

		void push_back(const T& x){//留记录尾结点node* tail = _head->_prev;node* new_node = new node(x);//传入x值tail->_next = new_node;new_node->_next = _head;_head->_prev = new_node;new_node->_prev = tail;}

iteartor迭代器

整体框架:

	//iteartortemplate<class T>struct __list_iterator{typedef list_node<T> node;node* _node;//成员变量//构造函数__list_iterator(node* x):_node(x){}T& operator*(){return _node->_data;}//++返回相应的迭代器__list_iterator<T> operator++(){_node = _node->_next;return *this;}//是位置是否相等,不是内容是否相等。bool operator!=(__list_iterator<T> x){return _node != x._node;}};template<class T>class list{typedef list_node<T> node;public://迭代器重命名typedef __list_iterator<T> iterator;public://仅仅申请一个空的头结点,next都指向头list(){//两种申请方法都可以//_head = new list_node<T>_head = new node;_head->_next = _head;_head->_prev = _head;//_head->_data已经在new的时候调用构造了了}iterator begin(){iterator it(_head->_next);return it;}iterator end(){return iterator(_head);}

语法细节理解:

  1. 为什么把iterator和list进行单独封装?写一块不好吗?
    首先list只会用到迭代器的begin()和end()函数。其他的像++,其实都是通过迭代器的对象调用的(并不是list的对象调用的)。把iterator从list中抽离出来,不仅可以减少list的复杂性,还能让人更加清楚,迭代器其实也是一个模板,它能被抽离出来,用以实现各种数据结构的迭代!而list内部的begin和end接口,千篇一律。这样就达到的封装的目的!所以,还是分开写的好,逻辑更清晰,更容易维护。
  2. struct __list_iterator结构体里面为什么要写构造函数呢?
    在c++里struct也被当做是类!类里有构造函数很正常,还可以有拷贝构造呢!只不过struct默认是public的。这样我们在声明该类型的变量时,函数会自动调用构造函数,使该结构体的数据自动是被初始化过的。
	xty::list<int>::iterator it = lt.begin();  //调用对象需要用list//xty::list<int>::iterator it(lt.begin());  //调用对象需要用listwhile (it != lt.end()){cout << *it << endl;++it;}

写了构造函数之后,第二种声明方式也是可以的。而第一种方式其实调用的是拷贝构造函数,但是编译器给优化成了拷贝构造,我们没有写拷贝构造,编译器会调用默认的拷贝构造,是一个浅拷贝。但是我们不是经常说浅拷贝会造成析构问题?这里为什么不会?因为我们没有写析构函数,而且析构函数。为什么不写析构函数呢?因为没有什么可以析构的,函数的结点是list里的内容,不需要迭代器的析构函数管,因此我们不写析构函数。

  1. 迭代器++返回的是迭代器的类型。
  2. 注意:_list_iterator是类名,_list_iterator才是类型!
  3. list里面的begin要返回迭代器类型,然而怎么由指针转化成迭代器类型呢?要利用迭代器的构造函数来返回。

迭代器里面的其他接口

		bool operator==(const __list_iterator<T>& x){return _node == x._node;}//i++__list_iterator<T> operator++(int){__list_iterator<T> tem(*this);_node = _node->_next;return tem;}__list_iterator<T> operator--(){_node = _node->_prev;return *this;}__list_iterator<T> operator--(int){__list_iterator<T> tem(*this);_node = _node->_prev;return tem;}

语法细节理解:

  1. 注意迭代器传进来的参数基本上都是迭代器,如果不更改,最好加上const和&。
  2. 如果命名空间冲突,注意在函数声明和定义的时候也要加上空间名。
void Print(xty::list<int>& lt);
  1. 我们发现__list_iterator 有点长,我们重命名一下。
	//iteartortemplate<class T>struct __list_iterator{typedef list_node<T> node;typedef __list_iterator<T> self;node* _node;//成员变量//构造函数__list_iterator(node* x):_node(x){}T& operator*(){return _node->_data;}//++返回相应的迭代器self operator++(){_node = _node->_next;return *this;}//是位置是否相等,不是内容是否相等。bool operator!=(const __list_iterator<T>& x){return _node != x._node;}bool operator==(const __list_iterator<T>& x){return _node == x._node;}//i++self operator++(int){self tem(*this);_node = _node->_next;return tem;}self operator--(){_node = _node->_prev;return *this;}self operator--(int){self tem(*this);_node = _node->_prev;return tem;}};

const迭代器

要实现const迭代器只需要再写一个const类即可。
记住是指针可以修改,但是内容不可以修改,因此不能在this那加const。

		//迭代器重命名typedef __list_iterator<T> iterator;typedef const__list_iterator<T> const_iterator;public:const_iterator begin()const{const_iterator it(_head->_next);return it;}const_iterator end()const{return const_iterator(_head);}

list里面的迭代器修改仅仅需要,typedef一下,然后将构造函数改成所需要的const类型即可。


而我们需要再实现一个const类型的iterator

	template<class T>struct const__list_iterator{typedef list_node<T> node;typedef const__list_iterator<T> self;node* _node;//成员变量//构造函数const__list_iterator(node* x):_node(x){}const T& operator*()  //值不仅可以修改!!{return _node->_data;}//++返回相应的迭代器self operator++()  //指针可以修改!!{_node = _node->_next;return *this;}//是位置是否相等,不是内容是否相等。bool operator!=(const self& x)const{return _node != x._node;}bool operator==(const self& x)const{return _node == x._node;}//i++self operator++(int)  //指针可以修改!!!{self tem(*this);_node = _node->_next;return tem;}self operator--()  //指针可以修改!!!{_node = _node->_prev;return *this;}self operator--(int)  //指针可以修改!!!{self tem(*this);_node = _node->_prev;return tem;}};

问题:代码写完之后,我们发现实际上只有operator*()的返回值加了const,其他的地方没有改,因此,我们利用模板参数赋用解决问题。

通过模板参数实现复用

	template<class T,class Ref>struct __list_iterator{typedef list_node<T> node;typedef __list_iterator<T,Ref> self;node* _node;//成员变量//构造函数__list_iterator(node* x):_node(x){}Ref operator*(){return _node->_data;}...}template<class T>class list{typedef list_node<T> node;public://迭代器重命名typedef __list_iterator<T, T&> iterator;typedef __list_iterator<T,const T&> const_iterator;public://仅仅申请一个空的头结点,next都指向头list(){//两种申请方法都可以//_head = new list_node<T>_head = new node;_head->_next = _head;_head->_prev = _head;//_head->_data已经在new的时候调用构造了了}}

在这里插入图片描述

通过增加类模板参数,这种问题被很巧妙的解决了。请好好体会!

operator->()

当我们遇到自定义类型数据链表时,访问数据就会比较麻烦。

	struct AA{int _a1;int _a2;AA(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}};void test_aa(){xty::list<AA> lt;lt.push_back(AA(1, 1));lt.push_back(AA(2, 2));lt.push_back(AA(3, 3));lt.push_back(AA(4, 4));xty::list<AA>::iterator it = lt.begin();while (it != lt.end()){cout << (*it)._a1 << ":" << (*it)._a2 << endl;++it;}}

如上例子所示,cout方式,在这里很是别扭,因为it是迭代器,我们能不能通过重载->来访问这样的数据呢?
显然是可以的!如下:

		T* operator->(){return &(_node->_data);}

所以我们通过如下方式打印链表的数据:

		xty::list<AA>::iterator it = lt.begin();while (it != lt.end()){//cout << (*it)._a1 << ":" << (*it)._a2 << endl;cout << it->_a1 << ":" << it->_a2 << endl;++it;}

但是这个代码是不是有一点别扭?没看出来的话,我再打印一次:

			//两种打印方式一样!!!cout << it->_a1 << ":" << it->_a2 << endl;cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;

在这里插入图片描述
==解释:==之所以出现这样的原因,是因为编译器自动把两个连续的->优化成一个->,防止观感上的差异,这样设计能让人立马看出这个其实是在访问AA的数据。


为了适应const和非const两种迭代器,我们再设计一个模板参数。如下实例:

	//iteartortemplate<class T,class Ref, class Ptr>struct __list_iterator{typedef list_node<T> node;typedef __list_iterator<T,Ref,Ptr> self;node* _node;//成员变量Ptr operator->(){return &(_node->_data);}
...................}
template<class T>class list{typedef list_node<T> node;public://迭代器重命名typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T,const T&,const T*> const_iterator;public:

insert()

		//在pos位置前插入,返回插入位置的迭代器iterator insert(iterator pos, const T& x){node* new_node = new node(x);node* cur = pos._node;node* prev = cur->_prev;prev->_next = new_node;new_node->_prev = prev;new_node->_next = cur;cur->_prev = new_node;return iterator(cur);}

erase()

返回删除元素的下一个位置的迭代器。

		iterator erase(iterator pos){//不能删头assert(pos._node!=_head);node* cur = pos._node;node* prev = cur->_prev;prev->_next = cur->_next;cur->_next->_prev = prev;delete cur;return iterator(prev->_next)}

注意:删除元素后,pos位置的数据就被删除了,会产生迭代器失效问题,如果再使用pos需要重新赋值。

clear()

清空list的内容,可以借助迭代器和erase()来删除。

		void clear(){iterator it = begin();while (it != end()){it = erase(it);//erase(it++);}}

析构函数

结合clear()来编写。

		~list(){clear();delete _head;_head = nullptr;}

迭代器区间构造

因为迭代器区间构造,也需要一个头结点,所以,我们方便复用,又定义了一个初始化函数,具体改动如下:

		list(){empty_init();//两种申请方法都可以//_head = new list_node<T>//_head = new node;//_head->_next = _head;//_head->_prev = _head;//_head->_data已经在new的时候调用构造了了}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;}}

拷贝构造

提供了两种方法,第一种方法效率较低,第二种利用swap提高了效率。

		lt2(lt)//list(const list<T>& lt)//{//	empty_init();//	for (auto e : lt)//	{//		push_back(e);//	}//}void swap(list<T>& tem){std::swap(_head, tem._head);}list(const list<T>& lt){empty_init();//初始化thislist<T> tem(lt.begin(), lt.end());swap(tem);}

operator=()

实现较为简单。

//注意这里不能传引用list<T>& operator=(list<T> lt){swap(lt);return *this;}

最后一个问题:

const list<int> lt;

这行代码能调用构造函数吗?
答案是肯定的,因为变量在最开始是没有const属性的,定义好了以后,才有const属性。否则const都没法定义出来了。

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

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

相关文章

【Java】本地开发环境正常、测试或生产环境获取的文件路径不对的问题

引 Java 中经常获取本地文件或者resource下的文件&#xff0c;要获取文件&#xff0c;首先要获得本地路径。 Java 本身或一些开源工具包都提供了很多获取路径的方法。但使用时经常遇到本地开发环境正常、测试或生产环境获取的文件路径不对的问题。 本文将列出几种常见的获取…

python开发过程中注意编码规范~

文章目录 一、 代码编排二、 文档编排三、 空格的使用四、 注释五、 文档描述六、 命名规范总体原则&#xff0c;新编代码必须按下面命名风格进行&#xff0c;现有库的编码尽量保持风格。七 编码建议关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、…

【三维重建】摄像机几何

针孔相机模型 为了方便我们对针孔相机模型进行数学建模&#xff0c;我们往往对虚拟像平面进行研究&#xff0c;因为虚拟像平面的方向与我们实际物体的方向一致。 通过相似三角形法可以得到三维坐标到二维坐标映射 将像平面原点坐标移动到左下角&#xff1a; 加上现实世界单位&a…

【神经网络】【GoogleNet】

1、引言 卷积神经网络是当前最热门的技术&#xff0c;我想深入地学习这门技术&#xff0c;从他的发展历史开始&#xff0c;了解神经网络算法的兴衰起伏&#xff1b;同时了解他在发展过程中的**里程碑式算法**&#xff0c;能更好的把握神经网络发展的未来趋势&#xff0c;了解神…

【Linux笔记】Linux环境变量与地址空间

【Linux笔记】Linux环境变量与地址空间 一、命令行参数1.1、main函数的参数1.2、main函数的第三个参数 二、环境变量的概念与内容2.1、环境变量的概念2.2、环境变量的分类2.3、环境变量的组织形式2.4、常见的环境变量 三、设置环境变量3.1、通过命令获取或设置环境变量3.2、通过…

补偿 FIR 滤波器引入的延迟

补偿 FIR 滤波器引入的延迟 对信号进行滤波会引入延迟。这意味着相对于输入&#xff0c;输出信号在时间上有所偏移。此示例向您说明如何抵消这种影响。 有限冲激响应滤波器经常将所有频率分量延迟相同的时间量。这样&#xff0c;我们就很容易通过对信号进行时移处理来针对延迟…

SMART PLC模拟量上下限报警功能块(梯形图代码)

博途PLC模拟量偏差报警功能块请参考下面的文章链接: 模拟量偏差报警功能块(SCL代码)_RXXW_Dor的博客-CSDN博客文章浏览阅读594次。工业模拟量采集的相关基础知识,可以查看专栏的系列文章,这里不再赘述,常用链接如下:PLC模拟量采集算法数学基础(线性传感器)_plc傳感器數…

目标检测算法 - YOLOv1

文章目录 1. 作者简介2. 目标检测综述3. YOLOv1算法3.1 预测阶段3.2 预测阶段后处理3.3 训练阶段 YOLO的全称是you only look once&#xff0c;指只需要浏览一次就可以识别出图中的物体的类别和位置。 YOLO是目标检测模型。目标检测是计算机视觉中比较简单的任务&#xff0c;用…

OpenHarmony创新赛|赋能直播第五期

OpenHarmony创新赛赋能直播课程即将再次与大家见面&#xff01;本期基于之前的青蛙影院的UI界面设计的课程&#xff0c;介绍综合性APP的需求介绍和技术栈整合等内容。此外&#xff0c;课程同步赋能OpenHarmony创新赛&#xff0c;为参赛开发者带来新的思路和方法&#xff0c;也欢…

卷积神经网络中 6 种经典卷积操作

深度学习的模型大致可以分为两类&#xff0c;一类是卷积神经网络&#xff0c;另外一类循环神经网络&#xff0c;在计算机视觉领域应用最多的就是卷积神经网络&#xff08;CNN&#xff09;。CNN在图像分类、对象检测、语义分割等经典的视觉任务中表现出色&#xff0c;因此也早就…

【Python3】【力扣题】242. 有效的字母异位词

【力扣题】题目描述&#xff1a; 【Python3】代码&#xff1a; 1、解题思路&#xff1a;若字符串长度相同&#xff0c;依次遍历元素&#xff0c;比较两个字符串的该元素个数是否相同。【耗时长】 知识点&#xff1a;len(...)&#xff1a;获取序列&#xff08;字符串、列表等&…

【uniapp】通用列表封装组件

uniapp页面一般都会有像以下的列表页面&#xff0c;封装通用组件&#xff0c;提高开发效率&#xff1b; &#xff08;基于uView前端框架&#xff09; 首先&#xff0c;通过设计图来分析一下页面展示和数据结构定义 w-table组件参数说明 参数说明类型可选值默认值toggle列表是…

关于Android Studio中开发Flutter配置

配置系统环境变量&#xff1a;path下 &#xff0c;flutter的bin目录下 File->Settings->Languages&Frameworks->FlutterFile->Settings->Languages&Frameworks->DartFile->Settings->Languages&Frameworks->Android SDK 确认是…

单链表(增删改查)【超详细】

目录 单链表 1.单链表的存储定义 2.结点的创建 3.链表尾插入结点 4.单链表尾删结点 5.单链表头插入结点 6.单链表头删结点 7.查找元素&#xff0c;返回结点 8.在pos结点前插入一个结点 ​编辑 9.在pos结点后插入一个结点 10.删除结点 11.删除pos后面的结点 12.修改…

flink1.18.0 sql-client报错

报错 Flink SQL> > > select * from t1; [ERROR] Could not execute SQL statement. Reason: java.lang.ClassNotFoundException: org.apache.kafka.clients.consumer.OffsetResetStrategy 解决 注意 一定要重启flink服务 否则还会报错: Flink SQL> select *…

CDN是什么?

一.CDN的概念 内容分发网络&#xff08;Content Delivery Network&#xff0c;简称CDN&#xff09;是建立并覆盖在承载网之上&#xff0c;由分布在不同区域的边缘节点服务器群组成的分布式网络。CDN应用广泛&#xff0c;支持多种行业、多种场景内容加速&#xff0c;例如&#…

拆分代码 + 动态加载 + 预加载,减少首屏资源,提升首屏性能及应用体验

github 原文地址 我们看一些针对《如何提升应用首屏加载体验》的文章&#xff0c;提到的必不可少的措施&#xff0c;便是减少首屏幕加载资源的大小&#xff0c;而减少资源大小必然会想到按需加载措施。本文提到的便是一个基于webpack 插件与 react 组件实现的一套研发高度自定…

【算法与数据结构】39、LeetCode组合总和

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;这道题当中数字可以多次使用&#xff0c;那么我们在递归语句当中不能直接找下一个candidate的元素&…

Android Studio导入,删除第三方库

Android项目经常用到无私的程序员们提供的第三方类库。本篇博客就是实现第三方库的导入和删除。 一、导入第三方库 1、将需要的库下载到本地&#xff1b; 2、新建Moudle (1)File --- New Moudle (2)选择Android Library --- Next (3)填写Moudle名 --- Finish。一个新的Mou…

[RCTF 2019]nextphp

文章目录 考点前置知识PHP RFC&#xff1a;预加载FFI基本用法PHP RFC&#xff1a;新的自定义对象序列化机制 解题过程 考点 PHP伪协议、反序列化、FFI 前置知识 PHP RFC&#xff1a;预加载 官方文档 通过查看该文档&#xff0c;在最下面找到预加载结合FFI的危害 FFI基本用法 …