【ONE·C++ || list (一)】

news2024/11/16 1:25:00

总言

  主要介绍list的基本函数使用及部分函数接口模拟实现(搭框架)。

文章目录

  • 总言
  • 1、常用接口与举例演示
    • 1.1、接口总览
    • 1.2、部分例子
      • 1.2.1、头删、头插、尾删、尾插、遍历
      • 1.2.2、pos插入删除、迭代器失效问题
      • 1.2.3、一些相对陌生接口简介(std::sort和list::sort比较)
  • 2、list模拟实现
    • 2.1、list中单节点实现:list_node
    • 2.2、list大体框架搭建:list
      • 2.2.1、基本成员变量:_head,构造函数:list
      • 2.2.2、list::push_back (1.0版)
    • 2.2、list的迭代器
      • 2.2.1、问题引入与分析
      • 2.2.2、迭代器基础框架(1.0版本)
        • 2.2.2.1、__list_iterator框架搭建与构造函数
        • 2.2.2.2、list::begin()、list::end()
        • 2.2.2.3、迭代器中所需运算符重载(1.0版本) :!=、==、*、->、++、- -
      • 2.2.3、迭代器基础框架(2.0版本):引入const的迭代器写法
      • 2.2.4、迭代器基础框架(3.0版本):引入反向迭代器
    • 2.3、list中其它函数完善
      • 2.3.1、list::insert、list::push_back、list::push_front
      • 2.3.2、list::erase、list::pop_back、list::pop_front
    • 2.4、其它构造函数、析构、拷贝构造相关
      • 2.4.1、构造函数:使用迭代区间[first, last)、list::empty_init
      • 2.4.2、拷贝构造、赋值运算符重载、list::swap
      • 2.4.3、list::clear、析构

  
  

1、常用接口与举例演示

  list介绍:相关参考网址

1.1、接口总览

  有了stringvector相关铺垫,list接口理解起来相对容易,故而我们的重心不在各接口的使用上,而在其模拟实现。进一步理解类和对象的精华,以及学习迭代器、拷贝构造相关内容。

在这里插入图片描述

  

1.2、部分例子

1.2.1、头删、头插、尾删、尾插、遍历

  1)、演示实例一

void test_list1()
{
	list<int> lt;

	lt.push_back(1);//尾插
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);

	list<int>:: iterator it = lt.begin();//迭代器使用
	while (it != lt.end())
	{
		cout << *it << " ";//遍历
		++it;
	}
	cout << endl;

	it = lt.begin();
	while (it != lt.end())
	{
		*it *= 2;//修改
		++it;
	}

	for (auto e : lt)//范围for使用
	{//此处内置类型,所有没使用引用和const
		cout << e << " ";
	}
	cout << endl;

	lt.push_front(10);//头插
	lt.push_front(20);
	lt.push_front(30);
	lt.push_front(40);
	lt.push_front(50);
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.pop_back();//尾删
	lt.pop_back();
	lt.pop_front();//头删
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}


在这里插入图片描述

  
  

1.2.2、pos插入删除、迭代器失效问题

  2)、演示实例二

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

	auto pos = find(lt.begin(), lt.end(), 3);
	if (pos != lt.end())//insert插入验证
	{
		// 提问:此处的pos是否存在迭代器失效问题?
		// 回答:不会,pos指向的仍旧是我们find到的3
		lt.insert(pos, 30);
		lt.insert(pos, 40);//二次插入
		*pos *= 100;//修改pos值
	}

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

	pos = find(lt.begin(), lt.end(), 4);
	if (pos != lt.end())//erase删除验证
	{
		// 提问:此处的pos是否存在迭代器失效问题?
		// 回答:会,因为pos位置的节点被我们干掉了,此时指向为野指针
		lt.erase(pos);
		// cout << *pos << endl;
	}

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

在这里插入图片描述

  
  

1.2.3、一些相对陌生接口简介(std::sort和list::sort比较)

  这一部分接口相对list来说属于新内容,但从使用角度用到它们频率很少,从介绍角度还是需要了解一下。
在这里插入图片描述
  
  
  1)、list::splice 转移链表元素
在这里插入图片描述

  for (int i=1; i<=4; ++i)
     mylist1.push_back(i);      // mylist1: 1 2 3 4

  for (int i=1; i<=3; ++i)
     mylist2.push_back(i*10);   // mylist2: 10 20 30

  it = mylist1.begin();
  ++it;                         // points to 2

  mylist1.splice (it, mylist2); // mylist1: 1 10 20 30 2 3 4
                                // mylist2 (empty)
                                // "it" still points to 2 (the 5th element)

  注意此处mylist2的数据被挪走了。
  
  
  2)、list::remove 删除指定的所有元素
在这里插入图片描述

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

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

	lt.remove(3);

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

  如下图演示:可看到并非只删除一个指定元素,而是把链表中所有符合元素都删除。
在这里插入图片描述

  
  
  3)、list::unique 去重
  介绍:要使用该函数的前提是链表有序。
在这里插入图片描述
  
  
  4)、list::sort 为链表排序
在这里插入图片描述
  问题:我们的算法库中已经有了一个排序std::sort,为什么链表还要单独实现一个list::sort
  回答:算法库中实现的sort有一个基本前提,即物理空间必须连续,而List结构为非连续物理空间,因此为其单独设置排序函数。
  
  问题:这二者排序有区别吗?
  回答:一般来说,算法库中的sort底层是以快排实现的,而list中的sort其可以用并归实现。二者有一定区别,比如,访问大量数据并排序,我们使用vector+算法库的sort,还是使用list+自身的sort
  
  以下为相关验证:
  演示验证一:代码如下

void test01()
{
	//创建随机数
	srand(time(0));
	const int N = 10000000;

	//实例化vector、list
	vector<int> v;
	v.reserve(N);
	list<int> lt;

	//将随机数放入二者中
	for (int i = 0; i < N; ++i)
	{
		auto e = rand();
		v.push_back(e);
		lt.push_back(e);
	}

	//对vector排序
	int begin1 = clock();
	sort(v.begin(), v.end());
	int end1 = clock();

	//对list排序
	int begin2 = clock();
	lt.sort();
	int end2 = clock();

	printf("vector sort:%d\n", end1 - begin1);
	printf("list sort:%d\n", end2 - begin2);
}

  在release版本下验证结果如下:
在这里插入图片描述

  
  演示验证二:代码如下

void test02()
{
	//创建随机数
	srand(time(0));
	const int N = 1000000;

	//实例化两个list,辅助vector
	vector<int> v;
	v.reserve(N);
	list<int> lt1;
	list<int> lt2;

	//为lt1、lt2赋值相同随机数
	for (int i = 0; i < N; ++i)
	{
		auto e = rand();
		lt1.push_back(e);
		lt2.push_back(e);
	}

	//排序一:将数据拷贝到vector中使用算法库sort排序,然后再拷贝回来
	int begin1 = clock();
	for (auto e : lt1)
	{
		v.push_back(e);
	}
	sort(v.begin(), v.end());
	size_t i = 0;
	for (auto& e : lt1)
	{
		e = v[i++];
	}
	int end1 = clock();

	//排序二:直接使用list::sort排序
	int begin2 = clock();
	lt2.sort();
	int end2 = clock();

	//结果输出
	printf("copy vector sort:%d\n", end1 - begin1);
	printf("list sort:%d\n", end2 - begin2);
}

  在release版本下验证结果如下:
在这里插入图片描述

  
  
  

2、list模拟实现

2.1、list中单节点实现:list_node

  1)、list_node 1.0
  list中,链表是带头双向循环链表,我们在之前的数据结构中也曾用C言语实现过相关构造,这里大体逻辑一致,只是融入了C++与类模板等相关内容。
  如下,C中list单个节点node的实现:

typedef int LTDataType;
typedef struct ListNode
{
	ListNode* prev;//前驱指针
	ListNode* next;//后续指针
	LTDataType data;//数据
}LTNode;

  在list中,我们用类将其封装起来,同时,其使用模板参数创建类型:

	//链表中的单个节点:使用了struct,公有;使用模板,类型为T
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _prev;//注意这里前驱指针和后续指针的类型
		list_node<T>* _next;
	};

  注意事项:
  1、要注意C++中将结构体升级为类,只是和class相比,其成员默认公有。在后续list中我们将频繁访问节点,故而此处创建使用的是struct。
  2、在这层理解上,对于单个节点list_node,其也有自己的默认成员函数,以及,根据我们的需求,可实现其它函数接口。
  这里,考虑到后续对list的增删查改,我们先手动实现list_node的构造函数:

  2)、list_node 2.0

	template<class T>
	struct list_node
	{
		list_node(const T& val = T())//list_node:构造函数
			:_data(val)
			,_prev(nullptr)
			,_next(nullptr)
		{}

		T _data;
		list_node<T>* _prev;
		list_node<T>* _next;
	};

  const T& val = T()
  T()在vector(二)中我们也介绍过,这是缺省参数的使用,此处缺省值为T(),一个T类型的匿名对象,无论是自定义类型还是内置类型,都会调用默认构造。
  const T& 正因为其可以是内置类型,也可以是自定义类型,因此我们加上了const,并使用传引用。
  
  

2.2、list大体框架搭建:list

2.2.1、基本成员变量:_head,构造函数:list

  单个节点以及创建,现在我们来实现list,需要注意list中带有一个哨兵位的头结点:

	template<class T>
	class list
	{
		typedef list_node<T> Node;//私有

	public:
		//构造函数
		list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		//其它需要的成员函数
		//……
		
	private:
		Node* _head;//list中有一个类成员变量:哨兵位的头节点
	};

  关于list的无参构造,我们要做什么?
  类似于C中实现的ListInit,在那里,我们BuyListNode出一个哨兵位的头结点,并修正其prev、next指向关系。这里,构造函数要起到相同作用。
  

2.2.2、list::push_back (1.0版)

在这里插入图片描述

  尾插,从实现逻辑角度,和之前学习的带头双向循环链表大体一致:①找尾;②新增节点;③插入、修改指针间的指向关系。

		void push_back(const T& val)//链表:尾插
		{
			//因为已经有哨兵位头节点存在,插入就比较简单,也不必向单链表一样分情况讨论
			//找尾、插入、关系改变
			
			//找尾
			Node* tail = _head->_prev;//带头双向, _head的前驱指针指向链表尾部
			//开辟新节点
			Node* newnode = new Node(val);//注意new的用法,此外,这里new Node(val)是构造
			//关系更新:_head  ……  tail  newnode
			_head->prve = newnode;
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;

		}

  
  

2.2、list的迭代器

2.2.1、问题引入与分析

  1)、问题
  如上述尾插,假如我们要遍历测试自己的push_back,则需要遍历链表。那么问题来了,关于list,我们能否像vector、string一样,使用原身指针来作为迭代器呢?

  首先,思考vector、string二者的迭代器,它们的作用是取到对应的头尾数据值,故使用原身类型的指针即可得到所需数据。但在list中,Node*解引用得到的是整个节点,非其中的_data

  2)、一个演示
  以下为使用标准库中的list实现的迭代器,可以发现从表面看来它与vector、string的使用别无二致,那么说明是底层实现的细节问题。

void mylist_test_01()
{
	std::list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);

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

在这里插入图片描述

  我们可以简略查看一下源码中的实现:

在这里插入图片描述
  根据上述代码分析,list中实现了各类运算符重载,来完成迭代器的实现,那么封装后在上层看起来,仍旧没什么区别。
  
  

2.2.2、迭代器基础框架(1.0版本)

2.2.2.1、__list_iterator框架搭建与构造函数

  1)、基本演示
  以下是我们实现的list的迭代器的最基本框架:

	template<class T>
	struct __list_iterator//list中的迭代器:非原身指针,此处我们是用类来实现的
	{
		typedef list_node<T> Node;
		typedef __list_iterator iterator;//重命名:能在整体上保持一致性

		Node* _node;//类成员变量:节点

		__list_iteraotr(Node* node)//迭代器中节点的的构造
			:_node(node)
		{}
	};

  1、为什么需要构造函数?
  根据之前vector、string中的内容,我们要实现lsit::beginlist::end。在list中,二者一个为_head->next;另一个为_head,且其返回类型都是迭代器,指向范围为[begin,end)
  
  

2.2.2.2、list::begin()、list::end()

在这里插入图片描述
在这里插入图片描述

  二者相关代码如下:注意,beginend是在list类域中实现的。

	template<class T>
	class list
	{
	public:
		//……
		typedef __list_iterator iterator;//将迭代器重命名,typedef受制于访问限定符,此处要置为公有
		iterator begin()
		{
			return iterator(_head->next);//匿名对象:构造,并返回一个迭代器类
		}

		iterator end()
		{
			return iterator(_head);//匿名对象:构造,并返回一个迭代器类
		}
		//……

	private:
		//……
	};

  
  

2.2.2.3、迭代器中所需运算符重载(1.0版本) :!=、==、*、->、++、- -

  除了上述的begin、end,在__list_iterator中,我们还需要迭代器实现哪些行为操作?

	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";//打印对应节点中的数据_data
		++it;//来到下一个节点
	}

  回顾一下上述代码,可以看到,该迭代器it还需要完成*++!=等各类运算符,由于其是自定义类型,故需要我们自己在__list_iterator中实现。
  
  
  1)、iterator::operator!=

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

  注意理解:我们需要比较的是两节点是否相等,这在__list_iterator中是比较它的成员变量Node* _node。容易出错的写法:

return *this!=it;//这种写法下,我们比较的是整个迭代器iterator

  
  
  2)、iterator::operator==

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

  
  
  3)、iterator::operator*

		//*it == it.operator*()
		T& operator*()
		{
			return _node->_data;
		}

  这里需要注意其返回类型:*it我们需要达到什么效果?获取到对应节点中的数据,因此在实现时,我们要返回的是当前节点存储的的有效数据。
  
  相关验证如下:

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

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

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

在这里插入图片描述

  
  4)、iterator::operator->

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

  为什么要实现->运算符重载?
  以下为一个使用举例:我们使用list时,其模板类型不一定是内置类型,对于一些自定义类型,就需要该运算符重载来访问对应元素。(常见于结构体指针中)

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

void mylist_test_02()
{
	list<Pos> lt;
	lt.push_back(Pos(2, 3));
	lt.push_back(Pos(4, 6));

	list<Pos>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << it->_a1 << "," << it->_a2 << endl;
		++it;
	}
}

  it->_a1实际上为it->->_a1。只是在语法可读性上做了特殊处理,省略一个->
  如果不用->运算符,访问如下:

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

在这里插入图片描述

  
  
  5)、iterator::operator++
  前置自增:

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

  
  后置自增:

		//it++
		iterator operator++(int)
		{
			iterator tmp(*this);//拷贝构造:若我们没显示写,编译器自动生成
			_node = _node->_next;
			return tmp;
		}

  
  6)、iterator::operator- -
  前置自减:

		//--it
		iterator& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

  
  后置自减:

		//it--
		iterator operator--(int)
		{
			iterator tmp(*this);
			_node = _node->prev;
			return tmp;
		}

  
  
  

2.2.3、迭代器基础框架(2.0版本):引入const的迭代器写法

  根据上述1.0中实现,我们的迭代器只适用于普通类型,若被const修饰,如何实现迭代器?
  例如:下述有一个函数需要List作为参数,如果直接传值传参,代价很大,所以一般而言我们使用的是传引用传参,而传引用传参又通常加上const。此时函数中就需要const迭代器:const_iterator it

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

  那么,如何实现呢?
  
  方法一:按照普通迭代器的实现方法,拷贝一份重新实现。
  cosnt迭代器与普通迭代器的一大区别在于一些类型的返回值:例如下述的*lt,普通迭代器可读可写,而const迭代器只能读不能写,因此我们需要控制其返回值。

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

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

  然而上述函数只是返回类型不同,并不能构造函数重载,因此我们不能在一个类中同时存在,才有了此处的方法一,重新实现一个const修饰的迭代器类。
  
  
  方法二:考虑到方法一过于繁琐,这里还有另一个解决方案:使用模板参数,让其在实例化时根据迭代器类型自行区分。

	//迭代器实现。原先模板:template<class T>
	template<class T,class Ref,class Ptr>
	struct __list_iterator
	{
		typedef ListNode<T> Node;
		typedef __list_iterator<T,Ref, Ptr> iterator;
		
		//……
		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &(operator * ());
		}

		//……
	}

  
  那么在实例化时,就能根据需求来定义这里的class T、class Ref、class Ptr

		typedef __list_iterator<T, T&, T*> iterator;//普通迭代器
		typedef __list_iterator<T, const T&, const T*> const_iterator;//const迭代器

  如上述,根据我们实例化时使用的不同的参数类型,就能得到不同的迭代器。
  在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;//const迭代器
		
		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);
		}

		//……
		
	private:
		Node* _head;
	};

  
  
  

2.2.4、迭代器基础框架(3.0版本):引入反向迭代器

  该部分内容后续补上。
  
  

2.3、list中其它函数完善

2.3.1、list::insert、list::push_back、list::push_front

在这里插入图片描述

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

Return value
An iterator that points to the first of the newly inserted elements.

  可以看到insert中,节点位置也是用迭代器实现的,其返回新增节点位置的迭代器。
  
  相关实现如下:

		iterator insert(iterator pos, const T& val)
		{
			//保存节点
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			//新增节点
			Node* newnode = new Node(val);
			//修改关系: prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			//返回值
			return iterator(newnode);
		}

  有了insert,我们就可在此基础上实现push_backpush_front

		void push_back(const T& val)
		{
			insert(end(), val);
		}

		void Push_front(const T& val)
		{
			insert(begin(), val);
		}

  
  

2.3.2、list::erase、list::pop_back、list::pop_front

在这里插入图片描述

iterator erase (iterator position);
iterator erase (iterator first, iterator last);

  相关实现如下:

		iterator erase(iterator pos)
		{
			assert(pos != end());//删除数据有下限,不能无线删除。这里end指向的是尾结点的下一位,即哨兵位的头结点。
			
			Node* cur = pos._node;
			Node* next = cur->_next;
			Node* prev = cur->_prev;

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

			delet cur;

			return iterator(next);
			
		}

  有了erase就可以在pop_frontpop_back中复用:

		iterator pop_front()
		{
			erase(begin());
		}

		iterator pop_back()
		{
			erase(--end());
		}

  
  
  

2.4、其它构造函数、析构、拷贝构造相关

  同理,list中拷贝构造我们也有传统写法和现代写法。对于传统写法,即依照给定链表,自己开辟节点空间并逐一赋值。而对于现代写法,则是借助已经实现的构造函数或者对其它函数的复用来完成。
  这里我们模拟实现现代写法。

2.4.1、构造函数:使用迭代区间[first, last)、list::empty_init

  查看库中可得,list中实现了[first, last)区间中的元素构造list

template <class InputIterator>  
list (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type());

在这里插入图片描述
  要注意这里对InputIterator模板参数的理解,套用一层模板是为了方便[first, last)构造的类型。
  
  我们可以借助它来完成所需要的拷贝构造的现代写法。但在此之前,需要模拟实现该构造函数:

		//构造函数2.0
		template <class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			empty_init();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

  可以看到这里用了一个empty_init();的函数,其作用是为哨兵位的头结点开辟空间并初始化。后续[first,last)中节点构造是建立在已经拥有哨兵位的头结点的基础上的。
  相关实现如下:

		//开辟空间并初始化哨兵位的头结点
		void empty_init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		//构造函数1.2
		list()
		{
			empty_init();
		}

  有了它我们顺带可以把无参构造做些修改。
  

2.4.2、拷贝构造、赋值运算符重载、list::swap

  在此基础上我们来实现这两个涉及深拷贝的成员函数。
  拷贝构造:

		//拷贝构造:lt1(lt2)
		list(const list<T>& lt)
		{
			list<T>tmp(lt.begin(), lt.end);
			swap(tmp);
		}

  
  
  赋值运算符重载:
在这里插入图片描述

		//赋值运算符重载:lt1 = lt2
		list<T>&  operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

  
  list中也有一个自己的swap函数:
在这里插入图片描述

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

  
  
  相关验证:

void mylist_test_04()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		*it *= 2;
		++it;
	}
	cout << endl;

	list<int> lt2(lt);//验证拷贝构造
	for (auto e : lt2)
	{
		cout << e << " ";
	}
	cout << endl;

	list<int>::iterator pos=lt2.erase(lt2.begin());//验证erase
	lt2.insert(lt2.end(), 20);//验证insert

	list<int>lt3;
	lt3 = lt2;//验证赋值运算符重载
	for (auto e : lt3)
	{
		cout << e << " ";
	}
	cout << endl;

}

在这里插入图片描述

  
  

2.4.3、list::clear、析构

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

  此处使用了一个clear函数,这是因为list中也有该函数,其作用是清除所有有效节点数据。
在这里插入图片描述

		//清除list中数据:保留哨兵位的头结点
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it=erase(it);
			}
		}

  
  

  
  
  
  
  
  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/420223.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

D. Captain Flint and Treasure(拓扑排序 + 贪心)

Problem - D - Codeforces 芬特队长参与了另一个寻宝活动&#xff0c;但只发现了一个奇怪的问题。这个问题可能与宝藏的位置有关&#xff0c;也可能不是。这就是为什么弗林特船长决定把解决问题的工作交给他的船员&#xff0c;并提供了一个高得离谱的奖励:休息一天。问题本身听…

【日常】我的扬马最后一小时

文章目录1 Approxmation, Regularization and Relaxation赛前风波惨痛的主场之战释然的痛苦之路后记1 Approxmation, Regularization and Relaxation 在算法理论研究中&#xff0c;为了使得降低问题的求解复杂度&#xff0c;常常会选择牺牲算法的选择求解精度&#xff0c;这种…

Redis-----什么是Redis?

什么是Redis&#xff1f; redis是一个基于内存的key-value结构数据库。 基于内存存储&#xff0c;读写性能高适合存储热点数据&#xff08;热点商品、资讯、新闻&#xff09;企业应用广泛 Redis入门 redis简介 redis是一个开源的内存中的数据结构存储系统&#xff0c;数据库…

ASP宿舍管理系统设计与实现

学生宿舍的管理工作也将成为一项十分繁重的工作&#xff0c;建立一个学生宿舍管理系统是非常必要的&#xff0c;可行的。计算机能够极大地提高学生宿舍管理的办事效率&#xff0c;学校要想与先进科学技术接轨&#xff0c;就得科学化、正规化的进行管理。随着社会信息化步伐的加…

使用Unity模拟人群疏散的资料整理

本文地址&#xff1a;https://blog.csdn.net/t163361/article/details/130136283 UnityDemo Evacuation Simulator Unity_EvacuationSimulator Crowd-Simulation-and-Visualization-in-Unity Multi-agent-simulation-program-for-evacuation Crowd-Evacuation-Simulatio…

Android SQLite插入float类型浮点数小数位数异常(四舍五入过的两位小数变成13位小数)的原因和解决方法

浮点数异常截图&#xff1a; 说明&#xff1a; 正常保留两位小数并正确插入的记录是通过db.execSQL(sql);方法插入的&#xff0c;而浮点数异常的是通过ContentValues db.insert() 方式插入的,可以发现问题出在db.insert()方法上&#xff0c;我又试过在put的时候直接输入类似16…

zabbix代理服务器部署

分布式监控的作用&#xff1a; ●分担 server 的集中式压力 ●解决多机房之间的网络延时问题 部署zabbix代理服务器 1、关闭防火墙、修改主机名 systemctl disable --now firewalld setenforce 0 hostnamectl set-hostname zbx-proxy su 2、设置zabbix的下载源&#xff0c;按…

7.2 模拟乘法器及其在运算电路中的应用

模拟乘法器是实现两个模拟量相乘的非线性电子器件&#xff0c;利用它可以方便地实现乘、除、乘方和开方运算电路。此外&#xff0c;由于它还能广泛地应用于广播电视、通讯、仪表和自动控制系统&#xff0c;进行模拟信号的处理&#xff0c;所以发展很快&#xff0c;称为模拟集成…

【微信小程序-原生开发】添加自定义图标(以使用阿里图标库为例)

方式一 &#xff1a; 下载svg导入 优点&#xff1a;操作方便&#xff0c;支持多彩图标缺点&#xff1a;会增加源代码大小 下载 svg 格式的图标图片&#xff0c;放入源码中使用 小程序项目中的路径为 assets\icon\美食.svg 使用时-代码范例 <image class"imgIcon"…

【JSP学习笔记】3.JSP 指令及动作元素

前言 本章介绍JSP的指令和动作元素。 JSP 指令 JSP指令用来设置整个JSP页面相关的属性&#xff0c;如网页的编码方式和脚本语言。 语法格式如下&#xff1a; <% directive attribute"value" %>指令可以有很多个属性&#xff0c;它们以键值对的形式存在&am…

属性文法和语法制导翻译

前言 前面通过词法分析&#xff0c;语法分析&#xff0c;DFA最后接受了一个输入实际上是理解了某一句编程语句&#xff0c;编译器的角色是将高级程序语言编译&#xff08;翻译&#xff09;为汇编代码&#xff0c;通过词法、语法分析编译器可以理解高级程序语言了&#xff0c;那…

JavaScript + DOM

JavaScript 官方文档 https://www.w3school.com.cn/js/index.asp 基本说明 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SgjOIfTi-1681034533049)(E:\Kuangshen\学习笔记\韩顺平java\JavaScript_img\image-20230409130530115.png)] <!DOCT…

华为手表开发:WATCH 3 Pro(18)传感器订阅 方向传感器

华为手表开发&#xff1a;WATCH 3 Pro&#xff08;18&#xff09;传感器订阅 方向传感器初环境与设备方向传感器鸿蒙开发文件夹&#xff1a;文件新增展示的文本标记index.hmlindex.cssindex.js初 希望能写一些简单的教程和案例分享给需要的人 鸿蒙可穿戴开发 环境与设备 系…

Java设计模式之状态模式

状态模式 状态模式允许一个对象在其内部状态改变的时候改变其行为 应用场景 1、一个对象的行为取决于它的状态&#xff0c;并且它必须在运行时根据状态改变它的行为 2、操作中含有庞大的多分支的条件语句&#xff0c;且这些分支依赖于该对象的状态。这个状态通常用一个或者…

管廊隧道怎么定位人员?分享管廊隧道人员定位系统解决方案

管廊隧道施工的安全不仅关系着施工项目的质量与施工效率&#xff0c;更是关系着国家财产安全以及施工人员和人民群众的生命和财产安全。如何有效加强管廊隧道施工安全管理水平成为管廊隧道项目施工企业管理者最为关注的问题。 管廊隧道施工安全管理痛点难题 1.风险预警难 现场…

《数据库系统概论》第三章课后习题 (4个表+三建工程项目)

目录 5. 针对习题4中的4个表试用SQL完成以下各项操作&#xff1a; 9. 为三建工程项目建立一个供应情况的视图&#xff0c;包括供应商代码SNO, 零件代码PNO, 供应数量QTY&#xff0c;针对该视图完成下列查询&#xff1a; 5. 针对习题4中的4个表试用SQL完成以下各项操作&#x…

Resnet代码详解

这篇文章是用来讲解Resnet(残差网络)代码的&#xff0c;结合代码理解残差网络结构。 目录 Bottleneck类 Conv33 Conv11 BasicBlock ResNet _make_layer代码解析 完整的ResNet代码&#xff1a; 可以直接调用torch内置的resnet官方代码。 from torchvision.models impo…

华为手表开发:WATCH 3 Pro(19)传感器订阅 光线传感器

华为手表开发&#xff1a;WATCH 3 Pro&#xff08;19&#xff09;传感器订阅 光线传感器初环境与设备光线传感器鸿蒙开发文件夹&#xff1a;文件新增展示的文本标记index.hmlindex.cssindex.js初 希望能写一些简单的教程和案例分享给需要的人 鸿蒙可穿戴开发 环境与设备 系…

IP子网划分例题详解

子网划分概念&#xff1a; 通过改变ip的掩码长度来改变ip的网络地址&#xff0c;把原来的ip地址从网络位主机位&#xff0c;改成网络位子网位主机位。从而达到缩小主机个数或者扩大主机个数。缩小主机位&#xff0c;可以避免ip资源的浪费&#xff0c;减小广播域&#xff0c;提…

当对象释放时,避免析构函数调用两次

在上一篇文章中&#xff0c;我们提到过&#xff0c;在一个对象的析构函数中执行太多任务&#xff0c;可能导致对象被释放两次。解决此问题的标准方法是在析构过程中使用一个自定义的引用计数&#xff0c;如下图所示&#xff1a; >> 请移步至 topomel.com 以查看图片 <…