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

【STL】模拟实现list

目录

list需要实现的接口

结点类的创建

迭代器类的实现

构造函数

++运算符的重载

--运算符的重载

!=运算符重载和==运算符重载

operator*

operator->

list的模拟实现

构造函数

拷贝构造函数

 赋值运算符重载函数

析构函数

迭代器相关函数

begin和end

front和back

push_front和push_back   

pop_front和pop_back

其他函数

size

clear

empty

swap


list需要实现的接口

namespace cl
{//模拟实现list当中的结点类template<class T>struct _list_node{//成员函数_list_node(const T& val = T()); //构造函数//成员变量T _val;                 //数据域_list_node<T>* _next;   //后继指针_list_node<T>* _prev;   //前驱指针};//模拟实现list迭代器template<class T, class Ref, class Ptr>struct _list_iterator{typedef _list_node<T> Node;typedef _list_iterator<T, Ref, Ptr> self;_list_iterator(node* node);  //构造函数//各种运算符重载函数self operator++();self operator--();self operator++(int);self operator--(int);bool operator==(const self& s) const;bool operator!=(const self& s) const;Ref operator*();Ptr operator->();//成员变量Node* _node; //一个指向结点的指针};//模拟实现listtemplate<class T>class list{public:typedef _list_node<T> node;typedef _list_iterator<T, T&, T*> iterator;typedef _list_iterator<T, const T&, const T*> const_iterator;//默认成员函数list();list(const list<T>& lt);list<T>& operator=(const list<T>& lt);~list();//迭代器相关函数iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//访问容器相关函数T& front();T& back();const T& front() const;const T& back() const;//插入、删除函数void insert(iterator pos, const T& x);iterator erase(iterator pos);void push_back(const T& x);void pop_back();void push_front(const T& x);void pop_front();//其他函数size_t size() const;void clear();bool empty() const;void swap(list<T>& lt);private:Node* _head; //指向链表头结点的指针size_t _szie;};
}

结点类的模拟实现:

list在底层中,就是以带头双向循环链表实现的

所以实现list我们首先的创建一个结点类。

该结点类需要三个成员变量:

1. 数据

2. 上一个结点的指针

3.下一个结点的指针

结点类的创建

_list_node

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

在结点类中我们需要一个构造函数来帮我们完成结点以及数据的初始化,因为结点类需要被_list_iterator和list使用,所以这里我们使用struct

迭代器类的实现

之前string和vector我们都没说需要实现迭代器类,为什么list需要实现一个迭代器类呢?

因为string和vector对象都将其数据存储在了一块连续的内存空间,我们通过指针的自增自减以及解引用操作等一系列操作,就可以对相应位置的数据进行操作,所以string和vector当中的迭代器就是原生指针

对于list来说,它的构成并不是一块连续的空间,它的结点在内存中的位置是随机的,我们不能通过结点指针的自增自减以及解引用操作对相应结点的数据进行操作

迭代器的意义是,让使用者不必在意容器的底层实现原理,可以用简单统一的方式对容器内数据进行访问

因为迭代器·结点指针的行为不能满足迭代器定义,那么我们可以对这个结点指针进行封装,对结点指针的各种运算符进行重载,使得我们可以像使用string和vector当中的迭代器一样去使用list当中的迭代器

迭代器类的模板参数说明

_list_iterator<class T, class Ref, class Ptr>

在list类中,我们typedef了两种类型的模板类,分别实现的是const 迭代器和非 const 迭代器

typedef _list_iterator<T, T&, T*> iterator
typedef _list_iterator<T, const T&, const T*> const_ierator

从这里就可以看出,Ref是引用类型,Ptr是指针类型

当我们使用不同迭代器时编译器就会根据模板来实例化出一个普通迭代器,当我们使用const迭代器时,编译器就会实例化出一个const迭代器对象

若没有设计这三种模板参数,那么就不能很好的区分普通迭代器和const迭代器

构造函数

迭代器类实际上就是对结点指针进行了封装,成员变量只有一个,那就是结点指针,其构造函数直接根据所给结点指针构造出一个迭代器对象即可

_list_iterator(Node* node):_node(ndoe)
{}

++运算符的重载

前置++,作用是让结点指针指向后面一个位置,然后返回自增的结点指针即可

self& operator++()
{_node = _node->next;return *this;
}

对于后置++,我们需要记录结点指针当前的位置,让结点指针向指向后一个结点,然后返回自增前的结点指针即可

self operator++(int)
{self tmp(*this);_node = _node->next;return tmp;
}

注意:self是当前迭代器对象的类型:

_list_iterator<class T, class Ref, class Ptr>

--运算符的重载

前置--,作用是让结点指针指向前面一个位置,然后返回自减的结点指针即可

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
}

operator*

当我们使用解引用操作符时,是想得到该位置的数据内容,所以我们直接返回当前结点指针所指向的数据即可,但是这里需要解引用返回,得到数据之后,我们后面可能会对这个数据进行修改

Ref operator*()
{return _node->_val;
}

operator->

指向运算符重载我们只需要返回数据的地址就可以了

Ptr operator->()  
{return &_node->val
}

但是看到这里,你可能觉得有点问题

这里应该是两个箭头去访问it,第一个->调用指向运算符重载函数,返回一个指针,然后再通过指针加指向运算符去访问对象当中的变量

但是一个地方出现两个箭头可读性太差了,为了增加可读性,编译器做了优化处理,优化了一个箭头

list的模拟实现

list是一个带头双向循环链表

构造函数

将初始化操作进行封装,后续会多次使用到,创建一个指针结点_head,并且让前驱指针和后驱指针指向自己即可完成初始化

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

拷贝构造函数

 创建一个新头结点_head,使前驱指针和后驱指针指向自己,遍历lt对象,将lt的数据通过尾插的方式给新的容器

list(const list<T>& lt)
{empty_init();//这里加引用提高效率,减少拷贝//加const是防止e不被篡改for (const auto& e : lt){push_back(e);}
}

 赋值运算符重载函数

传统写法:

		list<T>& opertor=(const list<T>& lt){if (*this != &lt){clear();//清空容器for (const auto& e : lt){push_back(e);}}return *this;}

现代写法:

这个写法代码量较少,首先利用编译器机制,故意不使用引用接收参数,通过编译器自动调用list的拷贝构造函数生成一个临时对象,使用这个临时对象给新容器进行交换即可完成操作,结束之后lt会调用析构函数销毁

//现代写法:
list<T>& opertor = (list<T> lt)
{swap(lt);return *this;
}

析构函数

使用clear函数清理容器当中的数据,然后将头结点释放,最后将头指针置空

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

迭代器相关函数

begin和end

begin()迭代器指向第一个元素,end()迭代器指向末尾元素的后一个元素

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

以上两种方式都可以

第一种是Node*类型对iterator的强制类型转换

第二种是Node*类型对itreator的隐式类型转换

还有const类型的迭代器

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

front和back

//获取尾部元素
T& back() const
{return *(end() - 1);
}
//获取头部元素
T& front() const
{return *begin();
}//获取尾部元素
const T& back() const
{return *(end() - 1);
}
//获取头部元素
const T& front() const
{return *begin();
}

push_front和push_back   

pop_front和pop_back

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

插入,删除函数

insert

pos位置前插入新结点

先通过所给迭代器得到该位置出的结点指针cur,然后通过cur指针找到前一个位置的结点指针prev

然后根据所给val的值建立一个新结点指针,然后实现和prve,cur的双向连接连接

iterator insert(iterator pos, const T& val)
{//在pos位置前插入,保存pos前面的地址,以防找不到Node* cur = pos->_node;Node* prev = cur->_prev;Node* newnode = new Node(val);cur->_prev = newnode;newnode->_next = cur;newnode->_prev = prev;prev ->_next  = newnode;++_size;//返回插入结点的地址,更新迭代器,以免迭代器失效return newnode;
}

erase

先根据所给迭代器得到该位置处的结点指针cur,通过cur指针找到前一个位置的结点prev和后一个位置的结点next,然后释放cur,最后将prev与next两个结点连接起来

iterator erase(iterator pos)
{assert(pos != end());Node* cur = pos._nodeNode* Prev = cur->prev;Node* Next = cur->next;Prev->next = next;Next->_prev = prev;delete cur;--_size;return pos;
}

其他函数

size

这里size的设计是,创建_size成员,使用增加结点的函数(例:push_back insert时)时,对_size进行++处理,进行删除操作时_size做--操作,即_size就是当前结点个数

size_t size() const
{return _size;
}

clear

清理链表,删除所有结点,erase涉及迭代器失效问题,这里的处理方案是,让erase返回下一个结点的指针,不断去更新lt.

clear这里不能给clear加const,加上const了,就代表给我需要删除的对象加上了const,*this就不能进行删除操作了

	void clear(){iterator it = begin();while (it != end()){//接收下一个位置的指针,避免迭代器失效it = erase(it);}_size = 0;}

empty

判断链表是否为空

bool empty()
{return begin() == end();
}

swap

我们访问的是标准库里面的swap,所有需要再swap前面加上::(域作用限定符)

void swap(list<T>& lt)
{::swap(_node, lt._node);::swap(_size, lt._size);
}

http://www.mrgr.cn/news/49868.html

相关文章:

  • BERT的中文问答系统(羲和1.0)
  • 程序设计语言
  • 编程小白如何成为大神?大学新生的最佳入门攻略
  • 2024国内有哪些做的好的网站建设公司?
  • 自然语言处理:第五十二章 LongCite:助力RAG应用,让大模型精准找到引用
  • 从回溯法到剪枝优化: 找出相加之和为 n 的 k 个数的组合
  • 深圳500强揭榜 顺络等电感变压器企业强势跻身!
  • vmware中使用U盘安装win10系统
  • 【JAVA毕业设计】基于Vue和SpringBoot的渔具租赁系统
  • 霍尼C200系统CC-TUIO31通用输入输出模块电厂用
  • Java数组总结
  • LEETCODE 49场周赛 第K大完美二叉子树的大小
  • 消息人士称NVIDIA GeForce RTX 5090 GPU的价格不会与4090差距很大
  • 【React】React17+配置Babel实现无需导入React就可以使用jsx
  • 10个常用的大模型提示语式
  • 【MATLAB代码】TDOA最小二乘求三维下的位置(1主锚点、3副锚点)
  • IEC104规约的秘密之十一----扩展报文之文件传输
  • 沃尔玛死磕电商,上半年在线杂货业务增长强劲,Walmart沃尔玛产品采集上架刊登工具
  • OpenFeign中GET与POST请求的参数传递技巧
  • GC5931 在工业风扇中的应用分析且可替代A5931/Alegro