【C++】list类(使用方法和模拟实现)

一、标准库中的list类

1.1 list类介绍

1.2 list的常用接口

1.2.1 常用的构造函数

1.2.2 容量操作接口

(1)size

(2)empty

(3)resize

1.2.3 访问和遍历

(1)迭代器

(2)反向迭代器

(3)back

(4)front

1.2.4 list的增删查改

(1)push_front

(2)pop_front

(3)push_back

(4)pop_back

(5)find

(6)insert

(7)erase

(8)swap

(9)assign

(10)clear

1.2.5 list的顺序修改接口

(1)sort

(2)reverse

二、模拟实现list类


一、标准库中的list类

1.1 list类介绍

  • list的底层是一个带哨兵位的双向循环链表结构
  • 对比forward_list的单链表结构,list的迭代器是一个双向迭代器
  • 与vector等顺序结构的容器相比,list在任意位置进行插入删除的效率更好,但是不支持任意位置的随机访问

list是一个模板类,在使用的时候我们需要给出元素的类型

使用list类时,需要包含头文件<list>

1.2 list的常用接口

1.2.1 常用的构造函数

list();

list类的默认构造函数,构造一个空链表

例如:

list(size_type n, const value_type& val =  value_type());

构造一个list对象并用n个val初始化

例如:

list(const list& x); 

list类的拷贝构造函数

例如:

Template<class InputIterator>

list(InputIterator first, InputIterator last);

使用迭代器进行初始化构造

例如:

1.2.2 容量操作接口

(1)size

size_type size() const;

获取链表节点个数

例如:

(2)empty

bool empty() const;

判断链表是否为空

例如:

(3)resize

void resize(size_type n, value_type val = value_type());

缩减或增加节点个数为n

如果没有给出val,就用0作为元素

例如:

1.2.3 访问和遍历

(1)迭代器

iterator begin();

const_iterator begin() const;

iterator end();

const_iterator end() const;

迭代器,用于获取链表中第一个节点的位置和最后一个节点的下一个位置(即哨兵位)

例如:

(2)反向迭代器

reverse_iterator rbegin();

const_reverse_iterator rbegin() const;

reverse_iterator rend();

const_reverse_iterator rend() const;

反向迭代器,rbegin获取容器中最后一个节点的位置,rend获取容器中哨兵位的位置

例如:

需要注意,反向迭代器rit也要用++而不是--

(3)back

reference back();

const_reference back() const;

返回链表中最后一个节点存储的数据的引用

例如:

(4)front

reference front();

const_reference front() const;

返回链表中第一个节点存储的数据的引用

例如:

1.2.4 list的增删查改

(1)push_front

void push_front(const value_type& val);

从链表头部插入一个元素

例如:

(2)pop_front

void pop_front();

从链表头部删除一个元素

例如:

(3)push_back

void push_back(const value_type& val);

从链表尾部插入一个元素

例如:

(4)pop_back

void pop_back();

从链表尾部删除一个元素

例如:

(5)find

template <class InputIterator, class T>

InputIterator find(InputIterator first, InputIterator last, const T& val);

在两个迭代器区间寻找val并返回其所在处的迭代器

例如:

需要注意的是,该函数并非list的成员函数,是标准库中的函数,多个容器共用该find函数

可以看到,我们可以直接对list的迭代器进行解引用并修改其内容,但是迭代器不应该指向节点吗?为什么对其解引用能修改数据呢?

实际上list的迭代器并不是用原生态指针进行模拟实现的,需要进行底层的封装,这里会在list的模拟实现的源代码中体现。

(6)insert

iterator insert(iterator position, const value_type& val);

void insert(iterator position, size_type n, const value_type& val);

————————————————————————————————————————

template<class InputIterator>

void insert(iterator position, InputIterator first, InputIterator last);

在position位置的前面插入一个或多个元素

例如:

细心的读者可能已经发现了,list的insert操作是不会导致迭代器失效的,因为pos指向的节点不变,相对位置也不变。

但是list的删除操作一定会导致迭代器失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

(7)erase

iterator erase(iterator position);

iterator erase(iterator first, iterator last);

删除position位置的元素或者 [first,last) 区间的所有元素

例如:

(8)swap

void swap(vector& x);

交换两个list对象

例如:

(9)assign

template <class InputIterator>

void assign(InputIterator first, InputIterator last);

void assign(size_type n, const value_type& val);

为list指定新内容,替换其当前内容并修改节点个数

例如:

(10)clear

void clear();

删除链表所有节点

例如:

1.2.5 list的顺序修改接口

(1)sort

void sort();

template<class Compare>

void sort(Compare comp);

list由于结构的特殊性,是无法使用标准库中的sort函数的,因为迭代器无法进行相减的操作

并且在C++文档中,我们可以看到标准库中的sort也限定了迭代器的类型必须是可以进行随机访问(RandomAccess)的

迭代器有几个功能分类:

  • 单向迭代器,只能进行++
  • 双向迭代器,可以++和--
  • 随机迭代器,可以++,--,+和-

但是list的sort函数也是没什么必要的,因为直接对链表排序的效率比拷贝到vector进行排序再拷贝回链表要慢的多

(2)reverse

void reverse();

对链表进行逆置

例如:


二、模拟实现list类

学习了list类中各种常用接口的用法后,我们就可以开始自己手撕一个自己的list类了

为了不和标准库中的list类冲突,我们可以开一个自己的命名空间

#include <iostream>
#include <assert.h>
using namespace std;

namespace Eristic
{
	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _data;

		list_node(const T& x = T()) //匿名对象作为缺省值,变为默认构造函数
			:_next(nullptr)
			,_prev(nullptr)
			,_data(x)
		{}
	};
	 
	template<class T, class Ref, class Ptr> 
    //通过模板参数实现const迭代器和普通迭代器版本的复用
    //和list类中迭代器的typedef配合阅读会较好理解
	struct __list_iterator //重点:迭代器的底层封装
	{
		typedef list_node<T> node;
		typedef __list_iterator<T, Ref, Ptr> self;
		node* _node;
        //node*本身不支持解引用等操作,对其进行封装就可以模仿原生态指针的行为

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

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

		Ptr operator->() //误区:在使用箭头运算符的时候,为什么只需要一个箭头而不是两个?
        //答:为了可读性,编译器会帮助我们省略一个箭头
		{
			 return &_node->_data;
		}

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

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

		bool operator==(const self& s)
		{
			return _node == s._node;
		}
		//不需要写拷贝构造,因为我们用默认生成的进行浅拷贝就能完成需求
		//析构函数也不需要写,释放节点是list的事,与迭代器无关
	};

	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;
        //对应上面的多个模板参数

		void list_init()
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		list()
		{
			list_init();
		}

		list(int n, const T& x = T())
		{
			list_init();
			for (int i = 0; i < n; i++)
				push_back(x);
		}

		template<class iterator>
		list(iterator first, iterator last)
		{
			list_init();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		list(const list<T>& lt)
		{
			list_init();
			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}

		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

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

		int size()
		{
			iterator it = begin();
			int count = 0;
			while (it != end())
			{
				++it;
				++count;
			}
			return count;
		}

		void resize(size_t n, const T& x = T())
		{
			if (n < size())
				while (n < size())
					pop_back();
			else if (n > size())
				while (n > size())
					push_back(x);
		}

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

		T& front()
		{
			return _head->_next->_data;
		}

		const T& front() const
		{
			return _head->_next->_data;
		}

		T& back()
		{
			return _head->_prev->_data;
		}

		const T& back() const
		{
			return _head->_prev->_data;
		}

		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& x)
		{
			node* cur = pos._node;
			node* prev = cur->_prev;

			node* newnode = new node(x);

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

		iterator erase(iterator pos)
		{
			assert(pos != end()); //哨兵位不能删
			node* prev = pos._node->_prev;
			node* next = pos._node->_next;

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

			delete pos._node;

			return iterator(next);
		}

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

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

		iterator end()
		{
			return iterator(_head);
		}

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

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it++);
			}
		}

	private:
		node* _head;
	};
}

如有错误,欢迎在评论区指出

完.

相关推荐

  1. pytorch 笔记:dist cdist

    2024-03-28 01:16:01       38 阅读
  2. 接口抽象:如何使用普通模拟接口抽象

    2024-03-28 01:16:01       10 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-28 01:16:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-28 01:16:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-28 01:16:01       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-28 01:16:01       20 阅读

热门阅读

  1. 【逆向】利用Objection实现移动应用抓取https流量

    2024-03-28 01:16:01       20 阅读
  2. yarn的安装和使用

    2024-03-28 01:16:01       22 阅读
  3. Frechet分布

    2024-03-28 01:16:01       19 阅读
  4. Linux应用实战之网络服务器(一) HTTP协议介绍

    2024-03-28 01:16:01       15 阅读
  5. Mathematica使用笔记

    2024-03-28 01:16:01       18 阅读
  6. 蓝桥杯2018年第十三届省赛真题-复数幂

    2024-03-28 01:16:01       22 阅读
  7. AMS概念以及面试相关整理

    2024-03-28 01:16:01       13 阅读
  8. 第 1 章 信息化和信息系统 -5

    2024-03-28 01:16:01       16 阅读
  9. Python编程基础 001 开篇:为什么要学习编程

    2024-03-28 01:16:01       20 阅读
  10. spring和springboot的区别

    2024-03-28 01:16:01       15 阅读
  11. 计算理论基础:2、丘奇-图灵论题

    2024-03-28 01:16:01       16 阅读
  12. wkt转geojson

    2024-03-28 01:16:01       17 阅读