Map与Set的模拟实现封装

目录

一.   底层原理

 二.   红黑树节点的定义

三.   仿函数封装

四.   基本函数的封装

五.   迭代器的封装

5.1   迭代器的基本定义

5.2   *与->操作

5.3  迭代器的++操作

5.3.1   右子树不为空

5.3.2   右子树为空 

5.4   迭代器的--操作

5.4.1   当前节点的父节点为空

 5.4.2   左子树不为空

5.4.3   左子树为空

5.5   ==与!=操作

六.   Map整体封装

七.   Set整体封装

八.   红黑树整体封装


一.   底层原理

        我们需要知道的是Map和Set底层是由红黑树封装的。而我们红黑树的底层又是kv结构。那我们可以把红黑树的V变成Map和Set传参的地方,Map传的是Key,Set传的是pair<Key,value>

因此我们可以为了识别到底是Map还是Set定义一个模板参数T

template<class K,class T>
class RBTree{};

此处参数K依旧是Key,只不过参数T可以是Set的Key,也可以是Map的pair<Key,Value>

如果是Map,那么传参就是:

template<class K,class T>
class Map
{
private:
	RBTree<K, pair<K,T>> _map;
};

而如果是Set,那么传参就是:

template<class K>
class Set
{
private:
	RBTree<K,K> _set;
};

我们可以看见,无论是Map还是Set,好像T参数已经包含了K参数,那为什么还要第一个参数K参数呢?

因为我们除了去insert(const Value& v)以外,还有find(const Key& k)操作,而find函数就需要第一个参数K,而如果不要第一个参数Set是不能满足的,所以第一个参数是必需的。

 二.   红黑树节点的定义

这里节点的定义我们与前面普通的红黑树(具体的定义可看:http://t.csdnimg.cn/hlYqJ)不一样的是,我们需要去考虑到底是Map还是Set,也就是传的参数不一样。所以可以用一个模板参数来定义:

enum Colour
{
	RED,
	BLACK
};
template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	Colour _col;
	T _data;
	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_col(RED)
		,_data(data)
	{}
};

此处T,Map就传pair<Key,Value>,而Set就传Key。

三.   仿函数封装

我们可以看见对于Map的pair我们是不能做比较,也做不了比较的,但是我们可以知道的是Key是能做比较的,因此我们需要将pair中的Key取出来作比较,这里就能用到我们的仿函数。

仿函数(functor)是一种在C++中使用的概念,它允许一个类的对象表现得像函数一样。仿函数通过在其类定义中重载函数调用运算符operator()来实现这种行为。

  • 对于Map我们需要取出pair键值对中的第一个元素Key。
namespace yjy {
	template<class K,class T>
	class Map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K,T>& kt)
			{
				return kt.first;
			}
		};
	private:
		RBTree<K, pair<K,T>, MapKeyOfT> _map;
	};
}
  • 对于Set我们直接返回自带的Key就行。
namespace yjy {
	template<class K>
	class Set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	private:
		RBTree<K,K, SetKeyOfT> _set;
	};
}

 整体的仿函数传参即:

 那么有了我们的仿函数之后,我们就可以运用在下面这样的比较之中:

bool Find(const T& data)
{
	KeyOfT _rot;
	Node* cur = _root;
	while (cur)
	{
		if (_rot(cur->_data) < _rot(data))
		{
			cur = cur->_right;
		}
		else if (_rot(cur->_data) > _rot(data))
		{
			cur = cur->_left;
		}
		else
		{
			return true;
		}
	}
	return false;
}

四.   基本函数的封装

我们有了仿函数之后,就可以对一些基本操作函数进行编写(此处只是在红黑树的基础上加上了仿函数,如果对操作还有不懂的,可以去看:http://t.csdnimg.cn/577bU)。

bool Find(const T& data)
{
	KeyOfT _rot;
	Node* cur = _root;
	while (cur)
	{
		if (_rot(cur->_data) < _rot(data))
		{
			cur = cur->_right;
		}
		else if (_rot(cur->_data) > _rot(data))
		{
			cur = cur->_left;
		}
		else
		{
			return true;
		}
	}
	return false;
}
bool Insert(const T& data)
{
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		return true;
	}
	KeyOfT _rot;
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (_rot(cur->_data) > _rot(data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (_rot(cur->_data) < _rot(data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return true;
		}
	}
	cur = new Node(data);
	Node* newnode = cur;
	if (_rot(parent->_data) < _rot(data))
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;
	while (parent && parent->_col == RED)
	{
		Node* grandparent = parent->_parent;
		Node* uncle = nullptr;
		//parent和uncle是grandparent的左还是右不影响结果
		//cur是parent的左还是右不影响结果
		if (parent == grandparent->_left)
		{
			uncle = grandparent->_right;
			//uncle存在且为红
			if (uncle && uncle->_col == RED)
			{
				parent->_col = BLACK;
				uncle->_col = BLACK;
				grandparent->_col = RED;
				if (grandparent == _root)
				{
					grandparent->_col = BLACK;
				}
				else
				{
					cur = grandparent;
					parent = cur->_parent;
				}
			}
			else
			{
				//		g
				//	p		u
				//c
				if (cur == parent->_left)
				{
					RotaleR(grandparent);
					parent->_col = BLACK;
					grandparent->_col = RED;
				}
				//		g
				//	p		u
				//		c
				else
				{
					RotaleL(parent);
					RotaleR(grandparent);
					cur->_col = BLACK;
					grandparent->_col = RED;
				}
				break;
			}
		}
		else
		{
			uncle = grandparent->_left;
			if (uncle && uncle->_col == RED)
			{
				parent->_col = BLACK;
				uncle->_col = BLACK;
				grandparent->_col = RED;
				if (grandparent == _root)
				{
					grandparent->_col = BLACK;
				}
				else
				{
					cur = grandparent;
					parent = cur->_parent;
				}
			}
			else
			{
				//		g
				//	u		p
				//				c
				if (cur == parent->_right)
				{
					RotaleL(grandparent);
					parent->_col = BLACK;
					grandparent->_col = RED;
				}
				//		g
				//	u		p
				//		c
				else
				{
					RotaleR(parent);
					RotaleL(grandparent);
					cur->_col = BLACK;
					grandparent->_col = RED;
				}
				break;
			}
		}
	}
	return true;
}
void RotaleL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	parent->_right = subRL;
	subR->_left = parent;
	if (subRL)
	{
		subRL->_parent = parent;
	}
	Node* ppnode = parent->_parent;
	parent->_parent = subR;
	if (parent == _root)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (parent == ppnode->_left)
		{
			ppnode->_left = subR;
		}
		else if (parent == ppnode->_right)
		{
			ppnode->_right = subR;
		}
		subR->_parent = ppnode;
	}
}
void RotaleR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	parent->_left = subLR;
	subL->_right = parent;
	if (subLR)
	{
		subLR->_parent = parent;
	}
	Node* ppnode = parent->_parent;
	parent->_parent = subL;
	if (parent == _root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (ppnode->_left == parent)
		{
			ppnode->_left = subL;
		}
		else if (ppnode->_right == parent)
		{
			ppnode->_right = subL;
		}
		subL->_parent = ppnode;
	}
}

五.   迭代器的封装

我们写出了仿函数之后,一切都水到渠成了,就可以继续对迭代器进行封装了。

5.1   迭代器的基本定义

template<class T,class Ptr,class Ref>//此处Ptr是T*,Ref是T&
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T,Ptr,Ref> Self;
	Node* _node;
	RBTreeIterator(Node* node)
		:_node(node)
	{}
}

5.2   *与->操作

对于*操作,就是返回数据的引用,而->操作,就是返回数据的地址,即指针。

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

5.3  迭代器的++操作

此处我们需要分为右子树不为空和右子树为空两种情况。为什么呢?

我们可以根据二叉树的中序遍历来看,根节点遍历完了,就该遍历右子树,如果右子树为空,则直接跳到上一层,如果不为空,则进入右子树。那么我们下面来细讲一下这两种情况。 

Self& operator++()
{
	if (_node->_right)//右边不为空
	{
		//右子树的最左边节点
		Node* subright = _node->_right;
		while (subright->_left)
		{
			subright = subright->_left;
		}
		_node = subright;
	}
	else
	{
		//祖先里面孩子是父亲左的那个
		Node* cur = _node;
		Node* subparent = _node->_parent;
		while (subparent&&subparent->_right == cur)
		{
			cur = subparent;
			subparent = subparent->_parent;
		}
		_node = subparent;
	}
	return *this;
}

5.3.1   右子树不为空

 我们可以根据中序来解释,进入右子树之后,我们应该进入右子树的最左节点。

 如图,当前节点是50,右子树不为空,则走到右子树的最左节点56。

5.3.2   右子树为空 

当右子树为空的情况出现时,我们可以知道后面一步需要遍历到当前节点的父节点,那么我们再进一步思考一下,当前节点是父节点的右节点时,又说明父节点的右子树遍历完了,又需要向上迭代。所以我们要迭代到什么时候才行呢?

应该是迭代到当前节点是父节点的左节点时,此时后面一步就是到父节点。

如图:当前节点是48,右节点为空,则向上走,一直走到35的时候,此时35是50的左节点。 

5.4   迭代器的--操作

--操作与++操作不同,不只是迭代的方向不同,情况也有所不同。

我们这里要先判断当前节点的父节点是否为空节点。为什么呢?咱们下面再说。除了这种情况外,还有左子树不为空和左子树为空两种情况。

Self& operator--()
{
	if (_node->_parent==nullptr)
	{
		Node* maxright = _node;
		while (maxright->_right)
		{
			maxright = maxright->_right;
		}
		_node = maxright;
	}
	else if (_node->_left)//左边不为空
	{
		//左子树的最右边节点
		Node* subright = _node->_left;
		while (subright->_right)
		{
			subright = subright->_right;
		}
		_node = subright;
	}
	else
	{
		//祖先里面孩子是父亲右的那个
		Node* cur = _node;
		Node* subparent = _node->_parent;
		while (subparent && subparent->_left == cur)
		{
			cur = subparent;
			subparent = subparent->_parent;
		}
		_node = subparent;
	}
	return *this;
}

5.4.1   当前节点的父节点为空

当前节点的父节点为空,证明当前节点是此时的根节点。我们再进行--的话,就要走到最右边节点。因为在STL库定义中,是如下图一样的结构:

我们这里就没有定义header头结点,但是我们还是可以看到,根节点之后应该到最右节点

 5.4.2   左子树不为空

此时根据++操作右子树不为空时的情况可以得到此时应该走到左子树的最右节点

如图:当前节点是50,此后应该迭代到左子树的最右节点,即48。 

5.4.3   左子树为空

此时也应该向上迭代,到什么时候结束呢?

应该到当前节点是父节点的右节点为止,因为如果当前节点是父节点的左节点时,又说明左子树走完了,又要向上迭代。所以我们要一直迭代到当前节点是父节点的右节点时。

 

如图:当前节点是40,应该迭代到当前节点是父节点的右节点时,所以要迭代到45。 

5.5   ==与!=操作

对于==与!=操作就是判断数据是否相等。

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

六.   Map整体封装

对于Map来说,需要多一个operator[]操作。

由于我们知道,Map里面的Key是不能随意改变的,所以加上const修饰

namespace yjy {
	template<class K,class T>
	class Map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K,T>& kt)
			{
				return kt.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<const K,T>, MapKeyOfT>::iterator iterator;
		typedef typename RBTree<K, pair<const K,T>, MapKeyOfT>::const_iterator const_iterator;
		iterator begin()
		{
			return _map.begin();
		}
		iterator end()
		{
			return _map.end();
		}
		const_iterator begin() const
		{
			return _map.begin();
		}
		const_iterator end() const
		{
			return _map.end();
		}
		pair<iterator,bool> Insert(const pair<K,T>& kt)
		{
			return _map.Insert(kt);
		}
		iterator Find(const K& k)
		{
			return _map.Find(k);
		}
		T& operator[](const K& key)
		{
			pair<iterator, bool> ret = Insert(make_pair(key, T()));
			return ret.first->second;
		}
	private:
		RBTree<K, pair<const K,T>, MapKeyOfT> _map;
	};
}

七.   Set整体封装

此处的Key也要加上const修饰,因为是不可改变的。

namespace yjy {
	template<class K>
	class Set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K,const K, SetKeyOfT>::iterator iterator;
		typedef typename RBTree<K, const K, SetKeyOfT>::const_iterator const_iterator;

		iterator begin()
		{
			return _set.begin();
		}
		iterator end()
		{
			return _set.end();
		}
		const_iterator begin() const
		{
			return _set.begin();
		}
		const_iterator end() const
		{
			return _set.end();
		}
		pair<iterator, bool> Insert(const K& key)
		{
			return _set.Insert(key);
		}
		iterator Find(const K& k)
		{
			return _set.Find(k);
		}
	private:
		RBTree<K,const K, SetKeyOfT> _set;
	};
}

八.   红黑树整体封装

#include<iostream>
#include<vector>
using namespace std;
enum Colour
{
	RED,
	BLACK
};
template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	Colour _col;
	T _data;
	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_col(RED)
		,_data(data)
	{}
};
template<class T,class Ptr,class Ref>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T,Ptr,Ref> Self;
	Node* _node;
	RBTreeIterator(Node* node)
		:_node(node)
	{}
	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	Self& operator++()
	{
		if (_node->_right)//右边不为空
		{
			//右子树的最左边节点
			Node* subright = _node->_right;
			while (subright->_left)
			{
				subright = subright->_left;
			}
			_node = subright;
		}
		else
		{
			//祖先里面孩子是父亲左的那个
			Node* cur = _node;
			Node* subparent = _node->_parent;
			while (subparent&&subparent->_right == cur)
			{
				cur = subparent;
				subparent = subparent->_parent;
			}
			_node = subparent;
		}
		return *this;
	}
	Self& operator--()
	{
		if (_node->_parent==nullptr)
		{
			Node* maxright = _node;
			while (maxright->_right)
			{
				maxright = maxright->_right;
			}
			_node = maxright;
		}
		else if (_node->_left)//左边不为空
		{
			//左子树的最右边节点
			Node* subright = _node->_left;
			while (subright->_right)
			{
				subright = subright->_right;
			}
			_node = subright;
		}
		else
		{
			//祖先里面孩子是父亲右的那个
			Node* cur = _node;
			Node* subparent = _node->_parent;
			while (subparent && subparent->_left == cur)
			{
				cur = subparent;
				subparent = subparent->_parent;
			}
			_node = subparent;
		}
		return *this;
	}
	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}
	bool operator==(const Self& s)
	{
		return _node == s._node;
	}
};

//Set->RBTree<K,K,SetKeyOfT>
//Map->RBTree<K,pair<K,V>,MapKeyOfT>

//KeyOfT仿函数,取出T对象中的key
template<class K,class T,class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef RBTreeIterator<T,T*,T&> iterator;
	typedef RBTreeIterator<T,const T*,const T&> const_iterator;
	iterator begin()
	{
		Node* subleft = _root;
		while (subleft&&subleft->_left)
		{
			subleft = subleft->_left;
		}
		return iterator(subleft);
	}
	const_iterator begin() const
	{
		Node* subleft = _root;
		while (subleft && subleft->_left)
		{
			subleft = subleft->_left;
		}
		return const_iterator(subleft);
	}
	iterator end()
	{
		return iterator(nullptr);
	}
	const_iterator end() const
	{
		return const_iterator(nullptr);
	}
	iterator Find(const T& data)
	{
		KeyOfT _rot;
		Node* cur = _root;
		while (cur)
		{
			if (_rot(cur->_data) < _rot(data))
			{
				cur = cur->_right;
			}
			else if (_rot(cur->_data) > _rot(data))
			{
				cur = cur->_left;
			}
			else
			{
				return iterator(cur);
			}
		}
		return end();
	}
	pair<iterator,bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true);
		}
		KeyOfT _rot;
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (_rot(cur->_data) > _rot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (_rot(cur->_data) < _rot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return make_pair(iterator(cur),true);
			}
		}
		cur = new Node(data);
		Node* newnode = cur;
		if (_rot(parent->_data) < _rot(data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
		while (parent && parent->_col == RED)
		{
			Node* grandparent = parent->_parent;
			Node* uncle = nullptr;
			//parent和uncle是grandparent的左还是右不影响结果
			//cur是parent的左还是右不影响结果
			if (parent == grandparent->_left)
			{
				uncle = grandparent->_right;
				//uncle存在且为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandparent->_col = RED;
					if (grandparent == _root)
					{
						grandparent->_col = BLACK;
					}
					else
					{
						cur = grandparent;
						parent = cur->_parent;
					}
				}
				else
				{
					//		g
					//	p		u
					//c
					if (cur == parent->_left)
					{
						RotaleR(grandparent);
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					//		g
					//	p		u
					//		c
					else
					{
						RotaleL(parent);
						RotaleR(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}
					break;
				}
			}
			else
			{
				uncle = grandparent->_left;
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandparent->_col = RED;
					if (grandparent == _root)
					{
						grandparent->_col = BLACK;
					}
					else
					{
						cur = grandparent;
						parent = cur->_parent;
					}
				}
				else
				{
					//		g
					//	u		p
					//				c
					if (cur == parent->_right)
					{
						RotaleL(grandparent);
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					//		g
					//	u		p
					//		c
					else
					{
						RotaleR(parent);
						RotaleL(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}
					break;
				}
			}
		}
		return make_pair(iterator(newnode),true);
	}
	void RotaleL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		parent->_right = subRL;
		subR->_left = parent;
		if (subRL)
		{
			subRL->_parent = parent;
		}
		Node* ppnode = parent->_parent;
		parent->_parent = subR;
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parent == ppnode->_left)
			{
				ppnode->_left = subR;
			}
			else if (parent == ppnode->_right)
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}
	}
	void RotaleR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		parent->_left = subLR;
		subL->_right = parent;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		Node* ppnode = parent->_parent;
		parent->_parent = subL;
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else if (ppnode->_right == parent)
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
	}
private:
	Node* _root=nullptr;
};

总结

好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。

祝大家越来越好,不用关注我(疯狂暗示)

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

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

相关文章

CSS基础:最详细 padding的 4 种用法解析

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。 云桃桃&#xff0c;大专生&#xff0c;一枚程序媛&#xff0c;感谢关注。回复 “前端基础题”&#xff0c;可免费获得前端基础 100 题汇总&#xff0c;回复 “前端工具”&#xff0c;可获取 We…

Adobe Premiere Pro将加入AI生成式功能,以提高视频编辑的效率;OpenAI宣布在东京设立亚洲首个办事处

&#x1f989; AI新闻 &#x1f680; Adobe Premiere Pro将加入AI生成式功能&#xff0c;以提高视频编辑的效率 摘要&#xff1a;Adobe宣布&#xff0c;将为Premiere Pro引入由生成式AI驱动的新功能&#xff0c;以提高视频编辑的效率。这些功能包括“生成扩展”&#xff0c;能…

免费开源多客圈子婚恋社交校园跑腿线上线下陪玩 源码交付 可打包小程序 支持二开!

聊天软件作为一种现代化的通讯工具&#xff0c;其好处可以总结如下&#xff1a; 1.方便快捷&#xff1a;聊天软件只要有网络连接&#xff0c;就可以随时随地与他人进行交流&#xff0c;不受时间和地点的限制&#xff0c;可以随时随地进行沟通&#xff0c;大大方便了人们的日常…

【结构型模式】装饰器模式

​一、装饰器模式概述 装饰器模式&#xff08;装饰者模式&#xff09;定义&#xff1a;装饰器模式动态地将责任附加到对象上。若要拓展功能&#xff0c;装饰者提供了比继承更有弹性地替代方案。&#xff08;对象结构型模型&#xff09;通俗点来说&#xff1a;动态的给一个对象增…

适用于 Windows 的 10 个顶级 PDF 编辑器 [免费和付费]

曾经打开PDF文件&#xff0c;感觉自己被困在数字迷宫中吗&#xff1f;无法编辑的文本、无法调整大小的图像以及签署感觉像是一件苦差事的文档&#xff1f;好吧&#xff0c;不用再担心了&#xff01;本指南解开了在 Windows 上掌握 PDF 的秘密&#xff0c;其中包含 10 款适用于 …

vscode vue template模板中 tab键无法快速补全

之前记得一直可以的突然不知道咋的就不行了… 解决办法: 菜单栏 - 文件 - 首选项 - 设置- emmet:tab ✔就好了

Flink CDC 的 debezium-json 格式和 debezium 原生格式是一回事吗?

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

【介绍下负载均衡原理及算法】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

IP协议如何进行地址管理?

如今&#xff0c;IP协议有两个版本&#xff0c;分别是IPv4和IPv6&#xff0c;IPv4是目前主要应用的版本。IPv4的IP地址是以4个字节的数字来表示的&#xff0c;比如 127.0.0.1。因此&#xff0c;IPv4所能表示IP地址的个数是2^32次方&#xff0c;也就是42亿多个&#xff0c;看起来…

48.HarmonyOS鸿蒙系统 App(ArkUI)常用组件的使用

48.HarmonyOS鸿蒙系统 App(ArkUI)常用组件的使用 按钮触发事件 toast信息提示 单选按钮 复选框 切换按钮&#xff0c;开关按钮 进度条 textbox,textinput,TextArea文本输入框 气泡提示 import prompt from ohos.prompt; import promptAction from ohos.promptAction; …

Qt对象池,单例模式,对象池可以存储其他类的对象指针

代码描述&#xff1a; 写了一个类&#xff0c;命名为对象池&#xff08;ObjectPool &#xff09;&#xff0c;里面放个map容器。 3个功能&#xff1a;添加对象&#xff0c;删除对象&#xff0c;查找对象 该类只构建一次&#xff0c;故采用单例模式功能描述&#xff1a;对象池可…

【ARFoundation自学01】搭建AR框架,检测平面点击位置克隆物体

Unity开发ARFoundation相关应用首先安装ARFoundation包 然后设置XR 1.基础AR场景框架搭建 2.一个基本的点击克隆物体到识别的平面脚本 挂在XROrigin上 脚本AppController 脚本说明书 ## 业务逻辑 AppController 脚本旨在实现一个基本的 AR 应用程序功能&#xff1a;用户通过…

Spring Cloud+Uniapp 智慧工地云平台源码 智慧工地云平台AI视频分析应用

目录 AI应用与环境治理 设备管理与危大工程 塔吊安全监管 智慧工地APP端 智慧工地硬件设备 智慧工地主要功能模块 智慧工地可以通过以下几个方面为建筑行业赋能&#xff1a; 1.提高工程效率 2.提高工程安全性 3.提高工程质量 4.提高工程管理效率 绿色施工 质量管理…

Codeforces Round 924 (Div. 2) --- E. Modular Sequence ---- 题解

E. Modular Sequence&#xff1a; 题目描述&#xff1a; 思路解析&#xff1a; 这里第一个一定要需要填充x&#xff0c;然后后面每一位填充 ai-1 y 或者 ai-1 % y&#xff0c;那么其实相当于除了第一位固定&#xff0c;后面每一位都可以表现为 a ki * y&#xff1b;其中 a …

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之十二 简单人脸识别

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之十二 简单人脸识别 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之十二 简单人脸识别 一、简单介绍 二、简单人脸识别实现原理 三、简单人脸识别案例实现简…

RAID 磁盘阵列及RAID配置实战

目录 一.RAID磁盘阵列介绍 二.常用的RAID磁盘阵列的介绍 1.RAID 0 &#xff08;条带化存储&#xff09; 2.RAID 1&#xff08;镜像存储&#xff09; 3.RAID 5 4.RAID 6 5.RAID 10&#xff08;先做镜像&#xff0c;再做条带&#xff09; 6.RAID 01 &#xff08;先做条带…

Java代码执行顺序

Java代码的执行顺序 后面大量的涉及到了static&#xff0c;我曾经写过一篇static的博客&#xff0c;可以看一眼 我上次写了static的加载顺序&#xff0c;没看过的可以进去看一眼 JavaSE&#xff1a;static关键字详解 ---------------------分割线-------------------------…

魔方网表 存在 mailupdate.jsp接口 任意文件上传漏洞

声明&#xff1a; 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 简介 魔方网表mailupdate.jsp接口存在任意文件上传漏洞 …

Jenkins配置windows/linux从节点

背景&#xff1a; 环境&#xff1a;jenkins环境&#xff08;Ubuntu&#xff09; 节点机器&#xff1a;Linux、Windows 前置条件&#xff1a; 节点机器&#xff1a;安装java、allure、python 1 Linux节点管理机器添加 1.1 系统管理->节点列表->New Node 1.2 节点配置…

Python --- 在python中安装NumPy,SciPy和Matplotlib(Windows平台)

在python中安装NumPy&#xff0c;SciPy和Matplotlib(Windows平台) NumPy NumPy是Python的一个最常用最基本的扩展程序库之一&#xff0c;主要用于矩阵运算或数组计算。很多其他的python库都要依赖于NumPy才能跑。 NumPy的发展史&#xff1a; Matrix-sig 1995年&#xff0c;特殊…