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

news2024/12/23 13:37:26
❤️欢迎来到我的博客❤️

前言

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

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

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

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

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

相关文章

Pytest用例自定义 - 重复、并行、串行

简介&#xff1a;面对快速迭代和持续交付的需求&#xff0c;提高测试效率变得至关重要。并行测试因其显著的时间节省优势而备受青睐。然而&#xff0c;并非所有测试都适合并行执行。在某些情况下&#xff0c;串行执行是必要的&#xff0c;以确保测试的正确性和稳定性。本文将探…

进程和用户管理

查看进程的命令 ps top pstree 发送信号命令 kill 使用是后加-l 用户管理命令 添加用户:sudo adduser 用户名 修改组:sudo usermod -G 用户名1 用户名2 修改家目录:sudo usermod -d /home/用户名 -m 用户名 删除用户名:sudo deluser --remove -home 用户名

newinit.sh挖矿攻击处理与规避方案

目录 攻击分析 恢复措施&#xff1a; 问题排查 攻击入口分析 预防 临时处理方案&#xff1a; 攻击分析 攻击者&#xff1a;职业黑客&#xff08;99%&#xff09; 攻击方式&#xff1a;挖矿病毒newinit.sh和蠕虫病毒pnscan 中毒现象: 服务器负载异常&#xff0c;具体表…

Celery教程

一、什么是Celery 1.1、celery是什么 Celery是一个简单、灵活且可靠的&#xff0c;处理大量消息的分布式系统&#xff0c;专注于实时处理的异步任务队列&#xff0c;同时也支持任务调度。 Celery的架构由三部分组成&#xff0c;消息中间件&#xff08;message broker&#x…

利用基于CNN的人员检测与关键词识别的TinyML实现无接触电梯

目录 说明 论文概述 摘要 引言 现有非接触式电梯解决方案 新解决方案的需求 tinyML实施 系统构建和算法管道 CNN和TinyML实现 结果与讨论 结论 视频演示和代码可用性 一点感想 说明 我一直使用Google Schloar订阅最新的论文消息&#xff0c;今天看到一篇论文的标…

【Docker|漏洞】Docker api未授权导致rce

一、漏洞描述 扫描出http://ip地址:4243漏洞&#xff0c;该漏洞可通过Docker pai未授权访问可以直接执行命令&#xff0c;获取服务器权限。 二、解决方案 禁用Docker api远程访问功能&#xff0c;或者通过安全授权等方式限制其使用权限。升级duoker至最新版本。 三、漏洞排查…

java第十七课 —— 递归

方法递归调用 递归就是方法自己调用自己&#xff0c;每次调用时传入不同的变量&#xff0c;递归有助于编程者解决复杂问题&#xff0c;同时可以让代码变得简洁。 递归重要规则 执行一个方法时&#xff0c;就创建一个新的受保护的独立空间&#xff08;栈空间&#xff09;。方…

【CTF Web】CTFShow web11 Writeup(RCE+PHP+代码审计)

web11 1 阿呆听完自己菜死了&#xff0c;自己呆了。决定修好漏洞&#xff0c;绝对不能让自己再菜死了。 解法 可知 flag 在 config.php。 <?php # flag in config.php include("config.php"); if(isset($_GET[c])){$c $_GET[c];if(!preg_match("/system…

Maven高级详解

文章目录 一、分模块开发与设计分模块开发的意义模块拆分原则 分模块开发(模块拆分)创建Maven模块书写模块代码通过maven指令安装模块到本地仓库(install指令) 二、依赖管理依赖传递可选依赖排除依赖可选依赖和排除依赖的区别 三、聚合与继承聚合工程聚合工程开发创建Maven模块…

专业的Java工程管理软件源码:详尽的项目模块及其功能点清单

在工程项目管理软件领域&#xff0c;我们致力于提供全过程、全方位的综合管理解决方案。该软件覆盖了建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营的各个环节&#xff0c;确保项目管理的全面性和高效性。 工程项目管理软件包含…

VirtualBox虚拟机与bhyve虚拟机冲突问题解决@FreeBSD

问题 在安装完bhyve虚拟系统的主机上启动VirtualBox虚拟机的时候&#xff0c;报错&#xff1a;不能为虚拟电脑 debian 打开一个新任务. VirtualBox cant operate in VMX root mode. Please close all other virtualization programs. (VERR_VMX_IN_VMX_ROOT_MODE). 返回 代码…

Vue从入门到实战Day12~14 - Vue3大事件管理系统

一、用到的知识 Vue3 compositionAPIPinia / Pinia持久化处理Element Plus(表单校验&#xff0c;表格处理&#xff0c;组件封装)pnpm 包管理升级Eslint prettier 更规范的配置husky&#xff08;Git hooks工具&#xff09;&#xff1a;代码提交之前&#xff0c;进行校验请求模…

上海一儿童写真馆摄影师大量售卖女童照片!当你的肖像权或隐私权被侵犯时应如何写起诉状?

上海一儿童写真馆摄影师大量售卖女童照片&#xff01;当你的肖像权或隐私权被侵犯时应如何写起诉状&#xff1f; 近日&#xff0c;上海市一儿童写真馆摄影师被指大量售卖女童的照片和特写花絮。对此&#xff0c; 上海市公安局徐汇分局发布了警情通报&#xff08;见下图&#x…

广场舞团|基于SprinBoot+vue的广场舞团系统(源码+数据库+文档)

广场舞团系统 目录 基于SprinBootvue的广场舞团系统 一、前言 二、系统设计 三、系统功能设计 1 系统功能模块 2 后台登录模块 5.2.1管理员功能模块 5.2.2社团功能模块 5.2.3用户功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推…

【学习Day1】中央处理单元CPU

✍&#x1f3fb;记录学习过程中的输出&#xff0c;坚持每天学习一点点~ ❤️希望能给大家提供帮助~欢迎点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;指点&#x1f64f; 中央处理单元CPU 中央处理器&#xff08;CPU&#xff0c;central processing unit&#xff…

第97天:权限提升-Web 权限权限划分源码后台中间件第三方数据库等

前置知识 具体有哪些权限需要我们了解掌握的 后台权限&#xff0c;网站权限&#xff0c;数据库权限&#xff0c;接口权限&#xff0c;系统权限&#xff0c;域控权限等 以上常见权限获取方法简要归类说明 后台权限&#xff1a;SQL 注入,数据库备份泄露&#xff0c;默认或弱口…

对象解构与迭代器的猫腻?

前言 变量的解构赋值是前端开发中经常用到的一个技巧&#xff0c;比如&#xff1a; // 对象解构 const obj { a: 1, b: 2 }; const { a, b } obj; console.log(a, b)数组解构 const arr [1, 2, 3]; const [a, b] arr; console.log(a, b)工作中我们最经常用的就是类似上面…

全球伦敦银收盘时间一致吗

跟伦敦金市场相似&#xff0c;伦敦银市场也是一个全球化的无形市场&#xff0c;无论来自世界上什么地方的投资者参与其中&#xff0c;都可以得到全天接近24个小时的连贯行情&#xff0c;只要精力足够&#xff0c;根本不用担心没有交易获利的机会。但由于交易平台始终有维护的需…

exe4j --实现把jar包打成exe可执行文件

工具准备 1.Java编辑器&#xff0c;如&#xff1a;idea、eclipse等&#xff0c;下载地址&#xff1a; IntelliJ IDEA: The Capable & Ergonomic Java IDE by JetBrains https://www.jetbrains.com/idea/ 2.exe4j&#xff0c;下载地址&#xff1a; ej-technologies - Java A…

python fstring教程(f-string教程)(python3.6+格式化字符串方法)

文章目录 Python F-String 教程&#xff1a;深度探究与实用指南引言基础用法什么是F-String?表达式嵌入 格式化选项小数点精度宽度与对齐数字格式化 高级用法复杂表达式调用函数多行F-String嵌套格式化 总结 Python F-String 教程&#xff1a;深度探究与实用指南 引言 在Pyt…