C++STL学习之unordered_map与unordered_set(底层Hash)

前言:我们前面已经学习论map和set,现在又冒出来一个unordered_map和unordered_set,这两个有啥差别吗?前面我们已经说过,map和set的底层是红黑树,那unordered_map和unordered_set的底层是什么呢?接下来我们会逐渐揭开它神秘的面纱。

如果大家不了解map和set可以看我往期博客:C++之map与set的使用与原理+拓展avl树(详解)-CSDN博客

目录

一,unordered_map和unordered_set的使用

1)unordered_set

1)函数模板参数

2)构造函数

3)成员函数及功能

2)unordered_map

1)函数模板参数

2)构造函数

2)成员函数及功能

二,unordered_map和unordered_set的底层实现及代码

1)哈希表

2)常见哈希函数

3)线性探测法

4)线性探测法代码

1)提前准备

2)私有成员变量

3)插入

4)删除

5)查找

6)完整代码

5)悬挂法

1)私有成员变量

2)插入

3)删除

4)查找

5)完整代码


一,unordered_map和unordered_set的使用

1)unordered_set
1)函数模板参数

Hash模板参数默认是哈希桶(等下会讲原理),Pred则是一个判断两个key是不是相等的函数,用来查找元素和插入元素时判断是否存在,Alloc则类似于new开辟空间。

2)构造函数

explicit unordered_set ( const allocator_type& alloc );

 分配器构造法

template <class InputIterator>unordered_set ( InputIterator first, InputIterator last,size_type n = /* see below */,const hasher& hf = hasher(),const key_equal& eql = key_equal(),const allocator_type& alloc = allocator_type() );

 迭代器范围构造法

unordered_set ( const unordered_set& ust );
unordered_set ( const unordered_set& ust, const allocator_type& alloc );

 拷贝构造法

unordered_set ( unordered_set&& ust );
unordered_set ( unordered_set&& ust, const allocator_type& alloc );

 移动构造函数(右值引用构造,我后面的博客应该会讲右值引用,现在大家期待一下吧)

unordered_set ( initializer_list<value_type> il,size_type n = /* see below */,const hasher& hf = hasher(),const key_equal& eql = key_equal(),const allocator_type& alloc = allocator_type() );

 初始化列表构造法(在C++11后,一切皆可{}初始化,例如:

unordered_set<int> a={1,2,3,4,5}

 1,2,3,4,5会形成一个初始化列表,这样子方便连续的构造

可以一次性就把所有初始化列表里面的值构造,无需多次调用构造函数。

3)成员函数及功能

unordered_set - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/unordered_set/unordered_set/

2)unordered_map
1)函数模板参数

首先我们看模板参数,T也就是value,Hash函数默认是哈希桶,Pred则是一个判断两个key是不是相等的函数,用来查找元素和插入元素时判断是否存在,Alloc则类似于new开辟空间。

2)构造函数

explicit unordered_map ( const allocator_type& alloc );

分配器构造法

template <class InputIterator>  
unordered_map ( InputIterator first, InputIterator last,  size_type n = /* see below */,  const hasher& hf = hasher(),  const key_equal& eql = key_equal(),  const allocator_type& alloc = allocator_type() );

迭代器范围构造法

unordered_map ( const unordered_map& ump );  
unordered_map ( const unordered_map& ump, const allocator_type& alloc );

拷贝构造函数

unordered_map ( unordered_map&& ump );  
unordered_map ( unordered_map&& ump, const allocator_type& alloc );

移动构造函数(右值引用构造) 

unordered_map ( initializer_list<value_type> il,  size_type n = /* see below */,  const hasher& hf = hasher(),  const key_equal& eql = key_equal(),  const allocator_type& alloc = allocator_type() );

初始化列表构造法

2)成员函数及功能

由于这是老生常谈了,大家看文档估计也能看懂,这里给大家一个链接,有兴趣的自行观看

unordered_map - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/unordered_map/unordered_map/

二,unordered_map和unordered_set的底层实现及代码

1)哈希表

哈希表有点类似于我们的计数排序,但是计数排序有缺点就是只能排整形家族,哈希表就是会开大于我们要存储数据元素个数的大小(一般来说是数组),然后通过一个特点的转换公式把不同类型全部转化为无符号整形,然后按照下标存储,但是如何将不同类型的元素转换整形呢?这是一个问题,我们这里提供一个思路,假如是string或者插入类型,是不是它们都有ascll码值,将ascll码值转换为整型不就行了吗?

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};// 特化  是匹配问题,当为string类型是不会走上面的HashFunc函数,特化的模板函数的格式在下面
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash += e;hash *= 131;}return hash;}
};

我们也提供几种Hash整形的转换方法。

2)常见哈希函数

1. 直接定址法--(常用)
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
优点:简单、均匀
缺点:需要事先知道关键字的分布情况
使用场景:适合查找比较小且连续的情况
面试题:字符串中第一个只出现一次字符
2. 除留余数法--(常用)
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址
3. 平方取中法--(了解)
假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址;
再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况
4. 折叠法--(了解)
折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。
折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况
5. 随机数法--(了解)
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random为随机数函数。
通常应用于关键字长度不等时采用此法
6. 数学分析法--(了解)
设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址。例如:

大家看完上面的内容,有没有发现一个问题,不论是哪种方法都会出现一个问题,可能不同的元素会映射到同一个位置,那我们该怎么办呢?现在给大家提供两种解决方法。

3)线性探测法

这种方法的解决办法是当发现两个位置冲突了之后,就从这个元素开始查找下一个空的元素,如果发现空的元素就插入,有些人可能会说如果全满了呢?那就回到0号下标开始继续查找,因为数组大小是大于元素个数的,所有不要担心全部满了。那查找呢?这种情况不能只查找映射位置,要一直往后找,直到碰到空。那如何删除了,删除不能直接删除,这样子可能会导致下次查找元素出现错误,因为这个位置制空后,查找的原则是找到空就停止,原本应该继续往后找的,但是因为你删除元素导致此位置为空,无法向后继续找,所有我们要有一个标记方法标注这里是被删除的,而不是本身就为空。

4)线性探测法代码
1)提前准备

首先我们需要一个东西来标记当前位置的状态,辨别删除和为空,防止误判,我们采用枚举

enum State { EMPTY, EXIST, DELETE };
2)私有成员变量

数组加枚举变量,数组因为里面要包括枚举和元素所以使用结构体封装一下

class Hash{
struct Elem{pair<K, V> _val;  //一个存Key,一个存valueState _state;};
private:std::vector<Elem> _ht;size_t _size;size_t _totalSize;  // 哈希表中的所有元素:有效和已删除, 扩容时候要用到
};
3)插入
bool Insert(const pair<K, V>& val) {if (((double)_size/_ht.size())>=0.7) {//如果元素占了整个数组大小的7/10就扩容HashTable<K, V> a;               a._ht.resize(_ht.size()*2);        //开辟新的空间for (size_t i = 0; i < _ht.size(); i++) {if (_ht[i]._state == EXIST) {a.Insert(_ht[i]._val);}}a._ht.swap(_ht);                  //交换数据_totalSize = _size;               }size_t site = val.first % _ht.size();  //哈希函数求映射位while (_ht[site]._state ==EXIST) {      //找到空的位置或者已经被删除了的位置site++;site = site % _ht.size();}_ht[site]._state = EXIST;            //改变状态_ht[site]._val = val;                _size++;_totalSize++;                        //数据个数加加return true;}
4)删除
// 删除bool Erase(const K& key) {size_t site = Find(key); //复用Find函数if (site == 0)           //没找到,返回falsereturn false;           _ht[site - 1]._state = DELETE;    //改变状态和数据个数_size--;return true;}
5)查找
// 查找size_t Find(const K& key) {size_t site = key % _ht.size();        //利用哈希函数求映射值while (_ht[site]._state != EMPTY&&_ht[site]._val.first!=key) {   site++;                  //找到元素的位置并且位置状态位存在 site = site % _ht.size();}if (_ht[site]._state == EMPTY||_ht[site]._state==DELETE) return 0;return site+1;         //返回下标}
6)完整代码
#pragma once
#include<string>
#include<vector>
#include <algorithm>  
#include<iostream>
using namespace std;
namespace Close_Hash
{enum State { EMPTY, EXIST, DELETE };template<class K, class V>class HashTable{struct Elem{pair<K, V> _val;State _state;};public:HashTable(size_t capacity = 5): _ht(capacity), _size(0), _totalSize(0){for (size_t i = 0; i < capacity; ++i)_ht[i]._state = EMPTY;}// 插入bool Insert(const pair<K, V>& val) {if (((double)_size/_ht.size())>=0.7) {HashTable<K, V> a;a._ht.resize(_ht.size()*2);for (size_t i = 0; i < _ht.size(); i++) {if (_ht[i]._state == EXIST) {a.Insert(_ht[i]._val);}}a._ht.swap(_ht);_totalSize = _size;}//cout <<_size<<" "<<_ht.size() << "容量比:" << (double)_size / _ht.size() << endl;size_t site = val.first % _ht.size();while (_ht[site]._state ==EXIST) {site++;site = site % _ht.size();}_ht[site]._state = EXIST;_ht[site]._val = val;_size++;_totalSize++;return true;}// 查找size_t Find(const K& key) {size_t site = key % _ht.size();while (_ht[site]._state != EMPTY&&_ht[site]._val.first!=key) {site++;site = site % _ht.size();}if (_ht[site]._state == EMPTY||_ht[site]._state==DELETE)return 0;return site+1;}// 删除bool Erase(const K& key) {size_t site = Find(key);if (site == 0)return false;_ht[site - 1]._state = DELETE;_size--;return true;}size_t Size()const{return _size;}bool Empty() const{return _size == 0;}void Swap(HashTable<K, V>& ht){swap(_size, ht._size);swap(_totalSize, ht._totalSize);_ht.swap(ht._ht);}private:size_t HashFunc(const K& key){return key % _ht.capacity();}private:std::vector<Elem> _ht;size_t _size;size_t _totalSize;  // 哈希表中的所有元素:有效和已删除, 扩容时候要用到};
}
5)悬挂法

悬挂法人如其名,它的结构就是一个数组下面挂在一堆链表,也就是说数组里面并不直接存着元素,而是存着指针,这种方法有什么好处呢?前面我们的线性探测法如果当前位置被占了就需要找下一个空位,而悬挂法只需要在链表下面多加一个元素就行了。但是有一个小技巧,我们新加入的元素不需要挂在最后,只需要取代第一个结点的位置就行了。

1)私有成员变量

悬挂法需要一个一个数组,数组里面应该包括一个结点的struct指针,struct里面应该包括数据,和下一个结点的地址。

template<class T>struct HashNode{HashNode<T>* _next;T _data;HashNode(const T& data):_next(nullptr), _data(data){}};
private:vector<Node*> _table;size_t _size;      // 哈希表中有效元素的个数
2)插入

悬挂法插入结点先通过哈希函数找到下标,然后按照我上面说的那种方法,具体代码实现及注释如下

Node* Insert(const V& data){// 0. 检测是否需要扩容CheckCapacity();// 1. 通过哈希函数计算data所在的桶号size_t bucketNo = HashFunc(data);// 2. 检测该元素是否在bucketNo桶中//    本质:检测链表中是否存在data的节点Node* pCur = _table[bucketNo];while (pCur){if (pCur->_data == data)return nullptr;pCur = pCur->_pNext;}// 插入新节点,直接头插pCur = new Node(data);pCur->_pNext = _table[bucketNo];_table[bucketNo] = pCur;++_size;return pCur;}
3)删除

删除元素需要先通过哈希函数找到下标,然后按照顺序查找是否存在这个元素,然后删除,但是删除要注意必须保存其父节点不然删除无法更新链表,还有就要要注意删除头节点要单独处理

// 删除哈希桶中为data的元素(data不会重复)bool Erase(const V& data){//哈希函数找结点,然后保存下标,需要一个指针指向其父亲size_t bucketNo = HashFunc(data);Node* pCur = _table[bucketNo];Node* pPre = nullptr;//找到下标后循坏查找while (pCur){if (data == pCur->_data){// 删除if (_table[bucketNo] == pCur){// 删除第一个节点_table[bucketNo] = pCur->_pNext;}else{// 删除的不是第一个节点pPre->_pNext = pCur->_pNext;}delete pCur;--_size;return true;}pPre = pCur;pCur = pCur->_pNext;}return false;}
4)查找

如果仔细看了上面,估计查找已经手到擒来了,找到映射下标遍历就是了

Node* Find(const V& data){size_t bucketNo = HashFunc(data);Node* pCur = _table[bucketNo];while (pCur){if (data == pCur->_data)return pCur;pCur = pCur->_pNext;}return nullptr;}size_t Size()const{return _size;}bool Empty()const{return 0 == _size;}
5)完整代码
#pragma once
#include <string>
#include <vector>
#include "Common1.h"using namespace std;namespace OpenHash
{template<class T>class HashFunc{public:size_t operator()(const T& val){return val;}};template<>class HashFunc<string>{public:size_t operator()(const string& s){const char* str = s.c_str();unsigned int seed = 131; // 31 131 1313 13131 131313unsigned int hash = 0;while (*str){hash = hash * seed + (*str++);}return hash;}};template<class V>struct HashBucketNode{HashBucketNode(const V& data): _pNext(nullptr), _data(data){}HashBucketNode<V>* _pNext;V _data;};// 本文所实现的哈希桶中key是唯一的template<class V, class HF = HashFunc<V>>class HashBucket{typedef HashBucketNode<V> Node;typedef Node* PNode;typedef HashBucket<V, HF> Self;public:HashBucket(size_t capacity): _table(GetNextPrime(capacity)), _size(0){}~HashBucket(){Clear();}// 哈希桶中的元素不能重复Node* Insert(const V& data){// 0. 检测是否需要扩容CheckCapacity();// 1. 通过哈希函数计算data所在的桶号size_t bucketNo = HashFunc(data);// 2. 检测该元素是否在bucketNo桶中//    本质:检测链表中是否存在data的节点Node* pCur = _table[bucketNo];while (pCur){if (pCur->_data == data)return nullptr;pCur = pCur->_pNext;}// 插入新节点pCur = new Node(data);pCur->_pNext = _table[bucketNo];_table[bucketNo] = pCur;++_size;return pCur;}// 删除哈希桶中为data的元素(data不会重复)bool Erase(const V& data){size_t bucketNo = HashFunc(data);Node* pCur = _table[bucketNo];Node* pPre = nullptr;while (pCur){if (data == pCur->_data){// 删除if (_table[bucketNo] == pCur){// 删除第一个节点_table[bucketNo] = pCur->_pNext;}else{// 删除的不是第一个节点pPre->_pNext = pCur->_pNext;}delete pCur;--_size;return true;}pPre = pCur;pCur = pCur->_pNext;}return false;}Node* Find(const V& data){size_t bucketNo = HashFunc(data);Node* pCur = _table[bucketNo];while (pCur){if (data == pCur->_data)return pCur;pCur = pCur->_pNext;}return nullptr;}size_t Size()const{return _size;}bool Empty()const{return 0 == _size;}void Clear(){for (size_t i = 0; i < _table.capacity(); ++i){Node* pCur = _table[i];// 删除i号桶所对应链表中的所有节点while (pCur){// 采用头删_table[i] = pCur->_pNext;delete pCur;pCur = _table[i];}}_size = 0;}size_t BucketCount()const{return _table.capacity();}void Swap(Self& ht){_table.swap(ht._table);swap(_size, ht._size);}private:size_t HashFunc(const V& data){return HF()(data) % _table.capacity();}void CheckCapacity(){if (_size == _table.capacity()){
#if 0HashBucket<T> ht(_size * 2);// 将旧哈希桶中的元素向新哈希桶中进行搬移// 搬移所有旧哈希桶中的元素for (size_t i = 0; i < _table.capacity(); ++i){Node* pCur = _table[i];while (pCur){ht.Insert(pCur->_data); // new 节点pCur = pCur->_pNext;}}Swap(ht);
#endifSelf ht(GetNextPrime(_size));// 将旧哈希桶中的节点直接向新哈希桶中搬移for (size_t i = 0; i < _table.capacity(); ++i){Node* pCur = _table[i];while (pCur){// 将pCur节点从旧哈希桶搬移到新哈希桶// 1. 将pCur节点从旧链表中删除_table[i] = pCur->_pNext;// 2. 将pCur节点插入到新链表中size_t bucketNo = ht.HashFunc(pCur->_data);// 3. 插入节点--->头插pCur->_pNext = ht._table[bucketNo];ht._table[bucketNo] = pCur;}}this->Swap(ht);}}private:vector<Node*> _table;size_t _size;      // 哈希表中有效元素的个数};
}

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

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

相关文章

esp32CAM环境搭建(arduino+MicroPython+thonny+固件)

arduino ide 开发工具 arduino版本&#xff1a;1.8.19 arduino ide 中文设置&#xff1a;​ file >> preferences >> ​ arduino IDE 获取 ESP32 开发环境&#xff1a;打开 Arduino IDE &#xff0c;找到 文件>首选项 ,将 ESP32 的配置链接填入附加开发板管理网…

【Canvas与艺术】简约式胡萝卜配色汽车速度表

【效果图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>胡萝卜色汽车速度仪表盘简化版</title><style type"…

2月京东天猫淘宝茗茶电商数据分析(茗茶行业未来趋势分析)

随着消费者对健康饮食和品质生活的追求&#xff0c;茗茶行业受到许多青睐和关注。 根据鲸参谋数据显示&#xff0c;今年2月&#xff0c;茗茶行业在某东平台上销售数据呈现出一种特殊的趋势。销售量总计约450万件&#xff0c;同比去年下滑了7%。&#xff1b;销售额总计6.4亿元&…

Linux安装Maven

一、下载安装Maven 1.1 下载地址 官网下载地址: https://maven.apache.org/download.cgi 1.2 安装版本下载 进入下载页面选择需要的版本进行下载。 1.3 版本安装 将下载完的安装包&#xff0c;上传到Linux服务器上某个目录下&#xff0c;将其解压出来就好。 ## 创建安装…

《无名之辈》新手攻略:抢先领取神秘礼包!

欢迎来到《无名之辈》&#xff01;在这个丰富多彩的冒险世界里&#xff0c;你将踏上一段充满挑战与机遇的旅程。以下是针对新手玩家的详尽攻略&#xff0c;助你快速提升实力&#xff0c;成为一名优秀的冒险者。 第一步&#xff1a;迅速起步 当你第一次踏入《无名之辈》的世界时…

文献速递:基于SAM的医学图像分割--SAMUS:适应临床友好型和泛化的超声图像分割的Segment Anything模型

Title 题目 SAMUS: Adapting Segment Anything Model for Clinically-Friendly and Generalizable Ultrasound Image Segmentation SAMUS&#xff1a;适应临床友好型和泛化的超声图像分割的Segment Anything模型 01 文献速递介绍 医学图像分割是一项关键技术&#xff0c;用…

ubuntu卸载Anaconda

1. 删除配置的环境变量 sudo gedit ~/.bashrc # >>> conda initialize >>> # !! Contents within this block are managed by conda init !! __conda_setup"$(/work3/ai_tool/anaconda3/bin/conda shell.bash hook 2> /dev/null)" if [ $? -…

edge浏览器彻底删除用户账号

效果图 操作教程 -- 这个教程里面比较重要的是3,5,8 -- 如果不执行第8步&#xff0c;还是没有任何效果。 -- 教程地址 https://blog.csdn.net/qq_37579133/article/details/128777770 继续删除windows凭据 结束 -----华丽的分割线&#xff0c;以下是凑字数&#xff0c;大家不…

《无名之辈》天涯镖局攻略:高效拉镖窍门!

《无名之辈》天涯镖局开启要注意什么&#xff0c;在这里&#xff0c;每一次运镖都是一次刺激的冒险&#xff0c;而掌握合适的策略将让你事半功倍。以下是天涯镖局的开启攻略&#xff0c;助你在危机四伏的路途上赢得胜利。 ① 拉取适当级别的包子和加速卡 在天涯镖局中&#xf…

阿里云部署宝塔,设置了安全组还是打不开。

1.在安全组是开放正确的端口好。8888要开&#xff0c;但是不只是开放8888&#xff0c;举个例子&#xff0c;https://47.99.53.222:17677/49706cf7这个&#xff0c;要开放17677这个端口号。 2.安全组要挂载到实例上&#xff0c;从三个点的进入点击管理实例&#xff0c;加到对应的…

Rust 02.控制、引用、切片Slice

1.控制流 //rust通过所有权机制来管理内存&#xff0c;编译器在编译就会根据所有权规则对内存的使用进行 //堆和栈 //编译的时候数据的类型大小是固定的&#xff0c;就是分配在栈上的 //编译的时候数据类型大小不固定&#xff0c;就是分配堆上的 fn main() {let x: i32 1;{le…

图片格式转换:快速将PNG转换为JPG的步骤

在我们的日常生活中&#xff0c;经常会遇到需要改变图片格式的情况&#xff0c;有时候&#xff0c;我们可能需要将PNG格式的图片转换为jpg格式&#xff0c;以适应不同的需求和应用场景;本文将介绍哥实用的方法和工具&#xff0c;帮助您顺利将png图片转换为jpg格式。 压缩图网站…

面试经验分享 | 蓝队面试经验

关于蓝队面试经验 1.自我介绍能力 重要性 为什么将自我介绍能力放在第一位&#xff0c;实际上自我介绍才是面试中最重要的一点&#xff0c;因为护网面试并没有确定的题目&#xff0c;让面试官去提问 更多是的和面试官的一种 “交谈” &#xff0c;面试的难易程度也自然就取决…

【第三方登录】Twitter

创建应用 APPID 和 相关回调配置 重新设置api key 和 api secret 设置回调和网址 还有 APP的类型 拿到ClientID 和 Client Secret 源码实现 获取Twitter 的登录地址 public function twitterUrl() {global $db,$request,$comId;require "inc/twitter_client/twitte…

Springboot整合瀚高

需要下载highgo驱动,然后将jar包打入进自己本地maven中 下载地址: highgi6.2.4 1.打开jar包所在的文件&#xff0c;然后在该文件夹中打开命令窗口&#xff08;或者先打开命令窗口&#xff0c;然后cd到jar所在文件夹&#xff09; install-file -Dfile&#xff1a;jar包名Dart…

腾讯VS网易:一场不见终局的游戏未来之战

国内游戏霸主腾讯最近赚足了眼球。 总体上看&#xff0c;腾讯手握“游戏社交”两大王牌&#xff0c;最近发布的财报十分亮眼&#xff0c;其2023年总营收和净利润分别同比增长10%和36%&#xff0c;展现了互联网巨头的强劲活力。 然而巨头亦有焦虑&#xff0c;增值服务营收同比…

Prompt Engineering的4 种方法

此为观看视频 4 Methods of Prompt Engineering 后的笔记。 从通用模型到专用模型&#xff0c;fine tuning&#xff08;微调&#xff09;和prompt engineering&#xff08;提示工程&#xff09;是2种非常重要的方法。本文深入探讨了prompt engineering的4种方法。 首先&#…

23届嵌入式被裁,有什么好的就业建议?

最近看到了一个提问&#xff0c;原话如下&#xff1a; 本人23届毕业生&#xff0c;就业方向嵌入式软件&#xff0c;坐标深圳&#xff0c;工作3月公司裁员&#xff0c;目前接近12月开始找工作。 boss上投递简历&#xff0c;校招岗&#xff0c;比较有规模的好公司基本已读不回&am…

你的 Python 代码需要解释一下了!

Python 是一种相对简单的编程语言。它主要以解释型语言著称&#xff0c;这意味着每行代码都要通过解释器逐行执行。不过在某些时候&#xff0c;将 Python 代码翻译成计算机可以理解的内容&#xff0c;然后再逐行执行&#xff0c;可以减少繁琐。 在这种情况下&#xff0c;编译器…

VRAY渲染设置大神参数(建议收藏)

3dmax效果图云渲染平台——渲染100以3ds Max 2024、VR 6.2、CR 11.2等最新版本为基础&#xff0c;兼容fp、acescg等常用插件&#xff0c;同时LUT滤镜等参数也得到了同步支持。注册填邀请码【7788】可领30元礼包和免费渲染券哦~ 公用&#xff1a;输出大小&#xff1a;一般小图50…