[C++]STL---unordered_set与unordered_map的模拟实现

目录

前言

哈希桶的改造

哈希桶的初步改造

迭代器的模拟实现

operator++()

类互相typedef时的前置声明

友元声明

迭代器的出口

插入Insert()

查找Find()

哈希表的最终改造

unordered_set的模拟实现

unordered_map的模拟实现


前言

unordered_set与set的区别:

  1. set是基于红黑树实现的有序集合,而unordered_set是基于哈希表实现的无序集合
  2. set中的元素是按照特定的排序规则进行排序的,而unordered_set中的元素是无序的;
  3. unordered_set通过哈希表实现,查找元素的效率较高,平均时间复杂度为O(1),而set通过红黑树实现,查找元素的效率较低,平均时间复杂度为O(logN);
  4. unordered_set通常占用更多的内存,因为需要维护哈希表的结构,而set通常占用较少的内存;
  5. unordered_set的迭代器为单向迭代器,只能向前迭代,不支持反向迭代,而set为双向迭代器;

unordered_map与map的区别:

  1. map是基于红黑树实现的有序键值对容器,而unordered_map是基于哈希表实现的无序键值对容器;
  2. map中的键值对是按照键的特定排序规则进行排序的,而unordered_map中的键值对是无序的;
  3. unordered_map通过哈希表实现,查找键值对的效率较高,平均时间复杂度为O(1),而map通过红黑树实现,查找键值对的效率较低,平均时间复杂度为O(logN);
  4. unordered_map通常占用更多的内存,因为需要维护哈希表的结构,而map通常占用较少的内存;
  5. unordered_map的迭代器为单向迭代器,只能向前迭代,不支持反向迭代,而map为双向迭代器;

哈希桶的改造

unordered_map与unordered_set底层皆为哈希桶,因此底层采用一个哈希桶并且让它能够满足unordered_map和unordered_set的基本需求 ( 即采用一个哈希桶适配出unordered_set与unordered_map ),因此需要对哈希桶进行改造;

unordered_map为Key-Value模型,unordered_set为Key模型,因此首先修改链表节点中的数据类型,数据类型修改为T,如此T既可以接收键值对,也可以只接收键值;

template<class T>
struct HashNode
{
	HashNode<T>* _next;
	T _data;
	HashNode(const T& data)
		:_data(data)
		, _next(nullptr)
	{}
};

哈希表中 插入Insert() 需要根据键值key判断待插入的数据是否存在,查找Find() 需要根据键值key判断待查找的数据是否存在,删除Erase() 需要根据键值Key确定待删除数据的位置,但是unordered_map数据类型为键值对pair<K,V>,需要将键值Key从键值对pair<K,V>取出,因此HashTable需要增加一个模板参数KeyOfT,这个模板参数接收分别由unordered_set、unordered_map传递的仿函数,此仿函数的作用为取出键值key(由于unordered_set与unorder_map类中传递数据类型是确定的,因此unordered_set与unorder_map类中实现仿函数取出各自的键值Key);

思考:HashTable的模板参数Hash是否应该存在缺省值?

当数据类型为自定义类型(日期类、string类、......)当使用者使用unordered_set与unordered_map时,若HashTable(unordered_map与unordered_set的底层结构)中没有提供将某种数据类型转换为整型仿函数,需要使用者手动实现将此类型的数据转换为整型的仿函数,使用者显然不可以动手去修改底层代码;因此HashTable种Hash不应该存在缺省值,应该由哈希表的上层unordered_map和unordered_set传递,如此使用者也可以对于自定义类型的数据自定义Hash仿函数实现类型转换为整型

unordered_map和unordered_set框架

//HashBucket.h文件
template<class T>
struct HashNode
{
	HashNode<T>* _next;
	T _data;
	HashNode(const T& data)
		:_data(data)
		, _next(nullptr)
	{}
};

template<class K, class T,class KeyOfT, class Hash>
class HashTable
{
	typedef HashNode<T> Node;
public:
    //...
private:
	vector<Node*> _tables;
	size_t _n;//记录哈希表中实际存放的数据个数
};
//Unordered_Set.h文件
template<class K, class Hash = HashFunc<K>>
class Unordered_Set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:

private:
	HashTable<K, K, SetKeyOfT, Hash> _ht;
};
//Unordered_Map文件
template<class K,class V,class Hash=HashFunc<K>>
class Unordered_Map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
public:

private:
	HashTable<K,pair<K,V>,MapKeyOfT,Hash>
};

哈希桶的初步改造

template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

// 特化
template<>
struct HashFunc<string>
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash += e;
			hash *= 31;
		}
		return hash;
	}
};

	template<class T>
	struct HashNode
	{
		HashNode<T>* _next;
		T _data;
		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{}
	};

template<class K, class T,class KeyOfT, class Hash>
class HashTable
{
	typedef HashNode<T> Node;
public:
	//构造函数
	HashTable(size_t n = 10)
	{
		_tables.resize(n, nullptr);
		_n = 0;
	}
	//析构函数
	~HashTable()
	{
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			while (cur != nullptr)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}
			_tables[i] = nullptr;
		}
	}
	//拷贝构造函数  ht2(ht1)
	HashTable(const HashTable& ht)
	{
		Hash hs;
		KeyOfT kot;
		 //开辟相同大小的空间
		_tables.resize(ht._tables.size());
		//遍历旧表,头插到新表
		for (size_t i = 0; i < ht._tables.size(); i++)
		{
			Node* cur = ht._tables[i];
			while (cur != nullptr)
			{
				Node* next = cur->_next;

				//计算新表的插入位置
				size_t hashi = hs(kot(cur->_data)) % _tables.size();

				cur->_next = _tables[hashi];
				_tables[hashi] = cur;
				cur = next;
			}
		}
		_n = ht._n;
	}

	//赋值运算符重载 ht2=ht1
	HashTable& operator=(HashTable ht)
	{
		_tables.swap(ht._tables);
		swap(_n, ht._n);

		return *this;
	}

	Node* Find(const K& key)
	{
		KeyOfT kot;
		Hash hs;
		size_t hashi = hs(key) % _tables.size();
		Node* cur = _tables[hashi];
		while (cur != nullptr)
		{
			//仿函数对象kot获取结点中的键值
			if (kot((cur->_data)) == key)
			{
				return cur;
			}
			cur = cur->_next;
		}
		return nullptr;
	}


	bool Insert(const T& data)
	{
		//仿函数对象kot获取data中的键值key
		KeyOfT kot;
		//仿函数对象hs将data中的键值key转换为整型
		Hash hs;

		Node* ret = Find(kot(data));
		if (ret != nullptr)
		{
			return false;
		}

		if (_n == _tables.size())
		{
			vector<Node*> _newtables(_tables.size() * 2);
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur != nullptr)
				{
					Node* nextnode = cur->_next;
					//首先仿函数(KeyOfT)对象kot获取数据data的键值key
					//最后仿函数(Hash)对象hs将键值key的类型转换为整型
					size_t hashi = hs(kot(cur->_data)) % _newtables.size();
					cur->_next = _newtables[hashi];
					_newtables[hashi] = cur;
					cur = nextnode;
				}
				_tables[i] = nullptr;
			}
			_tables.swap(_newtables);
		}

		//首先仿函数(KeyOfT)对象kot获取数据data的键值key
		//最后仿函数(Hash)对象hs将键值key的类型转换为整型
		size_t hashi = hs(kot(data)) % _tables.size();
		Node* newnode = new Node(data);

		//单链表头插
		newnode->_next = _tables[hashi];
		_tables[hashi] = newnode;
		++_n;
		return true;
	}

	bool Erase(const K& key)
	{
		Hash hs;
		KeyOfT kot;
		size_t hashi = hs(key) % _tables.size();
		Node* cur = _tables[hashi];
		Node* prev = nullptr;
		while (cur != nullptr)
		{
			//仿函数(KeyOfT)对象kot获取数据data的键值key确定待删除数据的位置
			if (kot((cur->_data)) == key)
			{
				//删除
				if (prev != nullptr)
				{
					prev->_next = cur->_next;
				}
				else
				{
					_tables[hashi] = cur->_next;
				}
				delete cur;

				--_n;
				return true;
			}
			else
			{
				prev = cur;
				cur = cur->_next;
			}
		}
		return false;
	}
private:
	vector<Node*> _tables;
	size_t _n;
};

迭代器的模拟实现

所有容器必须具有迭代器,使用迭代器遍历容器中的数据;因此需要给哈希桶增加迭代器以供unordered_set与unordered_map使用;

思考:迭代器it的++如何实现?

++it操作:

  • 若it不是某个桶的最后一个元素,则it指向这个桶的下一个结点;
  • 若it为某个桶的最后一个元素,则it指向下一个桶的头节点;

若实现++it的操作,不仅需要结点指针记录当前结点,还需要一个哈希桶的指针,方便查找下一个桶;

template<class K, class T, class KeyOfT, class Hash>
struct __HTIterator
{
	typedef HashNode<T> Node;
	typedef HashTable<K, T, KeyOfT, Hash> HT;
	typedef __HTIterator<K, T, KeyOfT, Hash> Self;

	Node* _node;//记录当前结点
	HT* _ht;//哈希表指针用于查找下一个桶

	__HTIterator(Node* node, HT* ht)
		:_node(node)
		, _ht(ht)
	{}
};

operator++()

Self& operator++()
{
	//当前桶未遍历结束,指向桶中下一个结点
	if (_node->_next != nullptr)
	{
		_node = _node->_next;
	}
	//当前桶已遍历结束,寻找下一个桶
	else
	{
		//首先确定当前桶在哈希表中的位置
		Hash hs;
		KeyOfT kot;
		size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();
		//查找下一个桶
		++hashi;
		while (hashi < _ht->_tables.size())
		{
			//找到下一个桶,it指向桶的头节点
			if (_ht->_tables[hashi] != nullptr)
			{
				_node = ht->_tables[hashi];
				break;//++结束
			}
			++hashi;
		}
		//跳出循环具有两种情形
		//case1:未找到哈希表中的下一个桶,采用空指针作为迭代器的end()
		//case2:找到哈希表中的下一个桶的头节点,返回迭代器
		if (hashi == _ht->_tables.size())
		{
			//case1
			_node = nullptr;
		}
		
	}
        //case2
		return *this;
}

类互相typedef时的前置声明

迭代器中一个成员变量为哈希表指针,而哈希表中需要给迭代器提供出口,哈希表与迭代器的定义必然存在先后顺序,本文先定义迭代器,后定义哈希表(若先定义哈希表,后定义迭代器也会面临同样的问题),此时迭代器中在重定义哈希表时必然找不到哈希表的定义,由于编译器只会向上查找而不会向下查找,所以必须在__HTIterator类前面先声明HashTable类,这种操作叫做前置声明;

友元声明

迭代器it ++时,使用哈希表指针访问_tables,而HashTable中的_tables为私有成员,类外不可以被访问,本文采用友元声明解决此问题,类模板的友元声明需要写模板参数,类名前面加friend关键字

迭代器的出口

将哈希表中第一个桶的头节点作为遍历的起始位置,将空指针作为迭代器遍历的终止位置;

iterator end()
{
	return iterator(nullptr, this);
}

iterator begin()
{
	for (size_t i = 0; i < _tables.size(); i++)
	{
		// 哈希表中第一个桶的第一个节点
		if (_tables[i])
		{
			return iterator(_tables[i], this);
		}
	}
	return end();
}

插入Insert()

Insert()函数返回类型修改为pair<iterator, bool>,为实现unordered_map中的operator[]重载做准备;

  • 键值对<iterator,bool>其中iterator代表新插入结点的迭代器,bool值表示插入是否成功;
  • 若新结点键值key原先存在,则返回哈希表中原本存在结点的迭代器,并且插入失败,返回false;
  • 若新结点键值key原先不存在,则插入结点,返回新插入结点在哈希表中的迭代器,返回true;
pair<iterator, bool> Insert(const T& data)
{
	KeyOfT kot;
	Hash hs;

	iterator ret = Find(kot(data));
	if (ret != end())
	{
		//键值key原先已存在,插入失败,返回已存在结点的迭代器并且返回false
		return make_pair(ret, false);
	}

	if (_n == _tables.size())
	{
		vector<Node*> _newtables(_tables.size() * 2);
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			while (cur != nullptr)
			{
				Node* nextnode = cur->_next;

				size_t hashi = hs(kot(cur->_data)) % _newtables.size();
				cur->_next = _newtables[hashi];
				_newtables[hashi] = cur;
				cur = nextnode;
			}
			_tables[i] = nullptr;
		}
		_tables.swap(_newtables);
	}

	size_t hashi = hs(kot(data)) % _tables.size();
	Node* newnode = new Node(data);

	newnode->_next = _tables[hashi];
	_tables[hashi] = newnode;
	++_n;

	//键值key原先不存在,插入成功,返回新结点的迭代器并且返回true
	return make_pair(newnode, true);
}

查找Find()

//查找到返回哈希表指针与当前哈希表的结点指针
//若查找不到返回哈希表指针与空指针
iterator Find(const K& key)
{
	KeyOfT kot;
	Hash hs;
	size_t hashi = hs(key) % _tables.size();
	Node* cur = _tables[hashi];
	while (cur != nullptr)
	{
		//仿函数对象kot获取结点中的键值
		if (kot((cur->_data)) == key)
		{
			return iterator(cur, this);
		}
		cur = cur->_next;
	}
	return iterator(nullptr, this);
}

哈希表的最终改造

template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

// 特化
template<>
struct HashFunc<string>
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash += e;
			hash *= 31;
		}
		return hash;
	}
};

template<class T>
struct HashNode
{
	HashNode<T>* _next;
	T _data;
	HashNode(const T& data)
		:_data(data)
		, _next(nullptr)
	{}
};

//哈希表前置声明
template<class K, class T, class KeyOfT, class Hash>
class HashTable;

template<class K, class T, class KeyOfT, class Hash>
struct __HTIterator
{
	typedef HashNode<T> Node;
	typedef HashTable<K, T, KeyOfT, Hash> HT;
	typedef __HTIterator<K, T, KeyOfT, Hash> Self;

	Node* _node;//记录当前结点
	HT* _ht;//哈希表指针用于查找下一个桶

	__HTIterator(Node* node, HT* ht)
		:_node(node)
		, _ht(ht)
	{}

	Self& operator++()
	{
		//当前桶未遍历结束,指向桶中下一个结点
		if (_node->_next != nullptr)
		{
			_node = _node->_next;
		}
		//当前桶已遍历结束,寻找下一个桶
		else
		{
			//首先确定当前桶在哈希表中的位置
			Hash hs;
			KeyOfT kot;
			size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();
			//查找下一个桶
			++hashi;
			while (hashi < _ht->_tables.size())
			{
				//找到下一个桶,it指向桶的头节点
				if (_ht->_tables[hashi] != nullptr)
				{
					_node = ht->_tables[hashi];
					break;//++结束
				}
				++hashi;
			}
			//跳出循环具有两种情形
			//case1:未找到哈希表中的下一个桶,采用空指针作为迭代器的end()
			//case2:找到哈希表中的下一个桶的头节点,返回迭代器
			if (hashi == _ht->_tables.size())
			{
				//case1
				_node = nullptr;
			}
			//case2
			return *this;
		}
	}

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

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

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

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

template<class K, class T,class KeyOfT, class Hash>
class HashTable
{
	template<class K, class T, class KeyOfT, class Hash>
	friend struct __HTIterator;

	typedef HashNode<T> Node;
public:
	typedef __HTIterator<K, T, KeyOfT, Hash> iterator;

	iterator end()
	{
		return iterator(nullptr, this);
	}

	iterator begin()
	{
		for (size_t i = 0; i < _tables.size(); i++)
		{
			// 哈希表中第一个桶的第一个节点
			if (_tables[i])
			{
				return iterator(_tables[i], this);
			}
		}
		return end();
	}

	//构造函数
	HashTable(size_t n = 10)
	{
		_tables.resize(n, nullptr);
		_n = 0;
	}
	//析构函数
	~HashTable()
	{
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			while (cur != nullptr)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}
			_tables[i] = nullptr;
		}
	}

	//拷贝构造函数  ht2(ht1)
	HashTable(const HashTable& ht)
	{
		Hash hs;
		KeyOfT kot;
		 //开辟相同大小的空间
		_tables.resize(ht._tables.size());
		//遍历旧表,头插到新表
		for (size_t i = 0; i < ht._tables.size(); i++)
		{
			Node* cur = ht._tables[i];
			while (cur != nullptr)
			{
				Node* next = cur->_next;

				//计算新表的插入位置
				size_t hashi = hs(kot(cur->_data)) % _tables.size();

				cur->_next = _tables[hashi];
				_tables[hashi] = cur;
				cur = next;
			}
		}
		_n = ht._n;
	}

	//赋值运算符重载 ht2=ht1
	HashTable& operator=(HashTable ht)
	{
		_tables.swap(ht._tables);
		swap(_n, ht._n);

		return *this;
	}
	//查找到返回哈希表指针与当前哈希表的结点指针
	//若查找不到返回哈希表指针与空指针
	iterator Find(const K& key)
	{
		KeyOfT kot;
		Hash hs;
		size_t hashi = hs(key) % _tables.size();
		Node* cur = _tables[hashi];
		while (cur != nullptr)
		{
			//仿函数对象kot获取结点中的键值
			if (kot((cur->_data)) == key)
			{
				return iterator(cur, this);
			}
			cur = cur->_next;
		}
		return iterator(nullptr, this);
	}


	pair<iterator,bool> Insert(const T& data)
	{
		KeyOfT kot;
		Hash hs;

		iterator ret = Find(kot(data));
		if (ret != end())
		{
			//键值key原先已存在,插入失败,返回已存在结点的迭代器并且返回false
			return make_pair(ret, false);
		}

		if (_n == _tables.size())
		{
			vector<Node*> _newtables(_tables.size() * 2);
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur != nullptr)
				{
					Node* nextnode = cur->_next;
				
					size_t hashi = hs(kot(cur->_data)) % _newtables.size();
					cur->_next = _newtables[hashi];
					_newtables[hashi] = cur;
					cur = nextnode;
				}
				_tables[i] = nullptr;
			}
			_tables.swap(_newtables);
		}

		size_t hashi = hs(kot(data)) % _tables.size();
		Node* newnode = new Node(data);

		newnode->_next = _tables[hashi];
		_tables[hashi] = newnode;
		++_n;

		//键值key原先不存在,插入成功,返回新结点的迭代器并且返回true
		return make_pair(newnode, true);
	}

	bool Erase(const K& key)
	{
		Hash hs;
		KeyOfT kot;
		size_t hashi = hs(key) % _tables.size();
		Node* cur = _tables[hashi];
		Node* prev = nullptr;
		while (cur != nullptr)
		{
			//仿函数(KeyOfT)对象kot获取数据data的键值key确定待删除数据的位置
			if (kot((cur->_data)) == key)
			{
				//删除
				if (prev != nullptr)
				{
					prev->_next = cur->_next;
				}
				else
				{
					_tables[hashi] = cur->_next;
				}
				delete cur;

				--_n;
				return true;
			}
			else
			{
				prev = cur;
				cur = cur->_next;
			}
		}
		return false;
	}
private:
	vector<Node*> _tables;
	size_t _n;
};

unordered_set的模拟实现

#include "HashBucket.h"
template<class K, class Hash = HashFunc<K>>
class Unordered_Set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	typedef typename HashTable<K, K, SetKeyOfT, Hash>::iterator iterator;

	iterator begin()
	{
		return _ht.begin();
	}

	iterator end()
	{
		return _ht.end();
	}

	//插入
	pair<iterator, bool> insert(const K& key)
	{
		return _ht.Insert(key);
	}
	//查找
	iterator find(const K& key)
	{
		return _ht.Find(key);
	}
	//删除
	bool erase(const K& key)
	{
		return _ht.Erase(key);
	}
private:
	HashTable<K, K, SetKeyOfT, Hash> _ht;
};

unordered_map的模拟实现

#include "HashBucket.h"
template<class K,class V,class Hash=HashFunc<K>>
class Unordered_Map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	typedef typename HashTable<K, pair<K, V>, MapKeyOfT, Hash>::iterator iterator;

	iterator begin()
	{
		return _ht.begin();
	}

	iterator end()
	{
		return _ht.end();
	}
	//插入
	pair<iterator,bool> insert(const pair<K, V>& kv)
	{
		return _ht.Insert(kv);
	}
	//查找
	iterator find(const K& key)
	{
		return _ht.Find(key);
	}
	//删除
	bool erase(const K& key)
	{
		return _ht.Erase(key);
	}

	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = insert(make_pair(key, V()));
		return ret.first->second;
	}

private:
	HashTable<K, pair<K, V>, MapKeyOfT, Hash> _ht;
};

欢迎大家批评指正,博主会持续输出优质内容,谢谢各位观众老爷观看,码字画图不易,希望大家给个一键三连支持~ 你的支持是我创作的不竭动力~

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

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

相关文章

嵌入式系统中的实时操作系统(RTOS)深入应用与优化

引言 实时操作系统&#xff08;RTOS&#xff09;在嵌入式系统中扮演着至关重要的角色&#xff0c;特别是在需要快速响应和高度可靠性的应用中。 我将探讨如何在STM32单片机上实现RTOS&#xff0c;包括任务管理、内存管理以及中断处理&#xff0c;以提高系统的效率和响应速度。…

C++:const成员和取地址操作符

目录 一、const成员 二、取地址及const取地址操作符重载 一、const成员 将const修饰的“成员函数”称之为const成员函数&#xff0c;const修饰类成员函数&#xff0c;实际修饰该成员函数 隐含的this指针&#xff0c;表明在该成员函数中不能对类的任何成员进行修改。 注&…

JavaScript+B/S架构云LIS系统源码C# 6.0+MVC+SQLSugar医院版检验科云LIS系统源码 可提供演示

JavaScriptB/S架构云LIS系统源码MVCSQLSugar医院版检验科云LIS系统源码 可提供演示随着医疗技术的不断发展&#xff0c;医疗机构对于信息化、智能化的需求也越来越高。特别是对于检验科这样的核心科室&#xff0c;如何提高工作效率、降低误差率、提高数据安全性成为了亟待解决的…

2024年【安全生产监管人员】考试技巧及安全生产监管人员模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【安全生产监管人员】考试技巧及安全生产监管人员模拟考试&#xff0c;包含安全生产监管人员考试技巧答案和解析及安全生产监管人员模拟考试练习。安全生产模拟考试一点通结合国家安全生产监管人员考试最新大纲…

Type-C接口取电IC6500:优势与应用场景的深度解析

Type-C接口PD芯片取电IC的优势 随着科技的不断进步和移动设备在日常生活中的广泛应用&#xff0c;充电技术的革新变得愈发重要。Type-C接口PD芯片取电IC作为现代充电技术的关键组件&#xff0c;其优势日益凸显&#xff0c;为移动设备充电带来了革命性的改变。本文将深入探讨Ty…

(三)登录和注册(handle_auto.go)

登录和注册(handle_auto.go) 文章目录 登录和注册(handle_auto.go)一、所需要的结构体信息二、注册三、登录四、退出 一、所需要的结构体信息 type UserAuth struct{}type LoginReq struct {Username string json:"username" binding:"required"Password …

【汇编语言】直接定址表

【汇编语言】直接定址表 文章目录 【汇编语言】直接定址表前言一、移位指令移位指令过程逻辑移位指令shl 和 shr 二、操作显存数据显示的原理显示缓冲区的结构显示信息的一种“直接”方式 三、描述内存单元的标号关于标号去了冒号的数据标号数据标号同时描述内存地址和单元长度…

前端JS必用工具【js-tool-big-box】,防抖和节流的方法调用学习

这一小节&#xff0c;我们针对前端工具包&#xff08;npm&#xff09;js-tool-big-box的使用做一些讲解&#xff0c;主要是防抖和节流方面的。 目录 前言 1 安装和引入 2 防抖的调用学习 3 节流的调用学习 4 使用方法总结 前言 在前端项目中&#xff0c;经常涉及到防抖…

CNAS软件测评报告收费标准

随着信息技术的快速发展&#xff0c;软件测评在保障软件质量、提升用户体验等方面扮演着越来越重要的角色。CNAS&#xff08;中国合格评定国家认可委员会&#xff09;作为国内权威的认可机构&#xff0c;其软件测评报告收费标准受到了广泛关注。本文旨在解析CNAS软件测评报告的…

[华为OD]幼儿园两个班的小朋友 100

题目&#xff1a; 幼儿园两个班的小朋友在排队时混在了一起&#xff0c;每位小朋友都知道自己是否与前面一位小朋友 是否同班&#xff0c;请你帮忙把同班的小朋友找出来。 小朋友的编号为整数&#xff0c;与前一位小朋友同班用 Y 表示&#xff0c;不同班用 N 表示。 输入描…

索引【MySQL】

文章目录 什么是索引测试表 磁盘和 MySQL 的交互了解磁盘MySQL 的工作原理Buffer Pool 理解索引引入Page 的结构页内目录&#xff08;Page Directory&#xff09;多页情况B 树和 B树聚簇索引和非聚簇索引 主键索引创建 唯一索引主要特点与主键索引的区别使用场景创建 联合索引工…

思维+数学期望,CF 1525E Assimilation IV

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 Problem - 1525E - Codeforces 二、解题报告 1、思路分析 看数据量盲猜O(…

树的层序遍历(详解)

下面以一道力扣题为例&#xff1a; 代码和解释如下&#xff1a; /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(…

零基础HTML教程(31)--HTML5多媒体

文章目录 1. 背景2. audio音频3. video视频4. audio与video常用属性5. 小结 1. 背景 在H5之前&#xff0c;我们要在网页上播放音频、视频&#xff0c;需要借助第三方插件。 这些插件里面最火的就是Flash了&#xff0c;使用它有几个问题&#xff1a; 首先要单独安装Flash&…

华为Pura 70系列,一种关于世界之美的可能

1874年&#xff0c;莫奈创作了《印象日出》的油画&#xff0c;在艺术界掀起了一场革命。当时的主流艺术&#xff0c;是追求细节写实&#xff0c;追求场面宏大的学院派。他们称莫奈等人是“印象派”&#xff0c;认为莫奈的画追求光影表达&#xff0c;追求描绘抽象的意境&#xf…

echarts地图叠加百度地图底板实现数据可视化

这里写自定义目录标题 echarts地图叠加百度地图实现数据可视化echarts地图叠加百度地图实现数据可视化 实现数据可视化时,个别情况下需要在地图上实现数据的可视化,echarts加载geojson数据可以实现以地图形式展示数据,例如分层设色或者鼠标hover展示指标值,但如果要将echa…

【Redis 开发】一人一单,超卖问题(悲观锁,乐观锁,分布式锁)

锁 悲观锁乐观锁第一种&#xff1a;版本号法第二种&#xff1a;CAS法实现乐观锁 悲观锁与乐观锁的比较 一人一单分布式锁Redis实现分布式锁 悲观锁 认为线程问题一定会发生&#xff0c;因此在操作数据库之前先获取锁&#xff0c;确保线程串行执行&#xff0c;例如Synchronized…

好的猫咪主食冻干到底该咋选?品控稳定的主食冻干推荐

315中国之声报道的河北省邢台市南和区某宠粮代工厂的“行业潜规则”&#xff0c;给各位铲屎官拉响了警钟。配料表上写的鸡肉含量为52%&#xff0c;新鲜鸡小胸含量为20%&#xff0c;所谓的鲜鸡肉其实就是鸡肉粉。本来养宠物是为了让自己身心愉悦&#xff0c;但这样的行业乱象弄得…

prompt提示词:AI英语词典优化版Pro,让AI教你学英语,通过AI实现一个网易有道英语词典

目录 一、前言二、效果对比三、优化《AI英语词典》提示词四、其他获奖作品链接 一、前言 不可思议&#xff01;我的AI有道英语字典助手竟然与百度千帆AI应用创意挑战赛K12教育主题赛榜首作品差之毫厘 &#xff0c;真的是高手都是惺惺相惜的&#xff0c;哈哈&#xff0c;自恋一…

发票管理设计方案

1、背景介绍 在供应链金融业务场景下&#xff0c;供应商可以依赖与大型企业的合同、发票信息&#xff0c;到金融机构进行融资。本文探讨发票管理的设计方案。 2、需求分析 如上图所示&#xff0c;发票管理主要分为发票信息的管理以及发票可用余额管理2个部分。 名词解释&…