C++进阶:用哈希表封装unordered_map和unordered_set

目录

一.源码和框架分析

二.模拟实现unordered_map和unordered_set

2.1先实现出框架并支持Insert

2.2支持iterator的实现

三.完整代码实现


一.源码和框架分析

这里只是截取一段unordered_map和unordered_set用HashTable实现的一部分。

// stl_hash_set
template <class Value, class HashFcn = hash<Value>,
	class EqualKey = equal_to<Value>,
	class Alloc = alloc>
class hash_set
{
private:
	typedef hashtable<Value, Value, HashFcn, identity<Value>,
		EqualKey, Alloc> ht;
	ht rep;
public:
	typedef typename ht::key_type key_type;
	typedef typename ht::value_type value_type;
	typedef typename ht::hasher hasher;
	typedef typename ht::key_equal key_equal;

	typedef typename ht::const_iterator iterator;
	typedef typename ht::const_iterator const_iterator;
	hasher hash_funct() const { return rep.hash_funct(); }
	key_equal key_eq() const { return rep.key_eq(); }
};

// stl_hash_map
template <class Key, class T, class HashFcn = hash<Key>,
	class EqualKey = equal_to<Key>,
	class Alloc = alloc>
class hash_map
{
private:
	typedef hashtable<pair<const Key, T>, Key, HashFcn,
		select1st<pair<const Key, T> >, EqualKey, Alloc> ht;
	ht rep;
public:
	typedef typename ht::key_type key_type;
	typedef T data_type;
	typedef T mapped_type;
	typedef typename ht::value_type value_type;
	typedef typename ht::hasher hasher;
	typedef typename ht::key_equal key_equal;
	typedef typename ht::iterator iterator;
	typedef typename ht::const_iterator const_iterator;
};

// stl_hashtable.h
template <class Value, class Key, class HashFcn,
	class ExtractKey, class EqualKey,
	class Alloc>
class hashtable {
public:
	typedef Key key_type;
	typedef Value value_type;
	typedef HashFcn hasher;
	typedef EqualKey key_equal;
private:
	hasher hash;
	key_equal equals;
	ExtractKey get_key;
	typedef __hashtable_node<Value> node;

	vector<node*, Alloc> buckets;
	size_type num_elements;
public:
	typedef __hashtable_iterator<Value, Key, HashFcn, ExtractKey, EqualKey,
		Alloc> iterator;
	pair<iterator, bool> insert_unique(const value_type& obj);
	const_iterator find(const key_type& key) const;
};
template <class Value>
struct __hashtable_node
{
	__hashtable_node* next;
	Value val;
};

• 通过源码可以看到,结构上hash_map和hash_set跟map和set的完 全类似,复用同⼀个hashtable实现key和key/value结构,hash_set传给hash_table的是两个 key,hash_map传给hash_table的是pair<const Key,Value>。

• 需要注意的是源码里面跟map/set源码类似,命名风格比较乱,这里比map和set还乱,hash_set 模板参数居然用的Value命名,hash_map用的是Key和T命名。

二.模拟实现unordered_map和unordered_set

2.1先实现出框架并支持Insert

跟map和set相比而言unordered_map和unordered_set的模拟实现类结构更复杂一点,但是大框架和思路是完全类似的。因为HashTable实现了泛型不知道T参数导致是K,还是pair, 那么insert内部进行插入时要用K对象转换成整形取模和K比较相等,因为pair的value不参与计算取模,且默认支持的是key和value一起比较相等,我们需要时的任何时候只需要比较K对象,所以我们在unordered_map和unordered_set层分别实现一个MapKeyOfT和SetKeyOfT的仿函数传给 HashTable的KeyOfT,然后HashTable中通过KeyOfT仿函数取出T类型对象中的K对象,再转换成 整形取模和K比较相等,具体细节参考如下代码实现。

HashTable.h:

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

template<>
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (auto e : key)
		{
			hash *= 131;
			hash += e;
		}
		return hash;
	}
};

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

	template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<T> Node;

		inline unsigned long __stl_next_prime(unsigned long n)
		{
			// Note: assumes long is at least 32 bits.
			static const int __stl_num_primes = 28;
			static const unsigned long __stl_prime_list[__stl_num_primes] =
			{
			 53, 97, 193, 389, 769,
			 1543, 3079, 6151, 12289, 24593,
			 49157, 98317, 196613, 393241, 786433,
			 1572869, 3145739, 6291469, 12582917, 25165843,
			 50331653, 100663319, 201326611, 402653189, 805306457,
			 1610612741, 3221225473, 4294967291
			};
			const unsigned long* first = __stl_prime_list;
			const unsigned long* last = __stl_prime_list + __stl_num_primes;
			//查找范围内第一个不小于(大于或等于)指定值的元素。
			const unsigned long* pos = lower_bound(first, last, n);
			return pos == last ? *(last - 1) : *pos;
		}
	public:
		HashTable()
		{
			_tables.resize(__stl_next_prime(_tables.size()), nullptr);
		}
		~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}
		bool Insert(const pair<K, V>& kv)
		{
			KeyOfT kot;
			if (Find(kot(data)))
				return false;

			Hash hs;
			size_t hashi = hs(kot(data)) % _tables.size();
			//负载因子等于1就扩容
			if (_n = _tables.size())
			{
				//创建新的指针数组
				vector<Node*> newtables(__stl_next_prime(_tables.size()), nullptr);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						// 旧表中节点,挪动新表重新映射的位置 
						size_t hashi = hs(cur->_kv.first) % newtables.size();
						// 头插到新表 
						cur->_next = newtables[hashi];
						newtables[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newtables);
			}
			// 头插
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			return true;
		}
		Node* Find(const K& key)
		{
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}
		bool Erase(const K& key)
		{
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			//删除前需要记录
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					//删除的是数组里链接的第一个节点
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					--_n;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

	private:
		vector<Node*> _tables; // 指针数组 
		size_t _n = 0; // 表中存储数据个数 
	};
}

Myunordered_map.h:

#pragma once
#include"HashTable.h"
namespace lwz
{
	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:
		bool insert(const pair<K, V>& kv)
		{
			return _ht.Insert(kv);
		}
	private:
		hash_bucket::HashTable<K, pair<K, V>, MapKeyOfT, Hash> _ht;
	};
}

Myunordered_set.h:

#pragma once
#include"HashTable.h"
namespace lwz
{
	template<class K, class K, class Hash = HashFunc<K>>
	class Unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		bool insert(const K& key)
		{
			return _ht.Insert(key);
		}
	private:
		hash_bucket::HashTable<K, K, SetKeyOfT, Hash> _ht;
	};
}

2.2支持iterator的实现

iterator实现思路分析

 • iterator实现的大框架跟list的iterator思路是一致的,用一个类型封装结点的指针,再通过重载运算符实现,迭代器像指针一样访问的行为,要注意的是哈希表的迭代器是单向迭代器。

• 这难的难点是operator++的实现。iterator中有一个指向结点的指针,如果当前桶下面还有结点, 则结点的指针指向下一个结点即可。如果当前桶走完了,则需要想办法计算找到下一个桶。这里的 难点是反而是结构设计的问题,用key值计算出当前桶位置,依次往后找下一个不为空的桶即可。 • begin()返回第一个桶中第一个节点指针构造的迭代器,这里end()返回迭代器可以用空表示。

 • unordered_set的iterator也不支持修改,我们把unordered_set的第二个模板参数改成const  K即 可, HashTable<K, const K, SetKeyOfT, Hash> _ht;

• unordered_map的iterator不支持修改key但是可以修改value,我们把unordered_map的第二个模板参数pair的第一个参数改成const  K即可, HashTable<const K, pair<const K,V>, MapKeyOfT, Hash> _ht;

三.完整代码实现

HashTable.h:

//HashFunc把值变成数字
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
//特化
template<>
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (auto e : key)
		{
			hash *= 131;
			hash += e;
		}
		return hash;
	}
};

namespace hash_bucket
{
	//节点的值也不确定,可能是K也可能是pair,这里用模版代替
	template<class T>
	struct HashNode
	{
		T _data;
		HashNode<T>* _next;
		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 Ptr,class Ref,class KeyOfT,class Hash>
	class HTIterator
	{
	public:
		typedef HashNode<T> Node;
		typedef HTIterator<K,T,Ptr,Ref,KeyOfT,Hash> Self;
		//成员变量
		Node* _node;//当前位置
		//特别要注意这里要加const,因为在HashTable里需要实现consthashtable,如果不加权限会放大
		const HashTable<K, T, KeyOfT, Hash>* _pht;
		//这里的const与上面一样
		HTIterator(Node* node,const HashTable<K, T, KeyOfT, Hash>* pht)
			:_node(node)
			, _pht(pht)
		{}
		Ref operator*()
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
		Self& operator++()
		{
			if (_node->_next)
			{
				// 当前桶还有节点 
				_node = _node->_next;
			}
			else
			{
				// 当前桶走完了,找下一个不为空的桶 
				KeyOfT kot;
				Hash hs;
				size_t hashi = hs(kot(_node->_data)) % _pht -> _tables.size();
				++hashi;
				while (hashi < _pht->_tables.size())//当前位置小于哈希表大小
				{
					//如果当前哈希表的hashi位置不为空,说明找到了下一个不为空的位置,直接跳出循环执行下一步就行
					if (_pht->_tables[hashi])
					{
						break;
					}
					++hashi;
				}
				//这里找到了但是还是要判断一下是否到达了最后一个位置
				if (hashi == _pht->_tables.size())
				{
					_node = nullptr; // end()
				}
				else
				{
					_node = _pht->_tables[hashi];
				}
			}
			return *this;
		}
	};


	template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<T> Node;
		// 友元声明 
		template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>
		friend class HTIterator;
	public:
		typedef HTIterator<K, T, T*, T&, KeyOfT, Hash> Iterator;
		typedef HTIterator<K, T, const T*, const T&, KeyOfT, Hash> ConstIterator;

		Iterator Begin()
		{
			if (_n == 0)
				return End();
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur)
				{
					return Iterator(cur, this);//返回当前位置的迭代器
				}
			}
			//没有找到返回end()
			return End();
		}
		Iterator End()
		{
			return Iterator(nullptr, this);
		}
		ConstIterator Begin() const
		{
			if (_n == 0)
				return End();
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur)
				{
					return ConstIterator(cur, this);
				}
			}
			return End();
		}
		ConstIterator End() const
		{
			return ConstIterator(nullptr, this);
		}

		inline unsigned long __stl_next_prime(unsigned long n)
		{
			// Note: assumes long is at least 32 bits.
			static const int __stl_num_primes = 28;
			static const unsigned long __stl_prime_list[__stl_num_primes] =
			{
			 53, 97, 193, 389, 769,
			 1543, 3079, 6151, 12289, 24593,
			 49157, 98317, 196613, 393241, 786433,
			 1572869, 3145739, 6291469, 12582917, 25165843,
			 50331653, 100663319, 201326611, 402653189, 805306457,
			 1610612741, 3221225473, 4294967291
			};
			const unsigned long* first = __stl_prime_list;
			const unsigned long* last = __stl_prime_list + __stl_num_primes;
			//查找范围内第一个不小于(大于或等于)指定值的元素。
			const unsigned long* pos = lower_bound(first, last, n);
			return pos == last ? *(last - 1) : *pos;
		}
	public:
		HashTable()
		{
			_tables.resize(__stl_next_prime(_tables.size()), nullptr);
		}
		~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}
		pair<Iterator,bool> Insert(const T& data)
		{
			KeyOfT kot;
			Iterator it = Find(kot(data));
			//不能有相同的值,如果有,返回当前的迭代器,并且返回false
			if (it != End())
				return make_pair(it, false);

			Hash hs;
			size_t hashi = hs(kot(data)) % _tables.size();
			//负载因子等于1就扩容
			if (_n = _tables.size())
			{
				//创建新的指针数组
				vector<Node*> newtables(__stl_next_prime(_tables.size()), nullptr);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						// 旧表中节点,挪动新表重新映射的位置 
						size_t hashi = hs(kot(cur->_data)) % newtables.size();
						// 头插到新表 
						cur->_next = newtables[hashi];
						newtables[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newtables);
			}
			// 头插
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			//返回的是新插入节点的迭代器
			return make_pair(Iterator(newnode, this), true);
		}
		Iterator Find(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			//找到映射位置
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				//用的是节点指针,所以这里需要用kot找K
				if (kot(cur->_data) == key)
				{
					//找到就返回当前位置的迭代器
					return Iterator(cur, this);
				}
				cur = cur->_next;
			}
			//没有找到就返回End()
			return End();
		}
		bool Erase(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			//删除前需要记录
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					//删除的是数组里链接的第一个节点
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					--_n;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

	private:
		vector<Node*> _tables; // 指针数组 
		size_t _n = 0; // 表中存储数据个数 
	};
}

Myunordered_map.h: 

#pragma once
#include"HashTable.h"
namespace lwz
{
	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 hash_bucket::HashTable<K, pair<const K, V>,
			MapKeyOfT, Hash>::Iterator iterator;
		typedef typename hash_bucket::HashTable<K, pair<const K, V>,
			MapKeyOfT, Hash>::ConstIterator const_iterator;
		iterator begin()
		{
			return _ht.Begin();
		}
		iterator end()
		{
			return _ht.End();
		}
		const_iterator begin() const
		{
			return _ht.Begin();
		}
		const_iterator end() const
		{
			return _ht.End();
		}

		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _ht.Insert(kv);
		}
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
			return ret.first->second;
		}
		iterator Find(const K& key)
		{
			return _ht.Find(key);
		}
		bool Erase(const K& key)
		{
			return _ht.Erase(key);
		}
	private:
		hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
	};
}

Myunordered_set.h: 

#pragma once
#include"HashTable.h"
namespace lwz
{
	template<class K, class Hash = HashFunc<K>>
	class Unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT,
			Hash>::Iterator iterator;
		typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT,
			Hash>::ConstIterator const_iterator;
		iterator begin()
		{
			return _ht.Begin();
		}

		iterator end()
		{
			return _ht.End();
		}
		const_iterator begin() const
		{
			return _ht.Begin();
		}

		const_iterator end() const
		{
			return _ht.End();
		}

		pair<iterator, bool> insert(const K& key)
		{

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

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

相关文章

18.嵌入式QT开发环境找不到标准文件的问题(stdio.h)

前言 程序可以正常编译通过,但是总提示找不到标准头文件,问题如下: 1.QT找不到标准文件的位置报错. 在LED_and_TempHumi.pro中添加以下语句 INCLUDEPATH /home/book/buildroot-100ask_t113-pro/buildroot/output/host/arm-buildroot-linux-gnueabi/sysroot/usr/include

Qt界面设计时使各控件依据窗口缩放进行栅格布局的方法

图1 最终效果 想要达成上述图片的布局效果&#xff0c;具体操作如下&#xff1a; 新建一窗体&#xff1a; 所需控件如下&#xff1a; Table View控件一个&#xff1b; Group Box控件一个&#xff1b; Push Button控件2个&#xff1b; Horiziontal Spacer控件2个&#xf…

使用ENSP实现DHCP

一、项目拓扑 二、项目实现 将信息提示改为中文 language-mode chinese确认 y 进入系统试图 sys将交换机命名为SW1 sysname SW1关闭信息中心 undo info-center enable 创建valn10 vlan20 vlan batch 10 20进入vlan10虚拟接口 int vlanif 10将vlan10虚拟接口IP地址配置为192.16…

CANDENCE: 绘制好的封装元件 刷新(Refresh) 和 替换 (Replace)焊盘

绘制好的封装元件 刷新(Refresh) 和 替换 &#xff08;Replace&#xff09;焊盘 一、刷新(Refresh) 1、以下面这个bga484封装的元件为例 2、打开bga的焊盘文件 3、我们对上面这个焊盘稍加修改&#xff0c;如下&#xff0c;然后保存 4、在封装编辑页面&#xff0c;如下操作 5…

【学术讲座】视觉计算中的深度学习方法 AIGC图像视频生成模型的推理加速

视觉计算中的深度学习方法 发展历程 backbone 强化学习、LLM等&#xff1a;有监督 && 无监督的结合 目标检测 图像分割 网络结构搜索 搜索方法 1&#xff1a;强化学习 2&#xff1a;强化学习 3&#xff1a;梯度算法 结构选择的作用 1&#xff1a;开放环境感知网络…

摄像机视频分析软件下载LiteAIServer视频智能分析平台玩手机打电话检测算法技术的实现

随着科技的不断进步&#xff0c;摄像机视频分析软件的发展已经为我们的生活带来了许多便捷。其中&#xff0c;LiteAIServer视频智能分析平台的玩手机打电话检测算法技术尤为突出&#xff0c;它利用先进的图像处理和人工智能技术&#xff0c;能够自动识别并监控视频中的玩手机或…

基于UDP和TCP实现回显服务器

目录 一. UDP 回显服务器 1. UDP Echo Server 2. UDP Echo Client 二. TCP 回显服务器 1. TCP Echo Server 2. TCP Echo Client 回显服务器 (Echo Server) 就是客户端发送什么样的请求, 服务器就返回什么样的响应, 没有任何的计算和处理逻辑. 一. UDP 回显服务器 1. UD…

DICOM核心概念:显式 VR(Explicit VR)与隐式 VR(Implicit VR)在DICOM中的定义与区别

在DICOM&#xff08;Digital Imaging and Communications in Medicine&#xff09;标准中&#xff0c;VR&#xff08;Value Representation&#xff09; 表示数据元素的值的类型和格式。理解显式 VR&#xff08;Explicit VR&#xff09;与隐式 VR&#xff08;Implicit VR&#…

安卓应用安装过程学习

声明&#xff1a;此文章来自http://shuwoom.com/?p60的学习记录 启动式安装 public static final IPackageManager main(Context context, Installer installer,boolean factoryTest, boolean onlyCore) {PackageManagerService m new PackageManagerService(context, inst…

基于Java Springboot医疗垃圾分类系统

一、作品包含 源码数据库全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据库&#xff1a;…

SQL99版全外连接和交叉连接和总结

全外连接MySQL不支持 elect 查询列表 from 表名1 表别名1 cross join 表名2 表别名2 on 连接条件 ...... ; 交叉连接 就两个记录做笛卡尔积&#xff01;没什么好说的&#xff0c;基本也没用过&#xff01; 总结

推荐一款开源电子书阅读器Koodo Reader

Koodo Reader 是一个开源的电子书阅读器&#xff0c;支持多达15种主流电子书格式&#xff0c; 内置笔记、高亮、翻译功能&#xff0c;助力高效书籍阅读和学习。 官网地址&#xff1a;https://www.koodoreader.com/zh 一、下载软件 下载地址&#xff1a;https://dl.koodoreader.…

WebStorm 2024.3/IntelliJ IDEA 2024.3出现elementUI提示未知 HTML 标记、组件引用爆红等问题处理

WebStorm 2024.3/IntelliJ IDEA 2024.3出现elementUI提示未知 HTML 标记、组件引用爆红等问题处理 1. 标题识别elementUI组件爆红 这个原因是&#xff1a; 在官网说明里&#xff0c;才版本2024.1开始&#xff0c;默认启用的 Vue Language Server&#xff0c;但是在 Vue 2 项目…

Harbor2.11.1生成自签证和配置HTTPS访问

文章目录 HTTPS的工作流程部署Harbor可参考上一篇文章生成自签证书1.修改/etc/hosts文件2.生成证书a.创建存放证书路径b.创建ca.key密钥c.创建ca.crtd.创建给Harbor服务器使用密钥 yunzhidong.harbor.com.keye.创建给Harbor服务器使用证书签名请求文件 yunzhidong.harbor.com.c…

【深度学习之二】正则化函数(weight decay, dropout, label smoothing, and etc)详解,以及不同的函数适用的场景

在深度学习中正则化函数的重要性不言而喻&#xff0c;今天主要总结一些当前常用的一些正则化函数 在深度学习中&#xff0c;正则化&#xff08;Regularization&#xff09;是一种防止模型过拟合的技术。过拟合指的是模型在训练数据上表现很好&#xff0c;但在未见过的测试数据…

uni-app 修改复选框checkbox选中后背景和字体颜色

编写css&#xff08;注意&#xff1a;这个样式必须写在App.vue里&#xff09; /* 复选框 */ /* 复选框-圆角 */ checkbox.checkbox-round .wx-checkbox-input, checkbox.checkbox-round .uni-checkbox-input {border-radius: 100rpx; } /* 复选框-背景颜色 */ checkbox.checkb…

Ngrok实现内网穿透(Windows)

Ngrok实现内网穿透&#xff08;Windows&#xff09; 什么是内网穿透&#xff0c;内网穿透有什么用 内网穿透&#xff08;NAT traversal&#xff09;是一种技术手段&#xff0c;使得位于内网或防火墙后面的设备能够通过外网访问。例如&#xff0c;如果你的计算机、服务器等设备…

Simulink中Model模块的模型保护功能

在开发工作过程中&#xff0c;用户为想要知道供应商的开发能力&#xff0c;想要供应商的模型进行测试。面对如此要求&#xff0c;为了能够尽快拿到定点项目&#xff0c;供应商会选择一小块算法或是模型以黑盒的形式供客户测试。Simulink的Model模块除了具有模块引用的功能之外&…

Linux内核USB2.0驱动框架分析--USB包

一&#xff0c; 包的组成 每个包都由SOP&#xff08;包起始域&#xff09;、SYNC&#xff08;同步域&#xff09;、Packet Content&#xff08;包内容&#xff09;、EOP&#xff08;包结束域&#xff09;四部分组成&#xff0c;其中SOP、SYNC、EOP为所有包共有的域&#xff0c…

STM32F4----ADC模拟量转换成数字量

STM32F4----ADC模拟量转换成数字量 基本原理 当需要测量和记录外部电压的变化&#xff0c;或者根据外部电压的变化量来决定是否触发某个动作时&#xff0c;我们可以使用ADC&#xff08;模拟—数字转换器&#xff09;功能。这个功能可以将模拟的电压信号转换为数字信号&#x…