STL:List从0到1

Jupiter· 2024-06-26 13:05:13 阅读 78

请添加图片描述

在这里插入图片描述

🎉个人名片

🐼作者简介:一名乐于分享在学习道路上收获的大二在校生

🙈个人主页🎉:GOTXX

🐼个人WeChat:ILXOXVJE

🐼本文由GOTXX原创,首发CSDN🎉🎉🎉

🐵系列专栏:零基础学习C语言----- 数据结构的学习之路----C++的学习之路

🐓每日一句:如果没有特别幸运,那就请特别努力!🎉🎉🎉

————————————————

🎉文章简介

🎉本篇文章将 介绍如何使用C++编写代码来实现一个类似于STL中的List容器 相关知识进行分享!

💕如果您觉得文章不错,期待你的一键三连哦,你的鼓励是我创作动力的源泉,让我们一起加 油,一起奔跑,让我们顶峰相见!!!🎉🎉🎉

——————————————————

一.前言

这篇文章将介绍如何使用C++编写代码来实现一个类似于STL中的List容器。 list是一个可以在常数范围内在任意位置进行插入和删除的序列式容器。在这篇文章中,你将学习并实现List的常见功能,如添加元素、删除元素等。通过实现自己的List容器,你将更好地熟悉List的使用及相关特性,并提升对C++语言的理解和掌握。

————————————————

二.List的介绍

List文档介绍链接: link

1. list是一个可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代;

2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素;

3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效;

4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好(不需要挪动数据);

5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素);

三.List的结构及模拟实现

一.底层结构

List底层是一个带头双向循环链表

如图:

在这里插入图片描述

在这里插入图片描述

库里面的begin() 与end() 返回的节点位置:

begin()返回的是头节点的下一个节点;

而end()返回的是头节点;

二.List的模拟实现

重点::迭代器的实现

1. 构造函数

template<class T>

struct ListNode

{

ListNode<T>(const T& x=T())

:_next(nullptr)

,_prev(nullptr)

,_val(x)

{

}

ListNode<T>* _next;

ListNode<T>* _prev;

T _val;

};

库里面的List类的构造函数是另写一个函数,因为这个函数拷贝构造会使用,然后复用它,所以我们也这样实现;

template<class T>

class List

{

public:

void empty_List()

{

_phead = new node;

_phead->_next = _phead;

_phead->_prev = _phead;

}

List()

{

empty_List();

}

}

2. 拷贝构造函数

List(const List<T>& lt)

{

empty_List(); //初始化

for (const auto& e : lt)

{

push_back(e); //将lt里面的数据依次尾插

}

}

3. 插入函数

思路:记录前一个和后一个节点,然后连接

在这里插入图片描述

iterator insert(iterator pos, const T& x)

{

node* newnode = new node(x); //构造一个节点

node* next = pos._node;

node* prev = next->_prev; //记录前一个

newnode->_next = next;

next->_prev = newnode; //链接

newnode->_prev = prev;

prev->_next = newnode;

return pos;

}

4. 尾插函数

复用insert函数

void push_back(const T& x)

{

/*node* newnode = new node(x);

node* tail = _phead->_prev;

tail->_next = newnode;

newnode->_prev = tail; //不复用的写法

newnode->_next = _phead;

_phead->_prev = newnode;*/

insert(end(), x);

}

5. 头插函数

复用insert函数

void push_front(const T& x)

{

insert(begin(),x);

}

6. 删除函数

iterator erase(iterator pos)

{

assert(pos!=end());

node* prev = pos._node->_prev; //保存前一个节点

node* next = pos._node->_next; //保存后一个节点

prev->_next = next; //连接

next->_prev = prev;

delete pos._node; //释放掉该节点

return next; //返回删除元素的下一个节点

}

7. 尾删函数

复用删除函数

void pop_back()

{

//erase(end()._node->_prev);

erase(--end()); //头节点的前指针指向的是最后一个节点

}

8. 头删函数

复用删除函数

void pop_front()

{

erase(begin());

}

9. 迭代器的实现

因为链表的底层物理空间不是连续的,所以不能使用原生指针类实现。因为原生指针++,可以找到下一个数据,但是链表的节点与节点之间不是连续的,指针++,不能找到下一个节点,所以我们需要操作符重载,改变 ++, != ,* 等操作符的行为;又因为节点指针是内置类型,不能进行操作符重载,所以我们只能将它进行封装,封装在一个类里面,进行重载;

template<class T,class Ref,class Ptr>

struct __List_iterator

{

typedef ListNode<T> node;

typedef __List_iterator<T,Ref,Ptr> self;

__List_iterator(node* node) //构造函数

:_node(node)

{ }

self& operator++() //运算符的重载

{

_node = _node->_next; //前置++,返回++后的值

return *this;

}

self operator++(int)

{

self tmp(_node); //保存++前的值

_node = _node->_next;

return tmp; //返回++前的值

}

self& operator--()

{

_node = _node->_prev;

return *this;

}

self operator--(int)

{

self tmp(_node);

_node = _node->_prev;

return tmp;

}

Ref operator*()

{

return _node->_val;

}

Ptr operator->

{

return &_node->-val;

}

bool operator!=(const self& s)

{

return s._node!= this->_node;

}

bool operator==(const self& s)

{

return s._node == this->_node;

}

node* _node;

};

10. 赋值运算符重载

传统写法:

void clear()

{

iterator lt = begin();

while (lt != end())

{

lt = erase(lt);

}

}

//lt1=lt2

List<T>& operator=(const List<T>& lt)

{

clear(); //清空函数,将链表中的有效数据删除掉,保留头节点

for (const auto& e : lt)

{

push_back(e); //依次尾插

}

return *this;

}

现代写法:

void swap(List<T>& lt)

{

std::swap(_phead, lt->_phead);

}

//lt1=lt2

List<T>& operator=(List<T> lt) //lt是lt2的拷贝构造

{

swap(lt); //交换lt与lt1

return *this; //返回

}

补充知识:

typedef 放在类里面与外面的区别:

如果是放在公有里面,则类外面也可以使用,但是要指定类域;

如果是私有的话,则类外面不能使用;

三.总代码:

#pragma once

#include<iostream>

#include<assert.h>

using namespace std;

namespace L

{

template<class T>

struct ListNode

{

ListNode<T>(const T& x=T())

:_next(nullptr)

,_prev(nullptr)

,_val(x)

{

}

ListNode<T>* _next;

ListNode<T>* _prev;

T _val;

};

template<class T,class Ref,class Ptr>

struct __List_iterator

{

typedef ListNode<T> node;

typedef __List_iterator<T,Ref,Ptr> self;

__List_iterator(node* node)

:_node(node)

{ }

self& operator++()

{

_node = _node->_next;

return *this;

}

//self& operator++(int)//错误,

self operator++(int)

{

self tmp(_node);

_node = _node->_next;

return tmp;

}

self& operator--()

{

_node = _node->_prev;

return *this;

}

self operator--(int)

{

self tmp(_node);

_node = _node->_prev;

return tmp;

}

Ref operator*()

{

return _node->_val;

}

Ptr operator->()

{

return &_node->_val;

}

bool operator!=(const self& s)

{

return s._node!= this->_node;

}

bool operator==(const self& s)

{

return s._node == this->_node;

}

node* _node;

};

template<class T>

class List

{

public:

typedef ListNode<T> node;

typedef __List_iterator<T,T&,T*> iterator;

typedef __List_iterator<T,const T&,const T*> const_iterator;

iterator begin()

{

return iterator(_phead->_next);

}

iterator end()

{

return iterator(_phead);

}

const_iterator begin()const

{

return iterator(_phead->_next);

}

const_iterator end()const

{

return iterator(_phead);

}

void empty_List()

{

_phead = new node;

_phead->_next = _phead;

_phead->_prev = _phead;

}

List()

{

empty_List();

}

List(const List<T>& lt)

{

empty_List();

for (const auto& e : lt) //引用更好,如果T类型是自定义类型的话

{

push_back(e);

}

}

//lt1=lt2

List<T>& operator=(const List<T>& lt)

{

clear();

for (const auto& e : lt)

{

push_back(e);

}

return *this;

}

void swap(List<T>& lt)

{

std::swap(_phead, lt->_phead);

}

//lt1=lt2

List<T>& operator=(List<T> lt)

{

swap(lt);

return *this;

}

void clear()

{

iterator lt = begin();

while (lt != end())

{

lt = erase(lt);

}

}

~List()

{

clear();

delete _phead;

_phead = nullptr;

}

void push_back(const T& x)

{

/*node* newnode = new node(x);

node* tail = _phead->_prev;

tail->_next = newnode;

newnode->_prev = tail;

newnode->_next = _phead;

_phead->_prev = newnode;*/

insert(end(), x);

}

void push_front(const T& x)

{

insert(begin(),x);

}

iterator insert(iterator pos, const T& x)

{

node* newnode = new node(x);

node* next = pos._node;

node* prev = next->_prev;

newnode->_next = next;

next->_prev = newnode;

newnode->_prev = prev;

prev->_next = newnode;

return pos;

}

iterator erase(iterator pos)

{

assert(pos!=end());

node* prev = pos._node->_prev;

node* next = pos._node->_next;

prev->_next = next;

next->_prev = prev;

delete pos._node;

return next;

}

void pop_back()

{

//erase(end()._node->_prev);

erase(--end());

}

void pop_front()

{

erase(begin());

}

private:

node* _phead;

};

请添加图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。