C++ list

1. 前言


listC++ STL的一个容器,它的底层是双向循环链表

本篇文章的重点:

  • listiterator实现逻辑
  • 利用模板实现iteratorconst_iterator

2. 模拟实现


由于list的底层是双向循环链表,因此我们得有节点,并且链表最开始是有一个哨兵位的头节点的

C++ 复制代码
namespace byh
{
	template<class T>
	struct ListNode
	{
		ListNode(const T& val = T())
			:_next(nullptr)
			,_prev(nullptr)
			, _val(val)
		{}

		ListNode<T>* _next;
		ListNode<T>* _prev;
		T _val;
	};
    
	template<class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
		list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}

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

	private:
		Node* _head;
		size_t _size;
	};
}

2.1 iterator的设计

尾插数据后,我们想遍历数据,使用for循环不能遍历,因为链表的数据在内存中并不连续;范围for的底层则是迭代器

之前的stringvector的迭代器我们是拿原生指针实现的,一方面指针指向的内容就是我们要遍历的数据,另一方面数据在内存中是连续的,指针++就是往后走

list如果拿原生指针实现,存在下面的问题:

  • 解引用指针得到的是一个节点,而我们要的是节点中的值
  • list的节点在内存中不连续,指针++走到的不一定是下一个节点

迭代器的目的是不需要我们去考虑底层,所有容器遍历方式都是一样的

我们希望解引用和++按照我们指定的方式来操作节点,而C++中支持运算符重载,于是能不能将迭代器设计成一个类,在类中,我们自主定义*++ ,再由list提供begin()end()接口

C++ 复制代码
template<class T>
struct ListIterator
{
	typedef ListNode<T> Node;
	ListIterator(Node* node)
		:_node(node)
	{}
    
	T& operator*()
	{
		return _node->_val;
	}
    
	// ++it
	ListIterator& operator++()
	{
		_node = _node->_next;
		return *this;
	}
    
	// it++
	ListIterator operator++(int)
	{
		ListIterator temp = *this;
		_node = _node->_next;
		return temp;
	}
    
	// --it
	ListIterator& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
    
	// it--
	ListIterator operator--(int)
	{
		ListIterator temp = *this;
		_node = _node->_prev;
		return temp;
	}
    
	bool operator!=(const ListIterator& li)
	{
		return _node != li._node;
	}
    
	bool operator==(const ListIterator& li)
	{
		return _node == li._node;
	}
    
	Node* _node;
};

// list中定义
iterator begin()
{
	return _head->_next;
}

iterator end()
{
	return _head;
}

实际上,list的迭代器其本质还是一个指针,只不过我们对该指针进行封装,并根据语法规则自主定义了该指针的行为

迭代器模拟的就是指针的行为

如果list中的数据类型是一个自定义类型,解引用迭代器得到一个结构体,用.操作符,这点跟C语言相同,但更多的时候我们用结构体的指针加上->来访问结构体中的数据;因此,就需要我们对->进行重载

C++ 复制代码
template<class T>
struct ListIterator
{
    //...
    T* operator->()
	{
		return &this->_node->_val;
	}
    //...
}

struct A
{
	A(int a1 = 0, int a2 = 0)
		:_a1(a1)
		,_a2(a2)
	{}

	int _a1;
	int _a2;
};

void Test_list()
{
	list<A> li;
	li.push_back({ 10,10 });
	list<A>::iterator it = li.begin();
	cout << (*it)._a1 << " " << (*it)._a2 << endl;
	cout << it->_a1 << " " << it->_a1 << endl;
    // 实际上是 it.operator->()->_a1
    // 为了可读性,编译器简化成一个->
}

2.2 const_iterator的设计

C++ 复制代码
void Print_list(const list<int>& cli)
{
	typename list<int>::iterator it = cli.begin();
	while (it != cli.end())
	{
		cout << *it << " ";
         it++
	}
	cout << endl;
}

void Test_list()
{
	list<int> li;
	li.push_back(1);
	li.push_back(2);
	li.push_back(3);
	li.push_back(4);
	Print_list(li);
}

上面代码执行,编译器会报错,原因是cliconst对象,而begin是非const成员函数,const对象不能调用非const成员函数

那给begin()成员函数加上const修饰不就行了?这样做代码确实能跑,但问题是,cliconst对象,内容不能被修改,而我们现在能随意修改cli

正确的做法是重新设计一个const_iterator,给const对象使用

  1. ListIterator拷贝一份,修改成ConstListIterator
    • 缺点:代码冗余
  2. 利用模板特性,将Iterator改成模板
C++ 复制代码
template<class T, class Ref, class Ptr>
struct ListIterator
{
    //...
	Ref operator*()
	{
		return _node->_val;
	}
    
	Ptr operator->()
	{
		return &this->_node->_val;
	}
    //...
};

template<class T>
class list
{
    //...
	public:
		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T, const T&, const T*> const_iterator;
    //...
}

3. 完整代码


C++ 复制代码
namespace byh
{
	template<class T>
	struct ListNode
	{
		ListNode(const T& val = T())
			:_next(nullptr)
			,_prev(nullptr)
			,_val(val)
		{}

		ListNode<T>* _next;
		ListNode<T>* _prev;
		T _val;
	};

	template<class T, class Ref, class Ptr>
	struct ListIterator
	{
		typedef ListNode<T> Node;

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

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

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

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

		// it++
		ListIterator operator++(int)
		{
			ListIterator temp = *this;
			_node = _node->_next;
			return temp;
		}

		// --it
		ListIterator& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		// it--
		ListIterator operator--(int)
		{
			ListIterator temp = *this;
			_node = _node->_prev;
			return temp;
		}

		bool operator!=(const ListIterator& li)
		{
			return _node != li._node;
		}

		bool operator==(const ListIterator& li)
		{
			return _node == li._node;
		}

		Node* _node;
	};

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

	public:
		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T, const T&, const T*> const_iterator;

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

		list()
		{
			empty_init();
		}

		list(const list<T>& li)
		{
			empty_init();
			for (auto& e : li)
			{
				push_back(e);
			}
		}

		list<T>& operator=(list<T> li)
		{
			swap(li);
			return *this;
		}

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

		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& val)
		{
			insert(end(), val);
		}

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

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

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

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

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

		bool empty() const
		{
			return _size == 0;
		}

		size_t size() const
		{
			return _size;
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}
        
        // 析构
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
			_size = 0;
		}

	private:
		Node* _head;
		size_t _size;
	};
}
相关推荐
青青丘比特1 小时前
STL.string(下)
开发语言·c++
jjjxxxhhh1231 小时前
C++ 模板是为了解决啥问题
开发语言·c++·算法
c++初学者ABC1 小时前
GESP2级2403 小杨的日字矩阵
c++·算法
代码小将2 小时前
PTA数据结构编程题7-1最大子列和问题
数据结构·c++·笔记·学习·算法
HackKong2 小时前
高校网络安全_网络安全之道
java·网络·c++·python·学习·web安全·黑客技术
BUG制造机.2 小时前
修炼之道 ---其四
linux·c++
神经网络的应用3 小时前
C++程序设计例题——第三章程序控制结构
c++·学习·算法
zfenggo3 小时前
c/c++ 无法跳转定义
c语言·开发语言·c++
图灵猿3 小时前
【Lua之·Lua与C/C++交互·Lua CAPI访问栈操作】
c语言·c++·lua
A懿轩A4 小时前
C/C++ 数据结构与算法【树和二叉树】 树和二叉树,二叉树先中后序遍历详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·二叉树·