C++模拟实现list

C++教学总目录

C++模拟实现list

  • 1、成员变量
  • 2、迭代器
  • 3、insert函数
  • 4、erase函数
  • 5、pop_back、push_front、pop_front函数
  • 6、size和clear函数
  • 7、析构函数
  • 8、拷贝构造函数
  • 9、赋值运算符重载
  • 完整代码(包含测试代码)

1、成员变量

先来看看SGI版本STL中list的实现方式:
在这里插入图片描述
成员变量就是一个结点的指针。但是我们实现的时候多加一个size来保存结点个数,因为如果计算结点个数需要遍历一遍链表,时间复杂度为O(N),我们选择多提供一个size成员变量。

template<class T>
struct list_node
{list_node<T>* _prev;list_node<T>* _next;T _val;list_node(const T& val = T()):_prev(nullptr), _next(nullptr), _val(val){}
};template<class T>
class list
{typedef list_node<T> Node;
public:void empty_init(){_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;}list(){empty_init();}
private:Node* _head;size_t _size;
};

我们仿照SGI版本的实现,为了方便写将结点类型重命名。
这里提供了一个empty_init函数是用来创建哨兵位的头节点的。当我们调用空的构造函数、带参的构造函数、拷贝构造函数等都需要先创建哨兵位的头节点,所以我们将这一步写在函数里面,后面调用empty_init即可。

2、迭代器

list的迭代器不同于vector和string。vector和string基于底层的性质,可以对其开辟空间的指针进行++/–,所以vector和string的迭代器就是原生指针。但是list的迭代器无法是结点的指针,因为list空间并不连续,指针++/–无法找到下一个/前一个结点。
我们先来看看SGI版本是如何实现的:

在这里插入图片描述
可以看到,list的迭代器是创建了自定义类型来实现的,再对自定义类型重载operator++/–,这样就可以实现迭代器的功能,同时要获取数据就重载operator*。
实现如下:

template<class T>
struct __list_iterator
{typedef list_node<T> Node;Node* _node;__list_iterator(Node* node):_node(node){}T& operator*(){return _node->_val;}__list_iterator<T>& operator++(){_node = _node->_next;return *this;}__list_iterator<T> operator++(int){__list_iterator<T> tmp(*this);_node = _node->_next;return tmp;}__list_iterator<T>& operator--(){_node = _node->_prev;return *this;}__list_iterator<T> operator--(int){__list_iterator<T> tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const __list_iterator<T>& it) const{return _node != it._node;}bool operator==(const __list_iterator<T>& it) const{return _node == it._node;}
};

然后在list类中对该结构体类型重命名,并实现begin和end。

typedef __list_iterator<T> iterator;iterator begin()
{return _head->_next;
}iterator end()
{return _head;
}

接着我们实现一个push_back函数,然后就可以跑起来测试一下我们写的迭代器了:

void push_back(const T& x)
{Node* tail = _head->_prev;Node* newnode = new Node(x);newnode->_prev = tail;tail->_next = newnode;newnode->_next = _head;_head->_prev = newnode;_size++;
}

下面对我们写的迭代器进行测试:
在这里插入图片描述
可以发现我们写的迭代器成功跑起来了。
下面提出几个注意点:
在这里插入图片描述


普通迭代器已经实现了,那么const迭代器呢?
思路一:

	typedef const __list_iterator<T> const_iterator;

这样子行吗?
这样子是不行的,因为const修饰迭代器之后,迭代器内的成员变量是不能修改的,也就不能进行++/–了,所以这样写是不行了。

思路二:

template<class T>
struct __list_const_iterator
{typedef list_node<T> Node;Node* _node;__list_const_iterator(Node* node):_node(node){}const T& operator*(){return _node->_val;}__list_const_iterator<T>& operator++(){_node = _node->_next;return *this;}__list_const_iterator<T> operator++(int){__list_const_iterator<T> tmp(*this);_node = _node->_next;return tmp;}__list_const_iterator<T>& operator--(){_node = _node->_prev;return *this;}__list_const_iterator<T> operator--(int){__list_const_iterator<T> tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const __list_const_iterator<T>& it) const{return _node != it._node;}bool operator==(const __list_const_iterator<T>& it) const{return _node == it._node;}
};typedef __list_const_iterator<T> const_iterator;

将普通迭代器拷贝一份,名字改为__list_const_iterator,operator*改为const T,这样也可以实现const迭代器,但是这么写太冗余了。
我们来看看库里是怎么写的:
在这里插入图片描述
添加了两个模板参数,这里我们先看Ref,Ref实际上就是类型的引用,如果是普通迭代器就是T&,如果是const迭代器就是const T&。通过不同的模板参数实例化出不同类。

我们再来看Ptr,Ptr实际上是T*/const T*,因为迭代器还重载了一个函数:operator->:
在这里插入图片描述
为什么重载这个函数呢?
当我们的T类型是自定义类型时:

struct A
{int _a1;int _a2;A(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}
};int main()
{zzy::list<A> lt;lt.push_back(A(1, 1));lt.push_back(A(2, 2));lt.push_back(A(3, 3));zzy::list<A>::iterator it = lt.begin();while (it != lt.end()){cout << it->_a1 << " " << it->_a2 << endl;++it;}return 0;
}

在这里插入图片描述
这里重载operator->也是需要传模板参数控制,对于普通对象就是T*,对于const对象就是const T*。


那么综合上述,并且为了方便书写迭代器类型,我们对迭代器类型typedef为self,代码如下:

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->_val;}Ptr operator->(){return &(operator*());}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) const{return _node != it._node;}bool operator==(const self& it) const{return _node == it._node;}
};

并且在list类中加上const迭代器:

typedef __list_iterator<T, const T&, const T*> const_iterator;
const_iterator begin() const
{return _head->_next;
}const_iterator end() const
{return _head;
}

但是!!!!!这样写完还是不能调用const迭代器!!!!

zzy::list<A> lt;
lt.push_back(A(1, 1));
lt.push_back(A(2, 2));
lt.push_back(A(3, 3));
zzy::list<A>::const_iterator it = lt.begin();
while (it != lt.end())
{cout << it->_a1 << " " << it->_a2 << endl;++it;
}

上面的代码中,我们虽然指明it是const迭代器,但是lt调用的begin函数还是普通迭代器的begin函数。
这是因为lt是普通对象,所以调用普通迭代器begin,如果lt是const对象,那就会调用const迭代器的begin。
而因为lt.begin的返回值是普通迭代器,而it是const迭代器,所以就变成了用普通迭代器构造const迭代器,而我们迭代器模板类中并没有写普通迭代器构造const迭代器的函数。所以无法从普通迭代器转换成const迭代器,因此无法使用const迭代器。
解决办法很简单,添加一个普通迭代器构造const迭代器就行。

typedef __list_iterator<T, T&, T*> iterator;
__list_iterator(const iterator& it):_node(it._node){}

添加这个函数之后:
1、当模板类实例化为const迭代器时,支持普通迭代器构造const迭代器。
2、当模板类实例化为普通迭代器时,本质上就是普通迭代器的拷贝构造函数。

所以说:在那么早以前,C++祖师爷能搞出这些东西,是多么的牛逼!!!!!!


3、insert函数

iterator insert(iterator pos, const T& x)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return newnode;
}

insert函数实现在pos位置之前插入一个新结点,并返回新节点的迭代器。

4、erase函数

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;
}

5、pop_back、push_front、pop_front函数

这三个函数我们直接复用前面写好的insert和erase就行,前面写的push_back也可以复用insert。

void pop_back()
{erase(--end());
}void push_front(const T& x)
{insert(begin(), x);
}void pop_front()
{erase(begin());
}

6、size和clear函数

size_t size() const { return _size; }// 如果不提供_size成员变量,需要遍历链表求出节点个数。
size_t size() const
{size_t sz = 0;auto it = begin();while (it != end()){++it;++sz;}return sz;
}void clear()
{iterator it = begin();while (it != end()){it = erase(it);}_size = 0;
}

7、析构函数

复用clear函数:

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

8、拷贝构造函数

list(const list<T>& lt)
{empty_init();for (auto& e : lt)push_back(e);
}

9、赋值运算符重载

这个我们使用现代写法,但是需要先实现一个swap函数用来交换list的值。

void swap(list<T>& lt)
{std::swap(_head, lt._head);
}//list& operator=(list& lt) 也可以这么写,但是不推荐
list<T>& operator=(list<T>& lt)
{swap(lt);return *this;
}

可以看到上面的operator=重载的返回值和参数可以直接写为list,但是不推荐这么写。我们还是写成list<T>好一些。

完整代码(包含测试代码)

#pragma oncenamespace zzy
{template<class T>struct list_node{list_node<T>* _prev;list_node<T>* _next;T _val;list_node(const T& val = T()):_prev(nullptr), _next(nullptr), _val(val){}};template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;typedef __list_iterator<T, T&, T*> iterator;Node* _node;__list_iterator(Node* node):_node(node){}__list_iterator(const iterator& it):_node(it._node){}Ref operator*() {return _node->_val;}Ptr operator->(){return &(operator*());}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) const{return _node != it._node;}bool operator==(const self& it) const{return _node == it._node;}};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;iterator begin(){return _head->_next;}iterator end(){return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}void empty_init(){_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;}list(){empty_init();}list(const list<T>& lt){empty_init();for (auto& e : lt)push_back(e);}void swap(list<T>& lt){std::swap(_head, lt._head);}//list& operator=(list& lt) 也可以这么写,但是不推荐list<T>& operator=(list<T>& lt){swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;}size_t size() const { return _size; }//size_t size() const//{//	size_t sz = 0;//	auto it = begin();//	while (it != end())//	{//		++it;//		++sz;//	}//	return sz;//}void clear(){iterator it = begin();while (it != end()){it = erase(it);}_size = 0;}void push_back(const T& x){//Node* tail = _head->_prev;//Node* newnode = new Node(x);//newnode->_prev = tail;//tail->_next = newnode;//newnode->_next = _head;//_head->_prev = newnode;//_size++;insert(end(), x);}void pop_back(){erase(--end());}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return newnode;}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;}private:Node* _head;size_t _size;};void Print(const list<int>& lt){list<int>::const_iterator it = lt.begin();while (it != lt.end()){//(*it) += 1;cout << *it << " ";++it;}cout << endl;}void test_list1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;for (auto e : lt){cout << e << " ";}cout << endl;Print(lt);}struct A{A(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}int _a1 = 0;int _a2 = 0;};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;// 这里的it模拟的是自定义类型的指针,所以可以这样写cout << it->_a1 << " " << it->_a2 << endl;// 严格来说it->->_a1,这么写才是符合语法的// 因为运算符重载要求可读性,编译器特殊处理,省略了一个->it++;}cout << endl;}void test_list3(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_front(5);lt.push_front(6);lt.push_front(7);lt.push_front(8);for (auto e : lt){cout << e << " ";}cout << endl;lt.pop_front();lt.pop_back();for (auto e : lt){cout << e << " ";}cout << endl;lt.clear();lt.push_back(10);lt.push_back(20);lt.push_back(30);lt.push_back(40);for (auto e : lt){cout << e << " ";}cout << endl;cout << lt.size() << endl;}void test_list4(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);for (auto e : lt){cout << e << " ";}cout << endl;list<int> lt1(lt);list<int>::const_iterator it = lt1.begin();while (it != lt1.end()){cout << *it << " ";it++;}cout << endl;list<int> lt2;lt2.push_back(10);lt2.push_back(20);lt2.push_back(30);lt2.push_back(40);lt1 = lt2;for (auto e : lt1){cout << e << " ";}cout << endl;}
}

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

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

相关文章

【STM32】SD卡

(一)常用卡的认识 在学习这个内容之前&#xff0c;作为生活小白的我对于SD卡、TF卡、SIM卡毫无了解&#xff0c;晕头转向。 SD卡&#xff1a;Secure Digital Card的英文缩写&#xff0c;直译就是“安全数字卡”。一般用于大一些的电子设备比如&#xff1a;电脑、数码相机、AV…

品牌怎么找到用户发的优质内容,进行加热、复制?

在&#xff0c;相对传统媒体来说&#xff0c;社交媒体营销具有更高的成本效益。品牌可以通过相对较低的成本达到大量潜在客户&#xff0c;尤其是通过口碑营销和内容分享&#xff0c;可以实现倍增的传播效果。在社媒营销的过程中&#xff0c;去找到与品牌有关的优质、正向内容&a…

物联网设备如何助力实现高效远程老人监护

在发达国家&#xff0c;老龄化进程加速&#xff0c;老年人常需医疗、行动辅助、安全保障及个人卫生护理&#xff0c;费用高昂。传统老人监护依赖护士或助理现场照料&#xff0c;而物联网远程监控方案能有效改进此模式。它通过运用传感器等技术&#xff0c;实现全天候低成本实时…

如何使用和打开jconsole

配置: spring.jmx.enabledtrue spring.jmx.default-domainmybatiesdemo management.endpoints.jmx.exposure.include* 启动参数: -Dcom.sun.management.jmxremote.port9000 -Dcom.sun.management.jmxremote.authenticatefalse -Dcom.sun.management.jmxremote.sslfalse 启动项…

残差块(Residual Block)

1. **残差块的定义与作用**&#xff1a; 残差块通过引入跳跃连接&#xff08;skip-connection&#xff09;或称为快捷连接&#xff08;shortcut connection&#xff09;&#xff0c;允许网络学习输入与输出之间的残差映射&#xff0c;即学习函数&#xff0c;其中 是期望的底层映…

Sigrity Power SI VR noise Metrics check模式如何进行电源噪声耦合分析操作指导

SSigrity Power SI VR noise Metrics check模式如何进行电源噪声耦合分析操作指导 Sigrity Power SI的VR noise Metrics check模式本质上是用来评估和观测器件的电源网络的耦合对于信号的影响,输出S参数以及列出具体的贡献值。 以下图为例

讲个故事-HTTP/HTTPS 协议访问逻辑

一、HTTP/HTTPS 协议基本概念 1、协议 HTTP与 HTTPS 协议都是客户端 浏览器和服务器间的一种约定,约定如何将服务器中的信息下载到本地 ,并通过浏览器显示出来。 不同的是, HTTP 协议是一种明文传输协议,其对传输的数据不提供任何加密措施。而HTTPS 协议则是通过 SSL/TL…

生信入门第八课:RNA-seq比对、定量和差异分析

生信入门合集&#xff1a; 生信入门第一课&#xff1a;VirtualBox安装Ubuntu虚拟机 生信入门第二课&#xff1a;RNA-seq生信分析环境搭建-conda及常用软件安装 生信入门第三课&#xff1a;Linux操作系统简介及生信分析常用30个命令 生信入门第四课&#xff1a;生物信息学常…

打响反对人工智能的第一枪

序言&#xff1a;人工智能的讨论不能只有一片叫好的声音&#xff0c;一味的追捧反而可能隐藏巨大的危机。因此&#xff0c;必须有反对的声音&#xff0c;且越强烈越能激发深入思考。本篇文章的作者就以犀利的视角&#xff0c;漂亮地打响了反对人工智能应用的第一枪。 我以前一…

HR为什么都开始使用智能招聘系统?

数字化时代到来&#xff0c;人力资源管理领域正经历着前所未有的变革。 众所周知&#xff0c;今年的招聘市场&#xff0c;HR们正面临着越来越繁重的招聘任务。传统的招聘方式&#xff0c;如手动筛选简历、安排面试等&#xff0c;耗时费力极易出错。而且&#xff0c;传统的招聘…

机器人大模型GR2——在大规模视频数据集上预训练且机器人数据上微调,随后预测动作轨迹和视频(含清华RDT详解)

前言 上个月的24年10.9日&#xff0c;我在朋友圈看到字节发了个机器人大模型GR2&#xff0c;立马去看了下其论文(当然了&#xff0c;本质是个技术报告) 这次也是我头一次看paper&#xff0c;不看正文&#xff0c;而是直奔其References&#xff0c;​看有没有我预想中的文献&a…

【VSCode】配置

安装插件 C vscode-icons gdb调试 https://www.bilibili.com/video/BV15U4y1x7b2/?spm_id_from333.999.0.0&vd_sourcedf0ce73d9b9b61e6d4771898f1441f7f https://www.bilibili.com/video/BV1pU4y1W74Z?spm_id_from333.788.recommend_more_video.-1&vd_sourcedf0…

客服宝快捷回复软件:客服工作的得力助手

在从事客服工作的这段漫长时间里&#xff0c;响应率和满意度一直是我最为头疼的绩效指标。这两个指标就如同两座大山&#xff0c;压得我时常喘不过气来。 然而&#xff0c;幸运的是&#xff0c;最近我安装了客服宝这个快捷回复软件&#xff0c;这一举措如同为我打开了一扇新的…

Hive的数据存储格式

目录 一、前言 二、存储格式 2.1、文本格式&#xff08;TextFile&#xff09; 2.1.1、定义与特点 2.1.2、存储与压缩 2. 1.3、使用场景 2.2、行列式文件&#xff08;ORCFile&#xff09; 2.2.1、ORC的结构 2.2.2、ORC的数据类型 2.2.3、ORC的压缩格式 2.2.3、ORC存储…

蓝牙BLE开发——红米手机无法搜索蓝牙设备?

解决 红米手机&#xff0c;无法搜索附近蓝牙设备 具体型号当时忘记查看了&#xff0c;如果你遇到有以下选项&#xff0c;记得打开~ 设置权限

华为自研仓颉编程语言官网上线 首个公测版本开放下载

仓颉编程语言官网正式公开上线&#xff0c;同时首个公测版本开放下载。本次仓颉编程语言官网上线了首页、在线体验、文档、学习、下载、动态以及三方库共六个模块&#xff0c;可供开发和学习和体验。 据悉&#xff0c;仓颉编程语言是在今年6月的华为开发者大会上正式公布&…

【AI换脸整合包及教程】AI换脸技术新贵:Rope换脸工具全面解析

随着人工智能技术的快速发展&#xff0c;AI换脸技术逐渐走入大众视野&#xff0c;成为一种既有趣又实用的技术。从早期的DeepFace到后来的Faceswap&#xff0c;再到如今的Rope&#xff0c;每一次技术的革新都带来了更高效、更自然的换脸体验。Rope作为当前市场上最炙手可热的AI…

Ubuntu Linux

起源与背景 Ubuntu起源于南非&#xff0c;其名称“Ubuntu”来源于非洲南部祖鲁语或豪萨语&#xff0c;意为“人性”、“我的存在是因为大家的存在”&#xff0c;这体现了非洲传统的一种价值观。Ubuntu由南非计算机科学家马克沙特尔沃斯&#xff08;Mark Shuttleworth&#xff…

闪存学习_1:Flash-Aware Computing from Jihong Kim

闪存学习_1&#xff1a;Flash-Aware Computing from Jihong Kim 前言一、Storage Media&#xff1a;NAND Flash Memory1、概念2、编程和擦除操作3、读操作4、异地更新操作&#xff08;Out-Place Update&#xff09;5、数据可靠性6、闪存控制器&#xff08;SSD主控&#xff09;7…

嵌入式web开发:boa、lighttpd

嵌入式web开发&#xff1a;boa、lighttpd https://blog.csdn.net/m0_37105371/category_10937068.html BOA服务器的移植-CSDN博客 【第1部分&#xff1a;boa服务器部署到ubuntu里】 http://www.boa.org/boa-0.94.13.tar.gz tar xvzf boa-0.94.13.tar.gz cd boa-0.94.13/src/ a…