C++模拟实现list

C++教学总目录

C++模拟实现list

  • 1、成员变量
  • 2、迭代器
  • 3、insert函数
  • 4、erase函数
  • 5、pop_back、push_front、pop_front函数
  • 6、size和clear函数
  • 7、析构函数
  • 8、拷贝构造函数
  • 9、赋值运算符重载
  • 完整代码(包含测试代码)

1、成员变量

先来看看SGI版本STL中list的实现方式:
在这里插入图片描述
成员变量就是一个结点的指针。但是我们实现的时候多加一个size来保存结点个数,因为如果计算结点个数需要遍历一遍链表,时间复杂度为O(N),我们选择多提供一个size成员变量。

template<class T>
struct list_node
{
	list_node<T>* _prev;
	list_node<T>* _next;
	T _val;
	list_node(const T& val = T())
		:_prev(nullptr)
		, _next(nullptr)
		, _val(val)
	{}
};

template<class T>
class list
{
	typedef list_node<T> Node;
public:
	void empty_init()
	{
		_head = new Node;
		_head->_prev = _head;
		_head->_next = _head;
		_size = 0;
	}

	list()
	{
		empty_init();
	}
private:
	Node* _head;
	size_t _size;
};

我们仿照SGI版本的实现,为了方便写将结点类型重命名。
这里提供了一个empty_init函数是用来创建哨兵位的头节点的。当我们调用空的构造函数、带参的构造函数、拷贝构造函数等都需要先创建哨兵位的头节点,所以我们将这一步写在函数里面,后面调用empty_init即可。

2、迭代器

list的迭代器不同于vector和string。vector和string基于底层的性质,可以对其开辟空间的指针进行++/–,所以vector和string的迭代器就是原生指针。但是list的迭代器无法是结点的指针,因为list空间并不连续,指针++/–无法找到下一个/前一个结点。
我们先来看看SGI版本是如何实现的:

在这里插入图片描述
可以看到,list的迭代器是创建了自定义类型来实现的,再对自定义类型重载operator++/–,这样就可以实现迭代器的功能,同时要获取数据就重载operator*。
实现如下:

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

	__list_iterator(Node* node)
		:_node(node)
	{}

	T& operator*()
	{
		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;
	}

	__list_iterator<T>& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	__list_iterator<T> operator--(int)
	{
		__list_iterator<T> tmp(*this);
		_node = _node->_prev;
		return tmp;
	}

	bool operator!=(const __list_iterator<T>& it) const
	{
		return _node != it._node;
	}

	bool operator==(const __list_iterator<T>& it) const
	{
		return _node == it._node;
	}
};

然后在list类中对该结构体类型重命名,并实现begin和end。

typedef __list_iterator<T> iterator;

iterator begin()
{
	return _head->_next;
}

iterator end()
{
	return _head;
}

接着我们实现一个push_back函数,然后就可以跑起来测试一下我们写的迭代器了:

void push_back(const T& x)
{
	Node* tail = _head->_prev;
	Node* newnode = new Node(x);
	newnode->_prev = tail;
	tail->_next = newnode;
	newnode->_next = _head;
	_head->_prev = newnode;
	_size++;
}

下面对我们写的迭代器进行测试:
在这里插入图片描述
可以发现我们写的迭代器成功跑起来了。
下面提出几个注意点:
在这里插入图片描述


普通迭代器已经实现了,那么const迭代器呢?
思路一:

	typedef const __list_iterator<T> const_iterator;

这样子行吗?
这样子是不行的,因为const修饰迭代器之后,迭代器内的成员变量是不能修改的,也就不能进行++/–了,所以这样写是不行了。

思路二:

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

	__list_const_iterator(Node* node)
		:_node(node)
	{}

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

	__list_const_iterator<T>& operator++()
	{
		_node = _node->_next;
		return *this;
	}

	__list_const_iterator<T> operator++(int)
	{
		__list_const_iterator<T> tmp(*this);
		_node = _node->_next;
		return tmp;
	}

	__list_const_iterator<T>& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	__list_const_iterator<T> operator--(int)
	{
		__list_const_iterator<T> tmp(*this);
		_node = _node->_prev;
		return tmp;
	}

	bool operator!=(const __list_const_iterator<T>& it) const
	{
		return _node != it._node;
	}

	bool operator==(const __list_const_iterator<T>& it) const
	{
		return _node == it._node;
	}
};

typedef __list_const_iterator<T> const_iterator;

将普通迭代器拷贝一份,名字改为__list_const_iterator,operator*改为const T,这样也可以实现const迭代器,但是这么写太冗余了。
我们来看看库里是怎么写的:
在这里插入图片描述
添加了两个模板参数,这里我们先看Ref,Ref实际上就是类型的引用,如果是普通迭代器就是T&,如果是const迭代器就是const T&。通过不同的模板参数实例化出不同类。

我们再来看Ptr,Ptr实际上是T*/const T*,因为迭代器还重载了一个函数:operator->:
在这里插入图片描述
为什么重载这个函数呢?
当我们的T类型是自定义类型时:

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

int main()
{
	zzy::list<A> lt;
	lt.push_back(A(1, 1));
	lt.push_back(A(2, 2));
	lt.push_back(A(3, 3));
	zzy::list<A>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << it->_a1 << " " << it->_a2 << endl;
		++it;
	}
	return 0;
}

在这里插入图片描述
这里重载operator->也是需要传模板参数控制,对于普通对象就是T*,对于const对象就是const T*。


那么综合上述,并且为了方便书写迭代器类型,我们对迭代器类型typedef为self,代码如下:

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*() 
	{
		return _node->_val;
	}

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

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

并且在list类中加上const迭代器:

typedef __list_iterator<T, const T&, const T*> const_iterator;
const_iterator begin() const
{
	return _head->_next;
}

const_iterator end() const
{
	return _head;
}

但是!!!!!这样写完还是不能调用const迭代器!!!!

zzy::list<A> lt;
lt.push_back(A(1, 1));
lt.push_back(A(2, 2));
lt.push_back(A(3, 3));
zzy::list<A>::const_iterator it = lt.begin();
while (it != lt.end())
{
	cout << it->_a1 << " " << it->_a2 << endl;
	++it;
}

上面的代码中,我们虽然指明it是const迭代器,但是lt调用的begin函数还是普通迭代器的begin函数。
这是因为lt是普通对象,所以调用普通迭代器begin,如果lt是const对象,那就会调用const迭代器的begin。
而因为lt.begin的返回值是普通迭代器,而it是const迭代器,所以就变成了用普通迭代器构造const迭代器,而我们迭代器模板类中并没有写普通迭代器构造const迭代器的函数。所以无法从普通迭代器转换成const迭代器,因此无法使用const迭代器。
解决办法很简单,添加一个普通迭代器构造const迭代器就行。

typedef __list_iterator<T, T&, T*> iterator;
__list_iterator(const iterator& it)
		:_node(it._node)
	{}

添加这个函数之后:
1、当模板类实例化为const迭代器时,支持普通迭代器构造const迭代器。
2、当模板类实例化为普通迭代器时,本质上就是普通迭代器的拷贝构造函数。

所以说:在那么早以前,C++祖师爷能搞出这些东西,是多么的牛逼!!!!!!


3、insert函数

iterator insert(iterator pos, const T& x)
{
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* newnode = new Node(x);
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;
	++_size;
	return newnode;
}

insert函数实现在pos位置之前插入一个新结点,并返回新节点的迭代器。

4、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;
	--_size;
	return next;
}

5、pop_back、push_front、pop_front函数

这三个函数我们直接复用前面写好的insert和erase就行,前面写的push_back也可以复用insert。

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

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

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

6、size和clear函数

size_t size() const { return _size; }

// 如果不提供_size成员变量,需要遍历链表求出节点个数。
size_t size() const
{
	size_t sz = 0;
	auto it = begin();
	while (it != end())
	{
		++it;
		++sz;
	}
	return sz;
}


void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
	}
	_size = 0;
}

7、析构函数

复用clear函数:

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

8、拷贝构造函数

list(const list<T>& lt)
{
	empty_init();
	for (auto& e : lt)
		push_back(e);
}

9、赋值运算符重载

这个我们使用现代写法,但是需要先实现一个swap函数用来交换list的值。

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

//list& operator=(list& lt) 也可以这么写,但是不推荐
list<T>& operator=(list<T>& lt)
{
	swap(lt);
	return *this;
}

可以看到上面的operator=重载的返回值和参数可以直接写为list,但是不推荐这么写。我们还是写成list<T>好一些。

完整代码(包含测试代码)

#pragma once

namespace zzy
{
	template<class T>
	struct list_node
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _val;
		list_node(const T& val = T())
			:_prev(nullptr)
			, _next(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;
		typedef __list_iterator<T, T&, T*> iterator;
		Node* _node;

		__list_iterator(Node* node)
			:_node(node)
		{}

		__list_iterator(const iterator& it)
			:_node(it._node)
		{}

		Ref operator*() 
		{
			return _node->_val;
		}

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

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

		iterator end()
		{
			return _head;
		}

		const_iterator begin() const
		{
			return _head->_next;
		}

		const_iterator end() const
		{
			return _head;
		}

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

		list()
		{
			empty_init();
		}

		list(const list<T>& lt)
		{
			empty_init();
			for (auto& e : lt)
				push_back(e);
		}

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

		//list& operator=(list& lt) 也可以这么写,但是不推荐
		list<T>& operator=(list<T>& lt)
		{
			swap(lt);
			return *this;
		}

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

		size_t size() const { return _size; }
		//size_t size() const
		//{
		//	size_t sz = 0;
		//	auto it = begin();
		//	while (it != end())
		//	{
		//		++it;
		//		++sz;
		//	}
		//	return sz;
		//}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
			_size = 0;
		}

		void push_back(const T& x)
		{
			//Node* tail = _head->_prev;
			//Node* newnode = new Node(x);
			//newnode->_prev = tail;
			//tail->_next = newnode;
			//newnode->_next = _head;
			//_head->_prev = newnode;
			//_size++;

			insert(end(), x);
		}

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

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

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

		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			++_size;
			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;
			--_size;
			return next;
		}
	private:
		Node* _head;
		size_t _size;
	};

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

	void test_list1()
	{
		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;

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

		Print(lt);
	}

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

	};

	void test_list2()
	{
		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 << it->_a1 << " " << it->_a2 << endl;
			// 严格来说it->->_a1,这么写才是符合语法的
			// 因为运算符重载要求可读性,编译器特殊处理,省略了一个->
			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_front(5);
		lt.push_front(6);
		lt.push_front(7);
		lt.push_front(8);

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

		cout << lt.size() << endl;
	}

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

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

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

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

		lt1 = lt2;

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

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

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

相关文章

《高频电子线路》 —— 反馈型振荡器

文章内容来源于【中国大学MOOC 华中科技大学通信&#xff08;高频&#xff09;电子线路精品公开课】&#xff0c;此篇文章仅作为笔记分享。 反馈型振荡器基本工作原理 振荡器分类 自激&#xff1a;没有信号输入他激&#xff1a;有信号输入RC振荡器主要产生低频的正弦波&#x…

如何在Linux下安装和配置Docker

文章目录 安装前的准备在Debian/Ubuntu上安装Docker添加Docker仓库安装Docker验证安装 在CentOS/RHEL上安装Docker安装必要的软件包设置Docker仓库安装Docker启动Docker服务 Docker的基本使用拉取一个镜像运行一个容器 配置Docker创建Docker目录使用非root用户运行Docker 结语 …

1-petalinux 问题记录-根文件系统分区问题

在MPSOC上使用SD第二分区配置根文件系统的时候&#xff0c;需要选择对应的bootargs&#xff0c;但是板子上有emmc和sd两个区域&#xff0c;至于配置哪一种mmcblk0就出现了问题&#xff0c;从vivado中的BlockDesign和MLK XCZU2CG原理图来看的话&#xff0c;我使用的SD卡应该属于…

分类算法——支持向量机 详解

支持向量机&#xff08;Support Vector Machine, SVM&#xff09;的底层原理 支持向量机是一种用于分类和回归的强大机器学习算法&#xff0c;最常见的是用于二分类任务。SVM 的核心思想是通过找到一个最优超平面&#xff0c;将数据集划分成不同的类别。SVM 尤其擅长处理高维数…

系统集成项目管理工程师考试时间

系统集成项目管理基础知识考试信息 题量&#xff1a;共 75 道题。考试时间&#xff1a;该科目考试时间为上午 8&#xff1a;30 - 12&#xff1a;30&#xff08;或下午 14&#xff1a;30 - 18&#xff1a;30&#xff0c;但通常为上午&#xff09;。基础知识科目最短作答时长 90…

微服务实战系列之玩转Docker(十六)

导览 前言Q&#xff1a;基于容器云如何实现高可用的配置中心一、etcd入门1. 简介2. 特点 二、etcd实践1. 安装etcd镜像2. 创建etcd集群2.1 etcd-node12.2 etcd-node22.3 etcd-node3 3. 启动etcd集群 结语系列回顾 前言 Docker&#xff0c;一个宠儿&#xff0c;一个云原生领域的…

【论文解读】Med-BERT: 用于疾病预测的大规模结构化电子健康记录的预训练情境化嵌入

【论文解读】Med-BERT: 用于疾病预测的大规模结构化电子健康记录的预训练情境化嵌入 Med-BERT:pretrained contextualized embeddings on large-scale structured electronic health records for disease prediction ​ ​ 摘要:基于电子健康记录(EHR)的深度学习(DL)预…

API接口开放与安全管控 - 原理与实践

API安全是接口开放的前提条件 在API对外开放时&#xff0c;确保其安全性至关重要&#xff0c;因为API直接暴露给外部环境&#xff0c;容易成为攻击目标。一旦被恶意利用&#xff0c;可能导致数据泄露、服务滥用等严重后果。因此&#xff0c;通过API网关实施严格的接口安全管理…

python实现钉钉群机器人消息通知(消息卡片)

直接上代码 python """ 飞书群机器人发送通知 """ import time import urllib3 import datetimeurllib3.disable_warnings()class DingTalkRobotAlert():def __init__(self):self.webhook webhook_urlself.headers {Content-Type: applicatio…

32位汇编——通用寄存器

通用寄存器 什么是寄存器呢&#xff1f; 计算机在三个地方可以存储数据&#xff0c;第一个是把数据存到CPU中&#xff0c;第二个把数据存到内存中&#xff0c;第三个把数据存到硬盘上。 那这个所谓的寄存器&#xff0c;就是CPU中用来存储数据的地方。那这个寄存器有多大呢&a…

强大的接口测试可视化工具:Postman Flows

Postman Flows是一种接口测试可视化工具&#xff0c;可以使用流的形式在Postman工作台将请求接口、数据处理和创建实际流程整合到一起。如下图所示 Postman Flows是以API为中心的可视化应用程序开发界面。它提供了一个无限的画布用于编排和串连API&#xff0c;数据可视化来显示…

JavaScript 实战技巧:让你成为前端高手的必备知识3(进阶版)

一、DOM概述 &#xff08;一&#xff09;DOM操作 是指使用‌JavaScript操作文档对象模型&#xff08;Document Object Model&#xff09;的过程。‌文档对象模型是一种表示网页文档结构的方式&#xff0c;它将整个网页文档表示为一个树形结构&#xff0c;每个元素都是一个节点…

Python 网络爬虫教程:从入门到高级的全面指南

Python 网络爬虫教程&#xff1a;从入门到高级的全面指南 引言 在信息爆炸的时代&#xff0c;网络爬虫&#xff08;Web Scraping&#xff09;成为了获取数据的重要工具。Python 以其简单易用的特性&#xff0c;成为了网络爬虫开发的首选语言。本文将详细介绍如何使用 Python …

【抖音】a_bogus参数逆向分析

抖音回复评论&#xff1a; 点击——展开xxx条回复﹀就会出现 https://www.douyin.com/aweme/v1/web/comment/list/reply 直接搜又搜不到 分析调用堆栈 可以看到这个栈是有请求相关的数据的 上面一个栈 所以就是在bdms.js里面生成的 就在这里打上日志断点&#xff1a;“T…

基于SpringBoot的宠物健康咨询系统的设计与实现

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;宠物健康知识信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不…

基于大语言模型(LLM)自主Agent 智能体综述

近年来,LLM(Large Language Model)取得了显著成功,并显示出了达到人类智能的巨大潜力。基于这种能力,使用LLM作为中央控制器来构建自助Agent,以获得类人决策能力。 Autonomous agents 又被称为智能体、Agent。指能够通过感知周围环境、进行规划以及执行动作来完成既定任务。…

jmeter脚本-请求体设置变量and请求体太长的处理

目录 1、查询接口 1.1 准备组织列表的TXT文件&#xff0c;如下&#xff1a; 1.2 添加 CSV数据文件设置 &#xff0c;如下&#xff1a; 1.3 接口请求体设置变量&#xff0c;如下&#xff1a; 2、创建接口 2.1 见1.1 2.2 见1.2 2.3 准备创建接口的请求体TXT文件&#xff…

elasticsearch 8.x 插件安装(六)之Hanlp插件

elasticsearch 8.x 插件安装&#xff08;六&#xff09;之Hanlp插件 elasticsearch插件安装合集 elasticsearch插件安装&#xff08;一&#xff09;之ik分词器安装&#xff08;含MySQL更新&#xff09; elasticsearch 8.x插件&#xff08;二&#xff09;之同义词安装如何解决…

2024Python安装与配置IDE汉化教程

【一】Python解释器下载【运行环境】 【1】Python官网 [https://www.python.org]&#xff08;官网进不去的可以点击点击领取&#xff0c;100%免费&#xff01;安装包&#xff09; 包含编程资料、学习路线图、源代码、软件安装包等&#xff01;【[点击这里]】&#xff01; 【…

华为自研仓颉编程语言官网上线 首个公测版本开放下载

仓颉编程语言官网正式公开上线&#xff0c;同时首个公测版本开放下载。本次仓颉编程语言官网上线了首页、在线体验、文档、学习、下载、动态以及三方库共六个模块&#xff0c;可供开发和学习和体验。 据悉&#xff0c;仓颉编程语言是在今年6月的华为开发者大会上正式公布&…