C++初阶 | [九] list 及 其模拟实现

摘要:介绍 list 容器,list 模拟实现,list与vector的对比


list(带头双向循环列表)

导入:list 的成员函数基本上与 vector 类似,具体内容可以查看相关文档(cplusplus.com/reference/list/list/),这里不多赘述。以下对 list 的 Operations 部分的函数进行简单讲解。

Operations:

splice

Transfer elements from list to list (public member function)

remove

Remove elements with specific value (public member function)

remove_if

Remove elements fulfilling condition (public member function template)

unique

Remove duplicate values (public member function)

merge

Merge sorted lists (public member function)

sort

Sort elements in container (public member function)

reverse

Reverse the order of elements (public member function)

注意:list 没有扩容的概念,而是一份一份相对独立的节点串连起来的。

1)sort

  • #include<list> std::list::sort#include<algorithm> std::sort
    如上图,RandomAccessIterator 至少已经在名称上提示使用者,这个 sort 函数要求支持能够被随机访问的迭代器
    首先,list 的迭代器是双向迭代器;其次,从底层实现来看,std::sort 函数用到了迭代器相减,而 list 的地址是不连续的。所以 list 不支持使用 std::sort 函数。

  •  std::list::sort 的使用:该函数默认升序排列(底层是归并排序)
    如果要降序排序有如下代码以供参考:(std::greater<int>() 是一个 greater 类型的匿名对象,这种写法更常用)

    #include<functional>
    #include<list>
    
    int main()
    {
    	std::list<int> lt;
        //在 lt 中插入一些数据之后
    	std::greater<int> gt;
    	lt.sort(gt);//or:lt.sort(std::greater<int>());
    
    	return 0;
    }

  • std::list::sort 性能测试
    测试结果:
    ①在 Rlease 模式下, std::vector::sort 效率大约是 list 的 2 倍,并且数据量越大效率差距越大。(tip.性能测试要在 Rlease 模式下进行,Debug 模式下优化没有全开)
    ②通过 vector 给 list 排序:把 list 对象 → 拷贝数据到 vector 对象中 →对 vector 对象 sort → 把排序好的数据拷贝到 list 。这样对 list 排序,在数据量较大的情况下效率甚至比 list 直接排序要高。

sumlist 的 sort 在性能上没有什么优势,list 中的 sort 函数在对于数据量小的情况下可以使用,但平时能不用尽量不要频繁使用。

2)merge

归并两个 list 到一个 list(要先 sort 才可以 merge,实践中很少用)。

3)unique

去重,但也有要求——只能去除连续相同的,所以要先 sort 再 unique 才可以真正“去重”。

4)splice

转移(移动指针),如下图。

以上就是对 list 一些函数的简单介绍。 


list 的模拟实现

1)结构

如上图,list 中的每个节点是一个自定义类型 Node,对于双向链表,每个节点内包括自身储存的数据、前节点指针和后节点指针。
对于由一个一个节点组成的 list通过头节点来管理整个 list

代码示例

// List的节点类
	template<class T>
	struct ListNode
	{
		ListNode<T>* _pPre;
		ListNode<T>* _pNext;
		T _val;
	};

//List类
    template<class T>
	class list
	{
		PNode _pHead;//注意:这里是一个内置类型(指针)	
	};

2)初始化_Constructor

对 list 的初始化首先是对头节点的初始化。

// List的节点类
	template<class T>
	struct ListNode
	{
		ListNode(const T& val = T())
			: _val(val)
			, _pPre(nullptr)
			, _pNext(nullptr)
		{}

		ListNode<T>* _pPre;
		ListNode<T>* _pNext;
		T _val;
	};

//List类
    template<class T>
	class list
	{
		typedef ListNode<T> Node;
		typedef Node* PNode;
	public:
		///
		// List的构造
		list()
		{
			CreateHead();
		}
	
	private:
		void CreateHead()//对头结点进行初始化
		{
			_pHead = new Node;//这里会去调用struct ListNode的构造函数
			_pHead->_pNext = _pHead;
			_pHead->_pPre = _pHead;
		}
		PNode _pHead;//注意:这里是一个内置类型(指针)	
	};

3)Iterator

class Iterator——Iterator类

  1. 成员变量:Node* _pNode
  2. 成员函数:operator* 、operator++ 、operator-- 、operator!= 、operator==(模拟指针的行为)——这里体现了“封装”。封装屏蔽底层差异和实现细节,提供统一的访问修改遍历方式。

代码示例

	//List的迭代器类
	template<class T>
	class ListIterator
	{
		typedef ListNode<T>* PNode;
		typedef ListIterator<T> Self;
	public:
		//constructor
		ListIterator(PNode pNode = nullptr)
			:_pNode(pNode)
		{}
		ListIterator(const Self& l)//copy constructor
		{
			_pNode = l._pNode;
		}

		//operations
		T& operator*()
		{
			return _pNode->_val;
		}
		T* operator->()
		{
			return &_pNode->_val;
		}
		Self& operator++()
		{
			_pNode = _pNode->_pNext;
			return *this;
		}
		Self operator++(int)
		{
			Self tmp = _pNode;
			_pNode = _pNode->_pNext;
			return tmp;
		}
		Self& operator--()
		{
			_pNode = _pNode->_pPre;
			return *this;
		}
		Self operator--(int)
		{
			Self tmp = _pNode;
			_pNode = _pNode->_pPre;
			return tmp;
		}
		bool operator!=(const Self& l)
		{
			return _pNode != l._pNode;
		}
		bool operator==(const Self& l)
		{
			return _pNode == l._pNode;
		}
		PNode _pNode;
	};

对 operator-> 的补充说明

我们知道,对于自定义类型,可以通过对其指针解引用 " *(pointer). " 和 " (pointer)-> " 来访问其成员。而 iterator 实际上是在模拟指针的行为,对于 operator-> 的使用编译器做出了优化。如下图。

3)Const_Iterator 

注意!const_iterator 不是用 const 修饰 iterator,如上 iterator 中的模拟实现可以看出,iterator 底层是原生指针,用 const 修饰 iterator 是使得指针本身不可修改,const_iterator 本身是要能被进行 ++ 和 -- 操作的,否则无法实现遍历;而 const_iterator 针对的是被 const 修饰的 list 的对象,即 const 修饰的是 list 的实例化对象本身(ps. list 对象是 const 的,那储存在节点中的数据肯定也是 const 的,即为 const T)

如上图,实际上我们需要实现两个不同的 iterator —— class ListIteratorclass ListConst_Iterator ,而对于 const 对象,begin 和 end 函数将会返回 const_iterator。

优化:使用类模板实现 List 的 Iterator 类

代码示例

	//List的迭代器类
	template<class T, class Ref, class Ptr>
	class ListIterator
	{
		typedef ListNode<T>* PNode;
		typedef ListIterator<T, Ref, Ptr> Self;
	public:
		//constructor
		ListIterator(PNode pNode = nullptr)
			:_pNode(pNode)
		{}
		ListIterator(const Self& l)//copy constructor
		{
			_pNode = l._pNode;
		}

		//operations
		Ref operator*()
		{
			return _pNode->_val;
		}
		Ptr operator->()
		{
			return &_pNode->_val;
		}
		Self& operator++()
		{
			_pNode = _pNode->_pNext;
			return *this;
		}
		Self operator++(int)
		{
			Self tmp = _pNode;
			_pNode = _pNode->_pNext;
			return tmp;
		}
		Self& operator--()
		{
			_pNode = _pNode->_pPre;
			return *this;
		}
		Self operator--(int)
		{
			Self tmp = _pNode;
			_pNode = _pNode->_pPre;
			return tmp;
		}
		bool operator!=(const Self& l)
		{
			return _pNode != l._pNode;
		}
		bool operator==(const Self& l)
		{
			return _pNode == l._pNode;
		}
		PNode _pNode;
	};

	//list类
	template<class T>
	class list
	{
		typedef ListNode<T> Node;
		typedef Node* PNode;
	public:
		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T, const T&, const T&> const_iterator;
	public:
		///
		// List的构造
		list()
		{
			CreateHead();
		}

		///
		// List Iterator
		iterator begin()
		{
			return _pHead->_pNext;
		}
		iterator end()
		{
			return _pHead;
		}
		const_iterator begin() const
		{
			return _pHead->_pNext;
		}
		const_iterator end()const
		{
			return _pHead;
		}
    }

注意:同一个类模板,实例化参数不同,就是完全不同的类型,即对于 ListIterator<T, T&, T*> 和 ListIterator<T, const T&, const T&> 是两个不同的类型。(ps. iterator 和 const_iterator 都实现之后才可以支持使用范围 for)

4)其他成员函数

这些成员函数实现起来思路很简单,有问题建议去看数据结构的文章回顾一下。以下简略说明。

①insert

insert 之后 iterator 不失效,因为没有扩容的影响。

		// 在pos位置前插入值为val的节点
		iterator insert(iterator pos, const T& val)
		{
			PNode cur = pos._pNode;
			PNode newnode = new Node(val);
			newnode->_pNext = cur;
			newnode->_pPre = cur->_pPre;

			cur->_pPre = newnode;
			newnode->_pPre->_pNext = newnode;

			return pos;
		}

②erase

erase 之后 iterator 失效,因为这个被 erase 的节点被释放了,那么指向它的 iterator 也就失效了。

		// 删除pos位置的节点,返回该节点的下一个位置
		iterator erase(iterator pos)
		{
			if (!empty())
			{
				PNode next = pos._pNode->_pNext;
				pos._pNode->_pPre->_pNext = next;
				next->_pPre = pos._pNode->_pPre;
				delete pos._pNode;
				--_size;

				return next;
			}

			return _pHead;
		}

③push_back and push_front

复用 insert。

		// List Modify
		void push_back(const T& val) { insert(end(), val); }
		
		void push_front(const T& val) { insert(begin(), val); }	

④pup_back and pop_front

复用 erase。

		// List Modify
		void pop_back() { erase(--end()); }

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

⑤clear

用 iterator 遍历,依次 erase 每个节点。

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

⑥Destructor

clear → delete → nullptr,即清理 list,释放头节点,头结点指针指针置空。

		//destructor
		~list()
		{
			clear();
			delete _pHead;
			_pHead = nullptr;
		}

⑦Copy Constructor

范围 for 循环 push_back。(注意:使用范围 for 需要把 const_iterator 也实现了才能用)

		list(const list<T>& l)//copy constructor
		{
			CreateHead();
			for (auto e : l)
			{
				push_back(e);
			}
		}

⑧赋值重载

		//assign
		list<T>& operator=(list<T> l)
		{
			if (_pHead != l._pHead)
			{
				swap(l);
				return *this;
			}

		}

		void swap(list<T>& l)
		{
			std::swap(_pHead, l._pHead);
			std::swap(_size, l._size);
		}

⑨其他构造函数重载

		list(int n, const T& value = T())
		{
			CreateHead();
			while (n--)
			{
				push_back(value);
			}
		}
		template <class Iterator>
		list(Iterator first, Iterator last)
		{
			CreateHead();
			Iterator it = first;
			while (it != last)
			{
				push_back(*it);
				++it;
			}
		}

补充:list 的成员变量中可以加一个 size_t 类型的变量来记录节点个数,因为如果没有这个成员变量就需要遍历来获取有效数据个数,效率比较低。(提醒:如果增加了 size_t 类型的成员变量记得在 insert 和 erase 的函数实现中相应地做出调整)

5)补充:Print

针对于 list<int> / list<char> 等类型的打印函数很好实现,以下我们尝试写出更通用的打印函数。

打印 list<T> 而不只是针对某个具体的 T 类型

因为语法编译之前要先对模板进行实例化,对于 Btl::list<T>::const_iterator 由于模板没有被实例化,所以编译器不知道 const_iterator  list<T> 中的一个内嵌类型还是静态成员变量,这样的行为对于编译器是未知的。

所以,Btl::list<T>::const_iterator 前加 typename 来声明这是一个内嵌类型。代码如下。

template<typename T>
void print_l(const Btl::list<T>& _list)
{
	typename Btl::list<T>::const_iterator it = _list.begin();
	while (it != _list.end())
	{
		std::cout << *it;
		++it;
	}
	std::cout << std::endl;
}

打印任意容器

提醒:下列代码中要求 *it 支持流插入。

template<typename Container>
void print_l(const Container& _con)
{
	typename Container::const_iterator it = _con.begin();
	while (it != _con.end())
	{
		std::cout << *it;
		++it;
	}
	std::cout << std::endl;
}

回顾:vector模拟实现中涉及的深浅拷贝的问题

对于类似 vector<string> 而出现的深浅拷贝问题,因为 list 不涉及扩容的概念,所以不会出现深浅拷贝的问题。


list与vector的对比

vectorlist
底层结构动态顺序表,一段连续空间带头结点的双向循环链表
随机访问支持随机访问,访问某个元素的效率为O(1)不支持随机访问,访问某个元素的效率为O(N)
插入和删除任意位置插入和删除效率低,需要搬移元素(挪动数据),时间复杂度为O(N),插入时有可能需要增容——开辟新空间,拷贝元素,释放就空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层结点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生态指针对原生态指针(节点指针)进行封装
迭代器失效

在插入元素时,要给所有迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效;删除时,当前迭代器需要重新给赋值否则会失效

插入元素不会导致迭代器失效;删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景需要高效存储,支持随机访问,不关系插入删除效率大量插入和删除操作,不关心随机访问

完整代码链接My_List/My_List/My_List.h · fantansy-13-07/Cpp - 码云 - 开源中国 (gitee.com)


END

  • 17
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
引用和提供了关于实现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 ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

畋坪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值