C++进阶——用Hash封装unordered_map和unordered_set
目录
前言
源码怎么说
为什么要兼容?
兼容的具体做法?
为什么要把Key转为整数(HashFcn)?
模拟实现
一、建立框架
二、迭代器
++运算符重载
迭代器兼容大法
三、[ ]重载
四、实现不能修改key
实现及测试代码
HashTable.h
unordered_set.h
unordered_map.h
test.cpp
前言
本篇博客我会利用上一篇博客以链地址法实现的哈希表对unordered_map和unordered_set进行封装,大家如果想了解链地址法实现哈希表可以移步我的上一篇博客~
C++进阶——哈希思想及实现-CSDN博客
源码怎么说
我们之前用红黑树封装map和set的时候,就用到了兼容的思想,思路就是红黑树存储的结点由key、value转变成key、pair<key, value>,然后这两个统一以一个模板参数T(data)代替,set就是直接用key即可,map就是使用KeyOfT仿函数把key提取出来。
我们用哈希表封装unordered_map和unordered_set,原理同样如此。
大家和我一起来分析源码,用到的源码是SGI-STL30版本。
由于这个版本在C++11之前,也就是还没有unordered_map和unordered_set,所以里面的命名为hash_map和hash_set,大家明白是什么就好。
一些可能会出现的困惑:
为什么要兼容?
对于unordered_map和unordered_set,底层都是哈希表,如果要为他们都单独设计出一个哈希表出来,那么这两个哈希表的结构除了结点存储的数据不同,其它的逻辑基本上都一样,这样就会出现很多的代码冗余问题,如果我们想办法设计出一个哈希表,可以让unordered_map和unordered_set底层都调用它,就很好的解决了这个问题。
毕竟对于写源码的那些大佬而言,大量的重复代码不够优雅~
兼容的具体做法?
前面我们说到,unordered_map和unordered_set需要用到的哈希表的核心不同就是存储的数据类型不同,那么我们只要让这个哈希表能存储这两种数据不就行了。具体怎么做,那当然是用模板,把桶结点的模板类型写成T(T实例化出data)即可。
当unordered_set需要使用时,T就是key
当unordered_map需要使用时,T就是pair<K, V>
这样产生出来的问题就是我们在插入桶结点时,传入T不能直接拿到key的值了,而我们的unordered_map和unordered_set既然使用的是同一个哈希表模板,当然也不能去T.first去取key,不然unordered_set肯定会出错。
所以我们就需要设计出两个仿函数(各一个),这个仿函数由unordered_map和unordered_set传入哈希表,如果T是key,就返回key,如果T是pair<K, V>,就把其中的key提取并返回。
为什么要把Key转为整数(HashFcn)?
因为对于unordered_map和unordered_set而言,他们的key值最终都要转换为整数取映射到哈希表中然后存储(除法散列法),而他们的key值可能是非整形如一些自定义类型,这些编译器不能帮我们转变成整形的,我们就需要手动写一个仿函数来将其转变为整形。
这样将这个仿函数传入到哈希表里面,哈希表就能处理key值并根据其自身的size进行相应的映射了。
提示:SGI-STL30版本的默认仿函数只能处理常见的char、int、long等整形类别(char就返回ascll值,其它返回原值)以及string,其余的类型如果我们想用作为key都需要自己造一个仿函数
模拟实现
一、建立框架
以下的代码都是基于我上述对于源码的分析及之前的实现而来,只是命名风格可能有略微变化
#pragma once
#include <iostream>
#include <vector>
#include <string>
using namespace std;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
};inline unsigned long __stl_next_prime(unsigned long n)
{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;
}template<class K>
struct KeyToInt
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct KeyToInt<string>
{size_t operator()(const string& key){size_t ret = 0;for (auto& e : key){ret += e;ret *= 131;}return ret;}
};// 链地址法-哈希表-兼容版
namespace MyHashMS
{// 哈希表的桶结点// pair或keytemplate<class T>struct HashNode{HashNode(const T& data):_data(data),_next(nullptr){}T _data;HashNode* _next;};// 编译器为了提升效率,查找只会向上,迭代器要用到HashTable,故需要前置声明template<class K, class T, class KeyToInt, class KeyOfT>class HashTable;// 迭代器template<class K, class T, class Ref, class Ptr, class KeyToInt, class KeyOfT >struct HashTable_Iterator{typedef HashNode<T> Node;typedef HashTable<K, T, KeyToInt, KeyOfT> HashTable;typedef HashTable_Iterator<K, T, Ref, Ptr, KeyToInt, KeyOfT> Self;HashTable_Iterator(Node* cur, const HashTable* ht):_cur(cur),_ht(ht){}// 迭代器存储当前结点指针和其对应的哈希表指针(其实_hashtable也可以)Node* _cur;const HashTable* _ht;// 前置++Self& operator++()Ref operator*()Ptr operator->()bool operator==(const Self& other)bool operator!=(const Self& other)};// key pair或key KeyToInt由上层传,这样修改转整数的逻辑比较方便// KeyOfT也是从上层传,set传直接返回key的,map传pair<>.first的template<class K, class T, class KeyToInt, class KeyOfT>class HashTable{// 结构体的友元声明需要带着模板参数// 这里友元声明是因为HashTable_Iterator里需要用HashTable的指针去访问其私有成员template<class K, class T, class Ref, class Ptr, class KeyToInt, class KeyOfT >friend struct HashTable_Iterator;typedef HashNode<T> Node;public:typedef HashTable_Iterator<K, T, T&, T*, KeyToInt, KeyOfT> iterator;typedef HashTable_Iterator<K, T, const T&, const T*, KeyToInt, KeyOfT> const_iterator;public:HashTable():_hashtable(__stl_next_prime(0)),_n(0){}iterator begin()iterator end()const_iterator begin() const const_iterator end() const// 清除数据,释放结点,便于析构和赋值运算符重载复用void clear()// 有new申请的结点,这些结点是自定义类型,需要手动写析构函数~HashTable(){clear();}// 拷贝构造HashTable(const HashTable& other)// 赋值运算符重载HashTable& operator=(const HashTable& other)pair<iterator, bool> Insert(const T& data)iterator Find(const K& key)bool Erase(const K& key)private:vector<Node*> _hashtable;size_t _n;};
}
// unordered_set.h#pragma once
#include "HashTable.h"namespace MyHashMS
{template<class K, class KeyToInt = KeyToInt<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};typedef HashTable<K, K, KeyToInt, SetKeyOfT> HashTable;typedef typename HashTable::iterator iterator;typedef typename HashTable::const_iterator const_iterator;public: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)iterator find(const K& key)bool erase(const K& key)void print()private:// 关于KeyToInt和SetKeyOfT要不要传模板类型的问题// 模板参数是要传类型的,而前两者K,K都是类型,显然没问题// KeyToInt是unordered_set模板参数里定义好的类型,也没问题// SetKeyOfT是在unordered_set类里面定义的一个结构体,也可以直接作为类型HashTable _ht;// 当实例化 unordered_set<int> 时,K 就会被推导为 int,然后 SetKeyOfT 就会根据 K(即 int)来创建一个实例};}
// unordered_map.h#pragma once#include "HashTable.h"namespace MyHashMS{template<class K, class V, class KeyToInt = KeyToInt<K>>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};typedef HashTable<K, pair<K, V>, KeyToInt, MapKeyOfT> HashTable;typedef typename HashTable::iterator iterator;typedef typename HashTable::const_iterator const_iterator;public:iterator begin() { return _ht.begin(); }iterator end() { return _ht.end(); }const_iterator begin() const { return _ht.begin(); }const_iterator end() const { return _ht.end(); }V& operator[](const K& key)pair<iterator, bool> insert(const pair<K, V>& kv)iterator find(const K& key)bool erase(const K& key)void print()private:HashTable _ht;};}
二、迭代器
我们要实现迭代器,只需要给哈希表实现迭代器,然后外面套一层壳就可以实现unordered_map和unordered_set的迭代器啦~
我们要实现的迭代器为单向迭代器,因为之前的一个哈希桶的结点是由单向链表串起来的。
我们实现哈希表的迭代器,就需要一个指向哈希表桶结点的指针和指向哈希表的指针,这样才能进行遍历操作
++运算符重载
我们要实现单向迭代器,核心工程就是实现++运算符的重载。
如果让我们去实现该单向迭代器的++运算符重载,大部分人的第一反应就是,从第一个桶开始作为begin,遍历该桶的所有结点,然后再去找下一个桶,到最后一个桶遍历结束的下一个结点为end(nullptr)。
实际上库里面也是这么做的:
// 前置++
Self& operator++()
{ if (!_cur) return *this;KeyToInt kti;KeyOfT kot;const Node* old = _cur;_cur = _cur->_next;if (!_cur){// cur为nullptr,说明当前桶的结点已经都走过了,去寻找下一个桶的第一个结点size_t hashi = kti(kot(old->_data)) % _ht->_hashtable.size() + 1;for (; hashi < _ht->_hashtable.size(); ++hashi){if (_ht->_hashtable[hashi]){_cur = _ht->_hashtable[hashi];break;}}}return *this;
}
迭代器兼容大法
const迭代器和非const迭代器之间的区别就在于其对于容器的访问权限,通过迭代器访问容器的元素肯定需要使用的是迭代器的 * 以及 -> 运算符,所以我们只需要控制好这两个运算符的返回类型,就能控制迭代器对容器的访问权限。
// 迭代器
template<class K, class T, class Ref, class Ptr, class KeyToInt, class KeyOfT >
struct HashTable_Iterator
{typedef HashNode<T> Node;typedef HashTable<K, T, KeyToInt, KeyOfT> HashTable;typedef HashTable_Iterator<K, T, Ref, Ptr, KeyToInt, KeyOfT> Self;HashTable_Iterator(Node* cur, const HashTable* ht):_cur(cur), _ht(ht){}Node* _cur;const HashTable* _ht;Ref operator*(){return _cur->_data;}Ptr operator->(){return &(operator*());}};
我们利用模板参数来控制operator*以及operator->的返回值,来达到一份迭代器代码兼容两种版本迭代器的效果。
template<class K, class T, class KeyToInt, class KeyOfT>
class HashTable
{// 结构体的友元声明需要带着模板参数// 这里友元声明是因为HashTable_Iterator里需要用HashTable的指针去访问其私有成员template<class K, class T, class Ref, class Ptr, class KeyToInt, class KeyOfT >friend struct HashTable_Iterator;typedef HashNode<T> Node;public:// 通过模板参数来控制operator*以及operator->的返回值,来达到一份迭代器代码兼容两种版本迭代器的效果。typedef HashTable_Iterator<K, T, T&, T*, KeyToInt, KeyOfT> iterator;typedef HashTable_Iterator<K, T, const T&, const T*, KeyToInt, KeyOfT> const_iterator;public:HashTable():_hashtable(__stl_next_prime(0)), _n(0){}private:vector<Node*> _hashtable;size_t _n;
};
三、[ ]重载
在进行[ ]重载之前,我们要先对我们之前版本的HashTable的一些函数的返回值做修改,使其返回值提供的信息更丰富起来。
我就不详细粘贴代码了,所有代码都会在博客结尾给出 。
库里面对[ ]重载的效果是达到了既能实现插入又能实现修改(key不存在为插入,key存在为修改)。
[ ]的重载只对unordered_map有意义,所以我们只实现这一个版本即可。
namespace MyHashMS
{template<class K, class V, class KeyToInt = KeyToInt<K>>class unordered_map{// ...typedef HashTable<K, pair<K, V>, KeyToInt, MapKeyOfT> HashTable;typedef typename HashTable::iterator iterator;typedef typename HashTable::const_iterator const_iterator;public:V& operator[](const K& key){// ret拿到插入数据的返回值auto ret = insert({ key,V() });// 如果插入成功,返回value的引用 ( 插入功能 )// 如果插入失败,也就是原本就存在,返回原本就在的kv的value的引用 ( 修改功能 )return ret.first->second;}private:HashTable _ht;};}
四、实现不能修改key
数据是通过key来定位到哈希表中的,所以如果我们把key修改掉,会造成数据查找不到、数据完整性及安全性等问题,所以实现这个功能还是很有必要的。
在外层,用户通过只能迭代器拿到key,所以我们需要保证Ptr operator->()以及Ref operator*()返回的data中的key值为const,所以需要修改data存储的数据类型中的key为const,所以我们直接定位到哈希表的结点。
template<class T>
struct HashNode
{HashNode(const T& data):_data(data),_next(nullptr){}T _data; // 对于 unordered_map,T 是 pair<const K, V>;对于 unordered_set,T 是 const KHashNode* _next;
};
而T又是从外层的unordered_map和unordered_set传入:
如此我们便实现了不能修改key的功能。
实现及测试代码
下面是我本博客最终实现好的所有代码,供大家参考。
HashTable.h
#pragma once
#include <iostream>
#include <vector>
#include <string>
using namespace std;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
};inline unsigned long __stl_next_prime(unsigned long n)
{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;
}template<class K>
struct KeyToInt
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct KeyToInt<string>
{size_t operator()(const string& key){size_t ret = 0;for (auto& e : key){ret += e;ret *= 131;}return ret;}
};// 链地址法-哈希表-兼容版
namespace MyHashMS
{// 哈希表的桶结点// pair或keytemplate<class T>struct HashNode{HashNode(const T& data):_data(data),_next(nullptr){}T _data; // 对于 unordered_map,T 是 pair<const K, V>;对于 unordered_set,T 是 const KHashNode* _next;};// 编译器为了提升效率,查找只会向上,迭代器要用到HashTable,故需要前置声明template<class K, class T, class KeyToInt, class KeyOfT>class HashTable;// 迭代器template<class K, class T, class Ref, class Ptr, class KeyToInt, class KeyOfT >struct HashTable_Iterator{typedef HashNode<T> Node;typedef HashTable<K, T, KeyToInt, KeyOfT> HashTable;typedef HashTable_Iterator<K, T, Ref, Ptr, KeyToInt, KeyOfT> Self;HashTable_Iterator(Node* cur, HashTable* ht):_cur(cur),_ht(ht){}// 迭代器存储当前结点指针和其对应的哈希表指针(其实_hashtable也可以)Node* _cur;HashTable* _ht;// 前置++Self& operator++(){ if (!_cur) return *this;KeyToInt kti;KeyOfT kot;const Node* old = _cur;_cur = _cur->_next;if (!_cur){// cur为nullptr,说明当前桶的结点已经都走过了,去寻找下一个桶的第一个结点size_t hashi = kti(kot(old->_data)) % _ht->_hashtable.size() + 1;for (; hashi < _ht->_hashtable.size(); ++hashi){if (_ht->_hashtable[hashi]){_cur = _ht->_hashtable[hashi];break;}}}return *this;}Ref operator*(){return _cur->_data;}Ptr operator->(){return &(operator*());}bool operator==(const Self& other){return _cur == other._cur && _ht == other._ht;}bool operator!=(const Self& other){return !(*this == other);}};// key pair或key KeyToInt由上层传,这样修改转整数的逻辑比较方便// KeyOfT也是从上层传,set传直接返回key的,map传pair<>.first的template<class K, class T, class KeyToInt, class KeyOfT>class HashTable{// 结构体的友元声明需要带着模板参数// 这里友元声明是因为HashTable_Iterator里需要用HashTable的指针去访问其私有成员template<class K, class T, class Ref, class Ptr, class KeyToInt, class KeyOfT >friend struct HashTable_Iterator;typedef HashNode<T> Node;public:typedef HashTable_Iterator<K, T, T&, T*, KeyToInt, KeyOfT> iterator;typedef HashTable_Iterator<K, T, const T&, const T*, KeyToInt, KeyOfT> const_iterator;public:HashTable():_hashtable(__stl_next_prime(0)),_n(0){}iterator begin(){for (auto& e : _hashtable){if (e)return { e,this };}return end();}iterator end(){return { nullptr, this };}const_iterator begin() const {for (auto& e : _hashtable){if (e)return { e,this };}return end();}const_iterator end() const{return { nullptr, this };}// 清除数据,释放结点,便于析构和赋值运算符重载复用void clear(){// 走个循环就好for (size_t i = 0; i < _hashtable.size(); ++i){// 查找每个桶有无需要释放的结点,有则释放,没有就看下一个桶Node* cur = _hashtable[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_hashtable[i] = nullptr;}}// 有new申请的结点,这些结点是自定义类型,需要手动写析构函数~HashTable(){clear();}// 拷贝构造HashTable(const HashTable& other):_hashtable(other._hashtable.size()), _n(0){// 初始化列表是先走的,这里用_hashtable的size不用担心 for (size_t i = 0; i < _hashtable.size(); ++i){Node* cur = other._hashtable[i];while (cur){Insert(cur->_data);cur = cur->_next;}}}// 赋值运算符重载HashTable& operator=(const HashTable& other){// 自我赋值检测if(other != *this){// 先清除原来的数据clear();// 调整大小_hashtable.resize(other._hashtable.size());_n = 0;// 复制数据for (size_t i = 0; i < _hashtable.size(); ++i){Node* cur = other._hashtable[i];while (cur){Insert(cur->_data);cur = cur->_next;}}}return *this;}pair<iterator, bool> Insert(const T& data){KeyToInt kti;KeyOfT kot;// 不允许重复auto ret = Find(kot(data));if (ret != end())return { ret, false };// 扩容if (_n * 10 / _hashtable.size() >= 10){HashTable newHashTable;newHashTable._hashtable.resize(__stl_next_prime(_hashtable.size() + 1));for (size_t i = 0; i < _hashtable.size(); ++i){Node* cur = _hashtable[i];while (cur){size_t hashi = kti(kot(cur->_data)) % newHashTable._hashtable.size();newHashTable.Insert(cur->_data);cur = cur->_next;}}_hashtable.swap(newHashTable._hashtable);}Node* newnode = new Node(data);size_t hashi = kti(kot(data)) % _hashtable.size();// 头插newnode->_next = _hashtable[hashi];_hashtable[hashi] = newnode;++_n;return { { newnode,this }, true };}iterator Find(const K& key){KeyToInt kti;KeyOfT kot;size_t hashi = kti(key) % _hashtable.size();Node* cur = _hashtable[hashi];while (cur && kot(cur->_data) != key){cur = cur->_next;}return { cur,this };}bool Erase(const K& key){KeyToInt kti;KeyOfT kot;size_t hashi = kti(key) % _hashtable.size();Node* cur = _hashtable[hashi];Node* prev = nullptr;while (cur){if (kot(cur->_data) == key){// 头删/不是头删prev == nullptr ? _hashtable[hashi] = cur->_next : prev->_next = cur->_next;delete cur;--_n;return true;}else{prev = cur;cur = cur->_next;}}return false;}private:vector<Node*> _hashtable;size_t _n;};
}
unordered_set.h
#pragma once
#include "HashTable.h"namespace MyHashMS
{template<class K, class KeyToInt = KeyToInt<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};typedef HashTable<K, const K, KeyToInt, SetKeyOfT> HashTable;typedef typename HashTable::iterator iterator;typedef typename HashTable::const_iterator const_iterator;public: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){return _ht.Insert(key);}iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}void print(){for (auto& e : _ht){cout << e << endl;}}private:// 关于KeyToInt和SetKeyOfT要不要传模板类型的问题// 模板参数是要传类型的,而前两者K,K都是类型,显然没问题// KeyToInt是unordered_set模板参数里定义好的类型,也没问题// SetKeyOfT是在unordered_set类里面定义的一个结构体,也可以直接作为类型HashTable _ht;// 当实例化 unordered_set<int> 时,K 就会被推导为 int,然后 SetKeyOfT 就会根据 K(即 int)来创建一个实例};}
unordered_map.h
#pragma once#include "HashTable.h"namespace MyHashMS{template<class K, class V, class KeyToInt = KeyToInt<K>>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};typedef HashTable<K, pair<const K, V>, KeyToInt, MapKeyOfT> HashTable;typedef typename HashTable::iterator iterator;typedef typename HashTable::const_iterator const_iterator;public:iterator begin() { return _ht.begin(); }iterator end() { return _ht.end(); }const_iterator begin() const { return _ht.begin(); }const_iterator end() const { return _ht.end(); }V& operator[](const K& key){// ret拿到插入数据的返回值auto ret = insert({ key,V() });// 如果插入成功,返回value的引用 ( 插入功能 )// 如果插入失败,也就是原本就存在,返回原本就在的kv的value的引用 ( 修改功能 )return ret.first->second;}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);}void print(){for (auto& e : _ht){cout << e.first << " " << e.second << endl;}}private:HashTable _ht;};}
test.cpp
#include "unordered_set.h"
#include "unordered_map.h"void umap_test()
{MyHashMS::unordered_map<string, string> umap;string s[] = { "abcd", "thatgirl", "hahaha", "ohye", "handsome", "apple", "oppo", "vivo","svip" };for (auto& e : s){umap.insert({ e, e });}// 修改和插入umap["thatgirl"] = "那个女孩";umap["sort"] = "算法";umap.print();cout << endl;if (umap.find("sort") != umap.end())cout << "find it!" << endl;cout << endl;umap.erase("sort");if (umap.find("sort") == umap.end())cout << "don't find!" << endl;cout << endl;umap.print();// 测试不能修改keyauto it = umap.find("apple");// it->first = "pear";it->second = "pear";}void uset_test()
{MyHashMS::unordered_set<string> uset;string s[] = { "abcd", "thatgirl", "hahaha", "ohye", "handsome", "apple", "oppo", "vivo","svip" };for (auto& e : s){uset.insert(e);}uset.print();cout << endl;if (uset.find("ohye") != uset.end())cout << "find!" << endl;uset.erase("ohye");cout << endl;if (uset.find("ohye") == uset.end())cout << "don't find!" << endl;cout << endl;uset.print();// 测试不能修改keyauto it = uset.find("apple");// *it = "pear";}// main函数不能放到命名空间之中,不然编译器找不到程序的入口
int main()
{umap_test();// uset_test();//hash<int> int_hasher;//hash<string> s_hasher;//cout << int_hasher(123) << endl;//cout << s_hasher("acsjak") << endl;return 0;
}
完结撒花啦~
(๑•̀ㅂ•́)و✧
相关文章:

C++进阶——用Hash封装unordered_map和unordered_set
目录 前言 源码怎么说 为什么要兼容? 兼容的具体做法? 为什么要把Key转为整数(HashFcn)? 模拟实现 一、建立框架 二、迭代器 运算符重载 迭代器兼容大法 三、[ ]重载 四、实现不能修改key 实现及测试代码 …...

b612相机 13.5.5解锁会员hook
工具 lspatch(点击选最新版本下载) shizuku(点击选最新版本下载) SimpleHook(点击选最新版本下载) b612(自行百度下载) 效果图 教程 [{"packageName":"com.camp…...
iOS - 弱引用表(Weak Reference Table)
1. 基本数据结构 // 弱引用表的基本结构 struct weak_table_t {weak_entry_t *weak_entries; // 保存所有的弱引用对象size_t num_entries; // 当前存储的弱引用数量uintptr_t mask; // 哈希表大小掩码uintptr_t max_hash_displacement; /…...
C#语言的网络编程
C#语言的网络编程 引言 随着互联网的飞速发展,网络编程成为了软件开发中的一个重要领域。C#语言作为一种现代编程语言,凭借其丰富的类库、良好的可读性和强大的功能,广泛应用于开发各种网络应用程序。无论是Windows应用、Web应用还是云服务…...

【操作系统】课程 4调度与死锁 同步测练 章节测验
4.1知识点导图 处理机调度与死锁相关内容的文字整理: 基本准则 资源利用率:使系统中的处理机和其他所有资源都尽可能地保持忙碌状态。系统吞吐量:单位时间内系统所完成的作业数。公平性:使各进程都获得合理的CPU时间,而…...

如何查看已经安装的python版本和相关路径信息
如何查看已经安装的python版本和相关路径信息 本文目录: 一、通过命令行模式查询 1、通过命令where python 2、通过命令print(sys.executable) 二、在 Anaconda Navigator 中 三、只安装python的环境下 一、通过命令行模式查询 同时按windowR键,输入cmd&#x…...
设计模式-结构型-适配器模式
在软件开发中,随着系统的不断扩展和模块的不断增加,往往会遇到不同模块之间接口不兼容的情况。此时,如果我们能通过某种方式将一个接口转化为另一个接口,那么开发工作将变得更加灵活和高效。适配器模式(Adapter Patter…...
鸿蒙操作系统(HarmonyOS)
鸿蒙操作系统(HarmonyOS)是华为公司推出的一款面向未来、面向全场景的分布式操作系统。它旨在为用户提供一个更加智能、便捷和安全的操作环境,支持多种终端设备之间的无缝协作。在鸿蒙应用开发中,ArkUI作为官方推荐的用户界面开发…...

基于海思soc的智能产品开发(camera sensor的两种接口)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 对于嵌入式开发设备来说,除了图像显示,图像输入也是很重要的一部分。说到图像输入,就不得不提到camera。目前ca…...
解密LLM结构化输出:代码示例与原理分析
解密LLM结构化输出:代码示例与原理分析 一、LLM结构化输出概述 1. 结构化输出的定义与优势 结构化输出指的是语言模型(LLM)生成的遵循特定格式(如JSON、XML)的数据,这些数据易于解析和处理。相较于非结构…...
Go语言的数据类型
Go语言的数据类型详解 Go语言是一门具有简洁、高效并且强类型的编程语言。它的设计理念之一是让程序员能够以清晰、简明的方式表达自己的意图。在Go语言中,数据类型是其基础构建块之一,理解不同数据类型的特点和使用场景对于编写高效的Go程序至关重要。…...

复杂园区网基本分支的构建
目录 1、各主机进行网络配置。2、交换机配置。3、配置路由交换,进行测试。4、配置路由器接口和静态路由,进行测试。5、最后测试任意两台主机通信情况 模拟环境链接 拓扑结构 说明: VLAN标签在上面的一定是GigabitEthernet接口的,…...

如何很快将文件转换成另外一种编码格式?编码?按指定编码格式编译?如何检测文件编码格式?Java .class文件编码和JVM运行期内存编码?
如何很快将文件转换成另外一种编码格式? 利用VS Code右下角的"选择编码"功能,选择"通过编码保存"可以很方便将文件转换成另外一种编码格式。尤其,在测试w/ BOM或w/o BOM, 或者ANSI编码和UTF编码转换,特别方便。VS文件另…...

《C++11》Lambda 匿名函数从入门到进阶 优缺点分析 示例
Lambda 匿名函数从入门到进阶 C11 引入了 lambda 表达式,这是一种非常强大的功能,可以让我们在代码中定义匿名函数。它们不仅使代码更加简洁,而且在处理回调、算法和多线程编程时极为方便。本文将带你从入门到进阶,全面了解 C11 …...

连接Milvus
连接到Milvus 验证Milvus服务器正在侦听哪个本地端口。将容器名称替换为您自己的名称。 docker port milvus-standalone 19530/tcp docker port milvus-standalone 2379/tcp docker port milvus-standalone 192.168.1.242:9091/api/v1/health 使用浏览器访问连接地址htt…...
Linux——修改文件夹的所属用户组和用户
一、命令 举例: 授权 MOT17 文件夹 给 hust_xxx 用户: sudo chown -R hust_xxx:hust_xxx MOT17参考 Linux授权文件夹给用户...

Vue Amazing UI 组件库(Vue3+TypeScript+Vite 等最新技术栈开发)
Vue Amazing UI 一个 Vue 3 组件库 使用 TypeScript,都是单文件组件 (SFC),支持 tree shaking 有点意思 English | 中文 Vue Amazing UI 是一个基于 Vue 3、TypeScript、Vite 等最新技术栈开发构建的现代化组件库,包含丰富的 UI 组件和常…...

计算机Steam报错failedtoloadsteamui.dll怎么解决?DLL报错要怎么修复?
计算机Steam报错“Failed to Load SteamUI.dll”?这里有专业的解决方案! 作为软件开发领域的一名从业者,我深知电脑在运行过程中可能会遇到的各种问题,尤其是像Steam这样的大型游戏平台。今天,我将为大家科普一下Stea…...

如何开发一个简单的 dApp
后端合约 执行 sui move new resource_manage 创建一个包 接着就可以开始编写合约了 首先创建两个 Struct 用来创建 Profile 并记录在 State 中 public struct State has key {id: UID,users: Table<address, address>, }public struct Profile has key {id: UID,nam…...
TDengine 签约智园数字,助力化工园区智联未来
近年来,随着化工行业对安全、环保、高效运营的要求日益提高,化工园区的数字化转型成为必然趋势。从数据孤岛到全面互联,从基础监控到智能分析,如何高效管理和利用时序数据已成为化工园区智能化升级的关键环节。作为一家专注于时序…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...

stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...

均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...