【哈希】用哈希桶封装unordered_map unordered_set

图片名称
🎉博主首页: 有趣的中国人

🎉专栏首页: C++进阶

🎉其它专栏: C++初阶 | Linux | 初阶数据结构

在这里插入图片描述

小伙伴们大家好,本片文章将会讲解 用哈希桶封装 unordered_map & unordered_set 的相关内容。

如果看到最后您觉得这篇文章写得不错,有所收获,麻烦点赞👍、收藏🌟、留下评论📝。您的支持是我最大的动力,让我们一起努力,共同成长!

🎉系列文章: 1. 闭散列的线性探测实现哈希表

🎉系列文章: 2. 开散列的哈希桶实现哈希表

文章目录

  • `0. 前言`
  • `1. K模型和KV模型模板参数传递`
    • ==<font color = blue><b>🎧1.0 相关解释🎧==
    • ==<font color = blue><b>🎧1.1 模板参数传递思路🎧==
    • ==<font color = blue><b>🎧1.2 模板参数传递图解🎧==
  • `2. 哈希表中函数的修改`
    • ==<font color = blue><b>🎧2.1 Insert函数修改思路🎧==
    • ==<font color = blue><b>🎧2.2 Insert函数修改后的代码🎧==
    • ==<font color = blue><b>🎧2.3 Find & Erase 函数的修改🎧==
    • ==<font color = blue><b>🎧2.4 Find & Erase 修改后的代码🎧==
  • `3. 哈希表迭代器的实现`
    • ==<font color = blue><b> 🎧3.1 operator++() 的实现🎧==
    • ==<font color = blue><b> 🎧3.2 operator*() & operator->() 的实现🎧==
    • ==<font color = blue><b> 🎧3.3 operator!=()的实现🎧==
    • ==<font color = blue><b> 🎧3.4 Begin() & End()的实现==
  • `4. const迭代器的实现`
  • `4. unordered_map的operator[]的实现`
    • ==<font color = blue><b> 🎧4.1 Find()的修改==
    • ==<font color = blue><b> 🎧4.2 Insert()的修改==
    • ==<font color = blue><b> 🎧4.3 operator[]的实现==
  • `5. 哈希桶封装的完整代码`



0. 前言


在之前的文章中我们详细描述了如何用 开放寻址法(闭散列)的线性探测 和 开散列的哈希桶 的方法来实现哈希表。此篇文章我们将用 哈希桶 来实现 unordered_map & unordered_set 的封装。




1. K模型和KV模型模板参数传递


🎧1.0 相关解释🎧


由于unordered_mapunordered_set的分别是 KV 类型和 K 类型,存储的数据类型是不相同的, 但是底层的哈希桶只有一份,这个时候我们得想到用模板的方法来解决此问题。(这块类似于红黑树那的封装)

🎧1.1 模板参数传递思路🎧


  1. 我们用哈希表来实现unordered_mapunordered_set,因此他们两个的成员变量就是用哈希桶实现出的哈希表的对象;
  2. 哈希表的前两个模板参数是 KVKey: 关键字Value: 值),但是我们不能这样传递,我们可以将第二个参数改为TType: 类型):
    • 这个T是我们在上一层:unordered_mapunordered_set进行传递的;
    • 如果是unordered_map传递的就是KV类型的pair
    • 如果是unordered_set传递的就是K类型。
  3. 由于哈希表中还封装了哈希节点,这个哈希节点的模板参数一开始传递的也是两个: KVKey: 关键字Value: 值),我们此时就将两个模板参数换为一个TType: 类型)。

🎧1.2 模板参数传递图解🎧


unordered_map模板参数的传递图示:

在这里插入图片描述

unordered_set模板参数的传递图示:

在这里插入图片描述



2. 哈希表中函数的修改


由于传递的模板参数改变了,所以我们对应的函数实现也要发生改变。


🎧2.1 Insert函数修改思路🎧


Inert函数的修改

  1. 起初给Insert()函数传递的参数类型是const pair<K, V>& kv, 现在传递的参数类型是const T& data
  2. unordered_set调用Insert(),传入的参数是 K K K 类型( T T T 会被实例化成 K K K),当unordered_map调用Insert(),传入的参数类型是 K V KV KV 类型( T T T 会被实例化成pair<K, V>);
  3. 那么问题就来了,对于 K K K 类型插入数据时要对key使用哈希函数,对于 K V KV KV 类型的pair,比对pair的第一个参数使用哈希函数,因此我们要用仿函数来取出对应的值

仿函数逻辑

  1. 我们要在哈希表的模板参数中 增加一个仿函数的模板参数 KeyOfT,并在两个容器的成员变量中传入对应的仿函数。
  2. 对于unordered_map的仿函数,我们要的就是key,因此直接返回 K K K 类型的变量即可;
  3. 对于unordered_map的仿函数,我们要的是pair的第一个参数,所以要返回传入的参数的第一个参数。

关于哈希函数

之前的文章讲过,对于不同类型的数据,我们要采用相应的哈希函数,使其在整数范围内有唯一的映射。尤其是对于字符串类型string,我们经常使用,还采用了模板的特化。

这一操作(模板特化、模板缺省参数的传递)我们是在哈希表的实现中完成的。

但是由于我们要对哈希表进行封装,对外暴露的就是unordered_setunordered_map,要把以上的操作放在此层。

🎧2.2 Insert函数修改后的代码🎧


unordered_set的仿函数代码:

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

unordered_map的仿函数代码:

template<class K, class V>
struct mapKeyOfT
{const K& operator()(const pair<K, V>& kv){return kv.first;}
};

Insert的修改代码:

bool Insert(const T& data)
{// 定义哈希函数对象HashFunc hf;// 定义取值的对象KeyOfT kot;if (Find(kot(data))){return false;}// 判断负载因子扩容// 负载因子为1扩容if (_n == _tables.size()){vector<Node*> newtable;size_t newsize = 2 * _tables.size();newtable.resize(newsize, nullptr);for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];Node* next = nullptr;while (cur){next = cur->_next;// 先取出对应的Key值,然后用哈希函数映射到相应的整数size_t hashi = hf(kot(cur->_data)) % newtable.size();cur->_next = newtable[hashi];newtable[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtable);}// 先取出对应的Key值,然后用哈希函数映射到相应的整数size_t hashi = hf(kot(data)) % _tables.size();Node* newnode = new Node(data);// 头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;
}

🎧2.3 Find & Erase 函数的修改🎧


Find & Erase 函数的修改

  1. 由于Find函数和Erase函数传递参数的时候就是Key,因此在函数内部的代码逻辑无需进行修改。
  2. 这同样体现了哈希表的第一个模板参数 K K K 的作用,如果只有第二个模板参数,那么将无法进行查询。

🎧2.4 Find & Erase 修改后的代码🎧


Find代码:

Node* Find(const K& key)
{HashFunc hf;size_t hashi = hf(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return cur;}else{cur = cur->_next;}}return nullptr;
}

Erase代码:

bool Erase(const K& key)
{HashFunc hf;size_t hashi = hf(key) % _tables.size();Node* cur = _tables[hashi];Node* prev = nullptr;while (cur){if (kot(cur->_data) == key){if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;--_n;return true;}else{prev = cur;cur = cur->_next;}}return false;
}


3. 哈希表迭代器的实现


如果用 原生指针的解引用、加加、箭头等 操作无法满足哈希表的相关操作,因此要对 节点指针进行封装,并采用运算符重载 来实现迭代器的相关操作。

🎧3.1 operator++() 的实现🎧


operator++() 实现思路

  1. 假设当前所在的节点为cur,当前所在的哈希桶为i
  2. 如果当前节点不为空,加加到下一个节点;
  3. 如果当前节点为空,那么就要去下一个不为空的桶中寻找,问题就来了,如何寻找下一个桶呢?
    • 因为要找到下一个桶,并且要访问桶中的元素,因此要把 哈希表的指针传到迭代器的类中
    • 之后,判断下个位置的指针是否为空,如果不为空,就让_node == _node->next
    • 如果为空,就寻找下一个不为空的桶:
      • 先利用当前指针计算出当前位置所在的桶;
      • 向后寻找不为空的桶,如果找到了,就让cur = _tables[i],直到走到空为止,不然继续找下一个不为空的节点;
      • 如果一直到 i = _tables.size(),还没有找到不为空的节点,证明已经访问完毕,那么_node = nullptr
  4. 最后返回此迭代器类型的对象 return *this

operator()++代码:

__HashIterator<K, T, KeyOfT, HashFunc> operator++()
{KeyOfT kot;Node* cur = _node;// 判断下个位置的指针是否为空,如果不为空,就让`_node == _node->next`if (_node->_next){_node = _node->_next;}else{// 先利用当前指针计算出当前位置所在的桶size_t hashi = kot(cur->_data) % _pht->_tables.size();// 向后寻找不为空的桶,如果找到了,// 就让`cur = _tables[i]`,直到走到空为止,不然继续找下一个不为空的节点;++hashi;for (; hashi < _pht->_tables.size(); ++hashi){if (_pht->_tables[hashi]){cur = _pht->_tables[hashi];break;}}// 出循环有两种情况,一种是找到了不为空的节点,一种是直到走到最后都没有找到不为空的节点,两种情况分别判断以下。// 如果走到最后还未找到,就让_node = nullptr;if (hashi == _pht->_tables.size()){_node = nullptr;}// 如果找到不为空的节点就让_node = cur;else{_node = cur;}}return *this;
}

🎧3.2 operator*() & operator->() 的实现🎧


operator*() 实现思路

  • 返回_node->_data即可。

operator->() 实现思路

  • 返回&_node->_data即可。
    • 这里在unordered_set底层调用的时候实际上是用了两次->
    • 一次是调用operator->()的运算符重载,访问到了pair的地址;
    • 然后再用->pair进行解引用访问到它的firstsecond
    • 但是编译器做了优化直接写一个->即可。

operator*() & operator->()的代码:

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

🎧3.3 operator!=()的实现🎧


operator!=() 实现思路

  • 判断两个_node的地址是否相同即可:_node != h._node

operator!=() 的代码:

bool operator!=(const __HashIterator<K, T, KeyOfT, HashFunc>& h)
{return _node != h._node;
}

🎧3.4 Begin() & End()的实现


Begin()的实现思路

  1. 开始节点就是第一个不为空的桶中存放的节点:
    • 我们先定义一个节点cur,让他向后寻找到第一个不为空的位置。
    • 找到了返回迭代器类型的数据,由于迭代器的构造函数是两个,要传两个参数,第一个是cur指针,第二个是this指针:return Iterator(cur, this)
      • 因为迭代器中需要的是哈希表类型的指针,而this就是能代表此哈希表类型的指针,所以用this即可。
  2. 如果找不到为空的节点返回迭代器构造的nullptrreturn Iterator(nullptr, this)

End()的实现思路

  • 返回迭代器构造的nullptrreturn Iterator(nullptr, this)

Begin() & End()的代码:

Iterator Begin()
{for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];if (cur){return Iterator(cur, this);}}return End();
}Iterator End()
{return Iterator(nullptr, this);
}


4. const迭代器的实现


const迭代器也是借用了模板的功能,只是解引用和箭头的返回值不同。


const迭代器的实现思路:

  1. 首先要给迭代器的模板加两个参数RefPtr,代表传入的T类型的引用和指针;
  2. 改变operator*() & operator->()的返回值,分别是:RefPtr
  3. 在哈希表类中增加const类型的 Begin() & End()
  4. 这里有三个问题:
    • __HashIterator中有一个HashTable的对象,所以要在__HashIterator前先声明一下HashTable,不然会报错。
    • 由于_tablesHashTable类中的private成员变量,因此要在HashTable增加__HashIterator的友元声明。
    • 🔎这两个问题都可以用内部类来解决,因为内部类是是外部类的友元,但是C++不太喜欢用内部类,博主这里就不用内部类实现了,而且内部类实现的方法也相对简单。🔍
    • 由于迭代器中有HashTable*类型的指针,而普通迭代器的指针类型就是HashTable*,而const迭代器的指针类型是const HashTable*,我们知道权限只能缩小,不能放大,因此要把__HashIterator中的HashTable*的成员变量一直修改为const HashTable*

完整的迭代器的相关代码:

// 先声明
template<class K, class T, class KeyOfT, class HashFunc>
class HashTable;template<class K, class T, class KeyOfT, class HashFunc, class Ref, class Ptr>
class __HashIterator
{
public:typedef HashNode<T> Node;typedef __HashIterator<K, T, KeyOfT, HashFunc, Ref, Ptr> Self;__HashIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht):_node(node),_pht(pht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self operator++(){KeyOfT kot;Node* cur = _node;if (_node->_next){_node = _node->_next;}else{size_t hashi = kot(cur->_data) % _pht->_tables.size();++hashi;for (; hashi < _pht->_tables.size(); ++hashi){if (_pht->_tables[hashi]){cur = _pht->_tables[hashi];break;}}if (hashi == _pht->_tables.size()){_node = nullptr;}else{_node = cur;}}return *this;}bool operator!=(const Self& h){return _node != h._node;}private:Node* _node;const HashTable<K, T, KeyOfT, HashFunc>* _pht;
};template<class K, class T, class KeyOfT, class HashFunc>
class HashTable
{
public:typedef typename __HashIterator<K, T, KeyOfT, HashFunc, T&, T*> Iterator;typedef typename __HashIterator<K, T, KeyOfT, HashFunc, const T&, const T*> Const_Iterator;typedef HashNode<T> Node;// 友元声明friend __HashIterator<K, T, KeyOfT, HashFunc, T&, T*>;friend __HashIterator<K, T, KeyOfT, HashFunc, const T&, const T*>;Const_Iterator Begin() const{for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];if (cur){return Const_Iterator(cur, this);}}return End();}Const_Iterator End() const{return Const_Iterator(nullptr, this);}Iterator Begin(){for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];if (cur){return Iterator(cur, this);}}return End();}Iterator End(){return Iterator(nullptr, this);}
private:vector<Node*> _tables;size_t _n = 0;
};


4. unordered_map的operator[]的实现


这里 o p e r a t o r [ ] operator[ ] operator[] 的用法和红黑树那里的相同:

1. 首先不管是否存在,都执行 插入的逻辑

  • 如果存在,则返回一个 pair 类型(first:对应值位置的迭代器,secondbool 类型,是否插入成功,此处为 false);
  • 如果不存在,依然返回一个 pair类型(first:新插入位置的迭代器,secondbool 类型,是否插入成功,此处 true)。

2. 取出 pairfirst,也就是迭代器,根据迭代器找到它的 second,也就是 value,返回它的 value

🎧4.1 Find()的修改


Find()的修改逻辑:

  • Find() 原本返回的是节点类型的指针,现在要返回迭代器类型:
    • 迭代器类型,第一个参数还是节点类型的指针,第二个参数是this指针 。

Find() 修改后的代码:

Iterator Find(const K& key)
{HashFunc hf;size_t hashi = hf(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return Iterator(cur,this);}else{cur = cur->_next;}}return Iterator(nullptr, this);
}

🎧4.2 Insert()的修改


Insert()的修改逻辑:

  1. 当对应的key值能用Find()函数找到的时候,则返回pair类型,first是对应节点的迭代器,secondfalse
  2. key找不到进行插入操作时,依然返回pair类型,first是插入节点的迭代器,secondtrue

Insert 修改后的代码:

pair<Iterator, bool> Insert(const T& data)
{HashFunc hf;KeyOfT kot;Iterator it = Find(kot(data));if (it != End()){return make_pair(it, false);}// 判断负载因子扩容// 负载因子为1扩容if (_n == _tables.size()){vector<Node*> newtable;size_t newsize = 2 * _tables.size();newtable.resize(newsize, nullptr);for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];Node* next = nullptr;while (cur){next = cur->_next;size_t hashi = hf(kot(cur->_data)) % newtable.size();cur->_next = newtable[hashi];newtable[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtable);}size_t hashi = hf(kot(data)) % _tables.size();Node* newnode = new Node(data);// 头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;// 先用当前节点的指针和this指针构造迭代器类,// 再用迭代器和bool类型构造pairreturn make_pair(Iterator(_tables[hashi], this), true);
}

🎧4.3 operator[]的实现



operator[]的实现思路:

  1. 对于传入的key值进行对应的插入操作,因为插入返回类型是pair,取它的first,也就是迭代器类型,取变量名为ret
  2. retsecond,也就是value的值即可,实现思路较为简单。

operator[]的实现代码:

V& operator[](const K& k)
{iterator ret = _ht.Insert(make_pair(k, V())).first;return ret.operator->()->second;
}


5. 哈希桶封装的完整代码



🎧有需要的小伙伴自取哈,博主已经检测过了,无bug🎧

🎨博主gitee链接: Jason-of-carriben 哈希桶封装的完整代码

在这里插入图片描述

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

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

相关文章

Linux系统使用Docker安装Drupal结合内网穿透实现远程访问管理后台

目录 前言 1. Docker安装Drupal 2. 本地局域网访问 3 . Linux 安装cpolar 4. 配置Drupal公网访问地址 5. 公网远程访问Drupal 6. 固定Drupal 公网地址 前言 作者简介&#xff1a; 懒大王敲代码&#xff0c;计算机专业应届生 今天给大家聊聊Linux系统使用Docker安装Drupal…

python在cmd中运行.exe文件时报错:不是内部或外部命令,也不是可运行的程序或批处理文件。的解决办法

添加系统环境变量&#xff1a; 设置环境变量&#xff0c;在用户变量里面添加 【PATH&#xff1a;%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;C:\Windows\SysWOW64】 在系统变量里面添加,【变量名&#xff1a;ComSpec】 【变量值&#xff1a;%SystemRoo…

ROS2从入门到精通2-1:launch多节点启动与脚本配置

目录 0 专栏介绍1 ROS2的启动脚本优化2 ROS2多节点启动案例2.1 C架构2.2 Python架构 3 其他格式的启动文件3.1 .yaml启动3.2 .xml启动 0 专栏介绍 本专栏旨在通过对ROS2的系统学习&#xff0c;掌握ROS2底层基本分布式原理&#xff0c;并具有机器人建模和应用ROS2进行实际项目的…

适合能源企业的文档安全外发系统应该是什么样的?

能源企业是市场经济中的重要组成&#xff0c;也是社会可持续长远发展的关键组成之一&#xff0c;能源行业在开拓新能源业务线、提升产能的日常经营中&#xff0c;也需要与外部合作伙伴、客户间进行密切的业务往来&#xff0c;文档可能涉及多个领域多个类型。 能源供应合同&…

IDEA2023.2单击Setting提示报错:Cannot get children Easy Code

1、单击Setting&#xff0c;不能弹出对话框 2、打开IDE Internal Errors发生错误 原因&#xff1a; 报错信息 "Cannot get children Easy Code" 通常指的是 IntelliJ IDEA 在尝试访问或操作 Easy Code 插件的子设置时遇到了问题。 主要检查是有网络&#xff08;断断…

【排序算法】选择排序以及需要注意的问题

选择排序的基本思想&#xff1a;每一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在序列的起始位置&#xff0c;直到全部待排序的数据元素排完 。 第一种实现方法&#xff1a; void SelectSort(int* arr, int n) {for (int j 0…

安装 Android Studio 2024.1.1.6(Koala SDK35)和过程问题解决

记录更新Android Studio版本及适配Android V应用配置的一些过程问题。 安装包&#xff1a;android-studio-2024.1.1.6-windows.exe原版本&#xff1a;Android Studio23.2.1.23 Koala 安装过程 Uninstall old version 不会删除原本配置&#xff08;左下角提示&#xff09; Un…

数据结构第二篇【关于java线性表(顺序表)的基本操作】

【关于java线性表&#xff08;顺序表&#xff09;的基本操作】 线性表是什么&#xff1f;&#x1f435;&#x1f412;&#x1f98d;顺序表的定义&#x1f9a7;&#x1f436;&#x1f435;创建顺序表新增元素,默认在数组最后新增在 pos 位置新增元素判定是否包含某个元素查找某个…

如何解决研发数据传输层面安全可控、可追溯的共性需求?

研发数据在企业内部跨网文件交换&#xff0c;是相对较为普遍而频繁的文件流转需求&#xff0c;基于国家法律法规要求及自身安全管理需要&#xff0c;许多企业进行内部网络隔离。不同企业隔离方案各不相同&#xff0c;比如银行内部将网络隔离为生产网、办公网、DMZ区&#xff0c…

Linux编程基础 8.4:epoll工作模式

1 简介 poll机制的工作原理及流程与select类似&#xff0c;但poll可监控的进程数量不受select中第二个因素——fd_set集合容量的限制&#xff0c;用户可在程序中自行设置被监测的文件描述符集的容量&#xff0c;当然poll在阻塞模式下也采用轮询的方式监测文件描述符集&#xf…

相对位姿估计

相对位姿估计 示意图 理论推导 离线数据库&#xff1a; P的位置 P [ X , Y , Z ] T P[X,Y,Z]^{T} P[X,Y,Z]T 相机内参 k 1 k_{1} k1​ 安卓手机&#xff1a; 相机内参 k 2 k_{2} k2​ 两个像素点位置 &#xff1a; p 1 和 p 2 p_1和p_2 p1​和p2​ 公式一&#xff1a;…

Python魔法之旅-魔法方法(04)

目录 一、概述 1、定义 2、作用 二、主要应用场景 1、构造和析构 2、操作符重载 3、字符串和表示 4、容器管理 5、可调用对象 6、上下文管理 7、属性访问和描述符 8、迭代器和生成器 9、数值类型 10、复制和序列化 11、自定义元类行为 12、自定义类行为 13、类…

2年go蓝炎科技、爱诗科技面试经历,期望薪资22K

广州蓝炎科技一面 1、简单自我介绍&#xff1f;用的什么技术栈&#xff1f; 2、go的map是线程安全的吗&#xff1f; 3、Channel一般会在什么场景下使用&#xff1f;往一个未初始化的channel发送数据&#xff0c;会怎样&#xff1f; 4、关于go里头的随机数是线程安全的吗&am…

网卡配置基础知识

1、网络设置方式 首先科普下Virtual Box虚拟机的几种主流的网络设置方式&#xff0c;官方文档&#xff1a; 2解释 Host-only&#xff1a;仅主机模式 虚拟机和宿主机、虚拟机之间能互通&#xff0c;但是不能访问外网&#xff0c;虚拟机和宿主机同网段的其他主机不能互通这种…

VScode远程连接linux服务器开发,误删了文件怎么找回。

因为远程服务器大家都在用&#xff0c;没有足够权限去折腾。找遍了没找到方法&#xff0c;就告诉我远程的文件本地没有缓存啊&#xff01;我就差点开始重写代码了&#xff0c;后来被我发现了TIMELINE功能&#xff0c;这个功能真的好啊&#xff01;&#xff01;&#xff01;关键…

[算法] 优先算法(三):滑动窗口(上)

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;Java …

C++系列——————类和对象(上)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、面向对象的三大特征二、类的引入2.1类的定义 三.类的访问限定符3.1访问限定符的介绍3.2.访问限定符的使用 四、类的作用域五、类的实例化六、类对象模型6.1…

透视AI技术:探索折射技术在去衣应用中的奥秘

引言&#xff1a; 随着人工智能技术的飞速发展&#xff0c;其在图像处理和计算机视觉领域的应用日益广泛。其中&#xff0c;AI去衣技术作为一种颇具争议的应用&#xff0c;引发了广泛的讨论和关注。本文将深入探讨折射技术在AI去衣中的应用及其背后的原理。 一、AI去衣技术简介…

【C语言】探索文件读写函数的全貌

&#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;C笔记专栏&#xff1a; C笔记 &#x1f308;喜欢的诗句:无人扶我青云志 我自踏雪至山巅 &#x1f525;引言 本章将介绍文件读取函数的相关知识和展示使用场景&am…

Stable Diffusion AI绘画:从创意词汇到艺术图画的魔法之旅

文章目录 一、Stable Diffusion的工作原理二、从提示词到模型出图的过程三、Stable Diffusion在艺术创作中的应用《Stable Diffusion AI绘画从提示词到模型出图》内容简介作者简介楚天 目录前言/序言本书特色特别提示 获取方式 在科技的飞速发展中&#xff0c;Stable Diffusion…