STL-list

news2024/9/17 4:08:23

目录

【本节目标】

1.list的介绍及使用

1.1 list的介绍(双向链表)

1.2 list的使用

1.2.1 list的构造

1.2.2 list iterator的使用(迭代器)

1.2.3 list capacity(容量)

1.2.4 list element access

1.2.5 list modifiers

1.2.6 list的迭代器失效

2.list的模拟实现

2.1节点类

2.2 list迭代器类 

 2.3 list类

2.4 输出遇到的问题

2.5 遇到const迭代器传参时的问题

3.list与vector的对比


【本节目标】

1. list的介绍及使用
2. list的深度剖析及模拟实现
3. list与vector的对比

1.list的介绍及使用

1.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来说这可能是一个重要的因素)

 

1.2 list的使用

list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口。

1.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

1.2.2 list iterator的使用(迭代器)

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

注意:

1.begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动

2.rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

1.2.3 list capacity(容量)

函数声明接口说明
empty检测list是否为空,是返回true,否则返回false
size返回list中有效节点的个数

1.2.4 list element access

函数声明接口说明
front返回list的第一个节点中值的引用
back返回list的最后一个节点中值的引用

1.2.5 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中的有效元素

1.2.6 list的迭代器失效

list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代
器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

void TestListIterator1()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> l(array, array + sizeof(array) / sizeof(array[0]));
	auto it = l.begin();
	while (it != l.end())
	{
		// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给
		其赋值
			l.erase(it);
		++it;
	}
}

删除时会导致迭代器失效,由于删除之后,之前的节点已经删除,但是迭代器还是指在这个位置,没有发生改变,从而导致迭代器失效。改为如下

// 改正
void TestListIterator()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> l(array, array + sizeof(array) / sizeof(array[0]));
	auto it = l.begin();
	while (it != l.end())
	{
		l.erase(it++); // it = l.erase(it);
	}
}

注意:

list只有删除时(erase)迭代器才会失效,插入时(insert)不会失效。

2.list的模拟实现

2.1节点类

首先将节点封装成一个类,节点类

//节点类
//初始化每个节点
template<class T>//用struct结构体时由于节点的数据全部公开,也可以时class类中的public中
struct ListNode
{
	ListNode<T>* _next;//指向下一个节点
	ListNode<T>* _prev;//指向前一个节点
	T data;


	//节点的构造函数
	//只用构造节点属性
	//默认缺省值为默认无参构造
	ListNode(const T& x = T()):
		_next(nullptr),
		_prev(nullptr),
		data(x)
	{}

};

运用结构体来封装,因为就结构体的默认时public类型,而节点的数据本来就要全部公开。

2.2 list迭代器类 

如果物理空间是连续的,迭代器就可以认为是原生指针。由于list迭代器的空间是不连续的,原生指针不满足需求。封装一个类来实现迭代器。由于STL的每个结构都有iterator迭代器,所以可以用内嵌类型解决,即用typedef重命名或者内部类。

list迭代器也就相当于一个节点的指针

ListIterator类

起初的ListIterator类

template<class T>
struct ListIterator
{
	typedef ListNode<T> Node;
	typedef ListIterator<T> Self;

	Node* _node; //一个迭代器节点

	//迭代器构造
	ListIterator(Node *node):_node(node)
	{}

	++it
	前置++,返回++以后的值
	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}

	it++
	后置++
	Self operator++(int)
	{
		Self tmp(*this);//浅拷贝,即两个迭代器指针指向同一个空间,直接应用默认拷贝构造
		_node = _node->_next;
		return tmp;//拷贝
	}

	--it
	Self& operator--()
	{
		//向前走
		_node = _node->_prev;
		return *this;
	}

	it--
	Self operator--(int)
	{
		Self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}

	*it
	解引用,返回的是数据
	T& operator*()
	{
		return _node->data;
	}

	==
	比较两个迭代器相等,即比较迭代器的位置(引用/地址)相同
	bool operator==(const Self& it)
	{
		return _node == it._node;
	}

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

	//->
	//返回的是数据的地址
	T* operator->()
	{
		return &_node->data;
	}

};

前置++、后置++、前置--、后置--都额可以简单应用

重点: 

解引用运算符重载

 2.3 list类

封装一个list类,成员变量是哨兵位头节点(_head)和计数节点的个数(_size),这是一个起初的list类之后会根据迭代器去变化

typedef ListIterator<T> iterator;是将迭代器名重定义到域内,就相当于只在list类内,即和内部类的功能一样

template<class T>
class list
{
public:
	//重定义节点类名
	typedef ListNode<T> Node;
	//重定义迭代器名,作用域在list域内
	//没有用const迭代器时
	typedef ListIterator<T> iterator;


private:
	Node *_head;//哨兵位
	size_t _size;//链表中节点的个数


};

迭代器的begin和end

//迭代器的引用
iterator begin()
{
	//iterator it(_head->_next);//有名对象
	//return it;
	return iterator(_head->_next);//这是应用的是一个匿名对象
}

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

由于哨兵位不算节点,哨兵位的下一个节点是第一个节点(begin)。

由于双向链表,即end就为_head。

构造函数

起初的构造函数,构造一个哨兵位头节点,_next和_prev都指向_head自己

//构造函数
//默认情况下,只有一个头节点的哨兵位
//并且_head->_next=_head
//_head->_prev=_head
list()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;

	_size = 0;
}

为了更好些拷贝构造函数,则把构造函数改为以下方式

void empty_init()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;

	_size = 0;
}
list()
{
	empty_init();
}

根据上述部分,写出拷贝构造

//拷贝构造
//lt2(lt1)
//逐节点拷贝
list(const list<T>& lt)
{
	empty_init();
	//插入
	for (auto& e : lt)
	{
		push_back(e);
	}

}

先构造出哨兵位头节点,之后再逐节点拷贝,即为得到

赋值重载

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

//赋值重载
//lt=lt1;
list<T>& operator=(const list<T> lt)
{
	//交换
	swap(lt);
	reurn* this;
}

插入

尾插

//尾插
//引用插入数据本身
//权限可以缩小,输入的实参可以是const类型数据或者普通类型数据都可以
void push_back(const T& x)
{
	开辟节点,存入数据
	//Node* newnode = new Node(x);
	//Node* tail = _head->_prev;// tail指向原来的最尾部的节点

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

    //已知insert函数时
	insert(end(), x);
}

 

insert函数

//c++中要隐藏底层,应用迭代器
//在pos位置插入
void 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;

	_size++;
}

头插

//头插
void push_front(const T& x)
{
	insert(begin(), x);
}

 删除

erase

//删除
//erase会导致迭代器失效,失效的原因是这个指针已经被释放
//需要更新节点指针,返回下一个节点的迭代器
iterator erase(iterator pos)
{
	Node *cur = pos._node;//当前位置
	Node *Prev = cur->_prev;//前一个位置
	Node *Next = cur->_next;//后一个位置

	Prev->_next = Next;
	Next->_prev = Prev;
	delete cur;//删除当前节点

	_size--;
	return iterator(Next);//匿名对象
	
}

头删和尾删

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

//尾删
void pop_back()
{
	//erase(end() - 1);不能使用,由于没有重载减号的运算符
	erase(--end());
}

2.4 输出遇到的问题

struct A
{
	int _a1;
	int _a2;

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

void test_list2()
{
	list<A> lt;
	A aa1(1, 1);
	A aa2 = { 1, 1 };
	lt.push_back(aa1);
	lt.push_back(aa2);
	lt.push_back(A(2, 2));
	lt.push_back({ 3, 3 });//多参数可以这样构造
	lt.push_back({ 4, 4 });

	A* ptr = &aa1;
	(*ptr)._a1;
	ptr->_a1;

	list<A>::iterator it = lt.begin();
	while (it != lt.end())
	{
		//*it += 10;
        //cout<<*it<<" ";    //问题出处
		
        cout << (*it)._a1 << ":" << (*it)._a2 << endl;//解决问题2
		
        //解决问题3
        //it.operator->()->_a1;
        //第一个是->是运算符重载,第二个是->原生指针
        cout << it->_a1 << ":" << it->_a2 << endl;
        cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;
		++it;
	}
	cout << endl;
}

由于之前输出时,用的int数据的链表,流插入可以输出结果,但是对应自定义类型时,是无法用流插入输出的。

解决方法有两种

1.写出流插入的运算符重载方法

2.用上面的方法解决:cout<<(*it)._a1<<":"<<(*it)._a2<<endl;即解决问题2

3.如果实在不想写流插入的运算符重载方法,可以运用解决问题3,在ListIterator类中重载->方法

解决问题3

直接在最初的ListIterator类的public部分中添加以下方法

//->
//返回的是数据的地址
T* operator->()
{
	return &_node->data;
}

之后可以应用问题解决3,他直接会去省略一个->,即为it->_a;原本是it.operator->()->_a;由于返回值为A*,所以解引用后可以访问到_a;

2.5 遇到const迭代器传参时的问题

由于上述的代码都是最初的代码,最初的ListIterator类,最初的list类

遇到下面代码时会报错

void PrintList(const list<int>& clt)
{
	list<int>::const_iterator it = clt.begin();
	while (it != clt.end())
	{
		//*it += 10;

		cout << *it << " ";
		++it;
	}
	cout << endl;
}

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

	PrintList(lt);

	list<int> lt1(lt);
	PrintList(lt1);
}

由于传参数是const类型的迭代器,和我们上面写的不同,上面最初的迭代器ListIterator类只有普通方法,没有const方法。

解决问题方法有两种:

1.可以用ctrl+c/v,再写一个ListConstIterator类

如下

//第一种解决const类型的迭代器
//const迭代器类
template<class T>
struct ListConstIterator
{
	typedef ListNode<T> Node;
	typedef ListConstIterator<T> Self;

	Node* _node; //一个迭代器节点

	//迭代器构造
	ListConstIterator(Node* node) :_node(node)
	{}

	++it
	前置++,返回++以后的值
	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}

	it++
	后置++
	Self operator++(int)
	{
		Self tmp(*this);//浅拷贝,即两个迭代器指针指向同一个空间,直接应用默认拷贝构造
		_node = _node->_next;
		return tmp;//拷贝
	}

	--it
	Self& operator--()
	{
		//向前走
		_node = _node->_prev;
		return *this;
	}

	it--
	Self operator--(int)
	{
		Self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}

	*it
	解引用,返回的是数据
	const T& operator*()
	{
		return _node->data;
	}

	==
	比较两个迭代器相等,即比较迭代器的位置(引用/地址)相同
	bool operator==(const Self& it)
	{
		return _node == it._node;
	}

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

	//->
	//返回的是数据的地址
	const T* operator->()
	{
		return &_node->data;
	}

};

 在list类中也要添加

typedef ListConstIterator<T> const_iterator;

//const迭代器,需要迭代器不能修改,还是迭代器指向的内容?
// 迭代器指向的内容不嫩被修改! const iterator不是我们需要的const迭代器
//以下是迭代器本身不能修改
//const iterator begin()错误
const_iterator begin() const
{
	//iterator it(_head->_next);//有名对象
	//return it;
	return const_iterator(_head->_next);//这是应用的是一个匿名对象
}

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

2.由于上述代码过于冗余,两个类的内容非常相似,可以用模板来解决问题

最终的ListIterator类

//迭代器类
// 一个链表指针用迭代器封装,实质上还是一个指针
//迭代器也就相当于指向一个节点的指针
//第二种解决const类型的迭代器问题
//利用模板来解决
template<class T,class Ref,class Ptr>
struct ListIterator
{
	typedef ListNode<T> Node;
	typedef ListIterator<T,Ref,Ptr> Self;

	Node* _node; //一个迭代器节点

	//迭代器构造
	ListIterator(Node *node):_node(node)
	{}

	++it
	前置++,返回++以后的值
	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}

	it++
	后置++
	Self operator++(int)
	{
		Self tmp(*this);//浅拷贝,即两个迭代器指针指向同一个空间,直接应用默认拷贝构造
		_node = _node->_next;
		return tmp;//拷贝
	}

	--it
	Self& operator--()
	{
		//向前走
		_node = _node->_prev;
		return *this;
	}

	it--
	Self operator--(int)
	{
		Self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}

	*it
	解引用,返回的是数据
	//T& operator*()
	Ref operator*()
	{
		return _node->data;
	}

	==
	比较两个迭代器相等,即比较迭代器的位置(引用/地址)相同
	bool operator==(const Self& it)
	{
		return _node == it._node;
	}

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

	//->
	//返回的是数据的地址
	//T* operator->()
	Ptr operator->()
	{
		return &_node->data;
	}

};

最终的list类

//list类
template<class T>
class list
{
public:
	//重定义节点类名
	typedef ListNode<T> Node;
	//重定义迭代器名,作用域在list域内
	//没有用const迭代器时
	/*typedef ListIterator<T> iterator;
	typedef ListConstIterator<T> const_iterator;*/

	//第二种方法解决const迭代器类
	typedef ListIterator<T,T&,T*> iterator;
	typedef ListIterator<T,const T&,const T*> const_iterator;


private:
	Node *_head;//哨兵位
	size_t _size;//链表中节点的个数


    ...
};

用模板实质上也是相当于创建了两个类,只是将创建类的工作都交给了编译器。

3.list与vector的对比

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

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

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

相关文章

一键接入大模型:One-Api本地安装配置实操,POSTMAN、APIFOX调用CURL

前言 最近准备学习一下 Semantic Kernel, OpenAI 的 Api 申请麻烦,所以想通过 One-api 对接一下国内的在线大模型,先熟悉一下 Semantic Kernel 的基本用法,本篇文章重点记录一下OneApi安装配置的过程。 讯飞星火有 3.5 模型的 200w 个人免费 token,可以拿来学习。 讯飞星…

2.冒泡排序

样例输入 5 8 3 6 4 9 样例输出 3 4 6 8 9 以下是解题答案&#xff1a; class demo1{public static void main(String[] args) {Scanner scnnew Scanner(System.in);int[] array new int[scn.nextInt()];if(array.length>0&&array.length<200){for(int…

单点登录(JWT实现)

单点登陆的英文名是&#xff1a;Single Sign On&#xff08;简称SSO&#xff09;&#xff0c;只需要登陆一次&#xff0c;就可以访问所有信任的应用系统。 在单体项目中&#xff0c;我们登陆之后可以把验证用户信息的值放入session中&#xff0c;单个tomcat中的session是可以共…

MySQL 8窗口函数详解:高效数据处理的必备技能

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 MySQL 8窗口函数详解&#xff1a;高效数据处理的必备技能 前言窗口函数概述窗口函数的基本语法常用窗口函数类型窗口帧的定义与使用性能优化与注意事项 前言 你是否曾经遇到过需要对数据进行复杂统计…

软考高项 各章节知识点【细】

文章目录 前五章项目管理概论项目立项管理项目整合管理范围管理进度管理成本管理质量管理资源管理沟通管理风险管理采购管理干系人管理绩效域配置与变更管理招投标、政府采购 前五章 数字经济是继农业经济、工业经济之后的主要经济形态&#xff0c;是以数据资源为关键要素&…

ElementPlus 步骤条嵌套Popover 气泡卡片

业务场景&#xff1a;当前步骤条鼠标悬浮提示框&#xff0c;步骤条是for循环出来的 如下图: <el-steps finish-status"success"><el-popoverv-for"item in uniqueReverseArr"placement"top-start":title"item.title":width&…

“胖东来”超市商业模式,为何被誉为中国零售业是神一般的存在?

“胖东来”超市商业模式&#xff0c;为何被誉为中国零售业是神一般的存在&#xff1f; 文丨微三云营销总监胡佳东&#xff0c;点击上方“关注”&#xff0c;为你分享市场商业模式电商干货。 - 胖东来是中国商超界的天花板&#xff0c;被小米老板雷军&#xff1a;誉为“中国零…

img标签添加::before ::after 伪元素无效,伪元素增加:hover伪类无效

1 问题 img标签添加::before ::after 伪元素无效伪元素增加:hover伪类无效 2 解决 只能在img前后增加dom元素可以这样写:hover::before{} :hover::after{} 3 分析 3.1 定义 ::before 创建一个伪元素&#xff0c;其将成为匹配选中的元素的第一个子元素。常通过 content 属…

项目方案:社会视频资源整合接入汇聚系统解决方案(三)

目录 一、概述 1.1 应用背景 1.2 总体目标 1.3 设计原则 1.4 设计依据 1.5 术语解释 二、需求分析 2.1 政策分析 2.2 业务分析 2.3 系统需求 三、系统总体设计 3.1设计思路 3.2总体架构 3.3联网技术要求 四、视频整合及汇聚接入 4.1设计概述 4.2社会视频资源分…

Modbus协议基础

文章目录 Modbus概述Modbus TCP/IP Modbus概述 Modbus是由Modicon&#xff08;现为施耐德电气公司的一个品牌&#xff09;在1979年发明的&#xff0c;是全球第一个真正用于工业现场的应用层总线协议。 为更好地普及和推动Modbus在基于以太网上的分布式应用&#xff0c;目前施…

单细胞 10X 和seurat对象学习

单细胞seurat数据的基础知识 rm(list ls()) library(Seurat) #注意这个报错 #Warning: Feature names cannot have underscores (_), replacing with dashes (-) folderslist.files(./,pattern[123]$) folders scList lapply(folders,function(folder){ CreateSeuratObject(…

2024-5-14-从0到1手写配置中心Config之基于数据库的分布式锁

DistributedLocks实现 自动注入数据源。创建连接&#xff0c;保证不影响其他连接。locked原子变量判断是否加锁成功&#xff1b;Executor 定时任务&#xff0c;尝试获取锁。 init()初始化方法 使用注解PostConstruct初始化&#xff0c;使用数据源创建连接。 开启定时任务&am…

一个投稿好方法让你的文章早日发表

作为一名单位信息宣传员,我初入此行时,满腔热情,怀揣着传播单位价值、展示团队风采的理想,一头扎进了稿件撰写的海洋。我的目标很简单,就是通过文字的力量,让外界听到我们的声音,感受到我们的活力。然而,理想很丰满,现实却给我上了生动的一课。 起初,我遵循传统路径,选择了一家…

虚拟化知识学习

虚拟化知识学习 关键概念和术语的简要介绍 虚拟化的基本概念 虚拟机 (VM)&#xff1a;一个虚拟机是一个模拟计算机系统的环境。它运行在物理硬件之上&#xff0c;但与物理硬件隔离&#xff0c;提供类似于物理计算机的功能。 虚拟化技术&#xff1a;这是指使用软件来创建虚拟版…

如何去除视频上的文字?免费无痕去水印分享!视频制作良器!

对于需要进行二次创作的视频素材&#xff0c;去除原有的文字可以提供一个更加干净的画布&#xff0c;方便创作者在其基础上进行新的创作和编辑。同时&#xff0c;去除文字后的视频也更方便分享到各种平台&#xff0c;避免因为平台对文字的限制而导致视频无法发布或传播。 要去除…

从零开始构建 Vision Transformer(ViT) 模型

Transformer 模型最早由 Vaswani 等人在 2017 年论文 Attention Is All You Need 中提出&#xff0c;并已广泛应用于自然语言处理。 2021年&#xff0c;Dosovitsky 等人在论文An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale中提出将 Transforme…

信息系统项目管理师0133:工具与技术(8项目整合管理—8.8实施整体变更控制—8.8.2工具与技术)

点击查看专栏目录 文章目录 8.8.2 工具与技术8.8.2 工具与技术 专家判断实施整体变更控制过程中,应征求具备如下领域相关专业知识或接受过相关培训的个人或小组的意见,涉及领域包括:关于项目所在的行业以及项目关注的领域的技术知识;法律法规;法规与采购;配置管理;风险管…

万物循环:在游戏开发与服务器监听中的极致应用

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、循环的极致场景 代码示例 二、无限循环的机智运用 代码示例 三、总结 一、循环的极致…

字典的创建和删除

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在Python中&#xff0c;字典与列表类似&#xff0c;也是可变序列&#xff0c;不过与列表不同&#xff0c;它是无序的可变序列&#xff0c;保存的内容…

mysql 、oss 结合使用

以下是一个使用 Express、MySQL、OSS 和 axios 的 Node.js 示例。这个示例创建了一个 Express 服务器&#xff0c;该服务器有一个路由用于处理视频上传的请求。视频文件首先被上传到 OSS&#xff0c;然后视频的 OSS URL 被存储到 MySQL 数据库。 首先&#xff0c;我们需要安装必…