【C++】“list”的介绍和常用接口的模拟实现

【C++】"list"的介绍和常用接口的模拟实现

  • [一. list的介绍](#一. list的介绍)
    • [1. list常见的重要接口](#1. list常见的重要接口)
    • [2. list的迭代器失效](#2. list的迭代器失效)
  • [二. list常用接口的模拟实现(含注释)](#二. list常用接口的模拟实现(含注释))
  • [三. list与vector的对比](#三. list与vector的对比)

一. list的介绍

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

1. list常见的重要接口


  1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
  2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

2. list的迭代器失效

迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响

二. list常用接口的模拟实现(含注释)

cpp 复制代码
#include<assert.h>
#include<iostream>
using std::cout;
using std::endl;
namespace wch
{
	//由于list中的val支持多种类型,定义模板参数T
	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _val;

		//T(),针对自定义类型会去调用它的构造函数,针对内置类型无影响
		list_node(const T& val = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _val(val)
		{}
	};

	// typedef __list_iterator<T, T&, T*> iterator;
	// typedef __list_iterator<T, const T&, const T*> const_iterator;
	//此处定义多个模板参数针对返回值类型
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;
		Node* _node;

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

		Ref operator*() const//重载普通对象解引用
		{
			return _node->_val;
		}

		Ptr operator->() const//重载指针对象解引用
		{
			return &_node->_val;
		}

		self& operator++()//返回对象在函数体执行结束后依旧存在,建议引用返回,减少拷贝开销
		{
			_node = _node->_next;
			return *this;
		}

		//返回对象(局部变量或指向局部变量的指针)在函数体执行结束后不存在,不可引用返回,值返回
		self operator++(int)
		{
			self tmp(*this);

			_node = _node->_next;

			return tmp;
		}

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

		self operator--(int)
		{
			self tmp(*this);

			_node = _node->_prev;

			return tmp;
		}

		//尽量使用引用形参, 避免拷贝开销。同时在不需要修改实参时,通过指定const 引用形参来限制。
		bool operator!=(const self& it) const
		{
			return _node != it._node;
		}

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

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

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

		// 这样设计const迭代器是不行的,因为const迭代器期望指向内容不能修改
		// 这样设计是迭代器本身不能修改
		// T* const ptr2;

		iterator begin() 
		{
			//return _head->_next;//单参构造函数支持隐式类型转换
			return iterator(_head->_next);
		}

		iterator end() 
		{
			return _head;//单参构造函数支持隐式类型转换
			//return iterator(_head);
		}

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

		const_iterator end() const
		{
			return _head;
			//return const_iterator(_head);
		}

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

			_size = 0;
		}

		list()//无参默认构造函数
		{
			empty_init();
		}

		// lt2(lt1)
		list(const list<T>& lt)//拷贝构造
			//list(const list& lt)//不加类型也可以,不建议
		{
			empty_init();

			for (auto& e : lt)//深拷贝
			{
				push_back(e);
			}
		}

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

		list<T>& operator=(list<T> lt)//赋值运算符重载
			//list& operator=(list lt)//不加类型也可以,不建议
		{
			swap(lt);

			return *this;
		}

		~list()//析构函数
		{
			clear();

			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);//带位置返回值的erase函数,防止迭代器失效
			}

			_size = 0;
		}

		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());
		}

		// pos位置之前插入
		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);

			prev->_next = newnode;
			newnode->_next = cur;

			cur->_prev = newnode;
			newnode->_prev = prev;

			++_size;

			return newnode;
		}

		//删除pos处节点
		iterator erase(iterator pos)
		{
			assert(pos != end());//不可删除头节点

			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			prev->_next = next;
			next->_prev = prev;

			delete cur;

			--_size;

			return next;
		}

		size_t size()
		{
			/*size_t sz = 0;
			iterator it = begin();
			while (it != end())
			{
				++sz;
				++it;
			}

			return sz;*/

			return _size;
		}

	private:
		Node* _head;
		size_t _size;
	};

	void Print(const list<int>& lt)
	{
		list<int>::const_iterator it = lt.begin();
		while (it != lt.end())
		{
			//(*it) += 1;
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

	void test_list1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

		list<int>::iterator it = lt.begin();

		//由于链表的各个节点地址是不连续的,前一个节点的地址可能比后一个地址大,所以不可已写为 it < it.end();
		while (it != lt.end())
		{
			(*it) += 1;
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

		Print(lt);
	}

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

		int _a1;
		int _a2;
	};

	void test_list2()
	{
		list<A> lt;
		lt.push_back(A(1, 1));
		lt.push_back(A(2, 2));
		lt.push_back(A(3, 3));
		lt.push_back(A(4, 4));

		list<A>::iterator it = lt.begin();
		while (it != lt.end())
		{
			//cout << (*it)._a1 << " " << (*it)._a2 << endl;
			//it->->_a1, it->->_a2,特殊处理为一个->
			cout << it->_a1 << " " << it->_a2 << endl;

			++it;
		}
		cout << endl;
	}

	void test_list3()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		lt.push_front(5);
		lt.push_front(6);
		lt.push_front(7);
		lt.push_front(8);
		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

		lt.pop_front();
		lt.pop_back();

		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

		lt.clear();
		lt.push_back(10);
		lt.push_back(20);
		lt.push_back(30);
		lt.push_back(40);
		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

		cout << lt.size() << endl;
	}

	void test_list4()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

		list<int> lt1(lt);
		for (auto e : lt1)
		{
			cout << e << " ";
		}
		cout << endl;

		list<int> lt2;
		lt2.push_back(5);
		lt2.push_back(6);
		lt2.push_back(7);
		lt2.push_back(8);

		for (auto e : lt2)
		{
			cout << e << " ";
		}
		cout << endl;

		lt1 = lt2;

		for (auto e : lt1)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}

三. list与vector的对比

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

相关推荐
Buleall1 分钟前
期末考学C
java·开发语言
重生之绝世牛码3 分钟前
Java设计模式 —— 【结构型模式】外观模式详解
java·大数据·开发语言·设计模式·设计原则·外观模式
小蜗牛慢慢爬行9 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试
荒古前16 分钟前
龟兔赛跑 PTA
c语言·算法
Colinnian19 分钟前
Codeforces Round 994 (Div. 2)-D题
算法·动态规划
Algorithm157619 分钟前
云原生相关的 Go 语言工程师技术路线(含博客网址导航)
开发语言·云原生·golang
用户00993831430125 分钟前
代码随想录算法训练营第十三天 | 二叉树part01
数据结构·算法
shinelord明29 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
小手cool30 分钟前
List反转的方法
list