解决list在模拟实现中出现的问题

文章介绍了STL中list迭代器与vector的不同,特别是在处理const迭代器时遇到的问题。通过实例展示了为何需要增加第二个模板参数来实现const_iterator,以确保不能修改链表中的内容。同时,解释了第三个模板参数的用途,即支持操作符->以方便访问自定义类型的成员变量,尤其是在const上下文中。
摘要由CSDN通过智能技术生成

前言

stl中list中list的迭代器跟vector有所不同,其中有一些难懂的地方,来帮助大家稍微理解一下
在这里插入图片描述
这是源码中实现的方式,难以理解的地方就是在于这三个模板参数
我们先仿照源码写一个简化版的迭代器,接下来我们将逐一讨论剩下的两个模板参数的作用


#include<iostream>
using namespace std;


namespace wzy
{
	//节点
	template<class T>
	struct list_node
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _data;
		//构造函数
		list_node(const T& x=T())
			:_next(nullptr)
			,_prev(nullptr)
			,_data(x)
		{}
	};

	//迭代器
	template<class T>
	struct __list_iterator
	{
		typedef __list_iterator<T> self;
		typedef list_node<T> node;
		node* _node;

		__list_iterator(node* n)
			:_node(n)
		{}
		//不同于vector的原生迭代器,list由于空间不连续,所以要重载一下++
		//而++就是让指针移动到下个节点
		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

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

		bool operator!=(const self& x)
		{
			return _node != x._node;
		}
	};
	
	//list
	template<class T>
	class list
	{
	public:
		typedef list_node<T> node;
		typedef __list_iterator<T> iterator;
		
		iterator begin()
		{
			return iterator(_head->_next);
		}
		iterator end()
		{
			return  iterator(_head);
		}

		list()
		{
			_head = new node();
			_head->_next = _head;
			_head->_prev = _head;
		}
		void push_back(const T& x)
		{
			node* new_node = new node(x);
			
			node* tail = _head->_prev;

			tail->_next = new_node;
			new_node->_prev = tail;
			_head->_prev = new_node;
			new_node->_next = _head;
		}

	private:
		node* _head;
	};

	void test()
	{
		list<int> l;
		l.push_back(1);
		l.push_back(2);
		l.push_back(3);

		list<int>::iterator it = l.begin();
		while (it != l.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
		print(l);
	}

}



一、增加第二个模板参数的原因

我们经常会遇到要使用const迭代器的场景,比如这样一个函数
在这里插入图片描述
此时我们要想遍历链表中的数据就只能使用const迭代器,毫无疑问再向上面一样去使用普通的迭代器已经不可以了,因为const对象无法调用非const成员函数

void print(const list<int>& ll)
{
		list<int>::iterator it = ll.begin();		
		while (it != ll.end())
		{
			cout << *it << " ";
			++it;
		}
}

此时我们一个想法就是直接在begin()函数后面加上const构成重载不就可以了么,可以么?我们来看

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

在这里插入图片描述
运行结果没错,也能遍历成功,但是我们在print()加上这样一句代码

void print(const list<int>& ll)
	{
		list<int>::iterator it = ll.begin();		
		while (it != ll.end())
		{
			(*it)++;
			cout << *it << " ";
			++it;
		}
	}

此时我们发现结果已经不符合我们预期了,因为按理来说,const迭代器是不允许对内容进行修改的,说明我们某个地方出错了
在这里插入图片描述
究其原因是因为我们还是用const对象去构造出了一个普通的迭代器,为什么会这样呢,是因为这里const修饰的是*this *this就是 _head这个指针,而指针(_head)不能被修改,不意味着不能被拷贝给其他。举个例子来说
在这里插入图片描述
这样可以么,也不可以,这里的 iterator 就相当于 T * 而这里的加上const之后,相当于T * const ,我们需要的是指向的内容不被改变,而不是指针本身不可改变,所以也是错误的

typedef __list_iterator<T> iterator;
typedef const iterator const_iterator;

正确的写法是,实现一个const版本迭代器

template<class T>
	struct __list_const_iterator
	{
		typedef __list_const_iterator<T> self;
		typedef list_node<T> node;
		node* _node;

		__list_const_iterator(node* n)
			:_node(n)
		{}
		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		const T& operator*() 
		{
			return _node->_data;
		}
		bool operator!=(const self& x)
		{
			return _node != x._node;
		}
	};

然后修改list部分
在这里插入图片描述
此时我们的运行结果就是正确的了
在这里插入图片描述

但是我们不难发现__list_const_iterator类和__list_iterator类在实现上几乎是一模一样的,就是const T& operator*() 返回值类型不一样,为了优化这一结果我们增加了第二个模板参数

template<class T, class Ref>
	struct __list_iterator
	{
		typedef __list_iterator<T, Ref> self;
		typedef list_node<T> node;
		node* _node;

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

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

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

并修改
在这里插入图片描述
在这里插入图片描述


二、增加第三个模板参数的原因

在介绍第三个模板参数之前,我们先来回忆一下什么情况下需要 ->

struct A
	{
		int _a1;
		int _a2;
		A(int a1=1, int a2=1)
			:_a1(a1)
			, _a2(a2)
		{}
	};
	void test2()
	{
		list<A> l;
		l.push_back(A(1, 2));
		l.push_back(A(3, 4));
		l.push_back(A(5, 6));

		list<A>::iterator it = l.begin();
		while (it != l.end())
		{
			cout << *it<<" ";
			++it;

		}
	}

这时*it就无法直接获取成员数据,对于自定义类型来说只能这样去做

cout << (*it)._a1 << " " << (*it)._a2<<" ";

这时就需要 ->发挥作用了

cout<<it->_a1<<" "<<it->_a2<<" ";

所以我们需要重载operator ->

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

这段代码的含义:_node->data 就是list存储的节点的类型 在这里就是自定义类型A,然后&(取地址)就是获得了指向A类型的指针,那么自定义类型指针再去获取成员变量就可以 使用->了
根据上面的描述应该是 it-> ->_a1才对,因为第一个是获取指针,第二个才是获取成员变量
这里只有一个的原因是因为编译器为了增强可读性 做了优化省略了一个 ->

那么增加第三个模板参数的原因就显而易见了,跟第二个相同,就是为了解决const这一版本。

完整的代码:


namespace wzy
{
	
	struct A
	{
		int _a1;
		int _a2;
		A(int a1 = 1, int a2 = 1)
			:_a1(a1)
			, _a2(a2)
		{}
	};
	template<class T>
	struct list_node
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _data;
	
		list_node(const T& x = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(x)
		{}
	};

	
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef __list_iterator<T, Ref, Ptr> self;
		typedef list_node<T> node;
		node* _node;

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

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

		Ref operator*()
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
		bool operator!=(const self& x)
		{
			return _node != x._node;
		}
	};
	
	template<class T>
	class list
	{
	public:
		typedef list_node<T> node;
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;

	

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

		const_iterator begin() const
		{
			return const_iterator(_head->_next);  
		}
		const_iterator end() const
		{
			return  const_iterator(_head);
		}
		list()
		{
			_head = new node();
			_head->_next = _head;
			_head->_prev = _head;
		}
		void push_back(const T& x)
		{
			node* new_node = new node(x);

			node* tail = _head->_prev;

			tail->_next = new_node;
			new_node->_prev = tail;
			_head->_prev = new_node;
			new_node->_next = _head;
		}

	private:
		node* _head;
	};

	
	void print(const list<int>& ll)
	{
		list<int>::const_iterator it = ll.begin();

		while (it != ll.end())
		{
			//(*it)++;
			cout << *it << " ";

			++it;
		}
	}

	void test()
	{
		list<int> l;
		l.push_back(1);
		l.push_back(2);
		l.push_back(3);

		list<int>::iterator it = l.begin();
		const type_info& objInfo = typeid(l.begin());
		cout << objInfo.name() << endl;
		while (it != l.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
		print(l);
	}

	void print(const list<A>& ll)
	{
		list<A>::const_iterator it = ll.begin();
		while (it != ll.end())
		{
			//(*it)++;
			cout << it->_a1 << " " << it->_a2 << " ";
			++it;
		}
	}
	void test2()
	{
		list<A> l;
		l.push_back(A(1, 2));
		l.push_back(A(3, 4));
		l.push_back(A(5, 6));

		list<A>::iterator it = l.begin();
		while (it != l.end())
		{
			cout << it->_a1 << " " << it->_a2 << " ";
			++it;
		}
		cout << endl;
		print(l);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值