当前位置: 首页 > article >正文

C++效率掌握之STL库:map set底层剖析及迭代器万字详解

文章目录

  • 1.map、set的基本结构
  • 2.map、set模拟实现
    • 2.1 初步定义
    • 2.2 仿函数实现
    • 2.3 Find功能实现
    • 2.4 迭代器初步功能实现
      • 2.4.1 ++运算符重载
      • 2.4.2 --运算符重载
      • 2.4.3 *运算符重载
      • 2.4.4 ->运算符重载
      • 2.4.5 !=运算符重载
      • 2.4.6 begin()
      • 2.4.7 end()
    • 2.5 迭代器进阶功能实现
      • 2.5.1 set:const迭代器及insert的实现
      • 2.5.2 map:const迭代器及insert、[ ]运算符重载的实现
  • 3.代码展示
  • 希望读者们多多三连支持
  • 小编会继续更新
  • 你们的鼓励就是我前进的动力!

mapset 的封装可以说是很天才的底层结构了,本篇将对其结构进行详细的解析,虽然会很复杂且难以理解,但是学完成就感满满,而且对底层理解和面试很有帮助

1.map、set的基本结构

在这里插入图片描述

通过查看官方文档,截取部分关键代码,我们可以发现 set 虽然事 k-k 类型,mapk-v 类型,但是实际上这两个类共用一个红黑树,准确来说是共用同一个模板类型,set<K,K>map<K,pair<K,V>>,下面会进行详细解析

  • size_type node_count:用于记录红黑树节点数量,跟踪树的大小
  • link_type header:是指向红黑树头节点的指针
  • Value value_field:存储节点的值

那么下面我们将自己实现简单的 setmap 类:

2.map、set模拟实现

2.1 初步定义

template<class K>
class set
{
private:RBTree<K, K> _t;
};template<class K, class V>
class map
{
private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

平常我们认为键值对指的就是 KV,但是在库里不是这样的,库里的 K 表示键值对的类型,V 表示插入红黑树的键值对,只不过对于 set 来说,KV 是一样的

在这里插入图片描述

在红黑树中,定义的模板参数 T,而不是原先的 pair,这里的 T 表示插入的数据 _data 的类型,这种定义方法能够共同使用同一参数模板,避免额外的代码编写

2.2 仿函数实现

template<class K>
class set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
private:RBTree<K, K, SetKeyOfT> _t;
};template<class K, class V>
class map
{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};
private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

我们知道 setmap 是通过比较 key,在红黑树中来插入的,但是由于上述的定义,如果每次对于 map 都频繁取出 first 就太麻烦了,因此就定义了仿函数

🚩为什么使用仿函数而不是普通函数呢?

红黑树中只要涉及到数据 _data 的地方,就需要使用到仿函数提取 key,使用普通函数消耗太大,而仿函数带有 inline 的性质,降低消耗。同时官方文档中还对比较进行了实现,即 Compare,模板要求参数必须是一个类型,而普通函数无法作为类型传递

🚩为什么要自己定义仿函数,pair自带的仿函数不行吗?

在这里插入图片描述
虽然 pair 确实有自己的仿函数比较,但是他是比较完 first 后不行,会接着比较 second,这不符合我们的设计思路


在这里插入图片描述

截取了部分 insert 中的代码,利用仿函数确实是能够简单的实现键值 first 的提取,我们再对整体的调用思路进行整理

在这里插入图片描述

其实仿函数主要是为了 map 而设计的,为的就是提取 firstset 为了保持设计模式的一致,因而也设计了相同的仿函数,这样就不用关心是否需要调用这一点了,保持一致性

这里我们不对 Compare 进行实现,有兴趣的可以自己去看底层代码

🔥值得注意的是: 仿函数内不实现比较功能是因为,比较功能是一个外层调用功能,如果放在内部就不能操作者自行去调用了,况且 Compare 也是以仿函数的形式实现的,两个仿函数嵌套过于复杂,不好使用

2.3 Find功能实现

Node* Find(const K& key)
{Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;
}

2.4 迭代器初步功能实现

在这里插入图片描述

类似的迭代器分析我们在 list 部分有做过解析,确实大体上是相像的,但是结构并不一样,这里的树形结构需要以中序遍历:左-根-右的方式遍历

template<class T>
struct __TreeIterator
{typedef RBTreeNode<T> Node;typedef __TreeIterator<T> Self;Node* _node;__TreeIterator(Node* node):_node(node){}
};

库里的迭代器模式并不能满足我们的设计需要,所以这里自己构建一个 __TreeIterator

2.4.1 ++运算符重载

Self& operator++()
{if (_node->_right){// 右树的最左节点(最小节点)Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}_node = subLeft;}else{Node* cur = _node;Node* parent = cur->_parent;// 找孩子是父亲左的那个祖先节点,就是下一个要访问的节点while (parent){if (cur == parent->_left){break;}else{cur = cur->_parent;parent = parent->_parent;}}_node = parent;}return *this;
}

中序遍历的方式是 左-根-右,因此可以总结为两种情况来遍历:

  • 当前节点有右子树

处理方式: 找到右子树的最左节点(即右子树中的最小值)

原因: 在中序遍历中,当前节点的下一个节点是其右子树的最左节点

  • 当前节点没有右子树

处理方式: 向上回溯,直到找到某个祖先节点,使得当前节点位于该祖先的左子树中

原因: 在中序遍历中,若无右子树,则下一个节点是第一个满足 “当前节点是其左子节点” 的祖先

🔥值得注意的是: 当前节点没有右子树的情况,是 左-根-右 的最后一步,无论是在根的左边还是右边,最终都会回到根节点,所以直接 _node = parent 即可

2.4.2 --运算符重载

Self& operator--()
{if (_node->_left){Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}else{// 孩子是父亲的右的那个节点Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;
}

operator-- 的思路和 operator++ 是一样的,反过来遍历就行了

2.4.3 *运算符重载

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

2.4.4 ->运算符重载

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

这里再提醒一下重载 -> 是因为用 * 的代码不够简洁,具体分析参考 list 部分的解析

传送门:C++效率掌握之STL库:list底层剖析及迭代器万字详解

2.4.5 !=运算符重载

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

_node:当前迭代器指向的节点
s._node:另一个迭代器(作为参数传入)指向的节点

2.4.6 begin()

//RBTree.h
iterator begin()
{Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return iterator(leftMin);
}//Set.h Map.h
iterator begin()
{return _t.begin();
}

2.4.7 end()

//RBTree.h
iterator end()
{return iterator(nullptr);
}//Set.h Map.h
iterator end()
{return _t.end();
}

现在已经可以基本实现遍历的功能了

2.5 迭代器进阶功能实现

2.5.1 set:const迭代器及insert的实现

typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;const_iterator begin() const
{return _t.begin();
}const_iterator end() const
{return _t.end();
}

由于 set 规定 key 是不可以被修改的,因此 iteratorconst_iterator 本质上其实都是const_iterator

🔥值得注意的是: begin()end()const 迭代器函数被 const 修饰是为了满足常量容器对象或非常量容器对象都能调用


insert 的错误代码:

pair<iterator, bool> insert(const K& key)
{return _t.Insert(key);
}

这里是返回红黑树的插入,红黑树的插入详见下面的代码展示

从之前的学习我们知道 insert 返回的是 pair<iterator, bool>,那么是不是直接返回insert的结果就好了呢?看似确实是没问题,但是这里理了个巨大的坑,我们实际分析一波:

  • _t.Insert(key) 返回的是 RBTree::iterator,是一个普通迭代器
  • pair<iterator, bool> insert(const K& key) 返回的是 set::iterator,是一个 const 迭代器

insert 的正确代码:

// iterator RBTree::const_iterator
pair<iterator, bool> insert(const K& key)
{// pair<RBTree::iterator, bool>pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);return pair<iterator, bool>(ret.first, ret.second);
}

正确的做法是先将 insert 返回的普通迭代器由变量 ret 存储,然后再用一个匿名对象进行构造,将 ret 的普通迭代器构造成 const 迭代器返回即可,下面将进行详细的构造原理解释:

在这里插入图片描述

回看官方文档发现 iteratorconst_iterator 都是被单独拿出来实例化的,并没有受到 RefPtr 的影响,那么此时就分为两种情况:

  • 普通迭代器的拷贝构造

__rb_tree_iterator 是普通迭代器时,iterator 就是自身类型,此时构造函数等价于:

__rb_tree_iterator(const __rb_tree_iterator<Value, Value&, Value*>& it): node(it.node) {}

这是一个标准的拷贝构造函数,用于创建一个新的普通迭代器,指向相同的节点

  • const迭代器的构造

__rb_tree_iteratorconst 迭代器时, iterator 指的是普通迭代器类型,此时构造函数等价于:

__rb_tree_iterator(const __rb_tree_iterator<Value, Value&, Value*>& it): node(it.node) {}

这变成了一个构造函数,允许从普通迭代器创建 const 迭代器

所以可以理解为单独拿出来实例化是为了不让 RefPtr 影响参数,而外面的类型就会受 RefPtr 影响,这样就能保证外面的类型是 const 迭代器,里面的参数是普通迭代器,成功构造出一个支持普通迭代器构造 const 迭代器的构造函数

在这里插入图片描述

那再转到实际代码上,ret.first 的类型是 typename RBTree<K, K, SetKeyOfT>::iterator ,返回值 pair 的第一个元素类型是 set 类中定义的 iterator,实际上是 typename RBTree<K, K, SetKeyOfT>::const_iterator

ret.first 会调用自定义的迭代器类型的构造函数 __TreeIterator(const Iterator& it) 进行单参数转换,变成 const_iterator

2.5.2 map:const迭代器及insert、[ ]运算符重载的实现

typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;iterator begin()
{return _t.begin();
}iterator end()
{return _t.end();
}const_iterator begin() const
{return _t.begin();
}const_iterator end() const
{return _t.end();
}

对于 map 来说,key 是不允许改变的,value 是可以改变的,但是如果像 set 那样写的话 keyvalue 都不能修改了,所以直接在 pairkeyconst ,控制 value 即可


insert 代码:

pair<iterator, bool> insert(const pair<K, V>& kv)
{return _t.Insert(kv);
}

map 就没有像 set 那么麻烦了,红黑树和 `map 的迭代器是一致的


[ ]运算符重载 代码:

V& operator[](const K& key)
{pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;
}

之前详细解释过,可以看之前的博客

传送门:C++漫溯键值的长河:map && set

3.代码展示

🚩MySet.h

#pragma once
#include"RBTree.h"namespace bit
{template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}// iterator RBTree::const_iteratorpair<iterator, bool> insert(const K& key){// pair<RBTree::iterator, bool>pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);return pair<iterator, bool>(ret.first, ret.second);}private:RBTree<K, K, SetKeyOfT> _t;};
}

🚩MyMap.h

#pragma once
#include"RBTree.h"namespace bit
{template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.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 _t.Insert(kv);}private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;};
}

🚩RBTree.h

#pragma once
#include<iostream>
using namespace std;enum Colour
{RED,BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};template<class T, class Ptr, class Ref>
struct __TreeIterator
{typedef RBTreeNode<T> Node;typedef __TreeIterator<T, Ptr, Ref> Self;typedef __TreeIterator<T, T*, T&> Iterator;__TreeIterator(const Iterator& it):_node(it._node){}Node* _node;__TreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node != s._node;}Self& operator--(){if (_node->_left){Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}else{// 孩子是父亲的右的那个节点Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}Self& operator++(){if (_node->_right){// 右树的最左节点(最小节点)Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}_node = subLeft;}else{Node* cur = _node;Node* parent = cur->_parent;// 找孩子是父亲左的那个祖先节点,就是下一个要访问的节点while (parent && cur == parent->_right){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}
};// set->RBTree<K, K, SetKeyOfT> _t;
// map->RBTree<K, pair<K, V>, MapKeyOfT> _t;
template<class K, class T, class KeyOfT>
struct RBTree
{typedef RBTreeNode<T> Node;
public:// 同一个类模板,传的不同的参数实例化出的不同类型typedef __TreeIterator<T, T*, T&> iterator;typedef __TreeIterator<T, const T*, const T&> const_iterator;iterator begin(){Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return iterator(leftMin);}iterator end(){return iterator(nullptr);}const_iterator begin() const{Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return const_iterator(leftMin);}const_iterator end() const{return const_iterator(nullptr);}Node* Find(const K& key){Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;}pair<iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(iterator(_root), true);}Node* parent = nullptr;Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return make_pair(iterator(cur), false);}}cur = new Node(data);cur->_col = RED;Node* newnode = cur;if (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;// u存在且为红if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上处理cur = grandfather;parent = cur->_parent;}else // u不存在 或 存在且为黑{if (cur == parent->_left){//     g//   p// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//   p//		cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else // parent == grandfather->_right{Node* uncle = grandfather->_left;// u存在且为红if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上处理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){// g//	  p//       cRotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{// g//	  p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(iterator(newnode), true);}void RotateL(Node* parent){++_rotateCount;Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;Node* ppnode = parent->_parent;parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}void RotateR(Node* parent){++_rotateCount;Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright)curright->_parent = parent;Node* ppnode = parent->_parent;cur->_right = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}// 17:20继续bool CheckColour(Node* root, int blacknum, int benchmark){if (root == nullptr){if (blacknum != benchmark)return false;return true;}if (root->_col == BLACK){++blacknum;}if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << root->_kv.first << "出现连续红色节点" << endl;return false;}return CheckColour(root->_left, blacknum, benchmark)&& CheckColour(root->_right, blacknum, benchmark);}bool IsBalance(){return IsBalance(_root);}bool IsBalance(Node* root){if (root == nullptr)return true;if (root->_col != BLACK){return false;}// 基准值int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++benchmark;cur = cur->_left;}return CheckColour(root, 0, benchmark);}int Height(){return Height(_root);}int Height(Node* root){if (root == nullptr)return 0;int leftHeight = Height(root->_left);int rightHeight = Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}private:Node* _root = nullptr;public:int _rotateCount = 0;
};

希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

请添加图片描述

相关文章:

C++效率掌握之STL库:map set底层剖析及迭代器万字详解

文章目录 1.map、set的基本结构2.map、set模拟实现2.1 初步定义2.2 仿函数实现2.3 Find功能实现2.4 迭代器初步功能实现2.4.1 运算符重载2.4.2 --运算符重载2.4.3 *运算符重载2.4.4 ->运算符重载2.4.5 !运算符重载2.4.6 begin()2.4.7 end() 2.5 迭代器进阶功能实现2.5.1 set…...

新三消示例项目《Gem Hunter》中的光照和视觉效果

《Gem Hunter》是 Unity 的全新官方示例项目&#xff0c;展示了如何在 Unity 2022 LTS 使用通用渲染管线 (URP) 打造抢眼的光效和视效&#xff0c;让 2D 益智/三消游戏在竞争中脱颖而出。 下载示例项目及其说明文档。准备潜入清澈湛蓝的海水中探寻财富吧&#xff0c;因为那里到…...

通用软件项目技术报告 - 导读III

现在,我们正式进入报告的第六个主要领域:6. 领域六:与第三方服务/API 集成 (含 LLM API)。 连接: 在现代软件开发中,很少有应用程序是完全孤立的。我们经常需要与各种外部的第三方服务或 API 进行集成,以利用它们提供的特定功能(如支付处理、地图服务、社交媒体登录、云…...

代码随想录训练营第二十三天| 572.另一颗树的子树 104.二叉树的最大深度 559.N叉树的最大深度 111.二叉树的最小深度

572.另一颗树的子树&#xff1a; 状态&#xff1a;已做出 思路&#xff1a; 这道题目当时第一时间不是想到利用100.相同的树思路来解决&#xff0c;而是先想到了使用kmp&#xff0c;不过这个题目官方题解确实是有kmp解法的&#xff0c;我使用的暴力解法&#xff0c;kmp的大致思…...

单向循环链表C语言实现实现(全)

#include<stdio.h> #include<stdlib.h> #define TRUE 1 #define FASLE 0//定义宏标识判断是否成功 typedef struct Node {int data;struct Node* next; }Node;Node* InitList() {Node* list (Node*)malloc(sizeof(Node));list->data 0;//创建节点保存datalist…...

【AI大模型】赋能【传统业务】

在数字化转型的浪潮下&#xff0c;传统业务流程&#xff08;如通知公告管理、文档处理等&#xff09;仍依赖人工操作&#xff0c;面临效率低、成本高、易出错等问题。以企业通知公告为例&#xff0c;从内容撰写、摘要提炼到信息分发&#xff0c;需耗费大量人力与时间&#xff0…...

Clion内置宏$PROJECT_DIR$等

CLion 内置宏 文章目录 CLion 内置宏通用路径相关宏路径相对化宏 官方文档地址&#xff1a; https://www.jetbrains.com/help/clion/built-in-macros.html 通用路径相关宏 宏名称含义说明示例$WORKSPACE_DIR$当前项目所属的工作区根目录路径。/home/user/workspace$PROJECT_D…...

团结引擎开源车模 Sample 发布:光照渲染优化 动态交互全面体验升级

光照、材质与交互效果的精细控制&#xff0c;通常意味着复杂的技术挑战&#xff0c;但借助 Shader Graph 14.1.0(已内置在团结引擎官方 1.5.0 版本中)&#xff0c;这一切都变得简单易用。通过最新团结引擎官方车模 Sample&#xff0c;开发者能切身感受到全新光照优化与编辑功能…...

hghac8008漏洞扫描处理

文章目录 环境文档用途详细信息相关文档 环境 系统平台&#xff1a;Linux x86-64 Red Hat Enterprise Linux 7 版本&#xff1a;4.5.10 文档用途 本文只要用于在客户提出hghac8008端口漏洞时&#xff0c;如何进行漏洞处理&#xff0c;本文章的方法已经应用于浪潮云&#xff…...

PyQt5教程:QComboBox下拉列表框的全面解析与实战应用

QComboBox概述 QComboBox是PyQt5中一个集按钮和下拉选项于一体的控件&#xff0c;通常被称为下拉列表框或组合框。它允许用户从预定义的选项列表中选择一个值&#xff0c;是GUI开发中最常用的输入控件之一。 主要特点&#xff1a; 紧凑的界面设计&#xff0c;节省屏幕空间提…...

GAN简读

Abstract 我们提出了一个通过同时训练两个模型的对抗过程来评估生成模型的新框架:一个生成模型 G G G用来捕捉数据特征,还有一个用于估计这个样本是来自训练样本还是 G G G的概率的判别模型 D D D, G G G的训练过程是最大化 D D D犯错的概率。这个框架就相当于一个minimax tw…...

精准测量“双雄会”:品致与麦科信光隔离探头谁更胜一筹

在电子技术飞速发展的当下&#xff0c;每一次精准测量都如同为科技大厦添砖加瓦。光隔离探头作为测量领域的关键角色&#xff0c;能有效隔绝电气干扰&#xff0c;保障测量安全与精准。在众多品牌中&#xff0c;PINTECH品致与麦科信的光隔离探头脱颖而出&#xff0c;成为工程师们…...

NSSCTF [HNCTF 2022 WEEK4]

题解前的吐槽&#xff1a;紧拖慢拖还是在前段时间开始学了堆的UAF(虽然栈还没学明白&#xff0c;都好难[擦汗])&#xff0c;一直觉得学的懵懵懂懂&#xff0c;不太敢发题解&#xff0c;这题算是入堆题后一段时间的学习成果&#xff0c;有什么问题各位师傅可以提出来&#xff0c…...

Step1

项目 SchedulerSim 已搭建完成 ✅ ⸻ ✅ 你现在拥有的&#xff1a; • &#x1f527; 两种调度器&#xff08;Round Robin SJF&#xff09; • &#x1f4e6; 模拟进程类 Process • &#x1f9f1; 清晰结构&#xff1a;OOP 风格 便于扩展 • ✍️ 主函数已演示调度器运行效…...

tornado_登录页面(案例)

目录 1.基础知识​编辑 2.脚手架&#xff08;模版&#xff09; 3.登录流程图&#xff08;processon&#xff09; 4.登录表单 4.1后&#xff08;返回值&#xff09;任何值&#xff1a;username/password &#xff08;4.1.1&#xff09;app.py &#xff08;4.1.2&#xff…...

YOLOv12模型部署(保姆级)

一、下载YOLOv12源码 1.通过网盘分享的文件&#xff1a;YOLOv12 链接: https://pan.baidu.com/s/12-DEbWx1Gu7dC-ehIIaKtQ 提取码: sgqy &#xff08;网盘下载&#xff09; 2.进入github克隆YOLOv12源码包 二、安装Anaconda/pycharm 点击获取官网链接(anaconda) 点击获取…...

BGP实验练习1

需求&#xff1a; 要求五台路由器的环回地址均可以相互访问 需求分析&#xff1a; 1.图中存在五个路由器 AR1、AR2、AR3、AR4、AR5&#xff0c;分属不同自治系统&#xff08;AS&#xff09;&#xff0c;AR1 在 AS 100&#xff0c;AR2 - AR4 在 AS 200&#xff0c;AR5 在 AS …...

Three.js知识框架

一、Three.js 基础概念 1. Three.js 简介 是什么&#xff1f; 基于 WebGL 的 3D JavaScript 库&#xff0c;用于在浏览器中渲染 3D 场景。 核心优势 简化 WebGL 的复杂 API&#xff0c;提供高层封装。 跨平台&#xff08;支持桌面和移动端&#xff09;。 适用场景 3D 可视…...

AWS技术助力企业满足GDPR合规要求

GDPR(通用数据保护条例)作为欧盟严格的数据保护法规,给许多企业带来了合规挑战。本文将探讨如何利用AWS(亚马逊云服务)的相关技术来满足GDPR的核心要求,帮助企业实现数据保护合规。 一、GDPR核心要求概览 GDPR的主要目标是保护欧盟公民的个人数据和隐私权。其核心要求包括: 数…...

HTML、CSS 和 JavaScript 基础知识点

HTML、CSS 和 JavaScript 基础知识点 一、HTML 基础 1. HTML 文档结构 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.…...

数据结构与算法分析实验12 实现二叉查找树

实现二叉查找树 1、二叉查找树介绍2.上机要求3.上机环境4.程序清单(写明运行结果及结果分析)4.1 程序清单4.1.1 头文件 TreeMap.h 内容如下&#xff1a;4.1.2 实现文件 TreeMap.cpp 文件内容如下&#xff1a;4.1.3 源文件 main.cpp 文件内容如下&#xff1a; 4.2 实现展效果示5…...

使用 Semantic Kernel 调用 Qwen-VL 多模态模型

使用 Semantic Kernel 调用 Qwen-VL 多模态模型 一、引言 随着人工智能技术的不断发展&#xff0c;多模态模型逐渐成为研究的热点。Qwen-VL 是阿里云推出的大规模视觉语言模型&#xff0c;支持图像、文本等多种输入形式&#xff0c;并能够进行图像描述、视觉问答等多种任务。…...

请求内存算法题

题意描述 有两个数组输入&#xff1a; mem [32,128,64,192,256]表示有数组长度个设备&#xff0c;每个设备能提供分配的内存大小值(均为4的倍数)&#xff0c;数组最大长度200000 reques [64,128,128,128,512]表示请求内存&#xff0c;在mem中找与请求内存大小最相近或相等的…...

(4)python开发经验

文章目录 1 使用ctypes库调用2 使用pybind11 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;Qt开发 &#x1f448;&#x1f449;python开发 &#x1f448; 1 使用ctypes库调用 说明&#xff1a;ctypes是一个Python内置的库&#xff0c;可以提供C兼容的数据类型…...

深度剖析 GpuGeek 实例:GpuGeek/Qwen3-32B 模型 API 调用实践与性能测试洞察

深度剖析 GpuGeek 实例&#xff1a;GpuGeek/Qwen3-32B 模型 API 调用实践与性能测试洞察 前言 GpuGeek专注于人工智能与高性能计算领域的云计算平台&#xff0c;致力于为开发者、科研机构及企业提供灵活、高效、低成本的GPU算力资源。平台通过整合全球分布式数据中心资源&#…...

MindSpore框架学习项目-ResNet药物分类-数据增强

目录 1.数据增强 1.1设置运行环境 1.1.1数据预处理 数据预处理代码解析 1.1.2数据集划分 数据集划分代码说明 1.2数据增强 1.2.1创建带标签的可迭代对象 1.2.2数据预处理与格式化&#xff08;ms的data格式&#xff09; 从原始图像数据到 MindSpore 可训练 / 评估的数…...

e.g. ‘django.db.models.BigAutoField‘.

在Django框架中&#xff0c;django.db.models.BigAutoField 是一个用于数据库模型的字段类型&#xff0c;它用于自动增长的ID字段。这个字段类型特别适用于需要处理大量数据的应用&#xff0c;比如在大型网站或应用中&#xff0c;普通的 AutoField 可能不足以存储增长的ID值&am…...

ACM算法

在ACM模式下使用JavaScript/TypeScript获取输入值 在ACM编程竞赛或在线判题系统(如LeetCode、牛客网等)中&#xff0c;JavaScript/TypeScript需要特定的方式来获取输入值。以下是几种常见的获取输入的方法&#xff1a; 1. 使用Node.js的readline模块 这是最常见的处理ACM模式…...

MySQL入门指南:环境搭建与服务管理全流程

引言 各位开发者朋友们好&#xff01;今天我们将开启MySQL的学习之旅 &#x1f31f; 作为世界上最流行的开源关系型数据库&#xff0c;MySQL在Web应用、企业系统等领域占据着举足轻重的地位。无论你是刚入行的新手&#xff0c;还是想系统复习的老鸟&#xff0c;这篇教程都将为…...

【MySQL】别名设置与使用

个人主页&#xff1a;Guiat 归属专栏&#xff1a;MySQL 文章目录 1. 别名基础概念2. 列别名设置2.1 基础语法2.2 特殊字符处理2.3 计算字段示例 3. 表别名应用3.1 基础表别名3.2 自连接场景 4. 高级别名技术4.1 子查询别名4.2 CTE别名 5. 别名执行规则5.1 作用域限制5.2 错误用…...