【C++】list的使用方法和模拟实现

❤️欢迎来到我的博客❤️

前言

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

list相比于vector在insert和erase上有很大的区别
vector想在第五个位置插入数据可以直接+5

int main()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
	v1.push_back(7);

	v1.insert(v1.begin() + 5, 6);

	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	//输出结果为:1 2 3 4 5 6 7
	return 0;
}

而list想在第五个位置插入数据只能使用迭代器,不可以直接+5
在这里插入图片描述

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

	//lt.insert(lt.begin() + 5, 10);
	auto it = lt.begin();
	for (size_t i = 0; i < 5; i++)
	{
		++it;
	}
	lt.insert(it, 6);

	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
	//输出结果为:1 2 3 4 5 6 7
	return 0;
}

list使用insert之后迭代器不会失效,因为结点的位置没有发生改变
但list使用erase之后迭代器就会失效,因为结点位置发生了改变(结点已经被删除)


排序相关

在这里插入图片描述
可以看到list单独提供了sort库里明明有一个sort为什么list还要单独提供呢?
我们来试试:
在这里插入图片描述
可以看到,当我们使用库里的sort的时候编译报错了
在这里插入图片描述

我们转到库定义可以看到,库里的sort用了一个last-first,原理是快排,需要三数取中,但在链表中就不适合三数取中的场景

迭代器从功能的角度是会分类的,他分为:单向、双向和随机

单向可以进行 ++
双向可以进行 ++/–
随机可以进行 ++/–/+/-
单向迭代器有:forward_list / unordered_map / unordered_set
双向迭代器有:list / map / set
随机迭代器有:vector / string / deque

在形参的名字中就暗示了我们适合用哪一种算法,比如reverse就适合用双向,find适合用单向,sort适合用随机

在这里插入图片描述

InputIterator(find):只写迭代器(他在单向迭代器的上面)也就是说单向 / 双向 / 随机都可以使用
双向迭代器(reverse):双向 / 随机可以使用
随机迭代器(sort):只有随机能用

容器的迭代器类型在文档中是有说明的:
list:
在这里插入图片描述
vector:
在这里插入图片描述
set:
在这里插入图片描述
forward_list:
在这里插入图片描述


在数据量大的情况下,我们可以把list拷贝到vector中,使用库里的排序,再把数据拷贝回去效率更高,我们来对比一下:
数据个数为一千万

int main()
{
	srand(time(0));
	const int N = 10000000;
	vector<int> v;
	v.reserve(N);
	list<int> lt1;
	list<int> lt2;

	for (int i = 0; i < N; i++)
	{
		auto e = rand();
		lt2.push_back(e);
		lt1.push_back(e);
	}

	//拷贝到vector
	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排序
	int begin2 = clock();
	lt2.sort();
	int end2 = clock();

	cout << "使用vector排序:" << end1 - begin1 << endl;
	cout << "直接使用list排序:" << end2 - begin2 << endl;

	return 0;
}

在这里插入图片描述
可以看到效率差了近十倍,所以在数据量特别大的时候就不要直接使用list排序了,排少量数据则可以直接使用


merge

在这里插入图片描述
可以将两个链表归并(前提是链表有序)


unique

在这里插入图片描述

去重(前提是链表有序)


remove

在这里插入图片描述

remove就是find+erase,如果要删除的值不存在则不进行任何操作


splice

在这里插入图片描述
可以把一个链表的内容转移到另一个链表(直接把结点拿走)

转移全部

int main()
{
	list<int> list1, list2;
	list<int>::iterator it;

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

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

	cout << "list1转移前:";
	for (auto e : list1)
	{
		cout << e << " ";
	}
	cout << endl;

	cout << "list2转移前:";
	for (auto e : list2)
	{
		cout << e << " ";
	}
	cout << endl;

	it = list1.begin();
	++it;//2位置

	//把list2全部转移到list1中2之前的位置
	list1.splice(it, list2);
	cout << "list1转移后:";
	for (auto e : list1)
	{
		cout << e << " ";
	}
	cout << endl;

	cout << "list2转移后:";
	for (auto e : list2)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

在这里插入图片描述

转移某一个结点

//转移某一个结点
list1.splice(it, list2,++list2.begin());

在这里插入图片描述

部分转移

//部分转移
list1.splice(it, list2,++list2.begin(),list2.end());

在这里插入图片描述

自己转移自己

//把第二位置转移到第一个位置的前面
list1.splice(list1.begin(),list1,++list1.begin());

在这里插入图片描述

注意:转移重叠位置会造成死循环


模拟实现list

基本框架

namespace List
{
	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _val;

		//构造函数
		list_node(const T& val = T())	//缺省值不能给0,因为T不一定是内置类型
			:_next(nullptr)
			,_prev(nullptr)
			,_val(val)
		{}
	};

	template<class T>
	class list
	{
		typedef list_node<T> Node;
	public:
		list()
		{
			_head = new Node;
			//哨兵位指向自己
			_head->_prev = _head;
			_head->_next = _head;
		}

		//尾插
		void push_back(const T& x)
		{
			Node* tail = _head->_prev;//找尾
			Node* newnode = new Node(x);//调用构造函数

			tail->_next = newnode;
			newnode->_prev = tail;

			newnode->_next = _head;
			_head->_prev = newnode;
		}

	private:
		Node* _head;
	};

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

	}
}

尾插

//尾插
void push_back(const T& x)
{
	Node* tail = _head->_prev;//找尾
	Node* newnode = new Node(x);//调用构造函数

	tail->_next = newnode;
	newnode->_prev = tail;

	newnode->_next = _head;
	_head->_prev = newnode;
}

在这里插入图片描述

迭代器

由于结点的地址不是连续的,那我们的迭代器该如何设置呢,这时候就要用到运算符重载
我们先来看迭代器的使用:

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

那么我们的begin应该返回的是第一个数据的位置,end在最后一个数据的下一个位置
在这里插入图片描述

迭代器是一个自定义类型,这个自定义类型是一个结点的指针,所以在list中他的迭代器需要自己设计而不是像vector或string那样使用原生指针(不同平台下实现方式不同,个别平台也可能对其进行二次封装,也可能不是用原生指针实现)作为迭代器,原因如下:

  1. list 的元素在内存中不是连续存储的,而是通过指针链接起来的。这意味着你不能简单地使用原生指针作为迭代器,因为原生指针只能访问连续内存块中的元素
  2. list 的迭代器需要提供额外的功能,如在链表中前进或后退,这些操作涉及到修改指针以跳转到下一个或上一个元素。原生指针不支持这些操作,因此需要自定义迭代器来实现
  3. 自定义迭代器可以针对链表的特性进行优化。例如,链表迭代器在插入或删除操作时,只需要调整相邻元素的指针,而不需要移动大量元素,这使得链表在某些操作上比数组更高效
  4. list中,插入或删除元素通常不会使迭代器失效(除非删除的是迭代器指向的元素),这与vector不同。自定义迭代器可以确保这些操作的正确性,而原生指针无法提供这种保证
  5. 自定义迭代器可以提供更好的封装性,隐藏链表的内部实现细节,这样,即使链表的实现发生变化,只要迭代器的接口保持不变,使用迭代器的代码就不需要修改

begin和end

template<class T>
class list
{
	typedef list_node<T> Node;
public:
	typedef __list_iterator<T> iterator;

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

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

	list()
	{
		_head = new Node;
		//哨兵位指向自己
		_head->_prev = _head;
		_head->_next = _head;
	}

private:
	Node* _head;
};

* / ++ / – / != / ==

template<class T>
struct __list_iterator
{
	typedef list_node<T> Node;
	Node* _node;

	//用一个结点的指针构造一个迭代器
	__list_iterator(Node* node)
		:_node(node)
	{}

	T& operator*()
	{
		//*it默认解引用是结点,我们不想要结点,我们要的是数据
		return _node->_val;
	}

	//迭代器++返回的还是迭代器
	__list_iterator<T>& operator++()
	{
		//我们想++做的是让他走向下一个结点
		//(原生指针是加一个结点的大小,这样并不能到下一个结点的位置)
		_node = _node->_next;
		return *this;
	}
	
	//后置++
	__list_iterator<T> operator++(int)
	{
		__list_iterator<T> 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>& it)
	{
		//使用结点的指针来进行比较
		return _node != it._node;
	}
	
	bool operator==(const __list_iterator<T>& it)
	{
		//使用结点的指针来进行比较
		return _node == it._node;
	}
};

这样我们的迭代器就可以正常使用了

void test1()
{
	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();
	while (it != lt.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

int main()
{
	List::test1();
	
	return 0;
}

运行效果:
在这里插入图片描述

const迭代器

迭代器模拟的是指针的行为,指针有2种const指针:
1:const T* ptr1; (指针指向的内容不能修改)
2:T* const ptr2;(指针本身不能修改)
const迭代器模拟的是第一种指针的行为

我们可以通过一个类型来控制返回值,给不同的模板参数不同的实例化,他们就是不同的类

template<class T,class Ref>//添加一个class Ref参数
struct __list_iterator
{
	Ref operator*()//返回值改为Ref
	{
		return _node->_val;
	}
}

template<class T>
class list
{
	typedef list_node<T> Node;
public:
	typedef __list_iterator<T,T&> iterator;//普通迭代器
	typedef __list_iterator<T, const T&> const_iterator;//const迭代器
	
	//提供const接口
	const_iterator begin() const
	{
		return const_iterator(_head->_next);
	}

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

->

T* operator->()
{
	return &_node->_val;
}
struct A
{
	A(int a1 = 0,int a2 = 0)
		:_a1(a1)
		,_a2(a2)
	{}

	int _a1;
	int _a2;
};

void test1()
{
	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;
	}
	cout << endl;
}

运行效果
在这里插入图片描述

在这里插入图片描述
const迭代器,也需要添加一个模板参数

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

insert

insert默认都是在pos位置之前插入(任意位置都可以),所以insert不需要做检查
erase则需要检查(因为哨兵位的结点不可删)

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

	return newnode;
}

库里面的insert返回的是新插入位置的迭代器,所以我们实现时和库里保持一致

erase

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;

	return next;	//返回下一个位置
}

注意:这里会存在迭代器失效的问题,因为pos位置的结点已经被我们释放掉了,所以我们需要返回下一个位置的迭代器,而不是void

代码复用

当我们实现完insert和erase之后,尾插、头插、尾删、头删都可以复用

尾插

//尾插
void push_back(const T& x)
{
	//Node* tail = _head->_prev;//找尾
	//Node* newnode = new Node(x);//调用构造函数

	//tail->_next = newnode;
	//newnode->_prev = tail;

	//newnode->_next = _head;
	//_head->_prev = newnode;

	//复用
	//尾插 - 在哨兵位的前面插入
	insert(end(), x);
}

头插

void push_front(const T& x)
{
	//头插
	//在第一个位置插入,也就是begin的前面
	insert(begin(), x);
}

尾删

void pop_back()
{
	//尾删
	erase(--end());
}

头删

void pop_front()
{
	//头删
	erase(begin());
}

size

size有两种实现方法

方法一:

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

	return sz;
}

方法二:
增加一个_size成员初始化为0,每次插入++_size,每次删除–_size

size_t size()
{
	return _size;
}

private:
	Node* _head;
	size_t _size;//增加成员

clear和析构

void clear()
{
	//clear - 清除所有数据,不清哨兵位
	iterator it = begin();
	while (it != end())
	{
		//这里erase之后就不能进行++了,因为他失效了
		//所以得接收返回值(返回值是下一个结点)
		it = erase(it);
	}
}

析构可以直接复用clear

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

	delete _head;
	_head = nullptr;
}

测试一下:

void test1()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.insert(lt.end(), 3);
	lt.push_back(4);
	lt.push_back(5);
	lt.erase(lt.begin());

	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
	
	lt.push_front(1);
	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);

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

运行效果

在这里插入图片描述

拷贝构造和empty_init

拷贝构造

// lt2(lt1)
list(const list<T>& lt)
{
	//初始化
	_head = new Node;
	//哨兵位指向自己
	_head->_prev = _head;
	_head->_next = _head;

	//T对象不确定,所以最好加上引用
	//遍历lt1,把lt1的数据插入到lt2
	for (auto& e : lt)
	{
		push_back(e);
	}
}

empty_init

void empty_init()
{
	_head = new Node;
	//哨兵位指向自己
	_head->_prev = _head;
	_head->_next = _head;
}

复用

list()
{
	empty_init();
}

// lt2(lt1)
list(const list<T>& lt)
{
	empty_init();

	//T对象不确定,所以最好加上引用
	//遍历lt1,把lt1的数据插入到lt2
	for (auto& e : lt)
	{
		push_back(e);
	}
}

赋值和swap

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

赋值

list<T>& operator=(list<T> lt)
{
	//现代写法
	swap(lt);
	
	return *this;
}

测试一下

void test2()
{
	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);

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

	list<int> lt3;
	lt3.push_back(10);
	lt3.push_back(20);
	lt3.push_back(30);
	lt3.push_back(40);

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

运行效果
在这里插入图片描述


完整代码

#pragma once
#include<iostream>
#include<assert.h>

using namespace std;

namespace List
{
	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _val;

		//构造函数
		list_node(const T& val = T())	//缺省值不能给0,因为T不一定是内置类型
			:_next(nullptr)
			,_prev(nullptr)
			,_val(val)
		{}
	};

	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*()
		{
			//*it默认解引用是结点,我们不想要结点,我们要的是数据
			return _node->_val;
		}

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


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

		list()
		{
			empty_init();
		}

		// lt2(lt1)
		list(const list<T>& lt)
		{
			empty_init();

			//T对象不确定,所以最好加上引用
			//遍历lt1,把lt1的数据插入到lt2
			for (auto& e : lt)
			{
				push_back(e);
			}
		}

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

		list<T>& operator=(list<T> lt)
		{
			//现代写法
			swap(lt);
			return *this;
		}

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

			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			//clear - 清除所有数据,不清哨兵位
			iterator it = begin();
			while (it != end())
			{
				//这里erase之后就不能进行++了,因为他失效了
				//所以得接收返回值(返回值是下一个结点)
				it = erase(it);
			}
		}

		//尾插
		void push_back(const T& x)
		{
			//Node* tail = _head->_prev;//找尾
			//Node* newnode = new Node(x);//调用构造函数

			//tail->_next = newnode;
			//newnode->_prev = tail;

			//newnode->_next = _head;
			//_head->_prev = newnode;

			//复用
			//尾插 - 在哨兵位的前面插入
			insert(end(), x);
		}

		void push_front(const T& x)
		{
			//头插
			//在第一个位置插入,也就是begin的前面
			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;

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

			return next;
		}

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

			return sz;
		}

	private:
		Node* _head;
	};

	void Print(const list<int>& lt)
	{
		list<int>::const_iterator it = lt.begin();

		while (it != lt.end())
		{
			//(*it) += 1;
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

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

		int _a1;
		int _a2;
	};

	void test1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.insert(lt.end(), 3);
		lt.push_back(4);
		lt.push_back(5);
		lt.erase(lt.begin());

		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;
		
		lt.push_front(1);
		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);

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

	void test2()
	{
		list<int> lt1;
		lt1.push_back(1);
		lt1.push_back(2);
		lt1.push_back(3);
		lt1.push_back(4);

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

		list<int> lt3;
		lt3.push_back(10);
		lt3.push_back(20);
		lt3.push_back(30);
		lt3.push_back(40);

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

以上就是本篇文章的全部内容了,希望大家看完能有所收获

❤️创作不易,点个赞吧❤️

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

双击移动硬盘打不开?原因分析与数据恢复全攻略

在日常生活和工作中&#xff0c;移动硬盘作为数据存储和传输的重要工具&#xff0c;扮演着至关重要的角色。然而&#xff0c;当我们遇到双击移动硬盘无法打开的情况时&#xff0c;往往会感到十分困扰。本文将详细探讨双击移动硬盘打不开的原因&#xff0c;并提供两种有效的数据…

混合A*算法详解(二)路径平滑

描述 上一篇文章混合A*算法详解&#xff08;一&#xff09;路径搜索 路径损失函数使用Voroni势能图 根据之前的文章分析&#xff0c;决定A*路径长度的有两点&#xff1a;路径长度和距离障碍物远近。Voroni图用于权衡这两者。之前我在记录二维点云的阿尔法形状算法时简单介绍过…

AAA实验配置

一、实验目的 掌握AAA本地认证的配置方法 掌握AAA本地授权的配置方法 掌握AAA维护的方法 1.搭建实验拓扑图 2.完成基础配置&#xff1a; 3.使用ping命令测试两台设备的连通性&#xff1a; 二、配置AAA 1.打开R1&#xff1a;配置AAA方案 这两个方框内的可以改名&#xff0c…

linux安装Java报错cannot execute binary file: Exec format error

uname -a 查看自己jdk版本 下载JDK路径Java Archive Downloads - Java SE 17 下载对应的版本jdk jdk-17.0.10_linux-aarch64_bin.tar.gz jdk-17.0.10_linux-x64_bin.tar.gz

【C#】委托

文章目录 委托自定义委托模板方法&#xff08;工厂模式回调(callback)函数&#xff08;观察者模式多播&#xff08;multicast&#xff09;委托委托的高级使用使用接口 重构 模板方法代码注意参考 委托 委托&#xff08;delegate&#xff09;是一种类型&#xff0c;定义了一种方…

为什么会有websocket(由来)

一、HTTP 协议的缺点和解决方案 1、HTTP 协议的缺点和解决方案 用户在使用淘宝、京东这样的网站的时候&#xff0c;每当点击一个按钮其实就是发送一个http请求。那我们先来回顾一下http请求的请求方式。 一个完整的http请求是被分为request请求节点和response响应阶段的&…

element ui 下拉框Select 选择器 上下箭头旋转方向样式错乱——>优化方案

目录 前言1、问题复现2、预期效果3、input框样式修改解析4、修改方案 &#x1f680;写在最后 前言 测试A&#xff1a;那啥&#xff01;抠图仔&#xff0c;样式怎么点着点着就出问题了。 前端&#xff1a;啥&#xff1f;css样式错乱了&#xff1f;你是不是有缓存啊&#xff01…

淘宝扭蛋机小程序:探索未知,扭出惊喜

在数字时代&#xff0c;我们一直在寻找一种新颖、有趣且充满惊喜的购物方式。淘宝扭蛋机小程序正是为了满足这一需求而诞生的创新之作&#xff0c;它将传统扭蛋机的乐趣与淘宝的丰富商品库完美结合&#xff0c;为您带来前所未有的购物体验。 打破传统&#xff0c;创新玩法 淘…

编程零基础,如何学习Python?

初学者选择Python入手着实是一个不错的方向&#xff0c;入手简单且广泛的运用是它最显著的特色了。 那有几个问题&#xff0c;我想是开始学习Python之前应该了解的&#xff0c; python能做什么&#xff1f; 发展前景与工作机会有哪些&#xff1f; 需要学习哪些内容&#xf…

Windows 使用技巧

Windows 使用技巧 ①局域网内共享文件 ②CTRL Y 和 CTRL Z ①局域网内共享文件 第一步&#xff1a; 选择要共享的文件&#xff08;分享方操作&#xff09; 第二步&#xff1a; 右键打开属性&#xff0c;选择共享&#xff08;分享方操作&#xff09; 第三步&#xff1a; …

Python在忘mysql密码后该如何重新连mysql

步骤一 先到mysql的bin目录下 步骤二 用mysqld delete mysql 把之前的库删了 步骤三 通过管理员模式进去后 用命令mysqld --skip-grant-tables越过验证 再输入mysql -u root 直达账户 步骤四 用FLUSH PRIVILEGES; ALTER USER rootlocalhost IDENTIFIED BY new_password; 指…

JavaScript的当前时间设置及Date的运算

作者:私语茶馆 1.场景描述 如下图,在HTML刚加载时,需要将开始时间设置为默认当前时间,结束时间设置为当前时间后7天的时间。手工填写时间时,时间段不超过30天。 这里涉及到两个技术点: 1)Input Date的当前时间设置 2)date的运算 由于是动态修改HTML,所以采用…

go defer

type _defer struct {siz int32started boolopenDefer boolsp uintptrpc uintptrfn *funcval_panic *_paniclink *_defer }runtime._defer 结构体是延迟调用链表上的一个元素&#xff0c;所有的结构体都会通过 link 字段串联成链表。 只…

【电源专题】功率电感啸叫对策及案例

在文章:【电源专题】功率电感器啸叫原因及典型案例 中我们了解到了电感器啸叫的原因和一些典型电路中产生电感啸叫的案例。通过案例我们了解到很多时候啸叫来源是DC-DC转换器的功率电感器,所以如果我们要降低或消除啸叫,那有哪些对策呢? 避免流过人耳可听频率电流 首先我们…

左偏树,可合并堆

合并两个堆并维护最小或最大性质解决树上节点问题&#xff0c;从叶节点往根维护&#xff0c;每个节点看作一个堆表示到最近的叶节点的距离&#xff0c;所以每次对合并&#xff08;树高矮&#xff09;表示堆的顶点对应下标关键代码 static void dfs(int x){for(int ihead[x];i&g…

14、类与对象(采用图解方式分析内存结构)①

在idea中创建一个新文件&#xff0c;名称为Hello.java 其中&#xff0c;Hello就是一个类&#xff0c;main是这个类里面的方法&#xff0c;这意味着我们在学习的时候已经在使用类了。 对象和类 一、概念二、⭐内存分配机制分析Ⅰ、基本内存结构⭐⭐Ⅱ、调用类方法的内存分析&am…

【效率提升】Edge浏览器

现如今&#xff0c;无论是办公、学习&#xff0c;还是日常搜索、娱乐等&#xff0c;选择一个搜索快&#xff0c;准确率高&#xff0c;不卡顿&#xff0c;没广告的浏览器都是非常重要的。我想向大家推荐一款极具实力的浏览器&#xff1a;Microsoft Edge。 Microsoft Edge 浏览器…

具有固定宽度的盒子:\makebox, \parbox

makebox \makebox 是 LaTeX 中的一个命令&#xff0c;用于创建一个具有固定宽度的盒子&#xff0c;并在该盒子内放置内容。这个命令可以用于控制文本或对象的位置和对齐。 语法如下&#xff1a; \makebox[<width>][<alignment>]{<content>}其中&#xff1…

【c++leetcode】69. Sqrt(x)

问题入口 二分搜索 最困难的是能否意识到用二分搜索法解题。 算术平方根的区间在[1, x] 。代码如下&#xff1a; class Solution { public:int mySqrt(int x) {if (x 1 || x 0){return x;}int64_t start 1;int64_t end x;while (start < x){int64_t mid start (en…

HttpClient cookie爬虫记录

记录一次java语言使用httpclient爬取网站接口数据的经历 需要用到的依赖&#xff1a; httpclient和httpcore是封装了http请求的工具类 jsoup可以将返回的网页html找到你需要的xml节点&#xff0c;很方便 <dependency><groupId>org.apache.httpcomponents</gr…