c++——list实现细节反思

2023-12-28 12:38:46

list实现反思

Mystl

list实现

一开始没有什么好注意的,直接写就行了。


先写节点和list类
	template<class T>
	struct ListNode
	{
		T _val;
		ListNode<T>* _next = nullptr;
		ListNode<T>* _pre = nullptr;
		//ListNode() {}
		ListNode(const T& val = T())
		{
			//默认构造:无参,全缺省,不写编译器自动生成
			//三者只能存在一个
			_val = val;
		}
	};

class list{
	public:
		typedef ListNode<T> Node;
		typedef ListNode<T>* PNode;//不要动不动就是用iterator
		void empty_init()
		{
			_head = new Node;
			_head->_pre = _head->_next = _head;
		}
		list()
		{
			empty_init();
		}
	private:
		PNode _head = nullptr;
		size_t _size = 0;
};

默认构造:无参,全缺省,不写编译器自动生成,三个只能有一个,我们更喜欢使用全缺省参数的默认构造函数
链表中加入一个size,在计算链表长度的时候可以在0(1)的时间复杂度之内得到
我们让头指针指向一个哨兵的头节点


元素插入和删除
		void push_back(const T& val = T())
		{
			PNode node = new Node(val);
			PNode pre = _head->_pre;
			pre->_next = node;
			node->_next = _head;
			_head->_pre = node;
			node->_pre = pre;
			_size++;
		}
		void pop_back()
		{
			assert(_size);
			PNode tem = _head->_pre;
			PNode pre = _head->_pre->_pre;
			pre->_next = _head;
			_head->_pre = pre;
			delete tem;
		}
		void push_front(const T& val)
		{
			//不能给一个缺省值,这个函数要严格限制传入的数据,不传入就不能使用这个函数
			PNode node = new Node(val);
			PNode next = _head->_next;
			next->_pre = node;
			node->_pre = _head;
			_head->_next = node;
			node->_next = next;
			_size++;
		}

在实现插入函数的时候一定不能给一个缺省参数,如果给了默认参数,在没有传值的时候,就成了一个无参的调用,这不是扯呢吗,插入一定不能给默认参数


迭代器

先不要急着实现const_iterator

先分析迭代器需不需要,能不能像vector直接typedef一个iterator
iterator要能++,++是向后走到下一个节点,那么就不能将指针typedef成iterator
iterator要能进行++,–,==,!= ,这都是要对iterator进行操作,那么就要是现成一个类进行重载

	template<class T>
	struct ListIterator
	{
	private:
		typedef ListNode<T> Node;
		typedef ListNode<T>* PNode;
		typedef ListIterator<T,Ref,Ptr> iterator;
	public:
		ListNode<T>* _cur;
		ListIterator(ListNode<T>* val)
		{
			_cur = val;
		}

		iterator& operator++()
		{
			_cur = _cur->_next;
			return *this;
		}
		iterator& operator--()
		{
			_cur = _cur->_pre;
			return *this;
		}
		iterator operator++(int)
		{
			iterator tem = _cur;
			_cur = _cur->_next;
			return tem;
		}
		iterator operator--(int)
		{
			iterator tem = _cur;
			_cur = _cur->_pre;
			return tem;
		}
		bool operator==(const iterator& tem)
		{
			return _cur == tem._cur;
		}
		bool operator!=(const iterator& tem)
		{
			return _cur != tem._cur;
		}
		T& operator*()
		{
			//接引用返回的是这个对象本身
			//返回的是内容的地址,但是在使用的时候就可以直接使用这儿内容了
			return _cur->_val;
		}
		T* operator->()
		{
			//->返回的是这个地址
			//return &_cur;//应该不能返回地址的地址吧
			//使用-> 操作符,得到空间的地址,然后使用
			//但是有点儿想不懂啊,为什么->返回的是地址,在使用的时候就可以直接找到他的空间中的对象了呢

			//->隐藏了一个->
			return &_cur->_val; 
		}
	};

operator*是解引用,返回的是他的元素本身
operator->也是解引用,按照一般的使用习惯应该是lt->_val,如果像平常的时候方法重载的话就有点儿不知道重载的括号中的参数怎么传了,但是解引用之后返回的是什么东西呢
重载->的时候很怪,因为编译器进行了优化,他的原型是lt.operator->()->_val本身应该使用这种访问方式,中间隐藏掉了一个->


现在我们只要在list中实现begin和end就可以进行范围遍历

		typedef ListIterator<T> iterator;
		iterator begin()
		{
			return _head->_next;
		}
		iterator end()
		{
			return _head;
		}
		T& front()
		{
			assert(_head != _head->_next);
			return (_head->_next->_val);
		}
		T& back()
		{
			assert(_head != _head->_pre);
			return _head->_pre->_val;
		}
		const T& front() const 
		{
			assert(_head != _head->_next);
			return (_head->_next->_val);
		}
		const T& back() const 
		{
			assert(_head != _head->_pre);
			return _head->_pre->_val;
		}

功能函数
		iterator insert(iterator pos, const T& val)
		{
			// 在pos位置前插入值为val的节点
			PNode pre = pos._cur->_pre;
			PNode node = new Node(val);
			PNode next = pos._cur;
			next->_pre = node;
			node->_pre = pre;
			pre->_next = node;
			node->_next = next;
			_size++;
			return pre;//返回新的位置
		}
		iterator erase(iterator pos)
		{
			//删除某个位置,返回下一个位置
			PNode cur = pos._cur;;
			PNode pre = pos._cur->_pre;
			PNode next = pos._cur->_next;
			pre->_next = next;
			next->_pre = pre;
			delete cur;
			return next;
		}
		void clear()
		{
			//for (auto it = begin(); it != end(); it++)
			//{
			//	//erase(it);//把这个节点删除之后,it还在这个节点上,使用it++会发生迭代器失效
			//	//需要重新更正迭代器位置
			//}
			auto it = begin();
			while (it != end())
				it = erase(it);
			//会自动跳转
		}

在使用clear调用erase的时候要注意迭代器要向后转移


构造和析构
		list(int n, const T& val = T())
		{
			empty_init();//每一个都要使用empty_init进行初始化
			for (int i = 0; i < n; i++)
				push_back(val);
		}
		template <class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_init();
			while (first != last)
			{
				push_front(*first);
				first++;
			}
		}
		list(const list<T>& l)
		{
			empty_init();
			for (auto e : l)
				push_back(e);
		}
		void swap(list<T>& lt)
		{
			std::swap(this->_head, lt._head);
			std::swap(this->_size, lt._size);
		}
		list<T>& operator=(list<T> lt)
		{
			empty_init();
			swap(lt);
			return *this;
		}
		~list()
		{
			clear();
			delete _head;
		}

const_iterator实现分析

const_iterator 实现效果是内容不可改,指向可以改(因为需要const_iterator进行++)
目标是实现成const T* 这种效果
const iterator const修饰的是iterator的值,如果修饰的是iterator的值,那就不能使用++
所以需要iterator const,但是这种也是不对的,iterator 可以看成是一个类型iterator const = const iterator就像const int 和int const的道理一样
我们没有一个直接加const的方法来达到目的,所以我们只能要实现一个const_iterator类
我们可以使用iterator的大部分函数来重写const_iterator
那既然有那么多相同的部分,能不能进行复用

	template<class T,class Ref,class Ptr>
	struct ListIterator
	{
	private:
		typedef ListNode<T> Node;
		typedef ListNode<T>* PNode;
		typedef ListIterator<T,Ref,Ptr> iterator;
	public:
		ListNode<T>* _cur;
		ListIterator(ListNode<T>* val)
		{
			_cur = val;
		}

		iterator& operator++()
		{
			_cur = _cur->_next;
			return *this;
		}
		iterator& operator--()
		{
			_cur = _cur->_pre;
			return *this;
		}
		iterator operator++(int)
		{
			iterator tem = _cur;
			_cur = _cur->_next;
			return tem;
		}
		iterator operator--(int)
		{
			iterator tem = _cur;
			_cur = _cur->_pre;
			return tem;
		}
		bool operator==(const iterator& tem)
		{
			return _cur == tem._cur;
		}
		bool operator!=(const iterator& tem)
		{
			return _cur != tem._cur;
		}
		//PNode& operator->()
		//{
		//	return _cur;
		//}
		//T* operator*()
		//{
		//	return &_cur->_val;
		//}

		Ref operator*()
		{
			//接引用返回的是这个对象本身
			//返回的是内容的地址,但是在使用的时候就可以直接使用这儿内容了
			return _cur->_val;
		}
		Ptr operator->()
		{
			//->返回的是这个地址
			//return &_cur;//应该不能返回地址的地址吧
			//使用-> 操作符,得到空间的地址,然后使用
			//但是有点儿想不懂啊,为什么->返回的是地址,在使用的时候就可以直接找到他的空间中的对象了呢

			//->隐藏了一个->
			return &_cur->_val; 
		}
	};


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

template<class T,class Ref,class Ptr>多传了两个类型就能够实现复用了


		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T,const T&,const T*> 

传的是const类型的参数,得到的就是const类型的iterator;传的是普通类型,得到的就是普通的iterator,在类中还要写一个构造函数,在begin返回到时候需要进行类型转换

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

要将指针转换为iterator
*只能对内置数据类型使用,对自定义数据类型不能使用
内置数据类型如何进行输出?
一般情况下是不会将输出流进行重载,因为如果重载了,输出的形式就固定了,不好
对于这种情况,没有什么好的方法


补充

如果想写一个模板,进行输出

	template<typename T>
	void printList(const list<T>& lt)
	{
		//list<T>:;这种访问方式可以是静态成员,也可以是内嵌类型
		// 在类型未确定时,它是不会到类中寻找的
		//这里必须要加上typename进行修饰,告诉编译器这是一个类型,要等到类型确定之后再去类中寻找
		typename list<T>::const_iterator it = lt.begin();
		while (it != lt.end())
		{
			cout << *it << " ";
			it++;
		}
		puts("");
	}

list<T>::这种访问方式可以是静态成员,也可以是内嵌类型
在类型未确定时,它是不会到类中寻找的
这里必须要加上typename进行修饰,告诉编译器这是一个类型,要等到类型确定之后再去类中寻找

介绍这种情况可不是让各位去用,这种输出的方法不是好方法,而是让各位知道在类型未确定的时候要加typename关键字进行修饰,告诉编译器,等类型确定之后再去找

文章来源:https://blog.csdn.net/2201_76033304/article/details/135258120
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。