【C++】list介绍以及模拟实现(超级详细)

欢迎来到我的Blog,点击关注哦💕

list的介绍和模拟实现

前言

string vector的是存储是基于物理空间上连续的,而list是作为线性的链式结构,是值得学习的。

list介绍

  • std::list是C++标准模板库(STL)中的一个容器适配器,它内部实现为双向链表结构。
  • 这种设计使得std::list能够在常数时间内进行任意位置的插入和删除操作,这是其相对于其他序列容器如std::vector的显著优点。
  • std::list不支持随机访问,即无法直接通过索引来访问容器中的元素,这通常需要从头部或尾部开始迭代到目标位置.

标准库容器 std::liststd::vector 的优缺点

  • std::list: 作为一个双向链表,std::list 在插入和删除操作上具有优势,因为这些操作只涉及到改变相邻节点的指针,而不需要移动其他元素。此外,std::list 不需要预分配额外的内存,可以更好地处理动态内存分配,减少内存碎片.
  • std::vector: 作为一个动态数组,std::vector 提供了高效的随机访问能力,可以通过下标直接访问任意位置的元素,其访问效率为 O(1). 此外,std::vector 通常具有较高的空间利用率和缓存友好性,因为其元素在内存中是连续存储的.
缺点
  • std::list: 由于非连续的内存存储,std::list 在访问元素时效率较低,因为可能需要从头开始遍历链表。此外,每个节点都需要存储额外的指针信息,这增加了内存开销.
  • std::vector: 在 std::vector 的中间位置插入或删除元素可能会引起大量元素的移动,以维持内存的连续性,这会导致较差的性能。当 std::vector 的容量不足以容纳新增元素时,还需要进行动态扩容,这是一个成本较高的操作
vector list
底层 结构 动态顺序表,一段连续空间 带头结点的双向循环链表
随 机 访 问 支持随机访问,访问某个元素效率O(1) 不支持随机访问,访问某个元素 效率O(N)
插 入 删 除 任意位置插入和删除效率低,需要搬移元素, 时间复杂度为O(N),插入时有可能需要增容, 增容:开辟新空 间,拷贝元素,释放旧空间,导致效率更低 任意位置插入和删除效率高,不 需要搬移元 素,时间复杂度为 O(1)
插 入 删 除 底层为连续空间,不容易造成内存碎片,空间利用率 高,缓存利用率高 底层节点动态开辟,小节点容易 造成内存碎片,空间利用率低, 缓存利用率低
迭 代 器 原生态指针 对原生态指针(节点指针)进行封装
迭 代 器 失 效 在插入元素时,要给所有的迭代器重新赋值,因为插入 元素有可能会导致重新扩容,致使原来迭代器失效,删 除时,当前迭代器需要重新赋值否则会失效 插入元素不会导致迭代器失效, 删除元素时,只会导致当前迭代 器失效,其他迭代器不受影响
使 用 场 景 需要高效存储,支持随机访问,不关心插入删除效率 大量插入和删除操作,不关心随 机访问

list的基本操作

构造函数

【C++】vector介绍以及模拟实现 接口说明
list (size_type n, const value_type& val = value_type()) 构造的list中包含n个值为val的元素
list() 构造空的list
list (const list& x) 拷贝构造函数
list (InputIterator first, InputIterator last) 用[first, last)区间中的元素构造list

list iterator

此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

函数声明 接口说明
begin + end 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin + rend 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的 reverse_iterator,即begin位置

list capcacity

函数声明 接口说明
empty 检测list是否为空,是返回true,否则返回false
size 返回list中有效节点的个数

list modify

函数声明 接口说明
push_front 在list首元素前插入值为val的元素
pop_front 删除list中第一个元素
push_back 在list尾部插入值为val的元素
pop_back 删除list中最后一个元素
insert 在list position 位置中插入值为val的元素
erase 删除list position位置的元素
swap 交换两个list中的元素
clear 清空list中的有效元素

list模拟实现

存贮结构(双向带头循环)

  • 定义一个类(C语言:结构体)存储数据,作为指向下一个上一个的节点
cpp 复制代码
namespace MyList
{
    //LIst节点
	template<class T>
	struct _list_node
	{
		_list_node<T>* _next;
		_list_node<T>* _prev;
		T _val;

		_list_node(const T& val = T())
			: _next(nullptr)
			, _prev(nullptr)
			, _val(val)
		{}
	};
}

iterator

迭代器有两种实现方式,具体应根据容器底层数据结构实现:

  • 原生态指针,比如:vector string
  • 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:
    1. 指针可以解引用,迭代器的类中必须重载operator*()
    2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载 oprator->()
    3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
    4. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()

iterator结构

  • 三个模板参数
    1. 第一个模板参数控制类型
    2. 第二个模板参数控制返回值类型const 非const
    3. 第三个模板参数控制返回的list类的类型const 非const
  • Node* _node;需要定义一个指针,指向list节点
cpp 复制代码
template<class T,class Ref,class ptr>
    struct _list_iterator
    {
        typedef _list_node<T> Node;
        typedef _list_iterator<T, Ref,ptr> self;

        Node* _node;
    };

operator!= operator ==

cpp 复制代码
//重载operator!=
bool operator!=(const self& it)const 
{
    return _node != it._node;
}
//重载operator==
bool operator==(const self& it)const
{
    return _node == it._node;
}

operator++

cpp 复制代码
//重载operator++(前置)
self& operator++()
{
    _node = _node->_next;

    return *this;
}
//重载operator++(后置)
self& operator++(int)
{
    self tmp(*this);

    _node = _node->_next;

    return tmp;
}

operator--

cpp 复制代码
//重载operator--(前置)
self& operator--()
{
    _node = _node->_prev;

    return *this;
}
//重载operator--(后置)
self& operator--(int)
{
    self tmp(*this);

    _node = _node->_prev;

    return tmp;
}

operator* operator->

cpp 复制代码
//重载operator*
Ref& operator* ()
{
    return _node->_val;
}
//重载operator->
ptr operator->()
{
    return _node->_val;
}

list数据结构

构造函数

list的初始化

  • 双向带带头循环

_head

cpp 复制代码
//空list初始化
void empty_init()
{
    _head = new Node;
    _head->_next = _head;
    _head->_prev = _head;

    _size = 0;
}

节点初始化

  • 默认构造函数
  • 默认构造函数
  • 用迭代器初始化
  • 拷贝构造函数
  • 赋值运算符重载
  • 赋值运算符重载
cpp 复制代码
//默认构造函数
list()
{
    empty_init();
}
//默认构造函数
list(int n, const T& val = T())
{
    empty_init();

    while (n--)
    {
        push_back(val);
    }

}
//用迭代器初始化
template <class Iterator>
    list(Iterator first, Iterator last)
{
    empty_init();

    while (first != last)
    {
        push_back(*first);
        ++first;
    }
}
//拷贝构造函数
list(const list<T>& lt)
{
    empty_init();

    for (auto e : lt)
    {
        push_back(e);
    }
}
//赋值运算符重载
list<T> operator=(list<T> lt)
{
    swap(lt);

    return *this;
}
//赋值运算符重载
~list()
{
    clear();

    delete _head;
}

迭代器

cpp 复制代码
iterator begin()
{
    return _head->_next;
}
iterator end()
{
    return _head;
}
const_iterator begin()const
{
    return _head->_next;
}
const_iterator end()const
{
    return _head;
}

list modify

insert

  • 在pos位置插入节点,同数据结构是一样的,在这里面不过多赘述(可以参考
cpp 复制代码
//insert
iterator insert(iterator pos, const T& x)
{
    Node* newcode = new Node(x);
    Node* cur = pos._node;
    Node* prev = cur->_prev;

    prev->_next = newcode;
    newcode->_prev = prev;
    newcode->_next = cur;
    cur->_prev = newcode;

    ++_size;

    return pos;
}

erase

cpp 复制代码
//erase
iterator erase(iterator pos)
{
    assert(pos._node != _head);

    Node* cur = pos._node;
    Node* prev = cur->_prev;
    Node* next = cur->_next;

    prev->_next = next;
    next->_prev = prev;

    delete cur;

    --_size;

    return next;
}

头插、头删、尾插、尾删

  • 可以复用insert ersae STL标准模板库也是进行复用
cpp 复制代码
// 头插
void push_front(const T& x)
{
    insert(begin(), x);
}
//头删
void pop_front()
{
    erase(begin());
}
//尾插
void push_back(const T& x)
{
	insert(begin());
}
//尾删
void pop_back()
{
    erase(--end());
}

list operator

交换

cpp 复制代码
void swap(list<T> lt)
{
    std::swap(_head, lt._head);
    std::swap(_size, lt._size);

}

clear

  • 析构函数也是利用了clear
cpp 复制代码
//删除
void clear()
{
    iterator it = begin();

    while (it != end())
    {
        it = erase(it);
        ++it;
    }

}

源码

cpp 复制代码
#pragma once
#include <iostream>
#include <assert.h>

namespace MyList
{
	//节点
	template<class T>
	struct _list_node
	{
		_list_node<T>* _next;
		_list_node<T>* _prev;
		T _val;

		_list_node(const T& val = T())
			: _next(nullptr)
			, _prev(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;
		
		Node* _node;

		_list_iterator (Node* node)
			:_node(node)
		{}
		//重载operator*
		Ref& operator* ()
		{
			return _node->_val;
		}
		//重载operator->
		ptr operator->()
		{
			return _node->_val;
		}
		//重载operator++(前置)
		self& operator++()
		{
			_node = _node->_next;

			return *this;
		}
		//重载operator++(后置)
		self& operator++(int)
		{
			self tmp(*this);

			_node = _node->_next;

			return tmp;
		}
		//重载operator--(前置)
		self& operator--()
		{
			_node = _node->_prev;

			return *this;
		}
		//重载operator--(后置)
		self& operator--(int)
		{
			self tmp(*this);

			_node = _node->_prev;

			return tmp;
		}


		//重载operator!=
		bool operator!=(const self& it)const 
		{
			return _node != it._node;
		}
		//重载operator==
		bool operator==(const self& it)const
		{
			return _node == it._node;
		}

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

		//空list初始化
		void empty_init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;

			_size = 0;
		}
		//用n个val初始化
		list(int n, const T& val = T())
		{
			empty_init();

			while (n--)
			{
				push_back(val);
			}

		}
		//用迭代器初始化
		template <class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_init();

			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		//默认构造函数
		list()
		{
			empty_init();
		}
		//拷贝构造函数
		list(const list<T>& lt)
		{
			empty_init();

			for (auto e : lt)
			{
				push_back(e);
			}
		}


		//赋值运算符重载
		list<T> operator=(list<T> lt)
		{
			swap(lt);

			return *this;
		}

		//析构函数
		~list()
		{
			clear();

			delete _head;
		}
		//删除
		void clear()
		{
			iterator it = begin();

			while (it != end())
			{
				it = erase(it);
				++it;
			}

		}


		//迭代器
		iterator begin()
		{
			return _head->_next;
		}
		iterator end()
		{
			return _head;
		}
		const_iterator begin()const
		{
			return _head->_next;
		}
		const_iterator end()const
		{
			return _head;
		}

		//void push_back(const T& x)
		//{
		//	Node* newcode = new Node(x);
		//	Node* tail = _head->_prev;

		//	tail->_next = newcode;
		//	newcode->_next = _head;

		//	newcode->_prev = tail;
		//	_head->_prev = newcode;

		//	++_size;
		//}
		
		// 头插
		void push_front(const T& x)
		{
			insert(begin(), x);
		}
		//头删
		void pop_front()
		{
			erase(begin());
		}
		//尾插
		void push_back(const T& x)
		{
			insert(begin());
		}
		//尾删
		void pop_back()
		{
			erase(--end());
		}
		//insert
		iterator insert(iterator pos, const T& x)
		{
			assert(pos._node != _head);

			Node* newcode = new Node(x);
			Node* cur = pos._node;
			Node* prev = cur->_prev;

			prev->_next = newcode;
			newcode->_prev = prev;
			newcode->_next = cur;
			cur->_prev = newcode;

			++_size;

			return pos;
		}
		//erase
		iterator erase(iterator pos)
		{
			assert(pos._node != _head);

			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			prev->_next = next;
			next->_prev = prev;

			delete cur;

			--_size;

			return next;
		}
		//大小
		size_t size()const
		{
			return _size;
		}
		//交换
		void swap(list<T> lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);

		}

	private:
		Node* _head;
		size_t _size;
	};

}
相关推荐
yuyanjingtao6 分钟前
CCF-GESP 等级考试 2023年9月认证C++四级真题解析
c++·青少年编程·gesp·csp-j/s·编程等级考试
闻缺陷则喜何志丹22 分钟前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
charlie11451419133 分钟前
C++ STL CookBook
开发语言·c++·stl·c++20
小林熬夜学编程44 分钟前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
倔强的石头1061 小时前
【C++指南】类和对象(九):内部类
开发语言·c++
A懿轩A2 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
机器视觉知识推荐、就业指导2 小时前
C++设计模式:享元模式 (附文字处理系统中的字符对象案例)
c++
半盏茶香2 小时前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
Ronin3053 小时前
11.vector的介绍及模拟实现
开发语言·c++
✿ ༺ ོIT技术༻3 小时前
C++11:新特性&右值引用&移动语义
linux·数据结构·c++