【C++】哈希表封装 unordered_map 和 unordered_set 的实现过程

在这里插入图片描述

C++语法相关知识点可以通过点击以下链接进行学习一起加油!
命名空间缺省参数与函数重载C++相关特性类和对象-上篇类和对象-中篇
类和对象-下篇日期类C/C++内存管理模板初阶String使用
String模拟实现Vector使用及其模拟实现List使用及其模拟实现容器适配器Stack与QueuePriority Queue与仿函数
模板进阶-模板特化面向对象三大特性-继承机制面向对象三大特性-多态机制STL 树形结构容器二叉搜索树
AVL树红黑树红黑树封装map/set哈希-开篇闭散列-模拟实现哈希
哈希桶-模拟实现哈希

本篇将讲述如何通过哈希表封装 unordered_mapunordered_set 容器。在开始本章内容之前,建议先阅读红黑树封装 mapset一文,以便更好地理解编译器如何区分 unordered_mapunordered_set 并调用底层哈希表。

请添加图片描述
Alt
🌈个人主页:是店小二呀
🌈C语言专栏:C语言
🌈C++专栏: C++
🌈初阶数据结构专栏: 初阶数据结构
🌈高阶数据结构专栏: 高阶数据结构
🌈Linux专栏: Linux

🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅 请添加图片描述

文章目录

  • 一、节点类型
  • 二、区分unordered_map/set容器
  • 三、相关文件修改后
    • 3.1 HashTable.h
    • 3.2 unoderded_set.h
    • 3.3 unoderded_map.h
  • 四、实现迭代器
    • 4.1 迭代器常见功能
    • 4.2 operator++实现
    • 4.3 编译器扫描顺序问题
      • 4.3.1 【第一个办法】:声明及其友元
      • 4.3.2 【第二办法】:内部类
    • 4.4 operator++内部逻辑
    • 4.5 获得begin和end迭代器位置
    • 4.6 迭代器调用流程图
  • 五、unoderded_map/set相关接口实现
    • 5.1 unoderded_map相关接口
    • 5.2 unoderded_set相关接口
    • 5.3 HashTable相关接口调整
  • HashTable.h

这里哈希表将采用哈希桶的方式解决哈希冲突且实现哈希表结构,那么我们需要对哈希表节点类型进行处理,同时设计编译器去识别unordered_map/set的逻辑。

一、节点类型

如果我们想使用哈希表封装unordered_map/set容器,就需要考虑泛型编程,将节点类型统一成T类型

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

二、区分unordered_map/set容器

编译器是无非确定你需要啥容器**,这里我们可以通过外部输入数据,使得编译器明确你需要啥容器,该容器都有一套属于返回K类型的仿函数**,只要unordered_set是否会多余,这里可以参考红黑树封装map/set文章,这里是为了跟map构成模板,陪太子读书。

在这里插入图片描述

既然我们希望可以通过手动输入数据,得到预计效果,那么关于HashFunc函数也是期望从外部调用,这里可以写到外面来。(图片写错了,这里看下面的)

template<class K,class Hash = HashFunc > and template<class K,class T,class Hash = HashFunc>

三、相关文件修改后

3.1 HashTable.h

#pragma once
#include <iostream>
using namespace std;
#include <string>
#include <vector>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 ch : key){hash *= 131;hash += ch;}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>class HashTable{public:typedef HashNode<T> Node;HashTable(){_tables.resize(10, 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;}}}bool Insert(const T& data){Hash hs;KeyOfT kot;if (Find(kot(data)){return false;}if (_n == _tables.size()){vector<Node*> NewTable(n * 10, 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)) % NewTable.size();cur->_next = NewTable[hashi];NewTable[hashi] = cur;cur = cur->_next;}_tables[i] = nullptr;}_tables.swap(NewTable);}size_t hashi = hs(kot(data)) % _tables.size();//进行头插操作Node* newnode = new Node(data);_tables[hashi]->_next = newnode;_tables[hashi] = newnode;++_n;return true;}Node* Find(const K& key){Hash hs;KeyOfT kot;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return &kot(cur->_data);}else{cur = cur->_next;}}return nullptr;}bool Erase(const K& key){Hash hs;size_t hashi = hs(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;return true;}else{prev = cur;cur = cur->_next;}}return false;}private:vector<Node*> _tables;size_t _n = 0;};}

这里关于HashFunc()需要放在哈希命名空间外部,因为在其他头文件需要使用到该HashFunc()

3.2 unoderded_set.h

namespace unoderded_set
{template<class K,class Hash = HashFunc<K>>class unoderded_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};private:hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;};
}

3.3 unoderded_map.h

namespace unoderded_map
{template<class K,class V,class Hash = HashFunc<K>>class unoderded_map{struct SetKeyOfT{const K& operator()(const pair<K,V>& key){return key.first;}};public:private:hash_bucket::HashTable<K, const pair<K,V>, MapKeyOfT, Hash> _ht;};
}

四、实现迭代器

实现哈希表中迭代器跟之前其他容器实现迭代器需要考虑的差不多,主要是对于operator++和operator–需要考虑不同容器的结构进行行为重定义。同时需要设计为模板template<class T,class Ptr, class Ref>去匹配const类型和非const类型调用同一个迭代器。

typedef __HTIterator<T*, T&> iterator;
typedef __HTIterator<const T*, const T&> const_iterator;

这里得到当前_node的位置,是浅拷贝。这不希望改变实参,而是希望通过该实参位置进行操作,得到预期数据,在下一次进行操作时还是之前位置。

4.1 迭代器常见功能

template<class T,class Ptr, class Ref>struct _HTIterator{//得到一个节点typedef HashNode<T>	Node;typedef _HTIterator Self;Node* _node;//这里希望是浅拷贝_HTIterator(const Node* node):_node(node){}Ptr operator->(){return &_node->_data;}Ref operator*(){return _node->_data;}bool operator!=(const Self& s){return _node != s._node;}};

4.2 operator++实现

大体思路】:在第一个不为空位置,向下遍历++直到_node-> _next ==nullptr说明该位置迭代器达到悬挂着哈希桶最后一个不为空的桶,那么需要通过当前节点数值,进行除留余数法得到当前表位置下标,以该下标为基准重新在表中找到下一个不为空的位置,直到i == tables.size()说明遍历完成。

4.3 编译器扫描顺序问题

目前关于迭代器实现某方面功能需要借助HashTable类的类型及其成员,但是问题在于HashTable需要将迭代器设为"武器",将迭代器类功能具体实现放在HashTabl类上方,编译器扫描是上到下扫描,这就导致了这两个类总有一方无法得到另外一份的东西。在这里插入图片描述

4.3.1 【第一个办法】:声明及其友元

我们可以在迭代器类中声明HashTable类作为类型定义一个哈希表指针,同时在HashTable类型设置迭代器友元,使之迭代器可以通过定义的哈希表指针访问到哈希表。

template<class K,class T,class Ptr, class Ref,class KeyOfT, class Hash>struct _HTIterator{	//前置声明template<class K, class T, class KeyOfT, class Hash>class HashTable;	}template<class K, class T, class KeyOfT, class Hash>class HashTable{public:template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>friend struct _HTIterator;}

前置声明

前置声明仅仅告诉编译器有一个 HashTable 类,但由于没有给出该类的完整定义和实现,编译器并不知道如何去使用它。也就是说,你不能在代码中使用 HashTable 类的功能,比如创建对象、调用其成员函数、访问数据成员等,除非你提供了类的完整定义。

4.3.2 【第二办法】:内部类

template<class K, class T, class KeyOfT, class Hash>class HashTable{typedef HashNode<T> Node;public:// 友元声明/*template<class K, class T, class KeyOfT, class Hash>friend struct __HTIterator;*/// 内部类template<class Ptr, class Ref>struct __HTIterator{typedef HashNode<T> Node;typedef __HTIterator Self;}}

这里就采用第一种方法(选择哪个方法都是差不多的)

4.4 operator++内部逻辑

Self& operator++()
{KeyOfT kot;Hash hs;if( _node->_next ){_node = _node->_next;}else{//访问该成员size_t i = hs(kot(_node->_data)) % _pht->_tables.size();i++;for (; i < _pht->_tables.size(); i++){if (_pht->_tables[i]){break;}}if (i == _pht->tables.size()){_node = nullptr;}else{_node = _pht->_tables[i];}}
}

由于在库中unoderded_map和unoderded_set只有单向迭代器,就没有operator–。如果有兴趣可以将单链表设计为双向链表,然后根据operator++逻辑,设计出一个operator–接口

4.5 获得begin和end迭代器位置

在这里插入图片描述

iterator begin()
{for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return iterator(cur, this);}}}iterator end()
{return iterator(nullptr, this);
}const_iterator begin() const
{for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return iterator(cur, this);}}
}const_iterator end() const
{return iterator(nullptr, this);
}

这里就是通过哈希表begin和end接口,构造相关需求的迭代器。这里很好体现了每个不同对象的迭代器指向表是自己对象的哈希表。

4.6 迭代器调用流程图

在这里插入图片描述

unoderded_map及其unoderded_set迭代器配置,由于unoderded_map支持修改元素,那么没有必要写const系列。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

五、unoderded_map/set相关接口实现

5.1 unoderded_map相关接口

namespace unoderded_map
{template<class K, class V, class Hash = HashFunc<K>>class unoderded_map{struct MapKeyOfT{const K& operator()(const pair<K, V>& key){return key.first;}};typedef typename hash_bucket::HashTable<K, const pair<K, V>, MapKeyOfT, Hash>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}private:hash_bucket::HashTable<K, const pair<K, V>, MapKeyOfT, Hash> _ht;};}

unoderded_map增添接口】:V& operator[](const K& key)pair<iterator, bool> insert(const pair<K, V>& kv)

5.2 unoderded_set相关接口

#pragma once
#include "HashTable.h"namespace unoderded_set
{template<class K, class Hash = HashFunc<K>>class unoderded_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT,Hash>::iterator iterator;typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::const_iterator  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){return _ht.Insert(key);}iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}private:hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;};
}

unoderded_set增添接口 pair<iterator, bool> insert(const K& key) iterator find(const K& key)及其bool erase(const K& key)

5.3 HashTable相关接口调整

iterator Find(const K& key)
{Hash hs;KeyOfT kot;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}else{cur = cur->_next;}}return end();
}pair<iterator, bool> Insert(const T& data)
{Hash hs;KeyOfT kot;iterator it = Find(kot(data));if (it != end())return make_pair(it, false);if (_n == _tables.size()){vector<Node*> NewTable(_n * 10, 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)) % NewTable.size();cur->_next = NewTable[hashi];NewTable[hashi] = cur;cur = cur->_next;}_tables[i] = nullptr;}_tables.swap(NewTable);}size_t hashi = hs(kot(data)) % _tables.size();//进行头插操作Node* newnode = new Node(data);_tables[hashi]->_next = newnode;_tables[hashi] = newnode;++_n;return make_pair(iterator(newnode, this), true);
}

unodered_map当中operator[]可以看作insert和find融合版本,不需要单独设计Find(),但是内部还是有调用Find()逻辑

HashTable.h

#pragma once
#include <iostream>
using namespace std;
#include <string>
#include <vector>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 ch : key){hash *= 131;hash += ch;}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 Ptr, class Ref,class KeyOfT, class Hash>struct _HTIterator{//前置声明template<class K, class T, class KeyOfT, class Hash>class HashTable;//得到一个节点typedef HashNode<T>	Node;typedef _HTIterator Self;Node* _node;const HashTable* _pht;//这里希望是浅拷贝//这里对象-->指向一个哈希表-->cur及其this。_HTIterator(const Node* node,const HashTable* pht):_node(node),_pht(pht){}Ptr operator->(){return &_node->_data;}Ref operator*(){return _node->_data;}bool operator!=(const Self& s){return _node != s._node;}Self& operator++(){KeyOfT kot;Hash hs;if( _node->_next ){_node = _node->_next;}else{//访问该成员size_t i = hs(kot(_node->_data)) % _pht->_tables.size();i++;for (; i < _pht->_tables.size(); i++){if (_pht->_tables[i]){break;}}if (i == _pht->tables.size()){_node = nullptr;}else{_node = _pht->_tables[i];}}}};template<class K, class T, class KeyOfT, class Hash>class HashTable{public:template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>friend struct _HTIterator;typedef _HTIterator< K, T, T*,  T&, KeyOfT, Hash> iterator;typedef _HTIterator<K,  T,const T*, const T&, KeyOfT, Hash> const_iterator;typedef HashNode<T> Node;HashTable(){_tables.resize(10, 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;}}}iterator Find(const K& key){Hash hs;KeyOfT kot;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}else{cur = cur->_next;}}return end();}pair<iterator, bool> Insert(const T& data){Hash hs;KeyOfT kot;iterator it = Find(kot(data));if (it != end())return make_pair(it, false);if (_n == _tables.size()){vector<Node*> NewTable(_n * 10, 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)) % NewTable.size();cur->_next = NewTable[hashi];NewTable[hashi] = cur;cur = cur->_next;}_tables[i] = nullptr;}_tables.swap(NewTable);}size_t hashi = hs(kot(data)) % _tables.size();//进行头插操作Node* newnode = new Node(data);_tables[hashi]->_next = newnode;_tables[hashi] = newnode;++_n;return make_pair(iterator(newnode, this), true);}iterator begin(){for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return iterator(cur, this);}}}iterator end(){return iterator(nullptr, this);}const_iterator begin() const{for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return iterator(cur, this);}}}const_iterator end() const{return iterator(nullptr, this);}bool Erase(const K& key){Hash hs;size_t hashi = hs(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;return true;}else{prev = cur;cur = cur->_next;}}return false;}private:vector<Node*> _tables;size_t _n = 0;};};

在这里插入图片描述

以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!

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

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

相关文章

SQL,力扣题目1709,访问日期之间最大的空档期

一、力扣链接 LeetCode_1709 二、题目描述 表&#xff1a; UserVisits ------------------- | Column Name | Type | ------------------- | user_id | int | | visit_date | date | ------------------- 该表没有主键&#xff0c;它可能有重复的行 该表包含用户访问…

第七篇: BigQuery中的复杂SQL查询

BigQuery中的复杂SQL查询 背景与目标 在数据分析中&#xff0c;我们通常需要从多个数据源中获取信息&#xff0c;以便进行深入的分析。这时&#xff0c;BigQuery提供的JOIN、UNION和子查询等复杂SQL语句非常实用。本文将以Google BigQuery的公共数据集为例&#xff0c;介绍如何…

SPIRE: Semantic Prompt-Driven Image Restoration 论文阅读笔记

这是一篇港科大学生在google research 实习期间发在ECCV2024的语义引导生成式修复的文章&#xff0c;港科大陈启峰也挂了名字。从首页图看效果确实很惊艳&#xff0c;尤其是第三行能用文本调控修复结果牌上的字。不过看起来更倾向于生成&#xff0c;对原图内容并不是很复原&…

Dubbo负载均衡

负载均衡策略与配置细节 Dubbo 内置了 client-based 负载均衡机制&#xff0c;如下是当前支持的负载均衡算法&#xff0c;结合上文提到的自动服务发现机制&#xff0c;消费端会自动使用 Weighted Random LoadBalance 加权随机负载均衡策略 选址调用。 如果要调整负载均衡算法…

FFmpeg 4.3 音视频-多路H265监控录放C++开发十二:在屏幕上显示多路视频播放,可以有不同的分辨率,格式和帧率。

上图是在安防领域的要求&#xff0c;一般都是一个屏幕上有显示多个摄像头捕捉到的画面&#xff0c;这一节&#xff0c;我们是从文件中读取多个文件&#xff0c;显示在屏幕上。 一 改动UI文件 这里我们要添加两个label&#xff0c;为了区分我们设置一下背景色&#xff08;这个是…

前言2、VS(Visual Studio)-2022使用

早前用VS-2010编译平台&#xff0c;进行C语言编程学习。 现如今&#xff0c;为了适应未来发展趋势以及日新月异的新功能&#xff0c;就此转到VS-2022编译平台&#xff1b; 由于都是VS编译平台&#xff0c;大多数基础功能都类似&#xff0c;关于一些基础操作可参考前言1&#…

深入了解逻辑回归:机器学习中的经典算法

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

[High Speed Serial ] Xilinx

Xilinx 高速串行数据接口 收发器产品涵盖了当今高速协议的方方面面。GTH 和 GTY 收发器提供要求苛刻的光互连所需的低抖动&#xff0c;并具有世界一流的自适应均衡功能&#xff0c;具有困难的背板操作所需的 PCS 功能。 Versal™ GTY &#xff08;32.75Gb/s&#xff09;&…

基于CNN-RNN的影像报告生成

项目源码获取方式见文章末尾&#xff01; 600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【PaddleNLP的FAQ问答机器人】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实现…

java list使用基本操作

import java.util.ArrayList; import java.util.Collection; import java.util.Iterator;public class Main {public static void main(String[] args) {ArrayList list new ArrayList();list.add("张三");list.add("李四");list.add("王五");l…

高级 <HarmonyOS主题课>借助AR引擎帮助应用实现虚拟与现实交互的能力的课后习题

持而盈之&#xff0c;不如其已&#xff1b; 揣而锐之&#xff0c;不可长保。 金玉满堂&#xff0c;莫之能守&#xff1b; 富贵而骄&#xff0c;自遗其咎。 功成身退&#xff0c;天之道也。 VR (Virtual Reality): 虚拟现实技术 AR (Augmented Reality): 增强现实) XR.(Extend…

高校实验室安全巡检系统设计与实现(源码+定制+开发)高校实验室巡检系统、实验室安全管理平台、实验室安全监控系统、智能实验室巡查系统、高校实验室风险管理

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

102、Python并发编程:Queue与生产者消费者模型实现解耦、协作

引言 在实际业务场景中&#xff0c;很多时候在处理复杂任务的时候&#xff0c;会拆分上下游各个环节&#xff0c;形成一个类似于流水线的处理方式。上游类似于生产者&#xff0c;下游要依赖上游的输出进行工作&#xff0c;类似于消费者。但是&#xff0c;很多时候&#xff0c;…

【梯度提升专题】XGBoost、Adaboost、CatBoost预测合集:抗乳腺癌药物优化、信贷风控、比特币应用|附数据代码...

全文链接&#xff1a;https://tecdat.cn/?p38115 分析师:Yang Yang&#xff0c;Kechen Zhao 在当今科技日新月异的时代&#xff0c;数据的有效利用成为各领域突破发展的关键。于医疗领域&#xff0c;乳腺癌的高发性与严重性不容忽视&#xff0c;优化抗乳腺癌候选药物的筛选与特…

机器学习与AI|如何利用数据科学优化库存周转率?

对于所有零售商来说&#xff0c;良好的库存管理都是非常重要的。众所周知&#xff0c;商品如果不放在货架上就无法出售&#xff0c;而如果库存过多则意味着严重的财务负担。 但是做好库存管理绝非易事&#xff0c;它依赖于对未来需求的准确预测和确保始终有合适库存的敏捷供应链…

安卓智能对讲终端|北斗有源终端|三防对讲机|单兵终端|单北斗

在当今快速发展的通信技术时代&#xff0c;智能对讲手持机已成为众多行业领域中不可或缺的通讯工具。QM240T安卓智能对讲手持机&#xff0c;作为一款集先进技术与实用功能于一身的高端设备&#xff0c;凭借其卓越的性能和多样化的应用特性&#xff0c;正逐步引领对讲机市场的革…

【数据集】【YOLO】【目标检测】抽烟识别数据集 6953 张,YOLO/VOC格式标注,吸烟检测!

数据集介绍 【数据集】抽烟识别数据集 6953 张&#xff0c;目标检测&#xff0c;包含YOLO/VOC格式标注。数据集中包含1种分类&#xff1a;“smoking”。数据集来自国内外图片网站和视频截图。检测范围园区吸烟检测、禁烟区吸烟检测、监控吸烟检测、无人机吸烟检测等。 主页私…

软件设计师-上午题-15 计算机网络(5分)

计算机网络题号一般为66-70题&#xff0c;分值一般为5分。 目录 1 网络设备 1.1 真题 2 协议簇 2.1 真题 3 TCP和UDP 3.1 真题 4 SMTP和POP3 4.1 真题 5 ARP 5.1 真题 6 DHCP 6.1 真题 7 URL 7.1 真题 8 浏览器 8.1 真题 9 IP地址和子网掩码 9.1 真题 10 I…

视频制作与剪辑怎么学,零基础入门视频剪辑和制作

视频制作与剪辑是一门充满创意与挑战的艺术形式&#xff0c;对于零基础的学习者来说&#xff0c;没选对软件不了解剪辑步骤&#xff0c;入门可能会显得有些棘手。接下来&#xff0c;我们将一同探讨如何开启视频剪辑与制作之旅&#xff0c;让新手从零基础入门&#xff0c;逐步迈…

[Element] el-table修改滚动条上部分的背景色

[Element] el-table修改滚动条上部分的背景色 ::v-deep .el-table__cell .gutter {background: red;}