set与map的详细封装步骤

目录

一.set与map在STL中的源码

二.修改红黑树

1.插入与查找时的比较方式

2.插入时的返回值

3.补充成员函数

三.封装set与map

1.迭代器的实现

2.函数接口

3.map中的operator[]

四.完整代码

set.h

map.h

RBTree.h


一.set与map在STL中的源码

想要简单实现set与map 需要我们稍微理解其底层代码。以下是简化部分

set:

template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set {
public:// typedefs:typedef Key key_type;typedef Key value_type;typedef Compare key_compare;typedef Compare value_compare;
private:typedef rb_tree<key_type, value_type, identity<value_type>, key_compare, Alloc> rep_type;rep_type t;  // red-black tree representing set//·····
}

map:

template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class map {
public:// typedefs:typedef Key key_type;typedef T data_type;typedef T mapped_type;typedef pair<const Key, T> value_type;typedef Compare key_compare;//·····private:typedef rb_tree<key_type, value_type, select1st<value_type>, key_compare, Alloc> rep_type;rep_type t;  // red-black tree representing map//·····
}

从两者私有部分不难看出,两者的底层都使用了红黑树。但一棵红黑树只能为K-V模型或K模型,那是如何同时满足map的K-V模型和set的K模型的呢?

不难看出,set与map虽然共用了一颗红黑树,但传入值的方式不一样

set传入的为<K,K>, map传入的是<K, pair<const K, V> >

关于两类模板参数,第一个K方便find和erase接口,第二个则决定我们的红黑树是那种模型,那怎样将两种模型泛化到一起呢?

由于用户只能从上层调用接口,我们在底层添加模板参数T,set对于T传入K,map对于T传入

pari<const K, V>。具体如下

set:

template<class K>
class set
{public:private:RBTree<K, const K, SetKeyOfT> _t;
};

map:

template<class K, class V>
class map
{public:private:// key不能修改(第一个k为什么不加const?//第一个K 只用于删除和查找, 外层修改不到。RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

RBTree:

enum Colour
{RED,   //0BLACK  //1
};//RBTree树节点
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 K, class T>
class RBTree
{typedef RBTreeNode<T> Node;public:private:Node* _root = nullptr;
};

二.修改红黑树

1.插入与查找时的比较方式

当我们插入元素时,set插入的元素为K,比较大小时可以直接比较,map插入的元素为pair<K,V>,在C++中pair直接比较则会首先按照first的大小比较,first相同时再按照second大小比较,这显然不是我们期望的只用K关键字比较。那么如何才能使用一种统一的方式解决这个问题呢?

采用仿函数

在map和set类中添加各自的仿函数,其功能是拿到各自的Key,便于红黑树的比较。

set:

template<class K>
class set
{//仿函数  因为map中红黑树的T 为pair, set中为K, 为了泛型编程//我们使用仿函数对map和set进行统一处理struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public:private:RBTree<K, const K, SetKeyOfT> _t;
};

map:

template<class K, class V>
class map
{//仿函数  因为map中红黑树的T 为pair, set中为K, 为了泛型编程//我们使用仿函数对map和set进行统一处理struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};
public:private:// key不能修改(第一个k为什么不加const?//第一个K 只用于删除和查找, 外层修改不到。RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

RBTree:模板中添加仿函数

enum Colour
{RED,   //0BLACK  //1
};//RBTree树节点
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 K, class T,class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;public:private:Node* _root = nullptr;
};

2.插入时的返回值

在C++源码中, set与map插入的返回值都是pair类型:其意为如果插入成功与否,都返回一个pair类型,如果成功则返回插入位置的迭代器与true,如果失败则返回重复元素位置的迭代器与false。所以,我们应修改Insert函数的返回值。

完善后的Insert函数(内含的迭代器部分在下面会讲,先关注返回值与比较部分即可):

//插入成功--->返回插入位置和true
//插入失败--->返回该元素存在的位置和false
pair<Iterator, bool> Insert(const T& data)
{if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(Iterator(_root), true);}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){// K// pair<K, V>// kot对象,是用来取T类型的data对象中的keyif (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;//记录cur位置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;//叔叔存在且为红, 往上变色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上cur = grandfather;parent = cur->_parent;}// 叔叔不存在或者存在且为黑else{if (cur == parent->_left){//     g  //   p   u// c RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//    g// p     u//    cRotateL(parent);RotateR(grandfather);//cur去了g的位置  g去了u的位置cur->_col = BLACK;grandfather->_col = RED;}//根一定是黑色的break;}}else//叔叔在左边{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且为黑{//    g// u     p//          cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//    g// u     p//    cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}// 如果根节点变红了 设回黑_root->_col = BLACK;return make_pair(Iterator(newnode), true);}

3.补充成员函数

在简单实现中,我们需要把必要的默认成员函数补上。

//我们手写了默认成员函数后 编译器不在为我们自动生成// + default 要编译器给我们生成默认构造 运行效率更好
RBTree() = default;//拷贝
RBTree(const RBTree<K, T, KeyOfT>& t)
{//深拷贝_root = _Copy(t._root);
}
//赋值 重载运算符
RBTree<K, T, KeyOfT>& operator=(const RBTree<K, T, KeyOfT> t)
{//t是形参 相当于和副本交换了swap(_root, t._root);return *this;
}~RBTree()
{_Distory(_root);_root = nullptr;
}private://递归式删除void _Distory(Node* root){if (root == nullptr)return;_Distory(root->_left);_Distory(root->_right);delete root;root = nullptr;}Node* _Copy(Node* root){if (root == nullptr)return nullptr;Node* newroot = new Node(root->_data);newroot->_col = root->_col;newroot->_left = _Copy(root->_left);if (newroot->_left)newroot->_left->_parent = newroot;newroot->_right = _Copy(root->_right);if (newroot->_right)newroot->_right->_parent = newroot;return newroot;}

三.封装set与map

1.迭代器的实现

对于迭代器,我们要实现begin()、end()、*iterator、iterator->、++iterator、!=这四种常见功能。另外,我们希望从外部访问迭代器,只访问到树节点的数据:set只访问K,map只访问pair<const K, V>,因为用户访问树节点的左右父亲颜色等没有意义。

初始化:用一个树的节点来初始化

begin()与end():

对于一棵红黑树,其中序遍历时有序的,也就是说我们在用迭代器从begin()访问到end()的过程中也是有序的基于此,迭代器的begin()指向的位置应当是整棵树中最小的数即最左的节点(图中改为1)。end()作为最大节点的后一个元素,为空即可。

*iterator:对迭代器解引用,应返回对数据的引用

iterator->:引出迭代器的元素,应返回对应数据的地址(编译器会帮我们优化)

++iterator:对于前置++操作,在上图中访问顺序为1-6-8-11-13-15-17-22-25-27

规律为:若一个节点有右子树,则访问其右子树的最左节点;若没有右子树,则倒着往上寻找一个孩子为父亲左的一个祖先。

!=:直接比较两个值相不相等即可。

在树外新建一个类进行实现,当然也可以嵌在树中当内部类。

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{typedef RBTreeNode<T> Node;typedef __RBTreeIterator<T, Ref, Ptr> Self;Node* _node;__RBTreeIterator(Node* node):_node(node){}// 迭代器常见操作//返回引用Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}//前置++Self& operator++(){//++操作迭代器指向的元素一定是按顺序的if (_node->_right){//如果有右树, 下一个节点为右数的最左边Node* leftMin = _node->_right;while (leftMin->_left){leftMin = leftMin->_left;}_node = leftMin;}else{//没有右树,倒着在祖先里找,找孩子是父亲左的祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}};Iterator Begin()
{//空不空无所谓Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return Iterator(leftMin);
}Iterator End()
{return Iterator(nullptr);
}

2.函数接口

在我们map与set里面对迭代器的接口封装以下即可。

set:

template<class K>
class set
{//仿函数  因为map中红黑树的T 为pair, set中为K, 为了泛型编程//我们使用仿函数对map和set进行统一处理struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public://消除二义性 让编译器知道你要给一个类型取别名 而不是 定义一个变量typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;iterator begin(){return _t.Begin();}iterator end(){return _t.End();}private:RBTree<K, const K, SetKeyOfT> _t;
};

map:

template<class K, class V>
class map
{//仿函数  因为map中红黑树的T 为pair, set中为K, 为了泛型编程//我们使用仿函数对map和set进行统一处理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;	iterator begin(){return _t.Begin();}iterator end(){return _t.End();}private:// key不能修改(第一个k为什么不加const?//第一个K 只用于删除和查找, 外层修改不到。RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

3.map中的operator[]

对于map,其支持方括号下标访问。

形如:

map<string, int> mp;

mp[”x”] = 1;

其原理为若有”x“,则修改其对应值为1,若无“x”,则插入”x“,值为1。

所以重载[]时应进行插入,用pair类型接收。

V& operator[](const K& key)
{pair<iterator, bool> ret = _t.Insert({key,V()});//map迭代器是一个节点的指针 "->"我们重载过 得到pair类型 pair<const K, V> 返回second;return ret.first->second;
}

四.完整代码

最后补充const迭代器以及一些细节,代码里有注释。附测试。

set.h

#pragma once//template <class Key, class Compare = less<Key>, class Alloc = alloc>
//class set {
//public:
//    typedef Key key_type;
//    typedef Key value_type;
//private:
//    typedef rb_tree<key_type, value_type,
//        identity<value_type>, key_compare, Alloc> rep_type;
//    rep_type t;  // red-black tree representing set
//}// 抓重点
// 化繁为简,只关注跟抓重点部分有关系的,其他通通不关注namespace Xiang
{template<class K>class set{//仿函数  因为map中红黑树的T 为pair, set中为K, 为了泛型编程//我们使用仿函数对map和set进行统一处理struct SetKeyOfT{const K& operator()(const K& key){return key;}};public://消除二义性 让编译器知道你要给一个类型取别名 而不是 定义一个变量typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator 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();}pair<iterator, bool> insert(const K& key){return _t.Insert(key);}iterator find(const K& key){return _t.Find(key);}private:RBTree<K, const K, SetKeyOfT> _t;};void PrintSet(const set<int>& s){for (auto e : s){cout << e << endl;}}void test_set(){set<int> s;s.insert(4);s.insert(2);s.insert(5);s.insert(15);s.insert(7);s.insert(1);s.insert(5);s.insert(7);PrintSet(s);set<int>::iterator it = s.begin();while (it != s.end()){//*it += 5;cout << *it << " ";++it;}cout << endl;for (auto e : s){cout << e << " ";}cout << endl;set<int> copy = s;for (auto e : copy){cout << e << " ";}cout << endl;//cout << copy._t.IsBalance() << endl;}
}

map.h

#pragma once
//template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
//class map {
//public:
//    typedef Key key_type;
//    typedef pair<const Key, T> value_type;
//
//private:
//    typedef rb_tree<key_type, value_type,
//        select1st<value_type>, key_compare, Alloc> rep_type;
//    rep_type t;  // red-black tree representing map
//};namespace Xiang
{template<class K, class V>class map{//仿函数  因为map中红黑树的T 为pair, set中为K, 为了泛型编程//我们使用仿函数对map和set进行统一处理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>::ConstIterator 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();}pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}//find 返回迭代器iterator find(const K& key){return _t.Find(key);}V& operator[](const K& key){pair<iterator, bool> ret = _t.Insert({key,V()});//map迭代器是一个节点的指针 "->"我们重载过 得到pair类型 pair<const K, V> 返回second;return ret.first->second;}private:// key不能修改(第一个k为什么不加const?//第一个K 只用于删除和查找, 外层修改不到。RBTree<K, pair<const K, V>, MapKeyOfT> _t;};void test_map1(){map<string, int> m;m.insert({ "乐",1 });m.insert({ "通",1 });m.insert({ "给",1 });m.insert({ "哦",3 });cout << m["乐"] << endl << "有乐" << endl;map<string, int>::iterator it = m.begin();while (it != m.end()){//it->first += 'x';it->second += 1;//cout << it.operator->()->first << ":" << it->second << endl;cout << it->first << ":" << it->second << endl;++it;}cout << endl;}void test_map2(){string arr[] = { "a", "", "a", "", "d", "c", "","他怕", "李", "好", "哦","给","888", "达瓦","h" };map<string, int> countMap;for (auto& e : arr){countMap[e]++;}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}cout << endl;}
}

RBTree.h

#pragma onceenum Colour
{RED,   //0BLACK  //1
};//RBTree树节点
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 Ref, class Ptr>
struct __RBTreeIterator
{typedef RBTreeNode<T> Node;typedef __RBTreeIterator<T, Ref, Ptr> Self;Node* _node;__RBTreeIterator(Node* node):_node(node){}// 迭代器常见操作//返回引用Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}//前置++Self& operator++(){//++操作迭代器指向的元素一定是按顺序的if (_node->_right){//如果有右树, 下一个节点为右数的最左边Node* leftMin = _node->_right;while (leftMin->_left){leftMin = leftMin->_left;}_node = leftMin;}else{//没有右树,倒着在祖先里找,找孩子是父亲左的祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}};template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef __RBTreeIterator<T, T&, T*> Iterator;typedef __RBTreeIterator<T, const T&, const T*> ConstIterator;//我们手写了默认成员函数后 编译器不在为我们自动生成// + default 要编译器给我们生成默认构造 运行效率更好RBTree() = default; //拷贝RBTree(const RBTree<K, T, KeyOfT>& t){//深拷贝_root = _Copy(t._root);}//赋值 重载运算符RBTree<K, T, KeyOfT>& operator=(const RBTree<K, T, KeyOfT> t){//t是形参 相当于和副本交换了swap(_root, t._root);return *this;}~RBTree(){_Distory(_root);_root = nullptr;}//begin 为最左节点   end为空节点Iterator Begin(){//空不空无所谓Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return Iterator(leftMin);}ConstIterator Begin() const{Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return ConstIterator(leftMin);}Iterator End(){return Iterator(nullptr);}ConstIterator End() const{return ConstIterator(nullptr);}Iterator Find(const K& key){Node* cur = _root;while (cur){if (KeyOfT(cur->_data) < key){cur = cur->_right;}else if (KeyOfT(cur->_data) > key){cur = cur->_left;}else{return Iterator(cur);}}return End();}//插入成功--->返回插入位置和true//插入失败--->返回该元素存在的位置和falsepair<Iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(Iterator(_root), true);}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){// K// pair<K, V>// kot对象,是用来取T类型的data对象中的keyif (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;//记录cur位置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;//叔叔存在且为红, 往上变色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上cur = grandfather;parent = cur->_parent;}// 叔叔不存在或者存在且为黑else{if (cur == parent->_left){//     g  //   p   u// c RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//    g// p     u//    cRotateL(parent);RotateR(grandfather);//cur去了g的位置  g去了u的位置cur->_col = BLACK;grandfather->_col = RED;}//根一定是黑色的break;}}else//叔叔在左边{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且为黑{//    g// u     p//          cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//    g// u     p//    cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}// 如果根节点变红了 设回黑_root->_col = BLACK;return make_pair(Iterator(newnode), true);}//右旋void RotateR(Node* parent){// 要更改六条边Node* subL = parent->_left;Node* subLR = subL->_right;// 先处理parent和SubLRparent->_left = subLR;if (subLR)subLR->_parent = parent;//再处理parent和 subl//因为 后面需要把 parent的parent和subl链接//需要先保存parent的parentsubL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;//处理 subl的 父节点if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}//左旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;_root->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){if (_root->_col == RED){return false;}int refNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++refNum;}cur = cur->_left;}return _Check(_root, 0, refNum);}int Height(){return _Height(_root);}int Size(){return _Size(_root);}private://递归式删除void _Distory(Node* root){if (root == nullptr)return;_Distory(root->_left);_Distory(root->_right);delete root;root = nullptr;}Node* _Copy(Node* root){if (root == nullptr)return nullptr;Node* newroot = new Node(root->_data);newroot->_col = root->_col;newroot->_left = _Copy(root->_left);if (newroot->_left)newroot->_left->_parent = newroot;newroot->_right = _Copy(root->_right);if (newroot->_right)newroot->_right->_parent = newroot;return newroot;}int _Size(Node* root){return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;}int _Height(Node* root){if (root == nullptr)return 0;return max(_Height(root->_left), _Height(root->_right)) + 1;}bool _Check(Node* root, int blackNum, const int refNum){if (root == nullptr){//cout << blackNum << endl;if (refNum != blackNum){cout << "存在黑色节点的数量不相等的路径" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << root->_kv.first << "存在连续的红色节点" << endl;return false;}if (root->_col == BLACK){blackNum++;}return _Check(root->_left, blackNum, refNum)&& _Check(root->_right, blackNum, refNum);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}private:Node* _root = nullptr;
};

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

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

相关文章

短视频矩阵工具有哪些?如何辨别是否正规?

随着短视频平台的持续火爆&#xff0c;搭建短视频矩阵成为各大品牌商家提高营销效果和完成流量变现的主要方式之一&#xff0c;类似于短视频矩阵工具有哪些等问题也在多个社群有着不小的讨论度。 而就短视频矩阵工具的市场现状而言&#xff0c;其整体呈现出数量不断增长&#x…

使用神卓互联来访问单位内部web【内网穿透神器】

在现代工作环境中&#xff0c;有时我们需要从外部访问单位内部的 web 资源&#xff0c;而神卓互联这款内网穿透神器就能完美地满足这一需求。 使用神卓互联来访问单位内部 web 其实并不复杂&#xff0c;以下是大致的使用步骤和配置方法。 首先&#xff0c;我们需要在单位内部的…

Three.js做了一个网页版的我的世界

前言 笔者在前一阵子接触到 Three.js 后, 发现了它能为前端 3D 可视化 / 动画 / 游戏方向带来的无限可能, 正好最近在与朋友重温我的世界, 便有了用 Three.js 来仿制 MineCraft 的想法, 正好也可以通过一个有趣的项目来学习一下前端 3D 领域 介绍 游戏介绍 相信大家对我的世…

vue3+ Element-Plus 点击勾选框往input中动态添加多个tag

实现效果&#xff1a; template&#xff1a; <!--产品白名单--><div class"con-item" v-if"current 0"><el-form-item label"平台名称"><div class"contaion" click"onclick"><!-- 生成的标签 …

Unity HoloLens2 MRTK 空间锚点 基础教程

Unity HoloLens2 MRTK 空间锚点 基础教程 Unity HoloLens2 空间锚点MRTK 空间锚点 准备Unity 工程创建设置切换 UWP 平台UWP 平台设置 下载并安装混合现实功能工具导入混合现实工具包和 OpenXR 包 Unity 编辑器 UWP 设置Unity 2019.4.40 设置Unity 2022.3.0 设置Unity 2022.3.0…

KCC@深圳-升压手电制作活动

这次我们将制作一款工业风升压手电。电路简单&#xff0c;适合入门型选手。也会进行原理讲解&#xff0c;方便大家升级改造。 活动概览 活动主题升压手电制作活动时间6月16日&#xff08;周日&#xff09;13:30活动地点月亮湾山庄A13人数限制15合作伙伴706深圳&#xff08;706青…

C语言----字符函数和字符串函数

在编程的过程中&#xff0c;我们要经常处理字符和字符串&#xff0c;为了方便操作字符和字符串&#xff0c;c语言标准库中提供的一系列库函数&#xff0c;接下来我们就开始学习与认识他们 1.字符分类函数 c语言中有一系列的函数是专门做字符分类的&#xff0c;也就是一个字符…

「漏洞复现」I Doc View 在线文档预览 qJvqhFt.json 任意文件读取漏洞(XVE-2024-2115)

0x01 免责声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;作者不为此承担任何责任。工具来自网络&#xff0c;安全性自测&#xff0c;如有侵权请联系删…

解决electron设置透明背景后,引入element-plus样式问题

首先给当前窗口设置自定义窗口以及背景色。 const mainWindow new BrowserWindow({width: 900,height: 670,show: false,autoHideMenuBar: true,...(process.platform linux ? { icon } : {}),webPreferences: {preload: join(__dirname, ../preload/index.js),sandbox: fal…

【 EI会议 | 西南大学主办 | 往届均已实现检索】第三届神经形态计算国际会议(ICNC 2024)

第三届神经形态计算国际会议&#xff08;ICNC 2024) 2024 3rd International Conference on Neuromorphic Computing (ICNC 2024) 一、重要信息 大会官网&#xff1a;www.ic-nc.org&#xff08;点击投稿/参会/了解会议详情&#xff09; 会议时间&#xff1a;2024年12月13-15…

Elasticsearch:智能 RAG,获取周围分块

作者&#xff1a;来自 Elastic Sunile Manjee 在检索增强生成 (RAG) 领域&#xff0c;一个持续存在的挑战是找到输入大型语言模型 (LLM) 的最佳数据量。数据太少会导致响应不足或不准确&#xff0c;而数据太多会导致答案模糊。这种微妙的平衡启发我开发了一个专注于智能分块和利…

社区论坛圈子软件APP ,提供互动交流、知识共享和专业交流的社交平台。

社区论坛圈子软件APP的开发能够为用户提供一个互动交流的社交平台&#xff0c;促进用户之间的知识分享、交流和互助。本文将突出社区论坛圈子软件APP的前景、作用和特点&#xff0c;以帮助您了解该系统的潜力和优势。 一、前景&#xff1a; 知识共享&#xff1a;社区论坛圈子软…

力扣每日一题-419

题目 给你一个大小为 m x n 的矩阵 board 表示甲板&#xff0c;其中&#xff0c;每个单元格可以是一艘战舰 X 或者是一个空位 . &#xff0c;返回在甲板 board 上放置的 战舰 的数量。 战舰 只能水平或者垂直放置在 board 上。换句话说&#xff0c;战舰只能按 1 x k&#xff…

一带一路情 相逢《中国缘》-诗琳探访湘西墨戎苗寨交流有感

一带一路情 相逢《中国缘》 诗琳探访湘西墨戎苗寨交流有感 5月21日至25日&#xff0c;《中国缘》栏目组组织的走进湘西苗疆边陲的文化交流活动&#xff0c;在群山环抱、绿树成荫、人文厚重的湘西古丈墨戎苗寨美丽绽放。这场以民间角度推演的中国和中亚人民的文化交流活动&am…

有一个主域名跟多个二级子域名时该怎么申请SSL证书?

当您拥有主域名以及多个子域名时&#xff0c;选择合适的SSL证书类型对于确保网站的安全性至关重要。以下是三种SSL证书类型的简要介绍&#xff1a; 单域名SSL证书&#xff1a; 功能&#xff1a;只能绑定单个域名&#xff0c;无论是主域名还是子域名。 适用场景&#xff1a;仅…

常用 磁力搜索 磁力链接 工具使用教程

一、什么是磁力链接&#xff1f; 磁力链接&#xff08;Magnet link&#xff09;是一种链接&#xff0c;它利用磁力编码来识别和获取文件的信息。它通常由一串以“magnet:?xturn:btih:”开头的字符串组成&#xff0c;后面跟着文件的哈希值。 二、如何使用磁力链接&#xff1f…

一篇文章看懂Redission原理

文章目录 ☃️可重入锁原理☃️锁重试和WatchDog机制☃️MutiLock原理 上一篇文章讲解了 Rediision的使用 ,这篇文章讲解其原理 ☃️可重入锁原理 在Lock锁中&#xff0c;他是借助于底层的一个voaltile的一个state变量来记录重入的状态的&#xff0c;比如当前没有人持有这把锁…

探索 cartesian_product:更深入理解范围库

理解范围库中的cartesian_product适配器 一、简介二、cartesian_product 适配器的动机三、将行为封装到算法中四、算法的局限性五、总结 一、简介 view::cartesian_product 适配器是range-v3 库一个新的组件。本文主要理解这个组件的功能以及它背后的设计理念&#xff0c;可以…

罗森伯格1800M 2000M 2400M 900M无源互调分析仪

在无线通信领域&#xff0c;频段是宝贵的资源&#xff0c;不同的通信系统通常会采用不同的频段以满足其传输需求。随着技术的发展&#xff0c;越来越多的通信系统被部署在各种频段上。为了准确、高效地测试和调试这些 信系统&#xff0c;各种测试设备也应运而生。源互调分析仪便…

Llama-3安装方法及应用

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…