C++——list常见函数的使用和模拟实现(1)

容器list的本质就是我们之前模拟实现过的链表中的双向带头循环链表,因此在使用的时候和之前我们实现的链表的功能是类似的。但是作为容器,list相较于我们之前实现的链表增加了迭代器的使用,同时这里的迭代器和之前模拟实现string和vector的迭代器也有所不同。因为string和vector的迭代器都可以用原生的指针来模拟实现,但是list中的每一个结点都是分散的,同时每个结点还要保存上一个结点以及下一个结点的信息,所以用原生的指针来实现不是理想,具体的实现方式会在后面具体提到。

一、构造函数

list作为容器也有多种构造函数,这里我们只对构造一个空的list进行模拟实现,其中只进行使用介绍。

链表中的结点不仅需要存储数据,同时还要记录前后结点的信息,因此我们需要用一个结构体来作为链表的结点。因为我们的list能够存储各种类型的数据,所以这里存储的数据类型要用模版来代替。

cpp 复制代码
	template<class T>
	struct list_Node
	{
		T _data;
		list_Node* _next;
		list_Node* _prev;
        //T的类型是自定义类型时,T()会调用T类型的构造函数
        //当T是内置类型是,T()会将T初始化为默认值
		list_Node(const T& data = T())
			:_data(data)
			,_next(nullptr)
			,_prev(nullptr)
		{
		}
	};

由于list的本质上是一个双向带头的循环链表是,所以我们只需要记录list的头结点和就能找到这一整个链表了,同时我们还需要记录一下list内部数据的个数方便后续的操作。因为list能够存储的数据类型是不确定的,我们的结点是一个模版类,所以我们的list也要是一个模版类。它的基本结构如下:

cpp 复制代码
template<class T>
class list
{
    //在后面的操作中我们会经常用到结点的类型,因此我们直接把它typedef成Node方便操作
    typedef list_Node<T> Node;

    Node* _head;
    size_t _size;
}

因为list的本质是一个双向带头的循环链表,所以当构造出一个链表时,它应该要一个用来标记的头结点,不论链表中的是否有数据,或者有多少个数据,我们链表都要满足它的基本结构的特点,所以这个用来标记的头结点也把它指向前后结点的指针都指向自己,这样才满足了条件。

cpp 复制代码
list()
{
    _head = new Node;
    _head->_next = _head;
    _head->_prev = _head;
    _size = 0;			
}
cpp 复制代码
//构造一个包含n个值为val的list
list (size_type n, const value_type& val =value_type())
cpp 复制代码
//用任意迭代器把[first,last)的数据存入一个list中
list (InputIterator first, InputIterator last)
cpp 复制代码
//list的拷贝构造函数,可以用一个list来构造另外一个list
list (const list& x)

二、list常见的插入和删除

和我们实现的list类似,容器list也分别有三种插入和删除的方式,push_back在链表的尾部插入一个数据,push_front在链表的头部插入一个数据,insert在指定位置前插入一个数据;pop_back删除链表尾部的数据,pop_front删除链表头部的数据,erase删除链表指定位置的数据。

这里先模拟实现在 头部和尾部操作数据的函数,在特定位置操作的函数放在实现了迭代器以后再进行模拟实现。

cpp 复制代码
    void push_back(const T& x)
    {
        //循环链表的首尾相连 头结点的前一个结点就是链表的最后一个数据
        //先保存下插入前尾部数据的信息 方便后续操作
        Node* cur = _head->_prev; 
        Node* newnode = new Node(x);
        //把数据先接到头结点之前的位置 也就是最后一个数据的位置
        _head->_prev = newnode;
        newnode->_next = _head;
        //再把插入前的最后一个数据和新的尾部数据连接起来
        cur->_next= newnode;
        newnode->_prev = cur; 
        ++_size;
    }
cpp 复制代码
    void push_front(const T& x)
    {
        //保存插入前的头部数据
        Node* cur = _head->next;
        Node* newnode = new Node(x);
        //先把新的数据挂在头结点的后面
        _head->_next = newnode;
        newnode->_prev = _head;
        //再把原先的头部数据接到新的头部数据的后面
        cur->_prev = newnode;
        newnode->_next = cur;
        ++_size;
    }
cpp 复制代码
    pop_back()
    {
        //保存尾部数据的信息
        Node* cur = _head->_prev;
        //将尾部数据前一个结点中指向下一个结点的指针指向头结点
        //再把头结点指向前一个结点的指针指向尾部数据的前一个结点
        //改变这两个指针的指向就可以删除尾部的数据
        _head->prev = cur->prev;
        cue->prev->next = _head;
        //再释放掉被删除结点申请的空间
        delete cur;
        --_size;
    }
cpp 复制代码
    pop_front()
    {
        //保存原先的头部数据的信息
        Node* cur =_head->_next;
        //把头结点指向下一个结点的指针指向头部数据的下一个节点
        //再把头部数据下一个结点指向前一个结点的指针指向头结点
        //这样原先的头部数据就被踢出链表了
        _head->_next =cur->_next;
        cur->next->prev =_head;
        //释放被删除结点申请的空间
        delete cur;
        --_size;
    }
相关推荐
xiaoyaohou1113 分钟前
023、数据增强改进(二):自适应数据增强与AutoAugment策略
开发语言·python
鬼圣14 分钟前
Python 上下文管理器
开发语言·python
Hical_W26 分钟前
为 C++ Web 框架设计三层 PMR 内存池:从原理到实战
c++·github
星空椰28 分钟前
JavaScript 基础进阶:分支、循环与数组实战总结
开发语言·javascript·ecmascript
yong999034 分钟前
IHAOAVOA:天鹰优化算法与非洲秃鹫优化算法的混合算法(Matlab实现)
开发语言·算法·matlab
BestOrNothing_20151 小时前
C++零基础到工程实战(3.6):逻辑实战示例—日志模块
c++·命令行参数·main函数·switch case·逻辑判断·if else·enum class
t***5441 小时前
有哪些常见的架构设计模式在现代C++中应用
开发语言·c++
人间打气筒(Ada)1 小时前
「码动四季·开源同行」python语言:用户交互
开发语言·python·基本数据类型·注释·变量·常量·文件头
zopple2 小时前
汇编、C、C++和Java核心技术对比
c语言·汇编·c++
kaikaile19952 小时前
C# 文件编码转换工具
开发语言·c#