C++STL详解(十) -- 使用哈希表封装unordered_set和unordered_map

news2024/9/20 15:01:38

文章目录

  • 哈希表模板参数改造
    • 针对模板参数V改造
    • 增加仿函数获取具体数据类型.
  • 哈希表的正向迭代器
    • 正向迭代器中的内置成员:
    • 正向迭代器的成员函数
  • 哈希表插入函数的修改(适用于unordered_map)
  • 一个类型K去做set和unordered_set他的模板参数的必备条件.
  • unordered_set的模拟实现(完整代码)
  • unordered_map的实现(完整代码)
  • 适用于unordered_set和unordered_map的哈希表代码

哈希表模板参数改造

针对模板参数V改造

因为不同容器的类型不同,如果是unordered_map,V代表一个键值对,如果unordered_set,V代表Key值,而底层哈希表并不知道上层容器所要传递的
V模板参数类型,为了与哈希表的模板参数区分,我们将哈希表中的V模板参
数改为T.
在这里插入图片描述

增加仿函数获取具体数据类型.

例如: 在哈希表中当我们使用Find函数进行查找时:
如果上层传递的数据类型为Key值,我们就可以直接与查找数据比较.
如果上层传递的数据类型为pair<K,V>键值对,我们就不可以直接比较,需要利用仿函数获取键值对的first进行比较.
unordered_set仿函数SetKeyOfT代码:

struct SetKeyOfT              //根据map还是set传递不同数据类型给底层.
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

unordered_map仿函数MapKeyOfT代码:

struct MapKeyOfT                   //根据map还是set传递不同数据类型给底层.
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;   //获取pair<K,V>键值对中的first.
			}
		};

所以,我们要在哈希表模板参数中增加一个仿函数KeyOfT.
如果是unordered_set,就传 SetKeyOfT类型的仿函数.
如果是unordered_map,就传MapKeyOfT类型的仿函数.

模板参数转换图示如下:
在这里插入图片描述
额外说明:
1: 我们对原先哈希表中的Hash进行了修改,不要缺省值,因为我们肯定是使用上层容器传递不同的仿函数类型,所以在unordered_set与unordered_map中传递仿函数类型.

哈希表的正向迭代器

正向迭代器中的内置成员:

哈希表正向迭代器有两个内置成员:
1: 结点指针
2: 哈希表的指针


template < class K, class T, class Hash, class KeyOfT >
class HashTable;                   //重新声明哈希表类模板.
//正向迭代器
template<class K, class T, class KeyOfT, class HashFunc = Hash<K>>
struct __HTIterator
{
	typedef HashNode<T> Node; //重新定义哈希结点的类型为Node.
	typedef HashTable<K, T, KeyOfT, HashFunc> HT; //重新定义哈希表的类型为HT.
	typedef __HTIterator<K, T, KeyOfT, HashFunc> Self; //重新定义正向迭代器的类型为Self.

	Node* _node; //结点指针
	HT* _pht; //哈希表指针
};

注意:
因为迭代器中要使用到哈希表类型,所以我们在哈希表迭代器前面必须声明一下哈希表类模板.

正向迭代器的成员函数

以下成员函数较为简单,就不另外说明:


		_HashIterator( Node* node,HT* pht )  //初始化列表
			:_node(node)
			, _pht(pht)
		{
		}
        
		T& operator*()                     //解引用运算符重载
		{
			return _node->_data;
		}

		T* operator->()                 //->运算符重载
		{
			return &_node->_data;
		}

		bool operator!=( const Self& s ) const  //外面增加const可以使const对象也能调用该成员函数.
		{
			return _node != s._node;  //实质上是迭代器中的_node比较
		}


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

前置++运算符重载:
前置++运算符重载主要有两个步骤:
1: 如果当前结点指针的下一个结点存在,则结点指针指向下一个结点.

2: 如果当前结点指针的下一个结点不存在:
(1): 通过哈希函数计算哈希桶的位置.

(2): 在哈希表中,从哈希桶中的下一个位置开始遍历查找.
如果哈希桶存在,则将结点指针指向该哈希桶.
如果哈希表遍历完还没有找到,则说明这是哈希表中的最后一个数据的迭代器,则结点指针指向空,表明最后一个数据的下一个结点.

Self& operator++()
		{
			if (_node->_next)  //在当前桶中迭代
			{
				_node = _node->_next;
			}
			                  //如果该哈希桶迭代完毕,则在哈希表中找下一个哈希桶迭代寻找.
			else
			{
				KeyOfT kot;
				Hash hash;
				size_t hashi = hash(kot(_node->_data)) % _pht->_table.size(); //迭代器访问哈希表的私有成员
				size_t i = hashi + 1;
				
				for (;  i < _pht->_table.size(); ++i)
				{
					if (_pht->_table[i])
					{
						_node = _pht->_table[i];
						break;                       //++完就退出.
					}
			        
				}
				if (i == _pht->_table.size())        //此时,这也说明正式哈希桶迭代器的end.
				{
					_node = nullptr;
				}

			}

			return *this;
		}

哈希表插入函数的修改(适用于unordered_map)

为了支持unordered_map中的[]操作符重载,我们针对哈希表中的插入函数返回值进行修改
(1): 如果哈希表中已有所给数据,则返回已有数据的迭代器与插入结果(失败)所构成的键值对.

(2): 如果哈希表没有所给数据,则将新数据头插该哈希桶组成的单链表上,返回新插入结点指针构造与插入结果(成功)组成的键值对.

插入函数代码如下:


	      pair< Iterator,bool> Insert(const T& data)
		   {
			Hash hash;
			KeyOfT kot;		                                                   
			Iterator ret = Find(kot(data));                    
			if (ret != end())
			{
				return make_pair( ret, false);
			}
			if (_size == _table.size())                                //扩容.
			{
				//	size_t newSize = _table.size() == 0 ? 10 : 2 * _table.size();
				vector<Node*> newTable;
				newTable.resize(GetNextPrime(_table.size()), nullptr); 

				Hash hash;
				//从旧表结点移动映射新表.
				for (size_t i = 0; i < _table.size(); ++i)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hash(kot(cur->_data)) % newTable.size();
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;

						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newTable);
               
			}
			size_t hashi = hash(kot(data)) % _table.size();
			Node* newNode = new Node(data);
			newNode->_next = _table[hashi];
			_table[hashi] = newNode;
			++_size;
			return make_pair(Iterator(newNode,this),true);                //如果是新插入的数据,则返回新插入结点构造的迭代器与插入结果组成的pair.
		}

一个类型K去做set和unordered_set他的模板参数的必备条件.

set容器模板参数:
(1):set模板参数要求能够支持小于比较,例如:二叉搜索树查找成员函数中,我们可以利用compare仿函数将当前结点值与所给值比较,从而决定cur遍历左子树还是右子树,为了能够支持大于比较的,我们可以交换一下实参位置,这也间接支持了大于比较.并且条件判断,进而也支持了等于.
unordered_set容器模板参数要求:
(1)针对于K类型(指针,打浮点数,有符号整型)可以转换为无符号整数取模.对于string,日期类类型,这两个类型与整型类型无关的,此时不可以直接强转为整数,需要相应的提供将该类型转换为整数的仿函数.

(2):K类型对象能够支持等于比较(因为在查找中就是要确定存储的对象与所传的对象是否相等,例如Data日期类),如果不支持需要额外提供等于比较的仿函数.

unordered_set的模拟实现(完整代码)

在上述中,我们已经将unordered_set重点内容讲解完毕,剩下的成员函数只需要复用哈希表中的就可以了.

namespace yzh
{
	//2:因为我们使用map,set是将实参传给,map,set的所以当string类型取模时要在map,set中写仿函数.
	template < class K,class Hash = HashFunc<K>> //1:由第二个模板参数决定要存储的数据.
	class unordered_set
	{
		struct SetKeyOfT                                  //根据map还是set传递不同数据类型给底层.
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename HashBucket::HashTable< K,K,Hash,SetKeyOfT>::Iterator iterator;  //重新定义一下迭代器类型为iterator.

		iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}
		
		pair<iterator,bool> Insert(const K& key)
		{
			return _ht.Insert(key);
		}
	private:
		HashBucket::HashTable<K,K,Hash,SetKeyOfT> _ht;
	};
	void test_set()                      //测试
	{
		unordered_set<int> set;
		set.Insert(1);
		set.Insert(2);
		set.Insert(3);
		
		unordered_set<int>::iterator it = set.begin();
		while ( it != set.end() )
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
	
}

unordered_map的实现(完整代码)

在上述中,我们已经将unordered_map重点内容讲解完毕,剩下的成员函数只需要复用哈希表中的就可以了.

namespace yzh
{
	template < class K, class V, class Hash = HashFunc<K> >            //由第二个模板参数决定要存储的数据类型,
	class unordered_map
	{
		struct MapKeyOfT                                             //根据map还是set传递不同数据类型给底层.
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
		
	public:
		typedef typename HashBucket::HashTable< K, pair<K, V>, Hash, MapKeyOfT>::Iterator 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);
		}
	   V& operator[]( const K& key  )                      //给一个key,返回V的引用.
	   {
		   pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));   //如果插入失败,返回已有数据的迭代器与返回结果组成的pair.                                                 //如果插入成功,返回新插入数据的迭代器与返回结果组成的pair.
		   return ret.first->second;
	   }
	   
	private:
		HashBucket::HashTable<K, pair<K, V>, Hash, MapKeyOfT> _ht;     //map和set都知道传递什么来类型的仿函数,以及要传递的数据类型.
	};
	void test_map()
	{
		unordered_map<string, string> dict;
		dict.Insert(make_pair("sort", "排序"));
		dict.Insert(make_pair("string", "排序"));

		unordered_map<string,string>::iterator it = dict.begin();
		while (it != dict.end())
		{
			cout << it->first << ":" << it->second  << endl;
     		++it;
		}
	}

	void testmap1()
	{
		unordered_map<string, int> countMap;
		string arr[] = { "苹果","西瓜","苹果","西瓜","苹果","西瓜","苹果","香蕉" };
		for (auto& e : arr)
		{
			countMap[e]++;
		}

		for (auto& kv : countMap)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
    }


	
}

适用于unordered_set和unordered_map的哈希表代码

#include <map>
#include <vector>
#include <string>
#include <string>
#include <iostream>
using namespace std;
template < class K >                   //仿函数的默认值,如果不显示传就会默认调用.
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

template< >                                 //1:对于常见类型,为了方便,我们可以对类模板进行特化.
struct HashFunc <string>                    //并且根据实参所传类型,优先走特化版本.	                                     
{
	size_t operator()(const string& key)
	{
		size_t  val = 0;
		for (auto& ch : key) //遍历string,将一个个字母替换成ASCI码进行相加.
		{
			val += ch;
		}
		return val;
	}
};
namespace HashBucket
{

	template < class T >
	struct HashNode
	{
		T _data;
		HashNode<T>* _next;

		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{}
	};

	template < class K, class T, class Hash, class KeyOfT >
	class HashTable;
	
	template < class K, class T, class Hash, class KeyOfT >
	struct _HashIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable< K, T, Hash, KeyOfT> HT;
		typedef _HashIterator< K, T, Hash, KeyOfT> Self;
		Node* _node;
	    HT* _pht;
		
		_HashIterator( Node* node,HT* pht )
			:_node(node)
			, _pht(pht)
		{
		}
        
		T& operator*()
		{
			return _node->_data;
		}

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

		Self& operator++()
		{
			if (_node->_next)  //在当前桶中迭代
			{
				_node = _node->_next;
			}
			                  //如果该哈希桶迭代完毕,则在哈希表中找下一个哈希桶迭代寻找.
			else
			{
				KeyOfT kot;
				Hash hash;
				size_t hashi = hash(kot(_node->_data)) % _pht->_table.size(); //迭代器访问哈希表的私有成员
				size_t i = hashi + 1;
				
				for (;  i < _pht->_table.size(); ++i)
				{
					if (_pht->_table[i])
					{
						_node = _pht->_table[i];
						break;                       //++完就退出.
					}
			        
				}
				if (i == _pht->_table.size())        //此时,这也说明正式哈希桶迭代器的end.
				{
					_node = nullptr;
				}

			}

			return *this;
		}

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


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

		
	};
	
	template < class K, class T, class Hash, class KeyOfT >
	struct HashTable
	{
		typedef HashNode<T> Node;
		template < class K, class T, class Hash, class KeyOfT > //为了迭代器能够访问哈希表的私有成员,我们设为迭代器是哈希表的
		                                                        //友元,类模板声明友元需要增加类模板.
		friend struct  _HashIterator;
		
	public:
		typedef _HashIterator< K, T, Hash, KeyOfT> Iterator;
		
		Iterator begin()//获取第一个迭代器
		{
			for (size_t i = 0; i < _table.size(); ++i)
			{
				if (_table[i])
				{
					return Iterator( _table[i], this );
				}
			}
			return end();
		}

		Iterator end()  //获取最后一个迭代器
		{
			return Iterator(nullptr, this);
		}

		size_t GetNextPrime(size_t prime)
		{
			const int PRIMECOUNT = 28;
			//素数序列
			const size_t primeList[PRIMECOUNT] =
			{
				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
			};
			size_t i = 0;
			for (i = 0; i < PRIMECOUNT; i++)
			{
				if (primeList[i] > prime)         //从这个数组里面取第一个大于prime的值.
					return primeList[i];
			}
			return primeList[i];                 //虽然数据不可能那么大,但是也有可能不会走if判断,
												 // 所以从语法上来说还要考虑所给值prime大于素数数组整个数据的情况.
		}

		~HashTable()         //vvector会调用析构函数,但是哈希桶必须自己写.
		{
			for (size_t i = 0; i < _table.size(); ++i)
			{
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
			//	cout << "~HushTable()" << endl;

		}

		bool  Erase(const K& key)
		{
			if (_table.size() == 0)
			{
				return true;
			}
			Hash hash;
			size_t hashi = hash(key) % _table.size();
			Node* prev = nullptr;
			Node* cur = _table[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev == nullptr)
					{
						_table[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					--_size;
					return true;
				}

				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

	      pair< Iterator,bool> Insert(const T& data)
		   {
			Hash hash;
			KeyOfT kot;
			
			                                                   //去重
			Iterator ret = Find(kot(data));                    //如果该数据已经有了,则返回已有数据的迭代器与插入结果构成的pair.
			if (ret != end())
			{
				return make_pair( ret, false);
			}
			if (_size == _table.size())                                //扩容.
			{
				//	size_t newSize = _table.size() == 0 ? 10 : 2 * _table.size();
				vector<Node*> newTable;
				newTable.resize(GetNextPrime(_table.size()), nullptr);    //扩容

				Hash hash;
				//从旧表结点移动映射新表.
				for (size_t i = 0; i < _table.size(); ++i)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hash(kot(cur->_data)) % newTable.size();
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;

						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newTable);
               
			}
			size_t hashi = hash(kot(data)) % _table.size();
			Node* newNode = new Node(data);
			newNode->_next = _table[hashi];
			_table[hashi] = newNode;
			++_size;
			return make_pair(Iterator(newNode,this),true);                //如果是新插入的数据,则返回新插入结点构造的迭代器与插入结果组成的pair.
		}

	     Iterator Find(const K& key)
		{
			if (_table.size() == 0)        //表为空,就返回nullptr.
			{
				return end();
			}
			Hash hash;
			KeyOfT kot;
			size_t hashi = hash(key) % _table.size();
			Node* cur = _table[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return Iterator(cur, this);
				}
				cur = cur->_next;
			}
			return end();
		}

		size_t size()
		{
			return _size;
		}
		size_t TablesSize()          //表的长度
		{
			return  _table.size();
		}

		size_t BucketNum()           //有多少个桶被用了.
		{
			size_t Num = 0;
			for (size_t i = 0; i < _table.size(); ++i)
			{
				if (_table[i])
				{
					++Num;
				}
			}
			return Num;
		}
		size_t MaxBucketLenth()
		{
			size_t maxLen = 0;
			for (size_t i = 0; i < _table.size(); ++i)
			{
				size_t len = 0;
				Node* cur = _table[i];
				while (cur)
				{
					++len;
					cur = cur->_next;
				}
				if (len > maxLen)
				{
					maxLen = len;
				}
			}
			return maxLen;
		}
	private:
		vector<Node*> _table;
		size_t _size = 0;
	};
}

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

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

相关文章

不得不的创建型模式-原型模式

原型模式是一种创建型模式&#xff0c;它通过复制一个已有对象来创建新的对象&#xff0c;而无需知道新对象的具体类型。 原型模型的结构&#xff1a; 下面是一个简单的C实现原型模式的代码示例&#xff1a; #include <iostream> #include <string> #include <…

React markdown 编辑器

react-markdown 是一款 github 上开源的适用于 react 的 markdown 组件&#xff0c;可以基本实现 markdown 的功能&#xff0c;且可以根据自己实际应用定制的 remark 组件。 安装 安装 markdown 预览插件 react-markdown npm install react-markdown或者&#xff1a; yarn …

Flask+mysql简单问答网站(实现公网可访问)

先到github下载仓库文件 https://github.com/QHCV/flask_mysql_blog python版本3.8&#xff0c;提前安装好Mysql数据库 1.安装python包 pip install -r requirements.txt2.修改配置文件config.py Mysql数据库用户名和密码用于发送验证码的邮箱配置 ​ 在设置->账户下开…

数仓建设规划核心问题!

小A进入一家网约车出现服务公司&#xff0c;负责公司数仓建设&#xff0c;试用期主要一项 OKR是制定数据仓库建设规划&#xff1b;因此小 A 本着从问题出发为原点&#xff0c;先对公司数仓现状进行一轮深入了解&#xff0c;理清存在问题&#xff0c;然后在以不忘初心原则提出解…

提取文本的摘要snownlp模块

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 提取文本的摘要 snownlp模块 [太阳]选择题 关于以下python代码说法错误的一项是&#xff1f; from snownlp import SnowNLP myText """ChatGPT的出现标志着人类科技发…

CSS3 grid网格布局

文章目录 CSS3 grid网格布局概述grid属性说明使用grid-template-rows & grid-template-columns 定义行高和列宽grid-auto-flow 定义项目的排列顺序grid-auto-rows & grid-auto-columns 定义多余网格的行高和列宽row-gap & column-gap 设置行间距和列间距gap 简写形…

Java版spring cloud 本工程项目管理系统源码-全面的工程项目管理

​ ​工程项目管理系统是指从事工程项目管理的企业&#xff08;以下简称工程项目管理企业&#xff09;受业主委托&#xff0c;按照合同约定&#xff0c;代表业主对工程项目的组织实施进行全过程或若干阶段的管理和服务。 如今建筑行业竞争激烈&#xff0c;内卷严重&#xff0c…

Leetcode605. 种花问题

Every day a leetcode 题目来源&#xff1a;605. 种花问题 解法1&#xff1a;贪心 贪心思想&#xff1a;在不打破种植规则的情况下种入尽可能多的花&#xff0c;然后用“最大种植数量”和“所需要种植数量”进行大小比较即可。 设地块长度为n&#xff0c;种花的情况可分为4…

分享一个菜单标签页动画,切换丝滑无比

先上效果图: 代码如下,复制粘贴大法拿走即可使用: <!DOCTYPE html> <html lang="en"> <head>

win系统使用frp端口映射实现内网穿透,配置“任务计划程序”提高稳定性

Github下载最新版frp: https://github.com/fatedier/frp/releases/download/v0.48.0/frp_0.48.0_windows_amd64.zip 解压把frpc.exe和frpc.ini放到D:\program\frp目录下&#xff0c;修改frpc.ini内容如下&#xff1a; [common] server_addr 服务器域名或IP&#xff0c;假设…

Dockerfile镜像LNMP的实战

Dockerfile镜像LNMP的实战 环境准备关闭防火墙拉取centos:7镜像自定义网络 部署nginx&#xff08;容器IP 为 172.18.0.10&#xff09;部署mysql&#xff08;容器IP 为 172.18.0.20&#xff09;部署php&#xff08;容器IP 为 172.18.0.30&#xff09; 环境准备 关闭防火墙 [ro…

taro之项目初始化模板

项目初始化模板 一直以来&#xff0c;在使用 Taro CLI 的 taro init 命令创建项目时&#xff0c;CLI 会提供若干内置模板给开发者选择。但是很多团队都有自己独特的业务场景&#xff0c;需要使用和维护的模板也不尽一致&#xff0c;因此 Taro 支持把项目模板打包成一个能力赋予…

JavaScript奇技淫巧:debugger拦截

debugger指令&#xff0c;一般用于调试&#xff0c;在如浏览器调试执行环境中&#xff0c;可以在JavaScript代码中产生中断。 如果想要拦截debugger&#xff0c;是不容易的&#xff0c;常用的函数替代、proxy方法均对它无效&#xff0c;如&#xff1a; window.debugger (fun…

电脑音乐相册软件推荐 电脑音乐相册制作方法

音乐相册就是把照片剪辑成视频&#xff0c;并配上动听的音乐。音乐相册很适合保存照片&#xff0c;记录生活&#xff0c;传达出拍摄者当时的心情。下面为大家带来电脑音乐相册软件推荐&#xff0c;电脑音乐相册制作方法。 一、电脑音乐相册软件推荐 很多小伙伴在制作音乐相册…

大数据Doris(三):Apache Doris分布式部署准备工作

文章目录 Apache Doris分布式部署准备工作 一、Apache Doris下载 二、节点划分 三、节点配置 1、设置文件句柄数 2、时间同步 3、关闭 Swap 分区 4、调大单个进程的虚拟内存区域数量 Apache Doris分布式部署准备工作 部署Apache Doris时需要分别部署FE、BE、Broker。然…

iOS - RunLoop 基本原理介绍

一、Runloop 简介 Runloop 是通过内部维护事件循环来对事件/消息进行管理的一个对象。 事件循环&#xff08;状态切换&#xff09; 没有消息需要处理时&#xff0c;休眠以避免资源占用&#xff08;用户态 -> 内核态&#xff09;有消息需要处理时&#xff0c;立刻被唤醒&a…

电视盒子什么牌子好?数码博主盘点2022电视盒子排行榜

网络电视盒子是电视机的标配&#xff0c;开放性的安卓系统能观看海量视频资源&#xff0c;我每年也会进行电视盒子的测评&#xff0c;今天要来分享五款最热门的网络电视盒子推荐&#xff0c;跟着我一起看看网络电视盒子哪个好。 一&#xff1a;泰捷WEBOX60Pro电视盒子 年度…

【华为HCIP | 高级网络工程师】刷题日记(1)

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大二在校生&#xff0c;讨厌编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;落. &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️ 零基础…

03-stable diffusion国风小姐姐

stable diffusion 文生图 – 生成国风小姐姐 一、模型在哪里下载 下载网站civitai&#xff1a; Civitai | Stable Diffusion models, embeddings, LoRAs and more国风主模型&#xff1a;https://civitai.com/models/14171/cutegirlmix4主模型放到sd-webui-aki-v4\models\Stab…

【AUTOSAR】【信息安全】CSM

目录 一、概述 二、依赖模块 三、功能描述 3.1 基本体系结构 3.2 通用行为 3.2.1 正常操作 3.2.2 设计说明 3.3 错误分类 3.3.1 开发错误 3.3.2 运行时错误 四、API接口 4.1 通用接口 4.2 加密接口 4.3 秘钥接口 4.4 Job接口 4.5 回调接口 4.6 调度接口 一、概…