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

C++STL-list 底层实现

目录

一、实现框架

二、list_node节点类的模拟实现

节点构造函数

三、list_iterator迭代器的模拟实现

迭代器类的模板参数说明

构造函数

*运算符重载

++运算符的重载

--运算符的重载

==运算符的重载

!=运算符的重载

list的模拟实现

默认成员函数

构造函数

拷贝构造函数

赋值运算符重载函数

析构函数

迭代器相关函数

begin和end

插入、删除函数

push_back

insert

erase

push_front

pop_back

pop_front

size

swap

clear


一、实现框架

namespace lzg
{//模拟实现list当中的结点类template<class T>struct list_node{//成员变量T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T x& = T()):_data(x),_next(nullptr),_prev(nullptr){}};//模拟实现list迭代器//这里写的一种是模板,最后由编译器实例化对应的类型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*();Ptr operator->();Self& operator++();Self& operator--();Self operator++(int);Self operator--(int);bool operator!=(const Self& s);bool operator==(const Self& s);};template<class T>class list{typedef list_node<T> Node;public://迭代器有两种(const和非const),通过传递的参数(有无const)来实例化对应迭代器typedef  list_iterator<T, T&, T*> iterator;typedef  list_iterator<T, const T&, const T*> const_iterator;//初始化空的带哨兵位的双向循环链表void list_empty();list();//默认构造函数(调用list_empty();)list(const list<T>& lt);//拷贝构造函数list(size_t n, const T& val = T());//初始化n个值为val的链表list<T>& operator=(list<T> lt); //赋值重载~list();  //析构//迭代器相关函数iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//链表的操作函数void push_front(const T& x);void pop_front();void pop_back();void push_back(const T& x);iterator insert(iterator pos, const T& val);iterator erase(iterator pos);void clear();void swap(list<T>& tmp);size_t size();private:Node* _head;size_t _size;};}

二、list_node节点类的模拟实现

list的底层是带头双向循环链表

因此,我们若要实现list,则首先需要实现一个结点类。而一个结点需要存储的信息有:数据、前一个结点的地址、后一个结点的地址,于是该结点类的成员变量也就出来了(数据、前驱指针、后继指针)

而对于该结点类的成员函数来说,我们只需实现一个构造函数即可。因为该结点类只需要根据数据来构造一个结点即可,而结点的释放则由list的析构函数来完成。

节点构造函数

list_node(const T x& = T()):_data(x),_next(nullptr),_prev(nullptr)
{}

传值时利用匿名构造来实现,这样节点就能存储任意类型的值

三、list_iterator迭代器的模拟实现

在之前string和vector中模拟实现迭代器我们都是在类里面完成的,并没有单独拎出来封装成一个类,这是因为在前两者中,物理地址空间是连续的可以完成自增自减,但链表不一样,两个节点的地址不是连续的不能通过+1来实现从一个节点到另一个节点的操作。

你可能还会问,那么为什么不在list里面封装形成一个内部类,这也是不合理的,这样会

降低灵活性​:难以共享迭代器实现细节(如const和非const迭代器)

增加耦合​:迭代器实现与特定容器绑定

模板特化困难​:不利于针对不同迭代器类别进行优化

迭代器类的模板参数说明

这里我们所实现的迭代器类的模板参数列表当中为什么有三个模板参数?

template<class T, class Ref, class Ptr>

我们是仿照c++底层源码实现的,Ref是reference(引用)的缩写,Ptr是pointer(指针)的缩写

我们在list中typedef了两种迭代器

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

传递的形参没有加const(<T, T&, T*> )也就是对应普通迭代器( iterator;)

加上了const修饰那么就是const迭代器 (const_iterator)

可以理解我们写的是一个迭代器模板,通过传递的参数不同由编译器帮我们实例化出是普通迭代器还是const迭代器,如果不用Ref和Ptr我们就要写两种迭代器并且它们是高度相似的

构造函数

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

list_iterator(Node* node):_node(node)
{}

*运算符重载

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

Ref operator*()
{return _node->_data;//访问节点中存储的数据
}

->运算符重载

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

++运算符的重载

typedef list_iterator<T, Ref, Ptr> Self;

前置++

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& s)
{return _node == s._node;
}

!=运算符的重载

可以复用==,也可以看两个迭代器的指针指向是否不同

bool operator!=(const Self& s)
{return _node != s._node;
}

四、list的模拟实现

默认成员函数

构造函数

list是一个带头双向循环链表,在构造一个list对象时,直接申请一个头结点,并让其前驱指针和后继指针都指向自己即可。

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

拷贝构造函数

拷贝构造函数就是根据所给list容器,拷贝构造出一个新对象。对于拷贝构造函数,我们先调用list_empty函数把链表初始化为空,再通过复用push_back函数一个个尾插到新构造的容器后面即可。

//lt2(lt1)
list(const list<T>& lt)
{list_empty();for (auto& e : lt){push_back(e);//将容器lt当中的数据一个个尾插到新构造的容器后面}
}

赋值运算符重载函数

对自定义类型传值传参要调用对应的拷贝构造函数,我们通常加上&(引用)是为了减少传值传参带来的拷贝冗余,但是我们这里故意不用引用,那么lt就是lt1的拷贝,通过交换lt来实现赋值,这样把lt1赋值给了lt2,并且我们交换的是lt(lt1的拷贝)并不会影响lt的数据,并且出函数时,临时对象lt会自动析构

// lt2=lt1                 
list<T>& operator=(list<T> lt)
{            swap(lt);   return *this;
}

析构函数

//析构函数
~list()
{clear(); //清理容器delete _head; //释放头结点_head = nullptr; //头指针置空
}

clear的实现在后面,并且clear也是复用erase函数

迭代器相关函数

begin和end

begin函数返回的是第一个有效数据的迭代器,end函数返回的是最后一个有效数据的下一个位置的迭代器。

由于返回类型是iterator (list_iterator<T,T&,T*>) 如果写出return _head->_next,编译器不会自动将指针转为自定义类类型,这里我们用迭代器的匿名构造返回地址

iterator begin()
{return iterator(_head->_next);
}iterator end()
{return iterator(_head);
}

当然,还需要重载一对用于const对象的begin函数和end函数

const_iterator begin() const
{	return const_iterator(_head->_next);
}
const_iterator end() const
{return const_iterator(_head);
}

插入、删除函数

push_back

画图后操作一目了然,完成insert函数后就能复用

void push_back(const T& x)
{/*Node* new_node = new Node(x);Node* tail = _head->_prev;tail->_next = new_node;new_node->_prev = tail;new_node->_next = _head;_head->_prev = new_node;*/insert(end(), x);
}

insert

iterator insert(iterator pos, const T& val)
{Node* cur = pos._node;       //pos是一个类需要的是里面的_node值Node* newnode = new Node(val);Node* prev = cur->_prev;//prev newnode curnewnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;_size++;return iterator(newnode);
}

erase

iterator erase(iterator pos)
{assert(pos != end());Node* del = pos._node;Node* prev = del->_prev;Node* next = del->_next;//prev del nextprev->_next = next;next->_prev = prev;delete del;_size--;return iterator(next);
}

push_front

void push_front(const T& x)
{insert(begin(), x);
}

pop_back

void pop_back()
{erase(--end());
}

pop_front

void pop_front()
{erase(begin());
}

size

为了方便在list中我们还定义了_size来表明链表中插入节点的个数,并且在上面insert和erase函数中都更新了对_size的操作,所以我们直接返回就行了

size_t size()
{return _size;
}

swap

交换两个链表的哨兵位节点就能交换两个链表

void swap(list<T>& tmp)
{swap(_head,tmp._head);swap(_size, tmp._size);
}

clear

clear函数用于清空容器,我们通过遍历的方式,逐个删除结点,只保留头结点即可。

void clear()
{auto it = begin();while (it != end()){it = erase(it);}
}

http://www.dtcms.com/a/340658.html

相关文章:

  • Adobe Adobe Illustrator Ai 2025最新版软件安装包下载与详细图文安装教程!!
  • 代码随想录Day57:图论(寻宝prim算法精讲kruskal算法精讲)
  • 【自动化运维神器Ansible】Roles中Tags使用详解:提升自动化效率的利器
  • STM32 外设驱动模块五:DHT11 温湿度传感器
  • 【Express零基础入门】 | 构建简易后端服务的核心知识
  • 如何查看和修改网络接口参数?
  • 计算机网络模型
  • 2025年Java后端最新场景题+八股文面试题
  • 田野科技“一张皮”,“AI+虚拟仿真”推动考古教学创新发展
  • 晨控EtherCAT设备分配IP操作手册
  • 详细的Git的安装教程
  • 运用平均值填充后的数据进行模型预测
  • 豆秒数科集团:汽车消费金融市场的领跑者
  • Linux中Cobbler服务部署与配置(快速部署和管理 Linux 系统)
  • TheadLocal相关
  • E10 通过RPC实现账号批量锁定与解锁
  • Json转txt
  • CTFshow系列——命令执行web38-40
  • 五种算法详解(SVM / Logistic Regression / kNN / Random Forest / HistGradientBoosting)
  • 无人机抗噪模块技术概述!
  • 20.web api 11
  • C5.6:双电源发射极偏置、特殊类偏置、PNP型偏置电路
  • 如何快速上手【Spring AOP】?核心应用实战(上篇)
  • 【买机器人,上BFT】香港大学联合项目论文解读 |Bunny-VisionPro:用于模仿学习的低成本实时双臂灵巧遥操作系统
  • SpringBoot 整合 Langchain4j RAG 技术深度使用解析
  • uv,下一代Python包管理工具
  • 机器学习-数据预处理全指南:从缺失值到特征编码
  • Tdesign-React 组件 Card 实现头部固定,内容区单独可滚动
  • vue:vue中的ref和reactive
  • 0820 SQlite与c语言的结合