C++——list的使用及其模拟实现

list


本章思维导图
在这里插入图片描述注:本章思维导图对应的 .png.xmind文件都以同步至 资源,可免费查阅


list也是C++标准模板库中的一大容器,事实上,他就是我们在数据结构中学的带头循环双向链表

在这里插入图片描述

接下来,让我们来学习它的基本使用以及模拟实现:

1. 基本使用

1.1 list对象的定义

template < class T, class Alloc = allocator<T> > class list;
  • 作为标准模板库中的容器,list同样也是一个类模板
  • 如果要定义一个list对象,就需要指定其存储的数据类型T。例如:list<int>

1.2 增(插入数据)

list作为带头循环双向链表,其支持头插(push_front)、尾插(push_back)、在pos位置之前插入(insert)等添加数据的操作

void push_back (const value_type& val);

void push_front (const value_type& val);

iterator insert (iterator position, const value_type& val);

接下来做简单的演示:

#include <iostream>
#include <list>

using namespace std;

int main()
{
	list<int> lt;

	//尾插
	for (int i = 0; i < 3; i++)	lt.push_back(i);
	for (auto& e : lt)	cout << e << " ";
	cout << endl;

	//头插
	for (int i = 3; i < 6; i++)	lt.push_front(i);
	for (auto& e : lt)	cout << e << " ";
	cout << endl;

	//在`pos`前插入
	lt.insert(lt.end(), 99);
	for (auto& e : lt)	cout << e << " ";
	cout << endl;

	return 0;
}

output:

0 1 2
5 4 3 0 1 2
5 4 3 0 1 2 99

1.3 删(删除数据)

和添加数据一样,list删除数据同样也支持**头删(pop_front)、尾删(pop_back)、删除pos位置的节点(erase)**等删除操作

void pop_front();

void pop_back();

iterator erase (iterator position);

接下来做简单的演示:

#include <iostream>
#include <list>

using namespace std;

int main()
{
	list<int> lt;
	for (int i = 0; i < 5; i++) lt.push_back(i + 1);

	//尾删
	lt.pop_back();
	for (auto& e : lt)	cout << e << " ";
	cout << endl;

	//头删
	lt.pop_front();
	for (auto& e : lt)	cout << e << " ";
	cout << endl;

	//删除`pos`处的节点
	lt.erase(lt.begin());
	for (auto& e : lt)	cout << e << " ";
	cout << endl;

	return 0;
}

output:

1 2 3 4
2 3 4
3 4

1.4 遍历访问

不同于stringvectorlist并不支持[]访问。list只有两种访问方式:

  • 迭代器访问
  • 范围for
  • 实际上,范围for使用的同样也是迭代器
  • 关于list的迭代器后面会做详细说明,这里仅做使用展示

接下来做简单的演示:

#include <iostream>
#include <list>

using namespace std;

int main()
{
	list<int> lt;
	for (int i = 0; i < 5; i++)	lt.push_back(i + 1);
	
    //迭代器访问
	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//范围for
	for (auto& e : lt)	cout << e << " ";
	cout << endl;

	return 0;
}

2. 模拟实现

不同于stringvector这种连续的结构,对于存储空间不连续的list而言,其模拟实现就要复杂了许多

接下来进行逐步分析:

2.1 节点类ListNode

  • list是带头循环双向链表,其每个节点都存储着:前一个节点和后一个节点的指针以及存储的数据。
  • 因此,我们需要创建一个ListNode类来存储对应的信息
template<class T>
struct ListNode
{
	ListNode<T>* _next;
	ListNode<T>* _prev;
	T	         _data;
	
    //构造函数
	ListNode(const T& value = T())
		: _next(nullptr)
		, _prev(nullptr)
		, _data(value)
	{}
};

注:

  • 不要忘记在C++中**struct不是结构体而是一个类**,只是其默认的访问限定符为public。因此它也需要构造函数
  • ListNOde是一个类模板,所以ListNode不是类名,而是模板名;ListNode<T>才是类名

2.2 封装ListNode类,实现list基本功能

实现好了ListNOde类后,我们就可以将其封装进list类中,利用list对其进行管理:

template<class T>
class list
{
    typedef ListNode<T> Node;

public:
    //构造
    list()
    {
        _head = new Node;
        _head->_next = _head;
        _head->_prev = _head;
    }
	
    //尾插
    void push_back(const T& value = T())
    {
        Node* newNode = new Node(value);

        Node* tail = _head->_prev;
        tail->_next = newNode;
        newNode->_prev = tail;
        newNode->_next = _head;
        _head->_prev = newNode;
    }
	
    //尾删
    void pop_back()
    {
        assert(begin() != end());

        Node* tail = _head->_prev;
        Node* newTail = tail->_prev;

        _head->_prev = newTail;
        newTail->_next = _head;

        delete tail;
    }
	
    //头插
    void push_front(const T& value = T())
    {
        Node* cur = _head->_next;
        Node* newNode = new Node(value);

        _head->_next = newNode;
        newNode->_prev = _head;
        newNode->_next = cur;
        cur->_prev = newNode;
    }
	
    //头删
    void pop_front()
    {
        assert(begin() != end());

        Node* cur = _head->_next;

        cur->_next->_prev = _head;
        _head->_next = cur->_next;

        delete cur;
    }
	
    //交换两个list
    void swap(list<T>& it)
    {
        std::swap(_head, it._head);
    }
private:
    Node* _head;
};

注:部分需要用到迭代器的功能放到后面实现

2.3 实现迭代器iterator

让我们再来回顾一下C++迭代器的概念:

迭代器是一个设计模式,它允许你遍历一个容器(如数组、列表、向量等)的所有元素,而无需知道容器的底层表示方式

也就是说,迭代器为我们提供了一个统一的方式来遍历容器。例如我们++一个迭代器就可以指向其后一个元素,--一个迭代器就可以指向前一个元素,而不要管这个容器具体是什么。

  • 迭代器的底层实际上都是指针
  • 对于stringvector这种连续的物理空间,他们的迭代器很好实现,就是原生指针
  • 但是对于list这种物理空间并不连续的存储结构一个节点的指针++后并不能指向它后面的指针,因此就不能使用原生指针来实现其迭代器了。

因此,为了让屏蔽 节点的指针++后不能指向它后面的指针 这一缺陷,我们就需要对ListNode类进行封装使其++- - 等操作符合迭代器的规范。而对其封装的类,就是我们的迭代器__List_iterator

template<class T>
struct __list_iterator
{
    typedef ListNode<T> Node;
    typedef __list_iterator<T> self;

    Node* _node;

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

    //++it
    self& operator++ ()
    {
        _node = _node->_next;
        return *this;
    }
    //it++
    self operator++ (int)
    {
        self temp(_node);
        _node = _node->_next;
        return temp;
    }
    //--it
    self& operator-- ()
    {
        _node = _node->_prev;
        return *this;
    }
    //it--
    self operator-- (int)
    {
        self temp(_node);
        _node = _node->_prev;
        return temp;
    }
    T& operator* ()
    {
        return _node->_data;
    }
    T* operator-> ()
    {
        return &_node->_data;
    }
    bool operator!= (const self& value)
    {
        return _node != value._node;
    }
    bool operator == (const self& value)
    {
        return _node == value._node;
    }
};

需要注意:

  • C++ 标准规定了没有参数的 operator++ 是前置递增运算符,而有一个 int 参数的 operator++ 是后置递增运算符
  • operator-- 同理

2.3.1 实现const迭代器const_iterator

要实现一个const迭代器,最容易想到的方法,当然就是复制一份普通的迭代器__list_iterator对象,再对operator*()operator->()的返回值做简单的修改就行了:

template<class T>
struct __list_const_iterator
{
    typedef ListNode<T> Node;
    typedef __list_const_iterator<T> self;

    Node* _node;

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

    //++it
    self& operator++ ()
    {
        _node = _node->_next;
        return *this;
    }
    //it++
    self operator++ (int)
    {
        self temp(_node);
        _node = _node->_next;
        return temp;
    }
    //--it
    self& operator-- ()
    {
        _node = _node->_prev;
        return *this;
    }
    //it--
    self operator-- (int)
    {
        self temp(_node);
        _node = _node->_prev;
        return temp;
    }
    const T& operator* ()
    {
        return _node->_data;
    }
    const T* operator-> ()
    {
        return &_node->_data;
    }
    bool operator!= (const self& value)
    {
        return _node != value._node;
    }
    bool operator == (const self& value)
    {
        return _node == value._node;
    }
};

但是,这样的做法无疑会添加大量冗余的代码,显然不是最恰当的。因此最初编写标准模板库的大佬们就想出了这样的办法——添加类模板参数。也就是说,我们可以将__list_iterator类模板写成这样:

template<class T, class Referance, class Ptr>
struct __list_iterator
{}
  • T就是list存储的数据类型
  • Referance就是operator*()的返回值,如果是非const对象,就传入T&;如果是const对象,就传入const T&
  • Ptr就是operator->()的返回值,如果是非const对象,就传入T*;如果是const对象,就传入const T*

这样,我们就可以将普通迭代器和const迭代器整合为一个迭代器__list_iterator

template<class T, class Referance, class Ptr>
struct __list_iterator
{
    typedef ListNode<T> Node;
    typedef __list_iterator<T, Referance, Ptr> self;

    Node* _node;

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

    //++it
    self& operator++ ()
    {
        _node = _node->_next;
        return *this;
    }
    //it++
    self operator++ (int)
    {
        self temp(_node);
        _node = _node->_next;
        return temp;
    }
    //--it
    self& operator-- ()
    {
        _node = _node->_prev;
        return *this;
    }
    //it--
    self operator-- (int)
    {
        self temp(_node);
        _node = _node->_prev;
        return temp;
    }
    Referance operator* ()
    {
        return _node->_data;
    }
    Ptr operator-> ()
    {
        return &_node->_data;
    }
    bool operator!= (const self& value)
    {
        return _node != value._node;
    }
    bool operator == (const self& value)
    {
        return _node == value._node;
    }
};

2.3.2 实现反向迭代器reverse_iterator

需要注意:

为了实现对称性,C++规定:

  • 反向迭代器的开头rbegin()实际上是正向迭代器的结束end();反向迭代器的结束rend()即为正向迭代器的开始begin()
  • 当对一个反向迭代器进行*操作时,并不会对当前的位置进行*操作,而是会对当前位置的之前一个位置进行*操作

和const迭代器,一样写出反向迭代器最简单的方式同样是复制一下普通迭代器类,再修改一下代码逻辑即可。这里不再做展示。

但很显然,这种方法同样也不是最好的。

最初编写标准模板库的大佬们在写反向迭代器的代码时会思考这样的问题:

是否可以写这样一份代码,可以将所有的正向迭代器都转化为对应的反向迭代器呢?

事实上,他们确实是这样做的:

  • 可以新建一个头文件reverse_iterator,其包含一个反向迭代器类Reverse_iterator,其实现了关于反向迭代器的各种操作。
  • 为了做到可以 将所有的正向迭代器转化为其对应的反向迭代器 ,其各种操作并不需要我们手动实现,而是需要复用正向迭代器的方法

我们可以先来看看其具体的实现:

template<class Iterator, class Ref, class Ptr>
struct Reverse_iterator
{
	Reverse_iterator(const Iterator& it)
		: _it(it)
	{}

	typedef Reverse_iterator<Iterator, Ref, Ptr> Self;

	Self& operator++ ()
	{
		--_it;
		return *this;
	}

	Self& operator-- ()
	{
		++_it;
		return *this;
	}

	Self operator++ (int)
	{
		Self tmp = _it;
		--_it;

		return tmp;
	}

	Self operator-- (int)
	{
		Self tmp = _it;
		++_it;

		return tmp;
	}
	
	//注意,*运算不是对当前位置
	//而是对当前位置的前一个位置进行运算
	Ref operator* ()
	{
		Iterator tmp = --_it;
		++_it;

		return *tmp;
	}

	Ptr operator-> ()
	{
		return &(operator*());
	}

	bool operator!= (const Self& it)
	{
		return _it != it._it;
	}

	bool operator== (const Self& it)
	{
		return _it == it._it;
	}

	Iterator _it;
};
  • Iterator即为对应正向迭代器
  • 这里通过对Iterator的封装,利用Iterator的方法来间接实现其反向迭代器的操作,从而就可以实现用同一份代码就可以将所有的正向迭代器都转换为其对应的反向迭代器

3. list容器模拟实现代码

reverse_list.h

template<class Iterator, class Ref, class Ptr>
struct Reverse_iterator
{
	Reverse_iterator(const Iterator& it)
		: _it(it)
	{}

	typedef Reverse_iterator<Iterator, Ref, Ptr> Self;

	Self& operator++ ()
	{
		--_it;
		return *this;
	}

	Self& operator-- ()
	{
		++_it;
		return *this;
	}

	Self operator++ (int)
	{
		Self tmp = _it;
		--_it;

		return tmp;
	}

	Self operator-- (int)
	{
		Self tmp = _it;
		++_it;

		return tmp;
	}

	Ref operator* ()
	{
		Iterator tmp = --_it;
		++_it;

		return *tmp;
	}

	Ptr operator-> ()
	{
		return &(operator*());
	}

	bool operator!= (const Self& it)
	{
		return _it != it._it;
	}

	bool operator== (const Self& it)
	{
		return _it == it._it;
	}

	Iterator _it;
};

list.h

#pragma once

#include <iostream>
#include <list>
#include <assert.h>
#include "reverse_iterator.h"

using namespace std;

namespace TEST
{
	template<class T>
	struct ListNode
	{
		ListNode<T>* _next;
		ListNode<T>* _prev;
		T	         _data;

		ListNode(const T& value = T())
			: _next(nullptr)
			, _prev(nullptr)
			, _data(value)
		{}
	};
	
    //正向迭代器
	template<class T, class Referance, class Ptr>
	struct __list_iterator
	{
		typedef ListNode<T> Node;
		typedef __list_iterator<T, Referance, Ptr> self;

		Node* _node;

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

		//++it
		self& operator++ ()
		{
			_node = _node->_next;
			return *this;
		}
		//it++
		self operator++ (int)
		{
			self temp(_node);
			_node = _node->_next;
			return temp;
		}
		//--it
		self& operator-- ()
		{
			_node = _node->_prev;
			return *this;
		}
		//it--
		self operator-- (int)
		{
			self temp(_node);
			_node = _node->_prev;
			return temp;
		}
		Referance operator* ()
		{
			return _node->_data;
		}
		Ptr operator-> ()
		{
			return &_node->_data;
		}
		bool operator!= (const self& value)
		{
			return _node != value._node;
		}
		bool operator == (const self& value)
		{
			return _node == value._node;
		}
	};

	template<class T>
	class list
	{
		typedef ListNode<T> Node;

	public:
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;
		typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;
		typedef Reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;

		list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		list(list<T>& it)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			for (const auto e : it)
			{
				push_back(e);
			}
		}

		list<T>& operator= (list<T> it)
		{
			swap(it);

			return *this;
		}

		void push_back(const T& value = T())
		{
			insert(end(), value);
		}

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

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

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

		iterator erase(iterator pos)
		{
			assert(begin() != end());

			Node* curNode = pos._node;
			Node* prevNode = curNode->_prev;
			Node* nextNode = curNode->_next;

			nextNode->_prev = prevNode;
			prevNode->_next = nextNode;
			delete curNode;

			return nextNode;
		}

		iterator insert(iterator pos, const T& value = T())
		{
			Node* newNode = new Node(value);
			Node* prevNode = pos._node->_prev;
			Node* curNode = pos._node;

			//prevNode newNode curNode;
			prevNode->_next = newNode;
			newNode->_prev = prevNode;
			newNode->_next = curNode;
			curNode->_prev = newNode;

			return newNode;
		}

		void swap(list<T>& it)
		{
			std::swap(_head, it._head);
		}

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

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

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

		iterator end()
		{
			return _head;
		}

		const_iterator end() const
		{
			return _head;
		}

		reverse_iterator rbegin()
		{
			return end();
		}

		const_reverse_iterator rbegin() const
		{
			return end();
		}

		reverse_iterator rend() 
		{
			return begin();
		}
		
		const_reverse_iterator rend() const
		{
			return begin();
		}

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;
		}

	private:
		Node* _head;
	};
}

本篇完
如果错误敬请指正

  • 17
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
引用和提供了关于实现vector的两种方法。其中,引用展示了一个使用reserve和push_back方法的示例,而引用展示了一个使用new和memcpy函数的示例。这两种方法都是常见的实现vector的方式。 在第一种方法中,通过reserve函数可以预留足够的内存空间,然后使用push_back函数逐个将元素添加到vector中。这种方法的好处是可以避免不必要的内存重分配,提高了效率。 而第二种方法使用new操作符在堆上分配内存空间,并使用memcpy函数将已有的vector对象的数据复制到新的内存空间中。通过这种方式,可以实现深拷贝,即两个vector对象拥有独立的内存空间。这种方法的好处是可以在不修改原始vector对象的情况下创建一个新的vector对象。 除了以上两种方法,还可以使用其他方式实现vector。例如,可以使用动态数组来实现vector的底层数据结构,然后通过成员函数实现vector的各种操作,如增加、删除、查找等。 总结来说,c语言模拟实现vector的关键是动态内存管理和对元素的增删改查操作。可以使用预留空间和逐个添加元素的方式,也可以使用动态数组和复制数据的方式来实现vector。具体的实现方式可以根据需求和实际情况选择。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C++——vector模拟实现](https://blog.csdn.net/weixin_49449676/article/details/126813526)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Forward♞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值