STL源码刨析_list

目录

一. list 介绍

1. 文章内容介绍

二. list 模拟实现

1. 成员变量

2. 构造函数

3. push_back

4. iterator

4.1 迭代器介绍

4.2 iterator

4.3 const_iterator

4.4  operator->

5. insert

6. size

7. erase

8. push_front

9. pop_back

10. pop_front

11. 拷贝构造 

12. 赋值重载

13. swap

14. claer

15. ~list


一. list 介绍

在C++的STL(Standard Template Library)中,list是一种双向链表容器,可用于存储和操作元素的集合。以下是关于list容器的介绍:

  • 双向链表:list底层使用双向链表来实现,每个节点都包含一个指向前一个节点和后一个节点的指针。这使得在list中插入、删除元素的操作效率较高,而不需要像数组或向量那样移动其他元素。
  • 无需连续内存:list中的元素不要求在内存中连续存储,因此它可以动态地分配和释放内存空间。这为存储大量元素或在运行时频繁插入和删除元素的场景提供了灵活性。
  •  插入和删除操作:list支持在任意位置插入和删除元素,包括头部、尾部和中间位置。这些操作具有常数时间复杂度(O(1)),因为只需要修改相邻节点的指针即可。
  • 不支持随机访问:与向量(vector)不同,list不支持通过索引进行随机访问。要访问或修改list中的元素,需要遍历链表,找到目标位置。因此,list适用于需要频繁插入和删除元素,而不是需要快速随机访问元素的场景。
  • 提供丰富的成员函数:list提供了许多成员函数来方便地操作元素,如插入、删除、访问等。它还与STL其他组件(如算法)配合使用,可以进行排序、搜索、遍历等更复杂的操作。

总体上,list是一个灵活、高效、易于使用的容器,用于存储和操作元素集合。在需要频繁插入和删除元素的场景中,list往往是一个很好的选择。

上面只是对 list 的一个基本介绍~

1. 文章内容介绍

这篇文章的主要内容并不是对 list 的模拟实现,这篇文章的主要内容是对 iterator (迭代器) 的介绍,我们前面见过的迭代器基本都是原生指针,所以我们很简单的就完成了对 iterator 的编写,但是并不是所有的 iterator 都是原生指针,我们更多的 iterator 是我们自己封装的,所有今天我们来学习一下自己封装的 iterator

二. list 模拟实现

1. 成员变量

我们的 list 里面存的是一个一个的节点,这个节点里面需要一个存值的变量,和两个指向相同类型结构的指针指向前一个和后一个节点,所以我们先看一下我们的节点,然后我们这个类里面还需要一个构造函数,方便创建节点的时候初始化

	template
	struct _list_node
	{
		T _val;
		_list_node* _next;
		_list_node* _prev;

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

我们现在在看一下我们 list 的成员变量,里面有一个 _list_node 的指针,指向该链表的头节点(哨兵位),还有一个 size 方便我们记录我们的数据个数,下面我们看一下我们的 list 的成员变量

	template
	class list
	{
		typedef _list_node Node;
	public:
	private:
		Node* _head;
		size_t _size;
	};

我们将上面的 _list_node 的节点 typedef 为 Node 节点,模板这里类名并不代表变量名,在模板这里类名+模板才是变量名,这里我们在模板那里就说过,所以我们这里就不多强调了,有需要的可以看一下模板那里

2. 构造函数

构造函数这里我们要写的是一个默认构造函数,所以就直接看实现吧!

		void emptyinit()
		{
			_head = new Node;
			_size = 0;
			_head->_next = _head;
			_head->_prev = _head;
		}

		list()
		{
			emptyinit();
		}

我们将让构造函数调用初始化,这里是因为我们后面也会用到这个,所以为例不让代码看起来很冗余,所以我们就这样写。这里在强调一下,我们的 _head 是一个哨兵位,里面什么都不用存储,我们刚开始的 prev 和 next 都指向 _head 自己

3. push_back

我们这里就直接说 push_back 这里是因为我向让代码可以快速的测试起来,让我们来看迭代器的是实现,这里在说明一下,我们今天的 list 的其他函数并不是重点,我们这里的重点是迭代器

我们的尾插就是插入到 end 的前面,我们想一下,我们的 begin是哪一个?我们的 begin就是第一个值,也就是我们的 _head 的下一个位置,而我们的 end 就是最后一个值的后一个位置,也就是 _head 的位置,所以我们这里的尾插就是插入带 end 前面,也就是 _head 的前面。

		void push_back(const T& val)
		{
			Node* tail = _head->_prev;
			Node* newnode = new Node(val);
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;

			++_size;
		}
        // 其实我们这里是可以复用 insert 的,但是我们的 insert 
        //是需要用 iterator 做参数的,所以我们就先自己写一个 push_back
        //但是这里也会把复用的版本贴出来

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

我们的 push_back 已经写完了,那么我们看是说我们的 iterator 我们这里就可以尾插一些数据,然后使用迭代器遍历,这个可以下去自己测试一下,这里我就不说了

4. iterator

4.1 迭代器介绍

 iterator 是我们 STL里面通用的,这里先简单的介绍一下迭代器,我们的迭代器实际上又可以分为三类

  • 单向迭代器
  • 双向迭代器
  • 随机迭代器

而我们的 list 就是双向迭代器,这里介绍一下着三个迭代器的不同

单项迭代器:就是迭代器只可以++操作

双向迭代器:就是既可以++操作,也可以--操作

随机迭代器:就是可以++,--,+,- ... 等操作

而我们的单项迭代器就是基类其他迭代器就是子类

4.2 iterator

我们的 list 是和 vector 和 string 是不同的,我们的 vector 和 string 底层是连续的空间,而我们的 list 不是连续的空间,所以我们的  list 的迭代器不可以是原生的指针,我们的  list 的迭代器只能是节点的指针,但是我们又不能只是节点的指针,因为我们的 iterator 需要进行 ++、-- 、*、==、
!=、 甚至是 -> 等操作...所以我们的 iterator 不能只是 节点的指针,我们还需要对他进行各种运算符的重载,所以我们的 list 的迭代器需要是一个类,然后我们对该类进行运算符重载,那么先看一个不怎么完善的迭代器。

template
	struct __list_iterator
	{
		typedef _list_node Node;
		Node* _node;

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

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

		_list_iterator& operator++(int)
		{
			Node* prev = _node;
			_node = _node->_next;
			return *this;
		}


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

		_list_iterator& operator--(int)
		{
			Node* prev = _node;
			_node = _node->_prev;
			return *this;
		}

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

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

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

	};

这就是我们的 iterator 的对象

然后我们的 list 对其进行 typedef __list_iterator iterator,然后我们的 list 就可以使用该 iterator 对象了

		typedef __list_iterator iterator;

所以我们平时就可以向下面这样使用

    vector v1;
    std::vector::iterator it = v1.begin();

所以实际上 iterator 是我们的类里面的一个对象,我们设计为 struct 是为了更方便访问里面的函数和成员变量,当然你自己也可以设计为 class 然后设置访问限定符都是可以的,但是我的stl 源码刨析里面就是 struct 我们这里是跟着stl里面来的

4.3 const_iterator

这里现在有一个问题,如果我们不想让 iterator 可以修改里面的值呢?

所以这是后我们就需要一个 const 的 iterator 那么我们应该怎么做呢?

我们看一下下面这样子可以不可以

    typedef const __list_iterator const_iterator;

这里回答我们这里是不可以的,为什么呢?因为我们这样子的话,我们的 iterator的对象是不可以被修改的,而我们是需要对我们的 iterator 进行++ 等操作的,所以我们并不是期望我们不修改 iterator 的对象,我们期望的是我们不会修改 *iterator ,所以我们是要让 *iterator 不会被修改,所以我们可以将 operator* 的返回值变为 const 的,那么我们应该怎么做呢?

解决方案1:

我们可以在写一个 __list_const_iterator 的对象,然后我这个对象和 __list_iterator 的对象的区别就是 operator* 的返回值是不同的,一个是 可以修改的,另一个是 const 的

	template
	struct __list_const_iterator
	{
		typedef _list_node Node;
		Node* _node;

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

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

		__list_const_iterator& operator++(int)
		{
			Node* prev = _node;
			_node = _node->_next;
			return *this;
		}

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

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

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

区别只有最下面的 operator* 函数,但是这个解决方案并不是我们所满意的,这样子代码就态冗余了,所以我们还有一个方法

解决方案2:

既然我们的差别就只有 operator* 的返回值是不同的,那么我们可不可以让他模板生成不同的返回值呢?可以的,所以我们这时候我们可以在增加一个类模板参数,我们将普通的 iterator 传入的是 T& 而我们的 const_iterator 的传入 const T& 我们下面可以看一下,但是这时候我们传入两个米板参数,我们要写的类型的名太长了,我们可以对该类型进行 typedef 

	template // 增加一个模板参数
	struct __list_iterator
	{
		typedef _list_node Node;
		typedef __list_iterator self; // 类型名太长,我们可以第其进行 typeddef
		Node* _node;

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

		self& operator++() // 我们的 operator++/-- 
                           // 返回的还是迭代器,所以我们返回整个类型就可以了
		{
			_node = _node->_next;
			return *this;
		}

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


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

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

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

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

		Ref operator*() // 我们的 operator* 返回的是一个 T类型的引用/const 引用
                        // 所以我们将 Ref 传为 T& 就是 普通迭代器,传为 const T& 
                        // 就是 const 的迭代器
		{
			return _node->_val;
		}

	};

我们下面看一下 list 里面如何typedef

		typedef __list_iterator iterator;
		typedef __list_iterator const_iterator;

4.4  operator->

实际上我们的指针还是需要进行 -> 操作的,假设我们 list 里面存的是一个结构体,那么我们要是光 解引用的话,那么我们拿到的是 结构体类型的对象,如果我们想要访问里面的值,我们还需要解引用后进行点(.)操作 例如:(*it). X

所以我们现在在看一下我们的 operator-> 

实际上我们的 operator-> 和 operator* 是一样的,假设我们现在不希望被修改,那么我们 
operator-> 的返回值是一个不可以被修改的指针,也就是 const T* 的保证我们的值不会被修改,所以我们的解决方案还是前面解决 operator* 的那两个方案,这里我们就不演示第一个方案了,我们直接说第二个。

我们同样是多增加一个类模板,然后普通的 iterator 传入 T* ,const_iterator 传入的是 const T*,下面我们就来看解决方案

	template
	struct __list_iterator
	{
		typedef _list_node Node;
		typedef __list_iterator self;
		Node* _node;

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

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

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


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

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

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

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

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

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

我们在看一下 list 里面如何 typedef

		typedef __list_iterator iterator;
		typedef __list_iterator const_iterator;

我们的 iterator 对象已经编写完了,但是我们还没又写我们的 list 里面的 begin()等函数

我们下面来看一下,但是这里我们也不多说,很容易明白的!

		typedef __list_iterator iterator;
		typedef __list_iterator const_iterator;

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

		iterator end() 
		{
			return _head;
		}

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

		const_iterator cend() const
		{
			return _head;
		}

迭代器就讲完了,这里我们还更深入了解了模板。

其实今天的重点就结束了,我们后面的 insert 那些也都是我们之前说过的,但是我们还是继续看一下。

5. insert

insert 就是给一个 iterator 位置,然后在给一个 val 然后就是插入到 pos 位置之前,下面直接看代码

		void insert(iterator pos, const T& val)
		{
			iterator prev = pos._node->_prev;
			Node* newnode = new Node(val);
			prev._node->_next = newnode;
			newnode->_prev = prev._node;
			newnode->_next = pos._node;
			pos._node->_prev = newnode;
			++_size;
		}

6. size

为了我们这个模拟实现的完整性,我们还是在说一下 size 这个函数

		size_t size()
		{
			return _size;
		}

7. erase

删除给定位置的元素,但是这里的位置是 iterator 

		void erase(iterator pos)
		{
			assert(pos != end());
			Node* prev = pos._node->_prev;
			Node* next = pos._node->_next;
			prev->_next = next;
			next->_prev = prev;
			delete pos._node;
			--_size;
		}

其实这里还有一个 erase 是删除一整段连续的节点,但是这里我们就比实现了,我们最简单的就是复用 eraser 上面这个函数,或者就是自己一个一个删除,这个相对麻烦一点。

8. push_front

从这里开始我们下面就都是复用 Insert 和 erase 了

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

9. pop_back

pop_back 是尾删

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

10. pop_front

pop_front 是头删

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

11. 拷贝构造 

拷贝构造,这里也需要和 vetor 是一样的,为了防止 list 里面的值需要深拷贝所以这里我们不能直接memcpy 里面的数据,我们可以一个一个插入

		list(const list& l)
		{
			emptyinit();
			const_iterator it = l.cbegin();
			while (it != l.cend())
			{
				push_back(*it);
				++it;
			}
		}

12. 赋值重载

这里我们的复制重载使用现代写法,顺便我们写一个专属于 list 的 swap 函数,swap 函数我们写在赋值重载后面。

		list& operator=(list l)
		{
			swap(l);

			return *this;
		}

13. swap

交换函数,这里是和 std::swap 是不一样的,我们的 swap 是创建一个 对象然后进行交换,我们自己实现的是值交换里面的成员变量

		void swap(list& l)
		{
			std::swap(_head, l._head);
			std::swap(_size, l._size);
		}

14. claer

clear 函数是将我们的 list 对象里面的值全部删掉,但是要分清楚我们的 clear和 ~list ,我们的 clear 只是删掉里面的值,而我们的 ~list 是不仅删掉里面的值,我们还要对头节点也进行释放,所以他们还是有区别的

		void clear()
		{
			while (_size)
			{
				pop_back();
			}
		}

15. ~list

析构函数

		~list()
		{
			clear();

			delete _head;
			_size = 0;
		}

想要源码的可以去我的 戳-> "码云"

那么今天就到这里~

你可能感兴趣的:(C++,c++,list,开发语言)