【C++ 】list 类

1. 标准库中的list类

list 类 的介绍:

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代

  2. list与forward_list非常相似:最主要的不同在于forward_list是单链表

  3. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好,只是不支持任意位置的随机访问

a. list 的构造函数

  • list() (无参构造函数)
  • list (const list& x) (拷贝构造)
  • list (InputIterator first, InputIterator last)

( 用[first, last)区间中的元素构造list )

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

( 构造的list中包含n个值为val的元素 )

注意:

list 的迭代器是双向迭代器(完成 ++ , --),可以支持传单向迭代器( 完成 ++ ) 和双向迭代器

b. list 增删查改

  • push_back (尾插)
  • push_front (头插)
  • pop_back (尾删)
  • pop_front (头删)
  • insert (在某一位置前增加新节点)

代码举例1

#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> lt;
	lt.push_back(0);
	lt.push_back(10);
	lt.push_back(20);
	lt.push_back(30);
	list<int> ::iterator it = lt.begin();
	++it;
	lt.insert(it,70);
	it = lt.begin();
	while(it != lt.end())
	{
		cout << *it << endl;
		++it;
	}
}

运行结果:

  • earse (删除某一位置的节点)

代码举例2

#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> lt;
	lt.push_back(0);
	lt.push_back(10);
	lt.push_back(20);
	lt.push_back(30);
	list<int> ::iterator it = lt.begin();
	++it;
	lt.erase(it);
	it = lt.begin();
	while(it != lt.end())
	{
		cout << *it << endl;
		++it;
	}
}

运行结果:

  • swap ( 交换两个list中的元素 )

代码举例3

#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> lt;
	lt.push_back(10);
	lt.push_back(20);
	lt.push_back(30);
	list<int> llt;
	llt.push_back(4);
	llt.push_back(5);
	llt.push_back(6);
	lt.swap(llt);
	auto it = lt.begin();
	while(it != lt.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	for (auto i : llt)
	{
		cout << i << " ";
	}
}

运行结果:

  • clear (清除有效节点,即不包括哨兵位)

c. list 容量

d. list 获取元素

e. list 迭代器

  • begin + end ( 返回第一个元素的迭代器+ 返回最后一个元素下一个位置的迭代器 )

画图分析

  • rbegin + rend ( 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的 reverse_iterator,即begin位置 )

注意:

反向迭代器的模拟实现和我们理解的有偏差,图上为了理解,我们可以认为rbegin是最后一个元素,rend是第一个元素的前一个位置

但是实际上,rbegin指向的位置就是end的位置,rend指向的位置就是rbegin的位置,但是在解引用时,会运算符重载 *,得到该位置的上一个位置 (详情看 list 模拟实现)

2. 迭代器失效

list 迭代器类似一个指针,指向节点的地址 (具体详情看 list 的模拟实现)

所以在发生 erase 的时候容易造成迭代器失效(即野指针)

3. list 类的模拟实现

代码

namespace lhy
{
    template<class T>
struct ListNode
{
public:
	ListNode* prev;
	ListNode* next;
	T val;
	ListNode(const T& t = T())
	{
		prev = next = nullptr;
		val = t;
	}
};
template<class T,class Ref,class Ptr>
class list_iterator
{
public:
	typedef list_iterator<T,Ref,Ptr>  self;
	list_iterator(ListNode<T>* n)
		:_node(n)
	{}
	Ptr operator->()
	{
		return &_node->val;
	}
	Ref operator*()
	{
		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;
	}
	bool operator!=(const list_iterator& t)
	{
		return _node != t._node;
	}
	bool operator==(const list_iterator& t)
	{
		return _node == t._node;
	}
	ListNode<T>* _node;
};

template<class iterator,class Ref,class Ptr>
	class list_converse_iterator
	{
	private:
		iterator com;
	public:
		typedef list_converse_iterator  self;
			list_converse_iterator(iterator& it)
			:com(it)
		{}
		Ptr operator->()
		{
			return &(*com);
		}
		Ref operator*()
		{
			iterator tmp = com;
			--tmp;
			return *tmp;
		}
		self& operator++()
		{
			--com;
			return *this;
		}
		self operator++(int)
		{
			self tmp = *this;
			--*this;
			return tmp;
		}
		self& operator--()
		{
			++com;
			return *this;
		}
		self operator--(int)
		{
			self tmp = *this;
			++*this;
			return tmp;
		}
		bool operator!=(const self& t)
		{
			return com != t.com;
		}
		bool operator==(const self& t)
		{
			return com == t.com;
		}
	};
	template<class T>
	class List
	{
	public:
		typedef list_iterator<T, T&, T*> iterator;
		typedef list_iterator<T, const T&, const T*>  const_iterator;
		typedef list_converse_iterator<iterator, T&, T*>  converse_iterator;
		typedef list_converse_iterator<iterator, const T&,const T*>  const_converse_iterator;
		List()
		{
			node = new ListNode<T>;
			node->next = node;
			node->prev = node;
		}
		iterator begin()
		{
			return iterator(node->next);
		}
		const const_iterator begin() const
		{
			return const_iterator(node->next);
		}
		iterator end()
		{
			return iterator(node);
		}
		const const_iterator end() const
		{
			return const_iterator(node);
		}
		converse_iterator rbegin()
		{
			return converse_iterator(end());
		}
		const_converse_iterator rbegin() const
		{
			return const_converse_iterator(end());
		}
		converse_iterator rend()
		{
			return converse_iterator(begin());
		}
		const_converse_iterator rend()
		{
			return const_converse_iterator(begin());
		}
	void push_back(const T& val)
	{
		ListNode<T>* ptail = node->prev;
		ListNode<T>* newnode = new ListNode<T>(val);
		ptail->next = newnode;
		newnode->next = node;	
		newnode->prev = ptail;
		node->prev = newnode;
	}
	void push_front(const T& x)
	{
		insert(begin(), x);
	}
	void insert(iterator pos,const T &x)
	{
		ListNode<T>* cur = pos._node;
		ListNode<T>* newnode = new ListNode<T>(x);
		newnode->next = cur;
		newnode->prev = cur->prev;
		cur->prev->next = newnode;
		cur->prev = newnode;
	}
	void earse(iterator pos)
	{
		ListNode<T>* cur = pos._node;
		assert(cur != node);
		ListNode<T>* _prev = cur->prev;
		ListNode<T>* _next = cur->next;
		_prev->next = _next;
		_next->prev = _prev;
		delete cur;
	}
private:
	ListNode<T>* node;
};
}

list 迭代器的实现

单看这一个类的实现,可能会疑惑,已经有一个 List 类了,为什么还要加一个 list_iterator 类,并且很容易发现,两个类的成员变量是一样的

如:list<int> :: iterator it;

我们希望 *it 得到的是T类型的变量(这里是int 类型)

而 it++ 得到的是下一个节点的地址

如果是只有 List 类,无法实现

因为如果 typedef ListNode* iterator

那么 *it 的类型就是 ListNode;

it++ 也不是下一个结点的地址(这是链表,开辟的空间不是连续的)

所以这里的 list_iterator 类是为了运算符重载 *和++

代码注意事项

可以和下面的对应

注意:

const iterator 修饰的是 (ListNode<T>*),即指针不可以更改,但是指针所指向的内容可以更改

const_iterator 修饰的是 const ListNode<T>* ,即指针所指向的内容不可更改

注意:

对于这个运算符重载,实际写的时候只要写一个 -> 就行,编译器简化两个 ->

模板第一个传的是 正向迭代器,利用正向迭代器来实现反向迭代器的功能

这里 运算符重载* 让正向迭代器--再解引用,是为了得到 原先T 类型的数据的前一个数据,原因如下:

这里传递是 end() ,但是 对应的数据不是我们想要的

才会在解引用得到前一个数据的值

相关推荐
Bucai_不才11 分钟前
【C++】初识C++之C语言加入光荣的进化(上)
c语言·c++·面向对象
木向13 分钟前
leetcode22:括号问题
开发语言·c++·leetcode
筑基.20 分钟前
basic_ios及其衍生库(附 GCC libstdc++源代码)
开发语言·c++
yuyanjingtao33 分钟前
CCF-GESP 等级考试 2023年12月认证C++三级真题解析
c++·青少年编程·gesp·csp-j/s·编程等级考试
王老师青少年编程2 小时前
gesp(二级)(12)洛谷:B3955:[GESP202403 二级] 小杨的日字矩阵
c++·算法·矩阵·gesp·csp·信奥赛
OTWOL3 小时前
两道数组有关的OJ练习题
c语言·开发语言·数据结构·c++·算法
QQ同步助手3 小时前
C++ 指针进阶:动态内存与复杂应用
开发语言·c++
不惑_3 小时前
List 集合安全操作指南:避免 ConcurrentModificationException 与提升性能
数据结构·安全·list
qq_433554543 小时前
C++ 面向对象编程:递增重载
开发语言·c++·算法
易码智能4 小时前
【EtherCATBasics】- KRTS C++示例精讲(2)
开发语言·c++·kithara·windows 实时套件·krts