【C++】深度解析:用 C++ 模拟实现 list 类,探索其底层实现细节
创始人
2024-11-04 15:07:12
0

 

目录

list介绍

list模拟实现

list 节点类

list 的迭代器

定义 

构造函数

解引用

operator前置++和--与后置++和--

operator==与operator!=

list 类

构造函数

 begin()和end()

 拷贝构造

erase()

clear()

析构函数

insert

 push_back 和 push_front

pop_back 和 pop_front

完整代码


 

⭐list介绍

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

⭐list模拟实现

  1. list的底层是双向链表结构,包含有一个哨兵节点。
  2. 模拟实现list,要实现下列三个类:
  •         ①list节点类
  •         ②迭代器的类
  •         ③list主要功能的类(size(),empty()...)

模拟实现list的类的基本功能(增删等操作)要建立在迭代器类和节点类均已实现好的情况下才得以完成。

✨list 节点类

  • 定义list中的节点ListNode,包含前驱指针,后驱指针和数据变量;
  • 使用struct而不使用class定义类,是为了方便访问每个一个节点 ,struct默认是pbulic,而class中成员变量要定义为private,不方便访问。
	template 	struct ListNode 	{ 		ListNode* _next; 		ListNode* _prev; 		T _data;  		ListNode(const T& x = T()) 			:_next(nullptr) 			,_prev(nullptr) 			,_data(x) 		{} 	};

✨list 的迭代器

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

1. 原生态指针,比如:vector

2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:

  1. 指针可以解引用,迭代器的类中必须重载operator*()
  2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
  3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
  4. 至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载--
  5. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()

📖定义 

	template 	struct ListIterator 	{ 		typedef ListNode Node; 		typedef ListIterator Self;  		//内置类型 指针 		Node* _node;     }
  • 可以发现这里list的模板含有三个类型参数,这样做是为了方便让编译器能依照模板自动生成一个const迭代器,而不需要我们手动写。
  • 两个参数名Ref(reference:引用)和Ptr(pointer:指针),见名知义。

📖构造函数

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

 迭代器指向所传节点

📖解引用

//*it 解引用 //T& operator*() Ref operator*() { 	return _node->_data; } //it-> //T* operator->() Ptr operator->() { 	return &_node->_data; }
  • 重载 * ,解引用就可以直接访问到节点里面的数据data 
  • 如果访问的数据是Date类型的,那么重载 -> 就可以访问到Date类里面的_year、_day等(如果是Date类,那data就说Date类里面的数据) 

📖operator前置++和--与后置++和--

//前置++ Self& operator++() { 	_node = _node->_next; 	return *this; } //后置++ Self operator++(int) { 	Self tmp(*this); 	_node = _node->_next; 	return tmp; // tmp 是一个局部变量,它在函数返回后将不再存在,所以不能返回引用 } //前置-- Self& operator--() { 	_node = _node->_prev; 	return *this; } //后置-- Self operator--(int) { 	Self tmp(*this); 	_node = _node->_prev; 	return tmp;// tmp 是一个局部变量,它在函数返回后将不再存在,所以不能返回引用 }

注:

  • 这里值得注意的是,为了区分前置和后置,我们会在后置的重载函数中传缺省值int,从而与前置构成重载 
  • 局部变量不能返回引用

📖operator==与operator!=

bool operator!=(const Self& it) { 	return _node != it._node; } bool operator==(const Self& it) { 	return _node == it._node; } 
  • 这个比较的就是两个迭代器中指向的节点的地址是否相等,从而可以判断迭代器是否指向了end() 。

✨list 类

template class list { 	 typedef ListNode Node;  public :  	typedef ListIterator iterator; 	typedef ListIterator const_iterator; private:  	Node* _head; 	size_t _size; }
  • 迭代器写成三个参数类型的模板,可以让编译器生成两个类,一个普通的iterator和一个const_iterator
  • const_iterator 只能读取它所指向的元素,不能修改。

📖构造函数

//带头双向循环链表的构造函数 list() { 	_head = new Node; 	_head->_next = _head; 	_head->_prev = _head; }
  • 初始化时,new一个头节点,然后使这个头节点的前后指针都指向自己 

📖begin()和end()

iterator begin() { 	return _head->_next;//也可以直接这么写,进行隐式类型转换(单参数的构造函数支持隐式类型转换) } iterator end() { 	return iterator(_head);//匿名对象,局部变量 不能返回引用 } //const迭代器需要的是迭代器指向的内容不能修改 //const iterator 是迭代器本身不能修改 const_iterator begin() const { 	return _head->_next; } const_iterator end()const { 	return const_iterator(_head); }
  • 返回时使用了匿名对象,不用实例化一个新的对象
  • 不能引用返回的匿名对象
  • const迭代器指向的内容不能修改 

 📖拷贝构造

//lt2(lt) list(const list& lt)  { 	empty_init(); 	for (auto& e : lt) 	{ 		push_back(e); 	}	 }
  • list的每个节点不连续,需要一个个拷贝 
  • 需要析构的时候,一般就需要自己写深拷贝

📖erase()

iterator erase(iterator pos) { 	//prev cur next 	Node* cur = pos._node; 	Node* prev = cur->_prev; 	Node* next = cur->_next; 	_size--; 	prev->_next = next; 	next->_prev = prev; 	delete cur; 	return iterator(next);//匿名对象,局部变量 不能返回引用 }
  • delete删除节点,每一个节点都是动态开辟出来的

  • 返回被删除元素后面一个元素的迭代器位置

📖clear()

void clear() { 	iterator it = begin(); 	while (it != end()) 	{ 		it = erase(it); 	} 	//不清除头节点  }
  • erase之后,it更新到下一个节点的位置继续erase
  • clear()不删除头节点

📖析构函数

~list() { 	clear(); 	delete _head; 	_head = nullptr; }
  • clear()删除除去头节点以外的所有节点
  • delete删除头节点

📖insert

void insert(iterator pos, const T& val) { 	Node* cur = pos._node; 	Node* newnode = new Node(val); 	Node* prev = cur->_prev; 	_size++; 	//prev newnode cur 	prev->_next = newnode; 	newnode->_prev = prev; 	newnode->_next = cur; 	cur->_prev = newnode; }
  • 插入到pos位置之前 

 📖push_back 和 push_front

void push_back(const T& x) { 	insert(end(), x); } void push_front(const T& x) { 	insert(begin(), x); }
  • 复用insert ()

📖pop_back 和 pop_front

void pop_back() { 	erase(--end()); } void pop_front() { 	erase(begin()); }
  • 复用erase() 

✨完整代码

template 	struct ListNode 	{ 		ListNode* _next; 		ListNode* _prev; 		T _data;  		ListNode(const T& x = T()) 			:_next(nullptr) 			,_prev(nullptr) 			,_data(x) 		{} 	};  	template 	struct ListIterator 	{ 		typedef ListNode Node; 		typedef ListIterator Self;  		//内置类型 指针 		Node* _node;  		ListIterator(Node* node) 			:_node(node) 		{}  		//*it 解引用 		//T& operator*() 		Ref operator*() 		{ 			return _node->_data; 		} 		//it-> 		//T* operator->() 		Ptr operator->() 		{ 			return &_node->_data; 		} 		//前置++ 		Self& operator++() 		{ 			_node = _node->_next; 			return *this; 		} 		//后置++ 		Self operator++(int) 		{ 			Self tmp(*this); 			_node = _node->_next; 			return tmp; // tmp 是一个局部变量,它在函数返回后将不再存在,所以不能返回引用 		}  		//前置-- 		Self& operator--() 		{ 			_node = _node->_prev; 			return *this; 		} 		//后置-- 		Self operator--(int) 		{ 			Self tmp(*this); 			_node = _node->_prev; 			return tmp;// tmp 是一个局部变量,它在函数返回后将不再存在,所以不能返回引用 		}  		bool operator!=(const Self& it) 		{ 			return _node != it._node; 		} 		bool operator==(const Self& it) 		{ 			return _node == it._node; 		}  	};  	template 	class list 	{ 		 typedef ListNode Node;  	public : 		//typedef ListIterator iterator; 		//typedef ListConstIterator const_iterator;//重新写一个ListConstIterator类(这个方法比较冗余)  		typedef ListIterator iterator; 		typedef ListIterator const_iterator;//写成模板,让编译器生成两个类,而不是我们自己写 		/*iterator begin() 		{ 			return iterator(_head->_next); 		}*/ 		iterator begin() 		{ 			return _head->_next;//也可以直接这么写,进行隐式类型转换(单参数的构造函数支持隐式类型转换) 		} 		iterator end() 		{ 			return iterator(_head);//匿名对象,局部变量 不能返回引用 		} 		//const迭代器需要的是迭代器指向的内容不能修改 		//const iterator 是迭代器本身不能修改 		const_iterator begin() const 		{ 			return _head->_next; 		} 		const_iterator end()const 		{ 			return const_iterator(_head); 		} 		void empty_init() 		{ 			_head = new Node; 			_head->_next = _head; 			_head->_prev = _head; 			_size = 0; 		} 		list() 		{ 			//this->empty_init(); 			empty_init(); 		} 		//lt2(lt) 		list(const list& lt)  		{ 			empty_init(); 			for (auto& e : lt) 			{ 				push_back(e); 			} 		} 		//需要析构,一般就需要自己写深拷贝 		void swap(list& lt) 		{ 			//lt是局部变量 			std::swap(_head, lt._head); 			std::swap(_size, lt._size); 		} 		//lt1 = lt3 		list& operator=(list lt) 		{ 			swap(lt); 			return *this; 		} 		void clear() 		{ 			iterator it = begin(); 			while (it != end()) 			{ 				it = erase(it); 			} 			//不清除头节点  		} 		~list() 		{ 			clear(); 			delete _head; 			_head = nullptr; 		} 		/*void push_back(const T& x) 		{ 			Node* newnode = new Node(x); 			Node* tail = _head->_prev; 			tail->_next = newnode; 			newnode->_prev = tail; 			newnode->_next = _head; 			_head->_prev = newnode; 		}*/ 		void push_back(const T& x) 		{ 			insert(end(), x); 		} 		void push_front(const T& x) 		{ 			insert(begin(), x); 		}  		void pop_back() 		{ 			erase(--end()); 		} 		void pop_front() 		{ 			erase(begin()); 		} 		void insert(iterator pos, const T& val) 		{ 			Node* cur = pos._node; 			Node* newnode = new Node(val); 			Node* prev = cur->_prev; 			_size++; 			//prev newnode cur 			prev->_next = newnode; 			newnode->_prev = prev; 			newnode->_next = cur; 			cur->_prev = newnode; 		} 		iterator erase(iterator pos) 		{ 			Node* cur = pos._node; 			Node* prev = cur->_prev; 			Node* next = cur->_next; 			_size--; 			prev->_next = next; 			next->_prev = prev; 			delete cur; 			return iterator(next);//匿名对象,局部变量 不能返回引用 		} 		size_t size()const 		{ 			return _size; 		} 		bool empty() 		{ 			return _size == 0; 		} 	private: 		Node* _head; 		size_t _size; 	};

____________________

⭐感谢你的阅读,希望本文能够对你有所帮助。如果你喜欢我的内容,记得点赞关注收藏我的博客,我会继续分享更多的内容。⭐

相关内容

热门资讯

黑科技代打!微乐游戏公众号辅助... 黑科技代打!微乐游戏公众号辅助器(辅助)确实确实有辅助挂(有挂方针)1、游戏颠覆性的策略玩法,独创攻...
相较于以往!闲来游戏辅助软件(... 相较于以往!闲来游戏辅助软件(辅助)本来存在有辅助神器(有挂技术)1)闲来游戏辅助软件免费钻石:进一...
2026版总结!奇迹陕西挂(辅... 2026版总结!奇迹陕西挂(辅助)好像是真的有辅助攻略(果真有挂)1、进入到奇迹陕西挂是否有挂之后,...
经调查!哈灵脚本ios(辅助)... 经调查!哈灵脚本ios(辅助)一贯存在有辅助脚本(有挂秘诀)1、哈灵脚本ios模拟器是什么优化,哈灵...
连日来!新超圣辅助器(辅助)本... 连日来!新超圣辅助器(辅助)本来确实有辅助攻略(有挂教学)1、玩家可以在新超圣辅助器线上大神俱乐部对...
此事引发广泛关注!小程序跑得快... 此事引发广泛关注!小程序跑得快的辅助(辅助)竟然存在有辅助攻略(有挂教学)1、小程序跑得快的辅助有没...
最终!潮友软件辅助器脚本(辅助... 最终!潮友软件辅助器脚本(辅助)好像是真的有辅助技巧(竟然有挂)1、上手简单,内置详细流程视频教学,...
最终!创思维激k看底牌辅助(辅... 最终!创思维激k看底牌辅助(辅助)竟然是真的有辅助神器(果真有挂)1、首先打开创思维激k看底牌辅助辅...
第三方技巧!微信小程序微乐辅助... 第三方技巧!微信小程序微乐辅助器脚本(辅助)确实真的有辅助神器(有挂透明挂)1、下载好微信小程序微乐...
据悉!福建天天开心辅助器真的假... 据悉!福建天天开心辅助器真的假的(辅助)果然确实有辅助脚本(的确有挂)1、许多玩家不知道福建天天开心...