C++ STL list

这篇具有很好参考价值的文章主要介绍了C++ STL list。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

✅<1>主页:我的代码爱吃辣
📃<2>知识讲解:C++之 STL list介绍和模拟实现
☂️<3>开发环境:Visual Studio 2022
💬<4>前言:上次我们详细的介绍了vector,今天我们继续来介绍一下TSTL中的另外一个容器list。list在基础的功能和结构上就是一个双向带头的循环链表,实现起来基本不难,但是list迭代器的封装是非常值得学习的。

一.认识list

list - C++ Reference

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2.  list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)。

结构:list使用的是双向的循环带头结点的链表。

 C++ STL list,C++,c++,开发语言

 二.list的使用

list的使用非常简单,有了我们之前vector和string的基础。上手list基本就是小菜一碟。

1.构造函数

构造函数( (constructor)) 接口说明
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

2.增删查改

	list<int> li;
	//尾插
	li.push_back(10);
	//头插
	li.push_front(20);
	//尾删
	li.pop_back();
	//头删
	li.pop_front();
	//迭代器位置插入数据
	li.insert(li.begin(), 100);
	//删除迭代器位置数据
	li.erase(li.begin());

3.list 迭代器

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

list的迭代器使用与string和vector一模一样,在此不多介绍。

四.list 模拟实现

1.链表结点

template<class T>
struct _list_node
{
	_list_node( const T& val = T())
		:_val(val),
		_next(nullptr),
		_prev(nullptr)
	{
	}

	T _val;           //存储数据
	_list_node* _next;//后一个结点的指针
	_list_node* _prev;//前一个结点的指针
};

注意:我们在此处的struct定义了一个类,我们在定义_next和_prev的时候就不用像C语言那样加上struct。

例如:C语言写法

template<class T>
struct _list_node
{
	_list_node(const T& val = T())
		:_val(val),
		_next(nullptr),
		_prev(nullptr)
	{
	}

	T _val;           //存储数据
	struct _list_node* _next;//后一个结点的指针
	struct _list_node* _prev;//前一个结点的指针
};

2.list整体结构

template<class T>
class List
{
public:
    typedef _list_node<T> node;

    //...成员方法 增删查改
    
private:
	node* _head;//头节点
};

3.list构造函数

因为我们的list是一个带哨兵位的双向循环链表。所以这里我们将new出来的结点,设置为循环双向结构。

	List()
		:_head(new node)
	{
		_head->_next = _head;
		_head->_prev = _head;
	}

4.push_back(),pop_back()

push_back()尾插一个数据,pop_back()尾删一个数据。

	//push_back
    void push_back(const T& val )
	{
        //创建结点
		node* newnode = new node(val);
		node* tail = _head->_prev;
        //改变指向
		tail->_next = newnode;
		newnode->_next = _head;
		_head->_prev = newnode;
		newnode->_prev = tail;
	}
	//pop_back
    void pop_back()
	{
		//判空
		assert(!empty());
		node* tail = _head->_prev;
		node* newtail = tail->_prev;
		//改变指向
		_head->_next = newtail;
		newtail->_prev = _head;
		//释放结点
		delete tail;
	}

C++ STL list,C++,c++,开发语言

5.迭代器

list的迭代器不同于string和vector,因为list的物理存储是不连续的,所以我们不能像list和vector一样仅仅使用原生指针来作为list的迭代器。

迭代器的实际就是一种模仿指针的行为来为容器提供一种通用的访问方法的设计。虽然list的迭代器不能使用原生指针来替代,但是我们可以对原生的指针进行类级别的封装来构造迭代器,并且使得迭代器具有指针的行为。

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

  1.   原生态指针,比如:vector
  2.   将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:
  • 指针可以解引用,迭代器的类中必须重载operator*()
  • 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
  • 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
  • 至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载--
  • 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()

 5.1迭代器结构

迭代器的整体结构如下。

template<class T>
class _list_iterator
{
public:
	typedef _list_node<T> node;    //结点类
	typedef _list_iterator<T> self;//迭代器本身

    //使用原生指针构造迭代器
	_list_iterator(node* n)
		:_node(n)
	{
	}
    
    //operator*()
    //operator++()
    //operator--()
    //operator->()
    //operator==()
    //operator!=()
	
private:
	node* _node;//迭代器是对原生指针的封装
};

5.2迭代器操作

5.2.1operator*()
    T& operator*()
	{
		return _node->_val;
	}

返回该结点的值的引用。

5.2.2 operator ++(),operator --()

++的操作其实就是让迭代器挪到下一个位置,--操作其实就是让迭代器挪到上一个位置。迭代器++或者--操作的返回值还是一个迭代器。

    //后置++
	self operator++(int)
	{
		self tmp(*this);
		_node = _node->_next;
		return tmp;
	}

    //前置++
	self operator++()
	{
		_node = _node->_next;
		return *this;
	}

    //前置--
    self operator--()
	{
		_node = _node->_prev;
		return *this;
	}

    //后置--
    self operator--(int)
	{
		self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}


5.2.3operator==(),operator!=()

比较两个迭代器是否相等直接比较迭代器封装的两个指针是否相等就可以了。

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

	bool operator==(const self& it)
	{
		return _node == it._node;
	}
5.2.4operator->()

我们知道一般的结构体,类指针都是支持使用->访问一些成员的。当list存储的数据是结构体,或者是类试迭代器也应该支持->去访问。

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

那么这样我们在外边的访问会不会很奇怪呢?


    struct AA
    {
    	AA(int aa = 10)
    		:_aa(aa)
    	{
    	}
        int _aa;
    };


    list<AA> lli(10, AA());	
    list<AA>::iterator lit = lli.begin();
    //1.调用:
    lit.operator->()->_aa;
    //2.调用:
    lit->_aa;

C++ STL list,C++,c++,开发语言

 在实际调用的时候我们可以直接写成调用2。

5.2.5begin(),end()
	
    typedef _list_iterator<T> iterator;

    iterator begin()
	{
		//使用哨兵位的下一个结点指针,构造begin
		return iterator(_head->_next);
	}
	iterator end()
	{
		//使用哨兵位结点指针,构造end
		return iterator(_head);
	}

可以构成 [ begin,end ).这种左闭右开的结构。

5.3 const 迭代器

const迭代器与普通迭代器最大的不同,就是const迭代器不允许修改迭代器指向的数据。在整个迭代器操作中只有,operator* 和 operator->是对指向数据操作的。我们仅需要将普通迭代器的这两个函数返回值修改即可。并且区分开普通迭代器与const迭代器。

template<class T>
class const_list_iterator
{
public:
	typedef _list_node<T> node;
	typedef const_list_iterator self;

	const_list_iterator(node* n)
		:_node(n)
	{
	}

    //返回值加const,不允许修改
	const T& operator*()
	{
		return _node->_val;
	}

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

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

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

	self operator--()
	{
		_node = _node->_prev;
		return *this;
	}

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

	bool operator==(const self& it)
	{
		return _node == it._node;
	}

    //返回值加const,不允许修改
	const T*  operator->()
	{
		return &_node->_val;
	}

private:
	node* _node;
};

但是这种设计让人觉得很不舒服,代码的重复太多。从代码的角度来看,const和普通迭代器仅仅只是返回值不同而已。我们只要给在给迭代器不同的类型,我们只需要给模板多架两个参数,如果是 T& 和 T*就实例化出普通迭代器,如果是 const T& 和 const T*就实例化出const迭代器。

list类内部:

template<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;//const迭代器

	List()
		:_head(new node)
	{
		_head->_next = _head;
		_head->_prev = _head;
	}

	iterator begin()
	{
		//使用哨兵位的下一个结点指针,构造begin
		return iterator(_head->_next);
	}
	iterator end()
	{
		//使用哨兵位结点指针,构造end
		return iterator(_head);
	}

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

	const_iterator end()const
	{
		return const_iterator(_head);
	}

private:
	node* _head;
};

迭代器:

//T:数据类型,Ref:T&/const T&, Ptr: T*/const T*
template<class T,class Ref,class Ptr>
class _list_iterator
{
public:
	typedef _list_node<T> node;
	typedef _list_iterator<T,Ref,Ptr> self;

	_list_iterator(node* n)
		:_node(n)
	{
	}

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

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

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

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

	bool operator==(const self& it)
	{
		return _node == it._node;
	}

    //返回迭代器内部的结点指针
	node* GetNodePtr()
	{
		return _node;
	}

private:
	node* _node;

};

根据不同的模板参数从而实例化出不同的代码。

5.4 insert(),erase()

insert()可以在某一迭代器位置插入数据,erase可以删除某一迭代器位置的数据。

    void insert(iterator pos,const T& val)
	{
        //创建结点
		node* newnode = new node(val);
		node* posnode = pos.GetNodePtr();
		node* prevnode = posnode->_prev;
        //改变指向
		newnode->_next = posnode;
		newnode->_prev = prevnode;
		prevnode->_next = posnode;
		posnode->_prev = newnode;
		
	}
    
    iterator erase(iterator pos)
	{
		node* posnode = pos.GetNodePtr();
		node* prevnode = posnode->_prev;
		node* nextnode = posnode->_next;
        //修改指向
		prevnode->_next = nextnode;
		nextnode->_prev = prevnode;
        //释放结点
		delete posnode;
        return iterator(nextnode);
	}

 erase在返回的删除位置的下一个结点的迭代器,也是为了绝迭代器失效的问题。

5.5 析构函数

    //清除数据,但是头节点需要保留
	void clear()
	{
		iterator it = begin();
		while (it != end())
		{
			erase(it++);
		}
	}

	~List()
	{
		//连同头节点一起释放
		clear();
		delete _head;
		_head = nullptr;
	}

5.6再看构造函数

迭代器初始化文章来源地址https://www.toymoban.com/news/detail-643181.html

   	void emptyInit()
	{
		_head->_next = _head;
		_head->_prev = _head;
	}

    template<class InputIterator>
	List(InputIterator frist, InputIterator lest)
		:_head(new node)
	{
		emptyInit();
		while (frist != lest)
		{
			push_back(*frist);
			frist++;
		}
	}

五.list模拟实现整体代码

#pragma once
#include<iostream>
#include<cassert>
using namespace std;

template<class T>
struct _list_node
{
	_list_node( const T& val = T())
		:_val(val),
		_next(nullptr),
		_prev(nullptr)
	{
	}

	T _val;           //存储数据
	_list_node* _next;//后一个结点的指针
	_list_node* _prev;//前一个结点的指针
};

/*
List 的迭代器
迭代器有两种实现方式,具体应根据容器底层数据结构实现:
  1. 原生态指针,比如:vector
  2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:
	 1. 指针可以解引用,迭代器的类中必须重载operator*()
	 2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
	 3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
		至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载--
	 4. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()
*/

//普通迭代器1.0版本
//template<class T>
//class _list_iterator
//{
//public:
//	typedef _list_node<T> node;
//	typedef _list_iterator self;
//
//	_list_iterator(node* n)
//		:_node(n)
//	{
//	}
//
//	T& operator*()
//	{
//		return _node->_val;
//	}
//
//	self operator++(int)
//	{
//		self tmp(*this);
//		_node = _node->_next;
//		return tmp;
//	}
//
//	self operator++()
//	{
//		_node = _node->_next;
//		return *this;
//	}
//
//	self operator--(int)
//	{
//		self tmp(*this);
//		_node = _node->_prev;
//		return tmp;
//	}
//
//	self operator--()
//	{
//		_node = _node->_prev;
//		return *this;
//	}
//
//	bool operator!=(const self& it)
//	{
//		return _node != it._node;
//	}
//
//	bool operator==(const self& it)
//	{
//		return _node == it._node;
//	}
//
//	T*  operator->()
//	{
//		return &_node->_val;
//	}
//private:
//	node* _node;
//};


//const迭代器1.0版本
//template<class T>
//class const_list_iterator
//{
//public:
//	typedef _list_node<T> node;
//	typedef const_list_iterator self;
//
//	const_list_iterator(node* n)
//		:_node(n)
//	{
//	}
//
//	const T& operator*()
//	{
//		return _node->_val;
//	}
//
//	self operator++(int)
//	{
//		self tmp(*this);
//		_node = _node->_next;
//		return tmp;
//	}
//
//	self operator++()
//	{
//		_node = _node->_next;
//		return *this;
//	}
//
//	self operator--(int)
//	{
//		self tmp(*this);
//		_node = _node->_prev;
//		return tmp;
//	}
//
//	self operator--()
//	{
//		_node = _node->_prev;
//		return *this;
//	}
//
//	bool operator!=(const self& it)
//	{
//		return _node != it._node;
//	}
//
//	bool operator==(const self& it)
//	{
//		return _node == it._node;
//	}
//
//	const T*  operator->()
//	{
//		return &_node->_val;
//	}
//private:
//	node* _node;
//};

//迭代器2.0版本
//T:数据类型,Ref:T&/const T&, Ptr: T*/const T*
template<class T, class Ref, class Ptr>
class _list_iterator
{
public:
	typedef _list_node<T> node;
	typedef _list_iterator<T, Ref, Ptr> self;

	_list_iterator(node* n)
		:_node(n)
	{
	}

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

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

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

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

	bool operator==(const self& it)
	{
		return _node == it._node;
	}

	node* GetNodePtr()
	{
		return _node;
	}
private:
	node* _node;
};

template<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;

	void emptyInit()
	{
		_head->_next = _head;
		_head->_prev = _head;
	}

	List()
		:_head(new node)
	{
		emptyInit();
	}

	template<class InputIterator>
	List(InputIterator frist, InputIterator lest)
		:_head(new node)
	{
		emptyInit();
		while (frist != lest)
		{
			push_back(*frist);
			frist++;
		}
	}

	void push_back(const T& val )
	{
		node* newnode = new node(val);

		node* tail = _head->_prev;

		tail->_next = newnode;
		newnode->_next = _head;
		_head->_prev = newnode;
		newnode->_prev = tail;
	}

	bool empty()
	{
		return _head->_next == _head;
	}

	void pop_back()
	{
		//判空
		assert(!empty());
		node* tail = _head->_prev;
		node* newtail = tail->_prev;
		//改变指向
		_head->_next = newtail;
		newtail->_prev = _head;
		//释放结点
		delete tail;
	}

	void insert(iterator pos,const T& val)
	{
		node* newnode = new node(val);
		node* posnode = pos.GetNodePtr();
		node* prevnode = posnode->_prev;

		newnode->_next = posnode;
		newnode->_prev = prevnode;
		prevnode->_next = posnode;
		posnode->_prev = newnode;
		
	}

	iterator erase(iterator pos)
	{

		node* posnode = pos.GetNodePtr();
		node* prevnode = posnode->_prev;
		node* nextnode = posnode->_next;
		prevnode->_next = nextnode;
		nextnode->_prev = prevnode;
		delete posnode;
		return iterator(nextnode);
	}

	iterator begin()
	{
		//使用哨兵位的下一个结点指针,构造begin
		return iterator(_head->_next);
	}
	iterator end()
	{
		//使用哨兵位结点指针,构造end
		return iterator(_head);
	}

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

	//清除数据,但是需要保留
	void clear()
	{
		iterator it = begin();
		while (it != end())
		{
			it = erase(it);
		}
	}

	~List()
	{
		//连同头节点一起释放
		clear();
		delete _head;
		_head = nullptr;
	}

private:
	node* _head;
};


到了这里,关于C++ STL list的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++学习】STL容器——list

    目录 一、list的介绍及使用 1.1 list的介绍  1.2 list的使用 1.2.1 list的构造  1.2.2  list iterator的使用 1.2.3 list capacity 1.2.4 list element access 1.2.5 list modifiers 1.2.6 list 迭代器失效 二、list的模拟实现 2.1 模拟实现list 三、list和vector的对比 一、list的介绍及使用 1.1 list的介绍 list的文档介绍

    2024年02月14日
    浏览(40)
  • C++ STL->list模拟实现

    list list文档 list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。 list与forward_list非常相似:最主

    2024年02月20日
    浏览(44)
  • C++ STL库详解:list

    一、list简介 二、list的使用 2.1list的构造 2.2list iterator迭代器的使用 2.3list element access 2.4list 常见接口 2.5迭代器失效 三、list与vector的对比 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 2. list的底层是双向链表结构,双向链

    2024年01月22日
    浏览(40)
  • 【C++入门到精通】C++入门 —— list (STL)

    文章绑定了VS平台下std::list的源码,大家可以下载了解一下😍 前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C++的命名空间的一些知识点以及关于C++的缺省参数、函数重载,引用 和 内联函数也认识了什么是类和对象以及怎么去new一个 ‘对象’ ,以及

    2024年02月12日
    浏览(40)
  • 【C++】STL---list基本用法介绍

    个人主页:平行线也会相交💪 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【C++之路】💌 本专栏旨在记录C++的学习路线,望对大家有所帮助🙇‍ 希望我们一起努力、成长,共同进步。🍓 list 是STL中的一种 容器 ,底层其实就是一个 双向链

    2024年02月16日
    浏览(45)
  • 【C++】STL---list的模拟实现

    上次模拟实现了一个vector容器,那么我们这次来实现一个list(链表)容器,链表在实际的开发中并不常见。但是也是一种很重要的数据结构,下面给大家介绍一下链表(list) 和 vector(顺序表)的区别。 list 和 vector 一样,是一个存储容器。不同的是vector在内存中是连续存储的,而

    2024年01月22日
    浏览(53)
  • C++ ——STL容器【list】模拟实现

    代码仓库: list模拟实现 list源码 数据结构——双向链表 源码的list是双向带头循环链表,所以我们定义两个节点,一个指向下一个,一个指向前一个 list类包含一个 _head 头节点,然后为了方便查出当前有多少个节点,还能多定义一个 _size 源码的迭代器设置了三个模板参数:

    2024年02月15日
    浏览(46)
  • [ C++ ] STL---list的模拟实现

    目录 结点类的模拟实现 迭代器类的模拟实现 构造函数 前置++与后置++ 前置- -与后置 - - == 与 !=运算符重载 * 运算符重载 - 运算符重载 普通迭代器总体实现代码 list类的实现 list类的成员变量 构造函数 迭代器 insert() erase() push_front/push_back/pop_front/pop_back front/back clear() empty()

    2024年04月09日
    浏览(60)
  • C++ [STL之list模拟实现]

    本文已收录至《C++语言》专栏! 作者:ARMCSKGT list的底层与vector和string不同,实现也有所差别,特别是在迭代器的设计上,本节将为大家介绍list简单实现,并揭开list迭代器的底层! 本文介绍list部分简单接口,以list迭代器的介绍为主! list底层是一个带头双向循环链表,在节

    2024年02月09日
    浏览(39)
  • 【C++ STL】 list 基础知识

    本篇将学习 list 的基础知识 🕺作者: 主页

    2024年02月05日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包