【c++篇】:解读Set和Map的封装原理--编程中的数据结构优化秘籍
✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨
✨ 个人主页:余辉zmh–CSDN博客
✨ 文章所属专栏:c++篇–CSDN博客
文章目录
- 前言
- 一.`set`和`map`的初步封装
- 1.树的节点封装修改
- 2.`Find()`查找函数
- 3.红黑树的封装修改
- 4.set和map的初步封装框架
- 二.红黑树的迭代器封装
- 1.基本框架
- 2.常用成员函数
- 3.前置`++`函数
- 4.前置`--`函数
- 5.红黑树封装中的迭代器函数
- 6.红黑树封装中的插入函数修改
- 三.set封装完整实现
- 1.set的迭代器函数
- 2.set的插入函数
- 3.测试
- 四.map封装完整实现
- 1.map的迭代器函数
- 2.map的插入函数
- 3.map的`operator[]`函数
- 4.测试
- 五.完整代码文件
- 1.`RBTree.h`文件
- 2.`Set.h`文件
- 3.`Map.h`文件
前言
在之前的文章中,我们知道,
set
和map
是两种常用的关联容器。他们内部通常使用红黑树来实现高效的查找,插入和删除操作,尽管它们提供了不同的接口函数,但它们依然可以通过共享相同的底层数据结构(也就是同一个红黑树)来实现。下面将详细讲解如何通过改造我们之前的红黑树来实现我们自己的set
和map
容器。(红黑树的实现在我上一篇文章中有详细讲解,不清楚的可以看我之前的文章)
一.set
和map
的初步封装
1.树的节点封装修改
首先我们来看一下我们之前的红黑树如何实现节点类的封装:
//节点类封装
template<class K,class V>
class RBTreeNode{
public://构造函数RBTreeNode(const pair<K,V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED) {}//成员变量RBTree<K,V>* _left;RBTree<K,V>* _right;RBTree<K,V>* _parent;pair<K,V> _kv;Colour _col;
};
我们学完set和map应该已经知道,set存储的是一个key值,而map存储的是一个键值对key-value,在上面这段代码中,如果通过这两个模板参数可以实现map,但set却没办法使用,因此,这里节点类的两个模板参数需要使用一个泛型参数来修改,这样就可以实现set和map能够共享一颗树。
修改如下:
//RBTree.h文件template<class T>
class RBTreeNode {
public://构造函数RBTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}//成员变量RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;
};
通过上面的修改为一个模板参数就可以实现共享效果:
set存储的是一个键值key,这里的模板参数T就是键值key的类型
map存储的是一个键值对key-value,这里的模板参数T就是容器pair<key,value> 类型
2.Find()
查找函数
在上面对节点封装进行修改后,这里又会产生新的问题,如果我们要通过键值Key来查找对应的节点(也就是Find()函数的参数是键值key,对于set来说直接通过键值key就能找到,但是map中的节点存储的是一个键值对key-value,也就是一个,pair(key,value)对象,直接通过参数key并不能查找,具体可以看下面函数
Node* Find(const K& key){Node* cur=_root;while(cur){//对于set来说,_data就是key//对于map来说,_data是一个piar(key,value)对象if(cur->_data<key){cur=cur->_right;}else if(cur->_data>key){cur=cur->_left;}else{return cur;}}return nullptr;
}
因此为了解决这个问题,这里需要借助一个仿函数来实现两个不同容器的查找
-
set的仿函数:
struct SetKeyOfT{const K& operator()(const K& key){return key;} };
-
map的仿函数:
struct MapKeyOfT{const K& operator()(const pair<K,V>& kv){return kv.first;} };
-
Find()函数:
Node* Find(const K& key){Node* cur=_root;//对于set来说,这里的KeyOfT就是SetKeyOfT//对于map来说,这里的KeyOfT就是MapKeyOfTKeyOfT kot;while(cur){if(kot(cur->_data)){cur=cur->_right;}else if(kot(cur->_data)>key){cur=cur->_left;}else{return cur;}}return nullptr; }
3.红黑树的封装修改
上面了解完如何实现两个不同容器之间的查找之后,这里就需要开始对原本的红黑树进行封装修改,从Find()函数中我们可以看到,需要一个新的模板参数KeyOfT,用来实现不同容器仿函数的查找功能。
修改如下:
//RBTree.h文件//增加一个新的模板参数KeyOfT
template<class K,class V,class KeyOfT>
class RBTree{typedef RBTreeNode<T> Node;
public: //构造函数RBTree():_root(nullptr){}//Node* Find(const K& key);//...其他成员函数private:Node* _root;
}
4.set和map的初步封装框架
有了前面三个的基础这里就可以开始对set和map进行初步的封装,set和map的底层都是借用同一个红黑树。
-
set的初步封装框架:
//Set.h文件namesapce MySet{template<class K>class Set{//将仿函数设置为内部类struct SetKeyOfT{const K& operator()(const K& key){return key;}};public://...其他成员函数private://第一个模板参数K是set存储的数据类型RBTree<K,K,SetKeyOfT> _t;}; };
-
map的初步封装框架:
//Map.h文件namesapce MyMap{template<class K,class V>class Map{//将仿函数设置为内部类struct MapKeyOfT{const K& operator()(const pair<K,V>& kv){return kv.first;}};public://...其他成员函数private://第一个模板参数K是set存储的数据类型RBTree<K,pair<const K,V>,MapKeyOfT> _t;}; };
二.红黑树的迭代器封装
这里红黑树的迭代器封装其实和容器list比较类似,不能像string和vector一样使用原生指针作为迭代器,只能通过封装结点指针来实现迭代器。
1.基本框架
//迭代器封装
template<class T,class Ptr,class Ref>
class TreeIterator{//重命名定义typedef RBTreeNode<T> Node;typedef TreeIterator<T,Ptr,Ref> self;public://节点指针Node* _node;//构造函数TreeIterator(Node* node):_node(node){}//...成员函数}
2.常用成员函数
-
operator*
函数://T& operator*() //const T& operator*() //用模板参数Ref来实现两个不同返回类型的替换 Ref opeartor*(){return _node->_data; }
-
operator->
函数://T* operator->() //const T* operator->() //用模板参数Ptr来实现两个不同返回类型的替换 Ptr operator->(){return &_node->_data; }
-
operator!=
函数:bool operator!=(const self& s)const {return _node!=s._node; }
-
operator==
函数:bool operator==(const self& s)const {return _node==s._node; }
3.前置++
函数
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节点是父节点的右子节点,继续往上if(cur==parent->_right){cur=parent;parent=parent->_parent;}//如果cur节点是父节点的左子节点,停止else{break;}}_node=parent;}return *this;
}
4.前置--
函数
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){if(cur==parent->_left){cur=parent;parent=parent->_parent;}else{break;}_node=parent;}return *this;}
}
5.红黑树封装中的迭代器函数
对于红黑树来说,有普通对象迭代器和const对象迭代器。
template<class ,class T,class KeyOfT>
class RBTree{//....public://普通对象迭代器typedef TreeIterator<T,T*,T&> iterator;//const对象迭代器typedef TreeIterator<T,const T*,const T&> const_iterator;iterator begin(){Node* cur=_root;while(cur&&cur->_left){cur=cur->_left;}return cur;} iterator end(){return nullptr;}const_iterator begin()const {Node* cur=_root;while(cur&&cur->_left){cur=cur->_left;}return cur;}const_iterator end()const {return nullptr;}
}
6.红黑树封装中的插入函数修改
有了前面红黑树封装的迭代器,这里插入函数就可以进行修改,从原本的bool类型,变为pair<iterator,bool>类型,其中,iterator表示插入位置的迭代器,如果差人成功,返回插入位置的迭代器和true;如果该值已经存在,返回该值位置的迭代器和false。
//这里第三个模板参数KeyOfT就可以用到
pair<iteraotr,bool> insert(const T& data){if(_root==nullptr){_root=new Node(data);_root->_col=BLACK;return make_pair(_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(cur,false);}}cur=new Node(data);cur=_col=RED;if(kot(parent->_data)<kot(data)){parent->_right=cur;}else{parent->_left=cur;}cur->_parent=parent;//更新平衡因子,旋转,变色//...return make_pair(newnode,true);
}
三.set封装完整实现
1.set的迭代器函数
在前面通过对红黑树的迭代器进行封装之后,这里就可以直接实现set的迭代器函数
- 代码实现:
namespace MySet{template<class K>class set{//内部类,用来获取set存储对象中的key值struct SetKeyOfT{const K& operator()(const K& key){return key;}};public://这里的类模板还没有实例化对象,所以要加上关键字typenametypedef typename RBTree<K,K,SetKeyOfT>::const_iterator iterator;typedef typename RBTree<K,K,SetKeyOfT>::const_iterator const_iterator;//set的begin()函数const_iterator begin()const {return _t.begin();}//set的end()函数const_iterator end()const {return _t.end();}private://第一个模板参数k是set存储的数据类型RBTree<K,K,SetKeyOfT> _t;};
};
-
实现原理:
-
首先就是要将红黑树原本的迭代器类型进行命名重定义,这里有一个注意点,因为
RBTree<K,K,SetKeyOfT>
是一个类模板,现在还没有进行实例化,所以直接加上作用域限定符::
后面加上迭代器类型会报错,因为编译器并不知道当前模板参数的具体类型,因此要加上关键字typename
。 -
其次set还有一个性质就是对于key值,不能进行修改,所以使用迭代器时,如果对于当前迭代器解引用获取key值,要求只能访问,不能修改。因此我们这里可以将set的普通类型迭代器
iterator
和const类型迭代器const_iterator
全都使用红黑树的const类型迭代器typename RBTree<K,K,SetKeyOfT>::const_iterator
。
-
2.set的插入函数
set的插入函数返回的是一个pair<iterator,bool>类型
pair<iterator,bool> insert(const K& key){return _t.insert(key);
}
注意:set的返回类型pair<iterator,bool>表面上看起来是普通类型的迭代器,但其实,我们是将红黑树的const_iterator
迭代器重命名定义成了iterator
,因此pair<iterator,bool>
中的其实是const_iterator
,但是红黑树的插入函数返回的又是一个iterator
,所以这里直接写成上面的代码显然是错误的。
正确的写法是要进行一次类型转换:
pair<iterator,bool> insert(const K& key){pair<typename RBTree<K,K,SetKeyOfT>::iterator ret=_t.insert(key);return pair<iterator,bool>(ret.first,ret.second);
}
为了实现从iterator
类型转换为const_iterator
,我们要在红黑树的迭代器封装中添加一个构造函数
TreeIterator(Node* node)
:_node(node)
{}
3.测试
测试代码:
#include"Set.h"int main(){MySet::set<int> s;s.insert(2);s.insert(10);s.insert(4);s.insert(7);MySet::set<int>::iterator sit=s.begin();while(sit!=s.end()){//(*sit)++;//这里修改key值就会报错cout<<*sit<<" ";++sit;}cout<<endl;return 0;
}
测试结果:
四.map封装完整实现
1.map的迭代器函数
这里map的迭代器函数和set的有些不同,因为set要求存储的key值不能被修改,而map只限定了键值对中的key值不能修改,而value值可以修改,所以这里使用红黑树类模板做参数时有些不同,通过pair<const K,V>
实现key值不能修改,而value值可以修改。
namespace MyMap{template<class K,class V>class Map{struct MapKeyOfT{const K& operator()(const pair<const K,V>& kv){return kv.first;}};public://map的普通迭代器iteratortypedef typename RBTree<K,pair<const K,V>,MapKeyOfT>::iterator iterator;//map的const类型迭代器const_iteratortypedef 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();}//其他成员函数//...private:RBTree<K,pair<const K,V>,MapKeyOfT> _t;};
};
2.map的插入函数
相较于set的插入函数,map的插入函数就比较简单,直接调用函数即可
pair<iterator,bool> insert(const pair<const K,V>& kv){return _t.insert(kv);
}
3.map的operator[]
函数
map相比于set还有一个其他的使用方法,就是[]
,[]
可以通过key值,返回value值。如果参数key值不存在map中,会将参数key值插入到map中,然后返回value的对应类型的初始化值,当然还可以通过[]
修改对应key值的value值。
V& operator[](const K& key){//V()是模板参数V的默认构造pair<iterator,bool> rit=insert(make_pair(key,v()));return rit.first->second;
}
4.测试
测试代码:
#include"Map.h"void test1(){MyMap::Map<int,int> m;m.insert(make_pair(3,3));m.insert(make_pair(2,2));m.insert(make_pair(1,1));MyMap::Map<int,int>::iterator it=m.begin();while(it!=m.end()){//(it->first)++;//这里对key值修改就会报错cout<<it->first<<" "<<it->second<<endl;++it;}cout<<endl;m[3]=1;m[4];m[5]=100;for(auto e : m){cout<<e.first<<" "<<e.second<<endl;}
}int main(){test1();return 0;
}
测试结果:
五.完整代码文件
1.RBTree.h
文件
#include<iostream>
#include<utility>
#include<vector>
#include<time.h>
using namespace std;enum Colour {RED,BLACK
};template<class T>
class RBTreeNode {
public://构造函数RBTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}//成员变量RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;
};//迭代器类封装
template<class T,class Ptr,class Ref>
class TreeIterator{//重命名定义typedef RBTreeNode<T> Node; typedef TreeIterator<T,Ptr,Ref> self;typedef TreeIterator<T,T*,T&> Iterator;public:Node* _node;TreeIterator(Node* node):_node(node){}TreeIterator(const Iterator& it):_node(it._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->_right){Node* subleft=_node->_right;while(subleft->_left){subleft=subleft->_left;}_node=subleft;}//如果该节点右子节点为空,则找到该节点父节点的左子节点的祖先节点else{Node* cur=_node;Node* parent=cur->_parent;while(parent){//如果cur节点是父节点的右子节点,继续往上if(cur==parent->_right){ cur=parent;parent=parent->_parent;}//如果cur节点是父节点的左子节点,停止else{break;}}_node=parent;}return *this;}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){if(cur==parent->_left){cur=parent;parent=parent->_parent;}else{break;}}_node=parent;}return *this;}};template<class K , class T, class KeyOfT>
class RBTree {typedef RBTreeNode<T> Node;
public://普通对象迭代器typedef TreeIterator<T ,T* ,T&> iterator;//const对象迭代器typedef TreeIterator<T ,const T* ,const T&> const_iterator;//构造函数RBTree():_root(nullptr){}iterator begin(){Node* cur=_root;while(cur&&cur->_left){cur=cur->_left;}return cur;}iterator end(){return nullptr;}const_iterator begin()const {Node* cur=_root;while(cur&&cur->_left){cur=cur->_left;}return cur;}const_iterator end()const {return nullptr;}Node* Find(const K& key){Node* cur=_root;KeyOfT kot;while(cur){//这里参数key已经是K类型的,所以不用调用仿函数kot()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(_root,true);}Node* parent=nullptr;Node* cur=_root;KeyOfT kot;while(cur) {//这里参数data是T类型的,是容器里存储的对象,不是K类型,所以要调用仿函数kot()获取key值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(cur,false);}}cur=new Node(data);cur->_col=RED;if(kot(parent->_data)<kot(data)){parent->_right=cur;}else{parent->_left=cur;}cur->_parent=parent;Node* newnode=cur;while(parent&&parent->_col==RED){Node* grandfather=parent->_parent;//如果parent节点在左子节点if(parent==grandfather->_left){Node* uncle=grandfather->_right;//如果uncle节点存在且节点为红色if(uncle&&uncle->_col==RED){//变色parent->_col=uncle->_col=BLACK;grandfather->_col=RED;//继续往上cur=grandfather;parent=cur->_parent;}//如果uncle节点不存在 或者 节点为黑色else{//如果cur节点在左子节点if(cur==parent->_left){//右单旋RotateR(grandfather);//旋转后变色grandfather->_col=RED;parent->_col=BLACK;}//如果cur节点在右子节点else{//左双旋//先左单旋,再右单旋RotateL(parent);RotateR(grandfather);//旋转后变色cur->_col=BLACK;grandfather->_col=RED;}break;}}//如果parent节点在右子节点else{Node* uncle=grandfather->_left;//如果uncle节点存在且节点为红色if(uncle&&uncle->_col==RED){//变色parent->_col=uncle->_col=BLACK;grandfather->_col=RED;//继续往上cur=grandfather;parent=cur->_parent;}//如果uncle节点不存在 后者 节点为黑色else{//如果cur节点在右子节点if(cur==parent->_right){//左单旋RotateL(grandfather);//变色parent->_col=BLACK;grandfather->_col=RED;}//如果cur节点在左子节点else{//右双旋//先右单旋,再左单旋RotateR(parent);RotateL(grandfather);//旋转后变色cur->_col=BLACK;grandfather->_col=RED;}break;}}}_root->_col=BLACK;return make_pair(newnode,true);}int Height(){return _Height(_root);}bool IsBlance(){return _IsBlance(_root);}private: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;}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<<"RED False"<<endl;return false;}return CheckColour(root->_left,blacknum,benchmark)&&CheckColour(root->_right,blacknum,benchmark);}bool _IsBlance(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);}//左单旋void RotateL(Node* parent){Node* cur=parent->_right;Node* curleft=cur->_left;Node* ppnode=parent->_parent;parent->_right=curleft;if(curleft){curleft->_parent=parent;}cur->_left=parent;parent->_parent=cur;if(ppnode){if(ppnode->_left==parent){ppnode->_left=cur;cur->_parent=ppnode;}else{ppnode->_right=cur;cur->_parent=ppnode;}}else{cur->_parent=nullptr;_root=cur;}}//右单旋void RotateR(Node* parent){Node* cur=parent->_left;Node* curright=cur->_right;Node* ppnode=parent->_parent;parent->_left=curright;if(curright){curright->_parent=parent;}cur->_right=parent;parent->_parent=cur;if(ppnode){if(ppnode->_left==parent){ppnode->_left=cur;cur->_parent=ppnode;}else{ppnode->_right=cur;cur->_parent=ppnode;}}else{cur->_parent=nullptr;_root=cur;}}private:Node* _root;
};
2.Set.h
文件
#include"RBTree.h"namespace MySet{template<class K>class set{//内部类,用来获取set存储对象中的key值struct SetKeyOfT{const K& operator()(const K& key){return key;}};public://这里的类模板还没有实例化对象,所以要加上关键字typenametypedef 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();}//这里返回的是const_iterator类型的迭代器pair<iterator,bool> insert(const K& key){//插入返回的是iterator类型的迭代器pair<typename RBTree<K,K,SetKeyOfT>::iterator,bool> ret=_t.insert(key);return pair<iterator,bool>(ret.first,ret.second);}private://第一个模板参数k是set存储的数据类型RBTree<K,K,SetKeyOfT> _t;};
};
3.Map.h
文件
#include"RBTree.h"namespace MyMap{template<class K,class V>class Map{struct MapKeyOfT{const K& operator()(const pair<const 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();}//operator[],通过key值,返回value值,具备插入和修改V& operator[](const K& key){pair<iterator,bool> rit=insert(make_pair(key,V()));return rit.first->second;}pair<iterator,bool> insert(const pair<const K,V>& kv){return _t.insert(kv);}private:RBTree<K,pair<const K,V>,MapKeyOfT> _t;};
};
以上就是关于set和map的封装讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!
相关文章:
【c++篇】:解读Set和Map的封装原理--编程中的数据结构优化秘籍
✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨ ✨ 个人主页:余辉zmh–CSDN博客 ✨ 文章所属专栏:c篇–CSDN博客 文章目录 前言一.set和map的初步封装1.树的节点封装修改2.Find()查找函数3.红…...
华为鸿蒙内核成为HarmonyOS NEXT流畅安全新基座
HDC2024华为重磅发布全自研操作系统内核—鸿蒙内核,鸿蒙内核替换Linux内核成为HarmonyOS NEXT稳定流畅新基座。鸿蒙内核具备更弹性、更流畅、更安全三大特征,性能超越Linux内核10.7%。 鸿蒙内核更弹性:元OS架构,性能安全双收益 万…...
ArcGIS API for Javascript学习
一、ArcGIS API for Javascript 介绍 ArcGIS API for Javascript 是由美国 Esri 公司推出,跟随ArcGIS 9.3 同时发布的,是Esri 基于dojo 框架和 REST 风格实现的一套编程接口。通过 ArcGIS API for Javascript可以对ArcGIS for Server 进行访问ÿ…...
LeetCode 3206.交替组 I:遍历
【LetMeFly】3206.交替组 I:遍历 力扣题目链接:https://leetcode.cn/problems/alternating-groups-i/ 给你一个整数数组 colors ,它表示一个由红色和蓝色瓷砖组成的环,第 i 块瓷砖的颜色为 colors[i] : colors[i] …...
环形缓冲区
什么是环形缓冲区 环形缓冲区,也称为循环缓冲区或环形队列,是一种特殊的FIFO(先进先出)数据结构。它使用一块固定大小的内存空间来缓存数据,并通过两个指针(读指针和写指针)来管理数据的读写。当任意一个指针到达缓冲区末尾时,会自动回绕到缓冲区开头,形成一个"环"。…...
Maven 仓库
Maven 仓库对于管理构建 Java 项目所需的依赖和插件至关重要。 Maven 仓库主要有三种类型:本地仓库、中央仓库和远程仓库。 本文将探讨每种仓库的用途以及如何有效使用它们。 Maven 仓库类型 本地仓库 本地仓库是位于您本地机器上的一个目录,Maven 在…...
29.UE5蓝图的网络通讯,多人自定义事件,变量同步
3-9 蓝图的网络通讯、多人自定义事件、变量同步_哔哩哔哩_bilibili 目录 1.网络通讯 1.1玩家Pawn之间的同步 1.2事件同步 1.3UI同步 1.4组播 1.5变量同步 1.网络通讯 1.1玩家Pawn之间的同步 创建一个第三人称项目 将网络模式更改为监听服务器,即将房主作为…...
计算机网络习题解答--个人笔记(未完)
本篇文章为关于《计算机网络-自顶向下方法第七版》的阅读总结和课后习题解答(未完待续) 第二章: cookie:(这里是比较老版本的HTTP,具体HTTPs是怎么实现的不是很清楚)cookie的原理其实很简单。就是在HTTP消息头上又多…...
Unity图形学之雾Fog
1.设置雾化: 2.雾化变化曲线:FogMode (1)线性: (2)一次指数: (3)二次指数: Shader "Custom/FogTest" {Properties{_Color ("Color…...
ML 系列:第 36 节 — 统计学中的抽样类型
ML 系列:第 36 天 — 统计学中的抽样类型 文章目录 一、说明二、抽样方法三、简单随机抽样四、 Stratified Sampling分层抽样五、 Cluster Sampling 整群抽样六、Systematic Sampling系统抽样七、Convenience Sampling便利抽样八、结论 一、说明 统计学中的抽样类型…...
docker-compose部署java服务
文章目录 一、下载安装docker-compose二、编写Dockerfile文件三、编写docker-compose.yml文件配置说明 四、服务启动五、测试与验证 一、下载安装docker-compose 在安装docker时,并不会同时把docker-compose安装好,需要额外安装一下 下载docker-compos…...
ubuntu22开机自动登陆和开机自动运行google浏览器自动打开网页
一、开机自动登陆 1、打开settings->点击Users 重启系统即可自动登陆桌面 二、开机自动运行google浏览器自动打开网页 1、安装google浏览器 sudo wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo dpkg -i ./google-chrome-stable…...
java接口对接标准
概述 最近在跟许多外部平台对接,遇到了很多问题,在此记录一下接口的对接标准。 接口对接标准 确认环境,分别获取di和prd环境的接口信息,比如域名。确认不同环境的防火墙是否连通。接口校验,接口携带的token信息如何…...
训练的decoder模型文本长度不一致,一般设置为多大合适,需要覆盖最长的文本长度么
在训练解码器模型时,文本长度不一致是常见的情况,需要根据任务的特性和数据集的长度分布来设置合理的最大长度 (max_length)。以下是一些指导原则,帮助你设置合适的最大长度: 1. 是否需要覆盖最长文本长度 覆盖最长文本长度: 如果任务对完整性要求很高(例如生成数学公式、…...
安装MySQL服务
安装版本MySQL8的安装包 安装界面 在这里选择MySQL中的Server only 只安装服务器端 如果选择custom需要如下图 进入配置导向,点击ready to configure,点击next即可 采用默认形式 执行成功后,会出现自动选择项 点击next然后再点击Finish 启动…...
十二、正则表达式、元字符、替换修饰符、手势和对话框插件
1. 正则表达式 1.1 基本使用 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title&g…...
Unreal从入门到精通之如何绘制用于VR的3DUI交互的手柄射线
文章目录 前言实现方式MenuLaser实现步骤1.Laser和Cursor2.移植函数3.启动逻辑4.检测射线和UI的碰撞5.激活手柄射线6.更新手柄射线位置7.隐藏手柄射线8.添加手柄的Trigger监听完整节点如下:效果图前言 之前我写过一篇文章《Unreal5从入门到精通之如何在VR中使用3DUI》,其中讲…...
如何提升编程能力第二篇
如何提升编程能力2 1. 引言2. 掌握理论基础2.1 理解编程语言的核心2.2 数据结构与算法2.3 计算机基础与系统设计3.1 多写代码3.2 参与开源项目3.3 开发自己的项目 4. 提高代码质量4.1 代码风格与可读性4.2 测试驱动开发 1. 引言 编程是推动现代科技发展的核心技能,…...
【AI日记】24.11.26 聚焦 kaggle 比赛
【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】 核心工作 1 内容:研究 kaggle 比赛时间:3 小时 核心工作 2 内容:学习 kaggle 比赛 Titanic - Machine Learning from Disaster时间:4 小时备注:这…...
计算机网络八股整理(一)
计算机网络八股文整理 一:网络模型 1:网络osi模型和tcp/ip模型分别介绍一下 osi模型是国际标准的网络模型,它由七层组成,从上到下分别是:应用层,表示层,会话层,传输层,…...
删除链表中的重复元素
删除链表中的重复元素 单链表的创建和使用删除链表中的重复元素 I题目描述解题思路代码实现 删除链表中的重复元素 II题目描述解题思路代码实现 单链表的创建和使用 使用vector结合单链表数据结构创建一个通用单链表。 #include <iostream> #include <vector>str…...
序列求和 牛客网
链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网 题目描述 定义S(n) 12 22 … n2,输出S(n) % 1000000007。 注意:1 < n < 1e18。 输入描述: 多组输入,输入直到遇到EOF为止;第一行输…...
【Oracle11g SQL详解】 SELECT 语句的基础用法与示例
SELECT 语句的基础用法与示例 在 Oracle 11g 中,SELECT 语句是最常用的 SQL 语句,用于从数据库表中查询数据。本文将从语法结构、使用方法和常见示例出发,系统讲解 SELECT 语句的基础用法。 一、SELECT 语句的基本语法 SELECT 列名1, 列名2…...
编译以前项目更改在x64下面时报错:函数“PVOID GetCurrentFiber(void)”已有主体
win32下面编译成功,但是x64报错 1>GetWord.c 1>md5.c 这两个文件无法编译 1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.22000.0\um\winnt.h(24125,1): error C2084: 函数“PVOID GetCurrentFiber(void)”已有主体 1>C:\Program Files (x…...
【小白学机器学习36】关于独立概率,联合概率,交叉概率,交叉概率和,总概率等 概念辨析的例子
目录 1 先说结论 2 联合概率 3 边缘概率 4 (行/列)边缘概率的和 总概率1 5 条件概率 5.1 条件概率的除法公式 5.2 条件概率和联合概率区别 1 先说结论 关于独立概率,联合概率,交叉概率,交叉概率和,总概率 类型含义 …...
如何使用 Tailwind CSS 构建响应式网站:详细指南
文章目录 前言一、安装 Tailwind CSS二、配置 Tailwind CSS三、使用 Tailwind CSS 构建响应式网站四、优化和部署结语 前言 在当今的数字时代,网站不仅需要在桌面浏览器上看起来出色,还需要在移动设备和平板电脑上提供一致的用户体验。响应式设计成为了…...
LabVIEW发动机热磨合试验台
在汽车发动机的研发和质量控制中,发动机热磨合试验是关键环节。它能够检验发动机在实际运行条件下的性能,及时发现异响、振动、漏油等潜在问题。通过搭建基于LabVIEW的高效测试平台,可以显著提高发动机的可靠性和使用寿命。下面介绍LabVIEW开…...
【GPT】力量训练是什么,必要吗,有可以替代的方式吗
什么是力量训练? 力量训练是一种通过抵抗力(如重量、阻力带、自身体重等)来刺激肌肉收缩,从而提高肌肉力量、耐力和体积的运动形式。它包括以下常见形式: 自由重量训练:使用哑铃、杠铃、壶铃等。固定器械…...
pikachu文件上传漏洞通关详解
声明:文章只是起演示作用,所有涉及的网站和内容,仅供大家学习交流,如有任何违法行为,均和本人无关,切勿触碰法律底线 目录 概念:什么是文件上传漏洞一、客户端check二、MIME type三、getimagesi…...
java hashCode() 详解
hashCode() 是 Java 中 Object 类 提供的一个重要方法,它在 Java 集合框架中扮演着关键角色,特别是在使用哈希表相关的集合(如 HashMap、HashSet 和 Hashtable)时。以下是对 hashCode() 方法的详解,包括概念、用法、规…...
鸿蒙学习自由流转与分布式运行环境-价值与架构定义(1)
文章目录 价值与架构定义1、价值2、架构定义 随着个人设备数量越来越多,跨多个设备间的交互将成为常态。基于传统 OS 开发跨设备交互的应用程序时,需要解决设备发现、设备认证、设备连接、数据同步等技术难题,不但开发成本高,还存…...
JavaWeb
JavaWeb 一、JavaWeb 是什么?二、JavaWeb 发展阶段三、JavaWeb 常用架构Servlet JSP 架构SSH 架构SSM 架构SpringBoot架构SpringCloud架构 四、JavaWeb 项目结构(带web.xml的)五、如何打包六、war包部署1. Tomcat 介绍2. Tomcat目录结构3. 开…...
加快发展社会保障事业的必要性
题目 【2011年浙江公务员考试】(二)某市将召开一次加快发展社会保障事业的形势分析会。会上,某领导要就加快发展社会保障事业的必要性做主题发言。请结合给定资料7~8,为领导拟一份发言要点。(25分) 要求&a…...
责任链模式在spring security过滤器链中的应用
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它允许多个对象按照顺序处理请求,并且每个对象可以选择自己是否处理该请求或将其传递给下一个对象。 在Spring Security中,责任链模式得到了广泛应…...
Netty基本原理
目录 前言 原生NIO VS Netty 原生NIO存在的问题 Netty的优点 线程模型 传统阻塞 I/O (Blocking I/O) 2. 非阻塞 I/O (Non-blocking I/O) 3. 多路复用 I/O (Multiplexed I/O) 4. Reactor 模式 常见的 Reactor 模式的变体: Netty线程模型 工作原理 前言 N…...
图论入门编程
卡码网刷题链接:98. 所有可达路径 一、题目简述 二、编程demo 方法①邻接矩阵 from collections import defaultdict #简历邻接矩阵 def build_graph(): n, m map(int,input().split()) graph [[0 for _ in range(n1)] for _ in range(n1)]for _ in range(m): …...
Haproxy
一、haproxy简介 HAProxy 是法国开发者 威利塔罗 (Willy Tarreau) 在 2000 年使用 C 语言开发的一个开源软件 是一款具备高并发 ( 万级以上 ) 、高性能的 TCP 和 HTTP 负载均衡器 支持基于 cookie 的持久性,自动故障切换,支持正则表达式及 web 状态统…...
旋转磁体产生的场 - 实验视频资源下载
先发几个视频,是2019年所作的实验内容 更多视频,到某宝找我吧。注意:是收费的。 20190312-180244-旋转磁体产生的场造成激光功率减小 https://download.csdn.net/download/u014161757/90038058 20190313-090956-旋转磁体产生的场对真空介电…...
Java Map
在Java的集合框架中,Map接口用于存储键值对,提供了一种基于键进行查找和操作的数据结构。Map接口的实现类提供了丰富的方法来操作键值对,例如添加、删除、更新和查找。本文将详细介绍Java中的Map接口及其常见实现类,包括HashMap、…...
长三角文博会:Adobe国际认证体系推动设计人才评价新标准
2024年11月22日,由上海、江苏、浙江、安徽三省一市党委宣传部共同发起的第五届长三角文化博览会(简称“长三角文博会”)在上海国家会展中心盛大启幕。长三角文博会自2018年起已成功举办多届,已成为展示区域文化产业发展成果、推动…...
GoogleTest做单元测试
目录 环境准备GoogleTest 环境准备 git clone https://github.com/google/googletest.git说cmkae版本过低了,解决方法 进到googletest中 cmake CMakeLists.txt make sudo make installls /usr/local/lib存在以下文件说明安装成功 中间出了个问题就是,…...
CSDN 博客自动发布脚本(Python 含自动登录、定时发布)
文章目录 关于 csdn auto publisher使用 关于 csdn auto publisher 源码地址:https://github.com/ezscode/csdn_auto_publisher 使用 def test_simple_pub():file_path /Users/xx/Documents/xxx/tool.md article Article(file_path) article.tags [python] art…...
RL78/G15 Fast Prototyping Board Arduino IDE 平台开发过程
这是一篇基于RL78/G15 Fast Prototyping Board的Arduino IDE开发记录 RL78/G15 Fast Prototyping Board硬件简介(背景)基础测试(方法说明/操作说明)开发环境搭建(方法说明/操作说明代码结果)Arduino IDE RL…...
VsCode 插件推荐(个人常用)
VsCode 插件推荐(个人常用)...
零基础学安全--云技术基础
目录 学习连接 前言 云技术历史 云服务 公有云服务商 云分类 基础设施即服务(IaaS) 平台即服务(PaaS) 软件即服务(SaaS) 云架构 虚拟化 容器 云架构设计 组件选择 基础设施即代码 集成部署…...
docker如何安装redis
第一步 如果未指定redis,则安装的是最新版的 docker pull redis 创建一个目录 mkdir /usr/local/docker/redis 然后直接可以下载redis,这是方式确实不怎么好,应该找在官网上找对应的redis配置文件 wget http://download.redis.io/redis-stab…...
搜维尔科技:仿人双臂遥操作系统,力反馈灵巧手操作解决方案
仿人双臂遥操作系统,力反馈灵巧手操作解决方案 搜维尔科技:仿人双臂遥操作系统,力反馈灵巧操作解决方案...
C++ 优先算法 —— 无重复字符的最长子串(滑动窗口)
目录 题目: 无重复字符的最长子串 1. 题目解析 2. 算法原理 Ⅰ. 暴力枚举 Ⅱ. 滑动窗口(同向双指针) 3. 代码实现 Ⅰ. 暴力枚举 Ⅱ. 滑动窗口 题目: 无重复字符的最长子串 1. 题目解析 题目截图: 此题所说的…...
R语言绘图过程中遇到图例的图块中出现字符“a“的解决方法
R语言绘图过程中遇到图例的图块中出现字符的解决方法 因为我遇到这个问题的时候没在网上找到合适的方法,找到个需要付费的,算了。也许是因为问的方式不同,问了半天AI也回答出来,莫名有些烦躁,打算对代码做个分析&…...
海康面阵、线阵、读码器及3D相机接线说明
为帮助用户快速了解和配置海康系列设备的接线方式,本文将针对海康面阵相机、线阵相机、读码器和3D相机的主要接口及接线方法进行全面整理和说明。 一、海康面阵相机接线说明 海康面阵相机使用6-pin P7接口,其功能设计包括电源输入、光耦隔离信号输入输出…...