c++----list模拟实现

news2024/11/29 2:53:22

目录

1. list的基本介绍

2. list的基本使用 

2.1 list的构造

用法示例 

2.2 list迭代器 

 用法示例

2.3. list容量(capacity)与访问(access)

用法示例

2.4 list modifiers 

用法示例 

2.5 list的迭代器失效 

3.list的模拟实现 

3.1 构造、析构

3.2 迭代器 

3.3 list modifiers 

4. list与vector的区别 

5.完整代码 


1. 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的底层结构。

从图中可以看出,list底层由一个双向带头循环链表构成。假设有一链表list<int> lt, 头节点为_head则lt.begin()指向_head->next, lt.end()指向_head。

2. list的基本使用 

2.1 list的构造

构造函数( (constructor))接口说明
list (size_type n, const value_type& val = value_type())构造的list中包含n个值为val的元素
list()构造空的list
list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)用[first, last)区间中的元素构造list

用法示例 

void list_test1()
{
	list<string> lt(5, "xy");//n val构造
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	list<string> llt(lt);//拷贝构造
	for (auto e : llt)
	{
		cout << e << " ";
	}
	cout << endl;

	list<string> lltt(++lt.begin(), --lt.end());//迭代器区间构造
	for (auto e : lltt)
	{
		cout << e << " ";
	}
}

2.2 list迭代器 

函数声明接口说明
begin +
end
返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin +
rend
返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的
reverse_iterator,即begin位置

 用法示例

void list_test2()
{
	list<int> lt{ 1,2,3,4,5};//这样构造很奇特
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

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

	list<int>::reverse_iterator  rit = lt.rbegin();//反向迭代器
	while (rit != lt.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	cout << endl;
}

那么大家肯定会有一个疑问,之前的string,vector迭代器可以++,是因为他们的底层物理空间是连续的,但list底层可是双向带头循环链表啊,它的结构物理空间可是不连续的,我们++,不就错了吗?

不急,我们后面模拟实现的时候细讲。

2.3. list容量(capacity)与访问(access)

函数声明接口说明
empty检测list是否为空,是返回true,否则返回false
size

返回list中有效节点的个数

front返回list的第一个节点中值的引用
back返回list的最后一个节点中值的引用

用法示例

void list_test3()
{
	list<int> lt{ 1,2,3,4,5 };//这样构造很奇特
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "链表是否为空:   ";
	if (lt.empty())
		cout << "true" << endl;
	else
		cout << "false" << endl;

	cout << "size: " << lt.size() << endl;

	cout << "Front Element: " << lt.front() << endl;

	cout << "Back Element: " << lt.back() << endl;

	lt.clear();
	cout << "已执行链表清空操作,链表是否为空:   ";
	if (lt.empty())
		cout << "true" << endl;
	else
		cout << "false" << endl;

}

2.4 list modifiers 

函数声明接口说明
push_front在list首元素前插入值为val的元素
pop_front删除list中第一个元素
push_back在list尾部插入值为val的元素
pop_back删除list中最后一个元素
insert在list position 位置中插入值为val的元素
erase删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素

用法示例 

void list_test4()
{
	list<int> lt{ 1,2,3,4,5 };//这样构造很奇特
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.push_back(0);
	lt.push_back(0);
	lt.push_front(9);
	lt.push_front(9);
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

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

	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		if (*it == 5)
		{
			it = lt.erase(it);
			break;
		}
			
		it++;
	}
	lt.insert(it, 999);
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

2.5 list的迭代器失效 

这里我们将迭代器理解为指针,list的迭代器失效是因为指针所指向的空间被销毁,导致指针变为野指针。list的底层是双向带头循环链表,因此list不存在插入导致迭代器失效的问题。list的迭代器失效出现在erase内,如果该节点被删除,空间已被销毁,那么就必须更新迭代器,否则迭代器失效。 

3.list的模拟实现 

list的模拟实现存在几个难点,首先是使用了大量的typedef,这会让人头晕眼花,其次是模板迭代器的构建。我们快来看看吧。

注意:list的迭代器由于list底层空间不连续的问题,因此要实现和vector一样的迭代器效果(即支持*it,it++)需要对操作符进行重载,所以要封装Node*。

list需要创建一个类内类Node,用list访问Node类的变量。

下面是ListNode类,类内包含双链表节点的三要素。

template<class T>
struct ListNode//节点的创建
{
	T _Date;
	ListNode<T>* _next;
	ListNode<T>* _prev;

	ListNode(const T& x=T())//节点的构造函数
		:_Date(x)
		,_next(nullptr)
		,_prev(nullptr)
	{}
};

3.1 构造、析构

这里的构造函数为简易版本,只支持创建空链表。 

void list_init()//初始化每一个节点
{
	_head = new Node;//new调用Node的默认构造,不传参
	_head->_next = _head;
	_head->_prev = _head;
}
list()
{
	list_init();
}
list(list<T>& lt)//拷贝构造
{
	list_init();//创建头节点

	for (const auto& e : lt)
	{
		push_back(e);
	}
}

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

//list& operator=(list lt)
list<T>& operator=(list<T> lt)
{
	swap(lt);
	return *this;
}
void clear()
{
	list<T>::iterator it = this->begin();
	while (it != end())
	{
		it = erase(it);
	}
}
~list()
{
	delete _head;
	_head = nullptr;
}

3.2 迭代器 

前面说,要对++等操作符进行重载,这时需要重新定义iterator类封装节点的指针,通过指针对节点进行操作。且iterator类需要支持普通对象与const对象的访问,因此需要定义成模板类。

模板类iterator 

template<class T,class Ref,class Ptr>
struct _list_iterator//迭代器本质就是指针,这里封装Node*,对Node的指针进行操作,重载++等操作符
{
	typedef ListNode<T> Node;
	typedef _list_iterator<T,Ref,Ptr> self;
	//typedef _list_iterator<T> self;
	Node* _node;//成员变量

public:
	_list_iterator(Node* node)//构造函数,直接用node构建
		:_node(node)
	{}
	//没有参数int ,前置++
	self& operator++()//重载++
	{
		_node = _node->_next;
		return *this;
	}

	self operator++(int)//后置++
	{
		Node* tmp = _node;
		_node = _node->_next;
		return tmp;
	}


	self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	//返回临时对象,出函数即销毁,不可传引用
	self operator--(int)//后置--
	{
		Node* tmp = _node;
		_node = _node->_prev;
		return tmp;
	}
	Ptr operator->()
	{
		return &_node->_Date;
	}
	Ref operator*()
	{
		return _node->_Date;
	}
	bool operator!=(const self& s)
	{
		return _node != s._node;
	}
	bool operator==(const self& s)
	{
		return _node == s._node;
	}
};

list类内的迭代器函数 

typedef _list_iterator<T,T&,T*> iterator; //普通对象的迭代器
typedef _list_iterator<T,const T&,const T*> const_iterator;  //const对象的迭代器
iterator begin()//指向头节点的下一个
{
	
	return _head->_next;
}

iterator end()//指向头节点
{
	return _head;
}

const_iterator begin() const//指向头节点的下一个
{

	return _head->_next;
}

const_iterator end() const//指向头节点
{
	return _head;
}

 这里模板参数有三个,分别是T,T&,T*,这是为什么呢?

T&:因为重载普通迭代器的*时,返回值需要设为引用类型,以便于外界可以更改。

 T*:重载->时,由于list的->逻辑不能自洽,list的重载->需要返回数据的地址

这里需要着重解释一下,如果是list<int>,那么重载->完全没有问题,但如果是list<Date>呢?

Node的数据域是自定义类型,又该怎么办呢?

因此我们返回Node.Date的地址,然后再解引用,如果是内置类型不受影响,如果是自定义类型,稍加控制就ok了。

我们来看看示例:

struct AA
{
	int _a1;
	int _a2;

	AA(int a1 = 1, int a2 = 1)
		:_a1(a1)
		, _a2(a2)
	{}
};
void test_list5()
{
	list<AA> lt;
	AA aa1;
	lt.push_back(aa1);

	lt.push_back(AA());

	AA aa2(2, 2);
	lt.push_back(aa2);

	lt.push_back(AA(2, 2));

	list<AA>::iterator it = lt.begin();
	while (it != lt.end())
	{
		std::cout << (*it)._a1 << ":" << (*it)._a2 << std::endl;
		std::cout << it.operator*()._a1 << ":" << it.operator*()._a2 << std::endl;

		std::cout << it->_a1 << ":" << it->_a2 << std::endl;
		std::cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << std::endl;

		++it;
	}
	std::cout << std::endl;
}

Node的数据域存放的是AA类对象,Node* ptr,ptr->Date就是AA类的实例化对象,此时返回AA对象的地址,也就是AA* p,p->就可以访问AA类对象的成员了。 

3.3 list modifiers 

这里需要注意erase的迭代器失效问题。 

void push_back(const T& x=T())
{
	Node* node = new Node;
	Node* tail = _head->_prev;
	node->_Date = x;

	tail->_next = node;
	node->_next = _head;
	_head->_prev = node;
	node->_prev = tail;
}

iterator insert(iterator pos,const T& x = T())//不存在迭代器失效问题
{
	Node* node = new Node;
	node->_Date = x;
	Node* cur = pos._node;
	Node* prev = cur->_prev;

	cur->_prev = node;
	node->_next = cur;
	node->_prev = prev;
	prev->_next = node;

	return node;
}

iterator erase(iterator pos)//这里的pos有可能会产生迭代器失效问题
{
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;

	prev->_next = next;
	next->_prev = prev;
	delete cur;
	cur = nullptr;
	return next;//更新pos
}

4. list与vector的区别 

list与vector都是c++STL序列容器的重要组成部分,由于底层结构的不同,造成了他们的功能和特性有所不同,详见下表。 

vectorlist
底 层 结 构动态顺序表,一段连续空间带头结点的双向循环链表
随 机 访 问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素
效率O(N)
插 入 和 删 除任意位置插入和删除效率低,需要搬移元素,时间复杂
度为O(N),插入时有可能需要增容,增容:开辟新空
间,拷贝元素,释放旧空间,导致效率更低
任意位置插入和删除效率高,不
需要搬移元素,时间复杂度为
O(1)
空 间 利 用 率底层为连续空间,不容易造成内存碎片,空间利用率
高,缓存利用率高
底层节点动态开辟,小节点容易
造成内存碎片,空间利用率低,
缓存利用率低
迭 代 器原生态指针对原生态指针(节点指针)进行封装
迭 代 器 失 效在插入元素时,要给所有的迭代器重新赋值,因为插入
元素有可能会导致重新扩容,致使原来迭代器失效,删
除时,当前迭代器需要重新赋值否则会失效
插入元素不会导致迭代器失效,
删除元素时,只会导致当前迭代
器失效,其他迭代器不受影响
使 用 场 景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随
机访问


 

5.完整代码 

template<class T>
struct ListNode//节点的创建
{
	T _Date;
	ListNode<T>* _next;
	ListNode<T>* _prev;

	ListNode(const T& x=T())//节点的构造函数
		:_Date(x)
		,_next(nullptr)
		,_prev(nullptr)
	{}
};

template<class T,class Ref,class Ptr>
struct _list_iterator//迭代器本质就是指针,这里封装Node*,对Node的指针进行操作,重载++等操作符
{
	typedef ListNode<T> Node;
	typedef _list_iterator<T,Ref,Ptr> self;
	//typedef _list_iterator<T> self;
	Node* _node;//成员变量

public:
	_list_iterator(Node* node)//构造函数,直接用node构建
		:_node(node)
	{}
	//没有参数int ,前置++
	self& operator++()//重载++
	{
		_node = _node->_next;
		return *this;
	}

	self operator++(int)//后置++
	{
		Node* tmp = _node;
		_node = _node->_next;
		return tmp;
	}


	self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	//返回临时对象,出函数即销毁,不可传引用
	self operator--(int)//后置--
	{
		Node* tmp = _node;
		_node = _node->_prev;
		return tmp;
	}
	Ptr operator->()
	{
		return &_node->_Date;
	}
	Ref operator*()
	{
		return _node->_Date;
	}
	bool operator!=(const self& s)
	{
		return _node != s._node;
	}
	bool operator==(const self& s)
	{
		return _node == s._node;
	}
};

template<class T>
class list
{
	typedef ListNode<T> Node;  //typedef
public:
	typedef _list_iterator<T,T&,T*> iterator; //普通对象的迭代器
	typedef _list_iterator<T,const T&,const T*> const_iterator;  //const对象的迭代器
	iterator begin()//指向头节点的下一个
	{
		
		return _head->_next;
	}

	iterator end()//指向头节点
	{
		return _head;
	}

	const_iterator begin() const//指向头节点的下一个
	{

		return _head->_next;
	}

	const_iterator end() const//指向头节点
	{
		return _head;
	}
	void list_init()//初始化每一个节点
	{
		_head = new Node;//new调用Node的默认构造,不传参
		_head->_next = _head;
		_head->_prev = _head;
	}
	list()
	{
		list_init();
	}
	list(list<T>& lt)//拷贝构造
	{
		list_init();//创建头节点

		for (const auto& e : lt)
		{
			push_back(e);
		}
	}

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

	//list& operator=(list lt)
	list<T>& operator=(list<T> lt)
	{
		swap(lt);
		return *this;
	}
	void clear()
	{
		list<T>::iterator it = this->begin();
		while (it != end())
		{
			it = erase(it);
		}
	}
	~list()
	{
		delete _head;
		_head = nullptr;
	}
	void push_back(const T& x=T())
	{
		Node* node = new Node;
		Node* tail = _head->_prev;
		node->_Date = x;

		tail->_next = node;
		node->_next = _head;
		_head->_prev = node;
		node->_prev = tail;
	}

	iterator insert(iterator pos,const T& x = T())//不存在迭代器失效问题
	{
		Node* node = new Node;
		node->_Date = x;
		Node* cur = pos._node;
		Node* prev = cur->_prev;

		cur->_prev = node;
		node->_next = cur;
		node->_prev = prev;
		prev->_next = node;

		return node;
	}

	iterator erase(iterator pos)//这里的pos有可能会产生迭代器失效问题
	{
		Node* cur = pos._node;
		Node* prev = cur->_prev;
		Node* next = cur->_next;

		prev->_next = next;
		next->_prev = prev;
		delete cur;
		cur = nullptr;
		return next;//更新pos
	}

	size_t size()const//返回size
	{
		int cnt = 0;
		list<int>::const_iterator it = begin();
		while (it != end())
		{
			cnt++;
			it++;
		};
		return cnt;
	}
	bool empty()const//判空
	{
		return _head ->_next== _head;
	}


private:
	Node* _head;//成员变量为ListNode* 即访问节点的指针
};


 

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

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

相关文章

【PythonGIS】Python实现批量导出面矢量要素(单个多面矢量->多个单面矢量)

可怜的我周六还在工作&#xff0c;已经很久没更新过博客了&#xff0c;今天正好有空就和大家分享一下。今天给大家带来的是使用Python将包含多个面要素/线要素的矢量批量导出单个要素的矢量&#xff0c;即一个要素一个矢量文件。之前写过多个矢量文件合并成一个矢量文件的博文&…

【C++STL详解(二)】——string类模拟实现

目录 前言 一、接口总览 二、默认成员函数 1.构造函数 2.拷贝构造 写法一&#xff1a;传统写法 写法二&#xff1a;现代写法&#xff08;复用构造函数&#xff09; 3.赋值构造 写法一&#xff1a;传统写法 写法二&#xff1a;现代写法(复用拷贝构造) 4.析构函数 三、…

武汉星起航:引领跨境电商新纪元,助力卖家实现全球业务飞跃

在全球化的时代背景下&#xff0c;越来越多的中国卖家正选择跨境电商作为他们拓展国际市场的重要途径。然而&#xff0c;对于许多新手卖家而言&#xff0c;如何进入海外市场、如何运营店铺、如何推广产品&#xff0c;都是一道道难以逾越的难题。在这个关键时刻&#xff0c;武汉…

智慧公厕:改变城市生活的革命性基础设施

在现代城市的高品质生活要求背景下&#xff0c;公共厕所成为了不可或缺的基础设施。然而&#xff0c;传统的公厕在服务质量、管理效率方面存在一定的问题。为了提供更好的公厕服务&#xff0c;智慧公厕应运而生。通过智能化的管理模式&#xff0c;智慧公厕实现了公厕使用与管理…

【并发编程】线程的基础概念

一、基础概念 1.1 进程与线程A 什么是进程&#xff1f; 进程是指运行中的程序。 比如我们使用钉钉&#xff0c;浏览器&#xff0c;需要启动这个程序&#xff0c;操作系统会给这个程序分配一定的资源&#xff08;占用内存资源&#xff09;。 什么线程&#xff1f; 线程是CP…

【Java八股面试系列】数据库(总结市面所有数据库知识点)

文章目录 索引索引是什么聚簇&非聚簇索引索引的实现方式HashBTree&#xff08;多路平衡二叉树&#xff09;两种实现方式区别 引申红黑树红黑树和AVL树的区别最大堆和最小堆 索引的优劣优点劣势 索引的使用索引失效的时候 事务特性事务并发问题隔离级别 锁锁的分类行锁表锁意…

udpflood是一种什么攻击,如何防御?

一、定义 UDPFlood攻击是一种利用UDP协议进行的拒绝服务&#xff08;DoS&#xff09;攻击&#xff0c;攻击者通过发送大量的UDP数据包来占用目标系统的网络带宽和资源&#xff0c;从而使系统无法正常响应合法用户的请求。 二、攻击方式 1. UDP协议特点&#xff1a; UDP协议是一…

【详解】运算放大器工作原理及其在信号处理中的核心作用

什么是运算放大器 运算放大器&#xff08;简称“运放”&#xff09;是一种放大倍数非常高的电路单元。在实际电路中&#xff0c;它常常与反馈网络一起组成一定的功能模块。它是一种带有特殊耦合电路和反馈的放大器。输出信号可以是输入信号的加法、减法、微分和积分等数学运算…

最大连续子序列和求值

文章预览&#xff1a; 题目算法遍历枚举代码如下 优化版本代码如下 陈越姥姥题目代码 题目 最大连续子序列求和&#xff0c;什么是连续最大子序列&#xff0c;先简单来说一个数组int a[]{-9,1,-10,2,3,4,-2,5,6,-2,-3}; 那它的连续最大子序列是2,3,4,-2,5,6 是18&#xff0c;就…

首页HF粗排模型优化

[work rus_env]$ pwd /home/work/xx/du-rus/offline-tools/du_rus/rus_env [work rus_env]$ python buildenv_rus.py 5a0e771e938a486df3b8b3e1cde1a39c2006882d 5f3241963a3e39a8e1eae05d7075fc5b9278a7c7 打开日志级别 [workxx conf]$ vim /home/work/xx/du-rus/du_rus_…

线程中的核心操作

线程中的核心操作 1:start()2:中断(终止)一个线程2.1:自己定义线程结束的代码2.1.1 存在的问题 2.2:使用Thread提供的interrupt()方法和isInterrupted()2.2.1 继续执行2.2.2 立即结束2.2.3 打印异常信息,再立即结束2.2.1 继续执行 22三级目录 1:start() start() 真正的创建线程…

LeetCode Python - 80. 删除有序数组中的重复项 II

目录 题目描述解法运行结果 题目描述 给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须在 原地 修改输入数组 并在使用 O…

奔赴智慧医院建设浪潮,迈瑞创新产品亮相中国医学装备大会

3月28日&#xff0c;第32届中国医学装备大会暨2024医学装备展览会在重庆顺利开展。迈瑞医疗以“助力医院高质量发展&#xff0c;共建智慧医院生态”为主题参展&#xff0c;多款核心零部件、创新产品及智慧医疗生态集体亮相。 满足需求&#xff0c;推动智慧医院建设提速 迈瑞医疗…

天下三分明月夜,独有快慢指针法(链表面试题篇)

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

什么是CVE? CVE漏洞应该如何防护?

CVE&#xff08;Common Vulnerabilities and Exposures&#xff09;的全称是公共漏洞和暴露&#xff0c;是公开披露的网络安全漏洞列表。IT人员、安全研究人员查阅CVE获取漏洞的详细信息&#xff0c;进而根据漏洞评分确定漏洞解决的优先级。 在CVE中&#xff0c;每个漏洞按CVE-…

Type-C一分二快充线智能分配方案

随着移动设备的普及和快充技术的迅猛发展&#xff0c;Type-C接口已成为众多手机、平板和笔记本电脑的标配。然而&#xff0c;在日常使用中&#xff0c;我们经常会遇到需要同时为多个设备充电的情况。这时&#xff0c;Type-C一分二快充线就显得尤为重要。为了更好地满足用户的充…

CSS之动画

一&#xff0c;动画的制作 实现盒子绕圈走 二&#xff0c; 动画的常用属性 三&#xff0c;动画简写属性 前面两个属性一定要写&#xff0c;第三个linear是指匀速的意思&#xff08;默认是ease&#xff09;

Day26 手撕各种集合底层源码(一)

Day26 手撕各种集合底层源码&#xff08;一&#xff09; 一、手撕ArrayList底层源码 1、概念&#xff1a; ArrayList的底层实现是基于数组的动态扩容结构。 2、思路&#xff1a; 1.研究继承关系 2.研究属性 3.理解创建集合的过程 – 构造方法的底层原理 4.研究添加元素的过程…

【Linux】图文详解Xshell远程连接服务器:以Amazon EC2 VPS为例

文章目录 问题描述解决方案Q&A 问题描述 本地cmd或powershell使用ssh -i “your.pem” user_nameip_address是可以登录Amazon EC2云服务器的。 然而&#xff0c;当使用XShell以SSH加载PEM文件方式登录亚马逊EC2云服务器&#xff0c;一直出现输入密码的问题&#xff0c;如…

FPGA亚稳态学习总结

首先是组合逻辑电路考虑的是竞争冒险&#xff0c;冒险会产生毛刺。重点研究如何去毛刺 时序逻辑电路考虑的是时序不满足会产生的亚稳态问题&#xff1a;如何考量时序满不满足呢&#xff1f;根据不同的场景又有不同的说法。 时序分析的两组基本概念 建立时间与保持时间 1.在…