C++ 红黑树的封装

一.map/set的封装

  在实现了红黑树的部分功能后,我们可以便可以将红黑树作为底层结构来封装map 和 set ,但是问题也随之而来。我们都知道map是k-v的数据模型,而set是k的数据模型,我们难道要去使用两棵红黑树来封装吗?显然不是这样的。

  接下来我们将使用同一棵红黑树实现 map和set。

1.1 封装的基本思路 

  因为我们是用一棵红黑树去封装两种容器,使用我们的这棵红黑树,使用我们的这棵树就不能像以前一样写死成k-v模型的数据结构,需要稍作修改。

  因为红黑树不能自己判断其是否是map/set类型,我们能做的努力就是提高其适应性,对红黑树的要求为:

    既能适配 K-V模型的map,又能适配出K 模型的set。 

对map和set的要求为:

    需要准确的传入数据,传入时让红黑树只能适配出一个类型的容器,对不需要的部分进行切割。

1.2 红黑树节点的调整

  这是我们之前写出的红黑树节点,现在来看显然不太合适,因为我们已经把K-V模型写死了,不太好适配出K模型的set。

  在这里,我们建议让pair类型变成 一个模糊的 T类型,至于是pair 类型还是 k类型,我们希望让map和set来准确的传入。

1.3 map 和 set 的定义 

  我们该如何适配map和set的底层,来让其匹配红黑树呢?

  在这里,我建议让其内部的模板,暂时先有两个数据类型,第一个为 K 模型,第二个为 T模型,也就是说 当为map时,传入map<K,K>时,底层实际传入RBTree<k,pair<k,k>>,传入set<k>时,底层实际传入RBTree<K,K>。

  这样有什么好处呢? 

   为因为我们的底层实现是红黑树,那么我们为了适应map又适应set,实际传入的代表数据参数至少得是两个,可能会有些人问:“map可以直接传入pair啊,那么为什么不直接传入一个RBTree<pair<K,V>>呢?。

  因为有find的存在,map的find是按照k来进行查找的,如果直接传入pair类型,那么红黑树里面的实际类型只有一个pair,当我们查找时,根本无法按照key类型来查找,所以我们的红黑树至少得有两个模板参数。

  因此,即使set是k模型,但是在这里的底层结构上,我们仍然需要传入两个k类型。

map和set基本定义如下:
 

#pragma once
#include"RBTree.h"using namespace MyRBTree;namespace MySet
{template<class K>class Set{private:RBTree<K, K> _set;};
} 
#pragma once
#include"RBTree.h"using namespace MyRBTree;namespace MyMap
{template<class K,class V>class Map{private:RBTree<K, pair<K,V>> _map;};
}

1.4 仿函数 KeyOfValue

  在insert中,我们经常会用到data进行一些比较,但是我们现在data的类型不固定,如果是map中的pair类型,我们就应该用其first进行比较,如果是k类型,我们就应该直接比较。

  最好的解决办法是,在map和set内部分别定义一个KetOfValue 仿函数,然后将其传入红黑树模板中,因为其传入的内容不同,所以我们用来分别处理不同的数据。

  如果是map中的data,因为是kv结构,所以我们取出其data.first;如果是set中的k模型,那么我们直接返回,仿函数定义如下:
  

#pragma once
#include"RBTree.h"
using namespace MyRBTree;namespace MyMap
{template<class K,class V>class Map{public:struct MapKeyOfvalue{const K& operator()(const pair<K, V>& kv){return kv.first;}};private:RBTree<K, pair<K,V>, MapKeyOfvalue> _map;};
}
#pragma once
#include"RBTree.h"using namespace MyRBTree;namespace MySet
{template<class K>class Set{public:struct SetKeyOfvalue{const K& operator()(const K& key){return key;}};private:RBTree<K, K, SetKeyOfvalue> _set;};
} 

同时我们因为要多传入一个仿函数,所以我们的模板和map,set内部的红黑树模板参数也要进行修改:

同时,我们在红黑树内部也需要对进行数据比对的函数进行修改,比如说insert和find等等。

	bool Insert(const pair<K, V>& data){//因为需要使用仿函数的机会不多,我们在需要用到的地方局部创建一下就可以了//如何大多数函数都需要用到,建议定义为全局变量KeyOfValue kot;if (!_root){_root = new Node(data);_root->_co = Black;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){//if (cur->_data.first < data.first)if (kot(cur->_data)< kot(data)){parent = cur;cur = cur->_right;}//else if (cur->_data.first > data.first)else if (kot(cur->_data) > kot(data.first)){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(data);cur->_co = Red;//if (parent->_data.first < cur->_data.first)if (kot(parent->_data) < kot(cur->_data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//开始判断是否需要变色while (parent && parent->_co == Red){Node* grand = parent->_parent;if (parent == grand->_left){Node* uncle = grand->_right;if (uncle && uncle->_co == Red){//这里只变色就好parent->_co = uncle->_co = Black;grand->_co = Red;cur = grand;parent = cur->_parent;}else{if (cur == parent->_left){_RotateR(grand);grand->_co = Red;parent->_co = Black;}else{_RotateL(parent);_RotateR(grand);cur->_co = Black;grand->_co = Red;}break;}}else{Node* uncle = grand->_left;if (uncle && uncle->_co == Red){//这里只变色就好parent->_co = uncle->_co = Black;grand->_co = Red;cur = grand;parent = cur->_parent;}else{if (cur == parent->_right){_RotateL(grand);grand->_co = Red;parent->_co = Black;}else{_RotateR(parent);_RotateL(grand);cur->_co = Black;grand->_co = Red;}break;}}}_root->_co = Black;return true;}
	Node* Find(const K& key){KeyOfValue kot;Node* cur = _root;while (cur){//if (cur->_data.first < key)if (kot(cur->_data) < key){cur = cur->_right;}//else if (cur->_data.first > key)else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;}

1.5 map/set的插入

  现在,在其内部直接套用红黑树的插入即可。

namespace MySet
{template<class K>class Set{public:struct SetKeyOfvalue{const K& operator()(const K& key){return key;}};bool insert(const K& k){return _set.Insert(k);}private:RBTree<K, K, SetKeyOfvalue> _set;};
} 
namespace MyMap
{template<class K,class V>class Map{public:struct MapKeyOfvalue{const K& operator()(const pair<K, V>& kv){return kv.first;}};bool insert(const pair<K,V> &kv){return _map.Insert(kv);}private:RBTree<K, pair<K,V>, MapKeyOfvalue> _map;};
}

 在map和set中先插入一些数据来看一下有没有更改错误:

这里建议在map和set内部封装进去红黑树的Inorder打印一下检查一下错误。

void Inorder()
{_map.Inorder();
}//判断一下是否平衡void IsBalance()
{_map.IsBalance();
}

同时我们的Inorder函数也需要更改

void _Inorder(Node* root)
{if (!root){return;}//这里我们也要更改一下_Inorder(root->_left);//cout << root->_data.first << ":" << endl;cout <<kot(root->_data) <<" ";_Inorder(root->_right);
}

验证一下map和set的准确性:

#include"MyMap.h"
#include"MySet.h"void testmap()
{MyMap::Map<int, int> _m;_m.insert(make_pair(1, 1));_m.insert(make_pair(2, 1));_m.insert(make_pair(3, 1));_m.insert(make_pair(4, 1));_m.insert(make_pair(5, 1));_m.Inorder();_m.IsBalance();cout << endl << endl;
}void testset()
{MySet::Set<int> _s;_s.insert(1);_s.insert(2);_s.insert(3);_s.insert(4);_s.insert(5);_s.Inorder();_s.IsBalance();cout << endl<<endl;
}int main()
{testmap();testset();return 0;
}

结果为:

二. map和set迭代器的实现 

  首先,我们要明白,map/set只是相当于一层壳子,真正的底层实现永远都在红黑树的部分,迭代器也是同理,我们撰写的迭代器应该书写在红黑树部分。

  迭代器的基本定义:

	//	   节点数据	 引用/const引用  指针/const指针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){}};

2.1 解引用运算符重载

  一般用来取出其节点中存储的数据,直接返回data即可,并且一般而言是是可以修改的,因此这里我们返回其引用。

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

2.2 成员访问运算符重载

  -> 运算符一般用来返回该节点的地址,主要还是适用于map中,用来访问pair里面的first和second成员。

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

2.3 ==  和 != 运算符重载

这个就比较简单了

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

 2.4 begin()和end()

  迭代器常用成员函数begin()与end(),其中begin()对应红黑树的最左节点,end()对应最后一个节点的下一个节点,即nullptr(这里我们并没有设置哨兵位,感兴趣的同学可以自己研究一下)

 
iterator begin()
{Node* left = _root;while (left && left->_left){left = left->_left;}return iterator(left);
}iterator end()
{return iterator(nullptr);
}

 

2.6 ++ 运算符重载

  首先我们要知道,红黑树也是一个二叉搜索树,我们对其的遍历顺序也是按照中序遍历,因此,++运算符在这里指的是,中序遍历的下一个节点。

  那我们该如何找寻中序遍历的下一个节点呢。

  我们先来观察一部分红黑树的遍历情况:

当it指向17 时,++实际上是去访问18这个节点;(17有右子树)

当it访问18时,++实际上是去访问33这个节点;(18无右子树,故向上遍历)

当it访问33时,++实际上是去访问37这个节点;(33有右子树)

当it访问37时,++实际上是去访问42这个节点;(37无右子树,向上遍历)

 我们可以看出,实际上++始终围绕着右子树进行移动:

1.当所在节点有右子树时,++访问右子树的最左节点

2.当所在节点没有右子树时,++ 找孩子不是父亲右节点的祖先(这里需要进行遍历)

故得到代码:
 

self& operator++() //迭代器++,依旧返回迭代器
{//如果右子树存在if (_node->_right){Node* left = _node->_right;//则寻找右子树的最左节点while (left->_left){left = left->_left;}_node = left;}//如果右子树不存在else{//找孩子不是父亲右节点的节点Node* parent = _node->_parent;Node* cur = _node; //一定要先判断parent是否存在,以此来避免越界while (parent&&cur == parent->_right){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;
}
//迭代器后置++self operator++(int){self it(_node);++(*this);return it;}

 

 2.7 --运算符重载

  --就是++ 代码思路反过来。

  1. 左子树不为空,进行 -- 则是指向左子树(最右节点)。
  2. 左子树为空,-- 找孩子不是父亲左节点的祖先(循环查找)

得到代码如下:

	self& operator--(){//如果左子树存在if (_node->left){//找左子树的最右节点Node* right = _node->_left;while (right->_right){right = right->_right;}_node = rihgt;}//如果左子树不存在else{//找孩子不是父亲左节点的节点Node* parent = _node->parent;Node* cur = _node;while (parent&&parent->_left == cur){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}//后置--self operator--(int){self it(_node);--(*this);return it;}

迭代器在map和set中的封装如下:


 

 

三 map的[] 下标访问运算符重载

我们可以看到其返回为,mapped_type类型,这是什么呢?

map文档中有如下声明:
 

翻译一下我们得知,在map中定义为其第二个模板参数T,(我们这里为V)的别名

在官方库里面,对这个【】是这样实现的:

 太迷茫了,我们拆解来看一下:

里面的第一层为:
​​​​​​​

  可见就是一层简单的插入 ,第一个传入的是k的值,第二个是我们map中第二个模板参数V的默认构造。

 第二步,将this指针指向insert返回的这个内容。

第三步, 取出this指向的内容中的first内容,那么问题来了,我们自己定义的insert返回的内容是bool类型,我们该取这个的first吗,显然不是的,让我们看看库中的insert函数。

 库中的返回值是一个pair类型,其内部分别是一个i迭代器和bool类型,那么这样的作法我们也就理解了,现在我们要修改一下insert。

 set中虽然用不到【】但其内部和set是一样定义的。

 

更改结果如下(RBTree内)

typedef _RBTreeIterator<T, T&, T*> iterator;
typedef _RBTreeIterator<T, const T&, const T*> const_iterator;iterator begin()
{Node* left = _root;while (left && left->_left){left = left->_left;}return iterator(left);
}iterator end()
{return iterator(nullptr);
}//bool Insert(const T& data)
pair<iterator, bool> Insert(const T& data)
{if (!_root){_root = new Node(data);_root->_co = Black;//如果成功,返回成功节点的迭代器和truereturn make_pair(iterator(_root), true);}Node* cur = _root;Node* parent = nullptr;while (cur){//if (cur->_data.first < data.first)if (kot(cur->_data)< kot(data)){parent = cur;cur = cur->_right;}//else if (cur->_data.first > data.first)else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{//return false;return make_pair(iterator(cur), false);}}cur = new Node(data);cur->_co = Red;//注意这里cur可能因为旋转,改变了原来的位置,所以需要提前记录一下Node* newnode = cur;//if (parent->_data.first < cur->_data.first)if (kot(parent->_data) < kot(cur->_data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//开始判断是否需要变色while (parent && parent->_co == Red){Node* grand = parent->_parent;if (parent == grand->_left){Node* uncle = grand->_right;if (uncle && uncle->_co == Red){//这里只变色就好parent->_co = uncle->_co = Black;grand->_co = Red;cur = grand;parent = cur->_parent;}else{if (cur == parent->_left){_RotateR(grand);grand->_co = Red;parent->_co = Black;}else{_RotateL(parent);_RotateR(grand);cur->_co = Black;grand->_co = Red;}break;}}else{Node* uncle = grand->_left;if (uncle && uncle->_co == Red){//这里只变色就好parent->_co = uncle->_co = Black;grand->_co = Red;cur = grand;parent = cur->_parent;}else{if (cur == parent->_right){_RotateL(grand);grand->_co = Red;parent->_co = Black;}else{_RotateR(parent);_RotateL(grand);cur->_co = Black;grand->_co = Red;}break;}}}_root->_co = Black;//return true;return make_pair(iterator(newnode), true);
}

map和set内

第四步,解引用返回,因为first是个迭代器类型,对其解引用返回data值 

 

但是我们自己实现时,应该返回其data值里面的second,因为map的特性,只有second可以被修改。

故可以得出以下代码:
  注意,这是定义在map里面的,并非定义在迭代器里面

	V& operator[](const K& key){pair<iterator, bool> result = insert(make_pair(key, V()));//如果存在,则插入失败//如果不存在,则插入数据//无论是否存在,都返回 second;return result.first->second;}

 四.源代码+ 测试用例

其实map和set还有很多可以值得封装的地方,这里我们给出一个比较完整的源代码:

map:
 

#pragma once
#include"RBTree.h"
using namespace MyRBTree;namespace MyMap
{template<class K,class V>class Map{public:struct MapKeyOfvalue{const K& operator()(const pair<K, V>& kv){return kv.first;}};//注意声明迭代器//如果想取一个类模板中的一个类型,要使用 typedname 进行声明。//告诉编译器这是一个类型,并不是一个静态变量typedef typename RBTree<K, pair<K, V>, MapKeyOfvalue>::iterator iterator;typedef typename RBTree<K, pair<K, V>, MapKeyOfvalue>::const_iterator const_iterator;pair<iterator,bool> insert(const pair<K,V> &kv){return _map.Insert(kv);}void Inorder(){_map.Inorder();}void IsBalance(){_map.IsBalance();}V& operator[](const K& key){pair<iterator, bool> result = insert(make_pair(key, V()));//如果存在,则插入失败//如果不存在,则插入数据//无论是否存在,都返回 second;return result.first->second;}iterator begin(){return _map.begin();}iterator end(){return _map.end();}{return _map.begin();}iterator end(){return _map.end();}private:RBTree<K, pair<K,V>, MapKeyOfvalue> _map;};
}

set:
 

#pragma once
#include"RBTree.h"using namespace MyRBTree;namespace MySet
{template<class K>class Set{public:struct SetKeyOfvalue{const K& operator()(const K& key){return key;}};//注意声明迭代器//如果想取一个类模板中的一个类型,要使用 typedname 进行声明。//告诉编译器这是一个类型,并不是一个静态变量typedef typename RBTree<K, K, SetKeyOfvalue>::const_iterator iterator;typedef typename RBTree<K, K, SetKeyOfvalue>::const_iterator const_iterator;pair<iterator,bool> insert(const K& k){return _set.Insert(k);}void Inorder(){_set.Inorder();}void IsBalance(){_set.IsBalance();}private:RBTree<K, K, SetKeyOfvalue> _set;};
} 

红黑树:

#pragma once
#include<iostream>
using namespace std;namespace MyRBTree
{enum Color{Red,Black};//直接实现kv模型的红黑树template<class T>struct RBTreeNode{RBTreeNode(const T& data):_co(Red), _left(nullptr), _right(nullptr), _parent(nullptr), _data(data){}Color _co;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;};//	   节点数据	 引用/const引用  指针/const指针template <class T, class Ref, class Ptr>struct _RBTreeIterator{typedef RBTreeNode<T> Node;typedef _RBTreeIterator<T, Ref, Ptr> self;Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator==(const self& s){return _node == s._node;}bool operator!=(const self& s){return _node != s._node;}self& operator++() //迭代器++,依旧返回迭代器{//如果右子树存在if (_node->_right){Node* left = _node->_right;//则寻找右子树的最左节点while (left->_left){left = left->_left;}_node = left;}//如果右子树不存在else{//找孩子不是父亲右节点的节点Node* parent = _node->_parent;Node* cur = _node; //一定要先判断parent是否存在,以此来避免越界while (parent&&cur == parent->_right){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}//迭代器后置++self operator++(int){self it(_node);++(*this);return it;}self& operator--(){//如果左子树存在if (_node->left){//找左子树的最右节点Node* right = _node->_left;while (right->_right){right = right->_right;}_node = right;}//如果左子树不存在else{//找孩子不是父亲左节点的节点Node* parent = _node->parent;Node* cur = _node;while (parent&&parent->_left == cur){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}//后置--self operator--(int){self it(_node);--(*this);return it;}_RBTreeIterator(Node* node):_node(node){}Node* _node;};template<class K, class T, class KeyOfValue>class RBTree{public:typedef RBTreeNode<T> Node;typedef _RBTreeIterator<T, T&, T*> iterator;typedef _RBTreeIterator<T, const T&, const T*> const_iterator;iterator begin(){Node* left = _root;while (left && left->_left){left = left->_left;}return iterator(left);}iterator end(){return iterator(nullptr);}const_iterator cbegin() const{Node* left = _root;while (left && left->_left){left = left->_left;}return const_iterator(left);}const_iterator cend() const {return const_iterator(nullptr);}//bool Insert(const T& data)//pair<iterator,bool> Insert(const T& data)	pair<Node*, bool> Insert(const T& data) //因为set中的iterator是 const迭代器,不可以转化为iterator类型,变成node在返回时可以构造出iterator和const迭代器{if (!_root){_root = new Node(data);_root->_co = Black;//如果成功,返回成功节点的迭代器和truereturn make_pair(_root, true);}Node* cur = _root;Node* parent = nullptr;while (cur){//if (cur->_data.first < data.first)if (kot(cur->_data)< kot(data)){parent = cur;cur = cur->_right;}//else if (cur->_data.first > data.first)else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{//return false;return make_pair(cur, false);}}cur = new Node(data);cur->_co = Red;//注意这里cur可能因为旋转,改变了原来的位置,所以需要提前记录一下Node* newnode = cur;//if (parent->_data.first < cur->_data.first)if (kot(parent->_data) < kot(cur->_data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//开始判断是否需要变色while (parent && parent->_co == Red){Node* grand = parent->_parent;if (parent == grand->_left){Node* uncle = grand->_right;if (uncle && uncle->_co == Red){//这里只变色就好parent->_co = uncle->_co = Black;grand->_co = Red;cur = grand;parent = cur->_parent;}else{if (cur == parent->_left){_RotateR(grand);grand->_co = Red;parent->_co = Black;}else{_RotateL(parent);_RotateR(grand);cur->_co = Black;grand->_co = Red;}break;}}else{Node* uncle = grand->_left;if (uncle && uncle->_co == Red){//这里只变色就好parent->_co = uncle->_co = Black;grand->_co = Red;cur = grand;parent = cur->_parent;}else{if (cur == parent->_right){_RotateL(grand);grand->_co = Red;parent->_co = Black;}else{_RotateR(parent);_RotateL(grand);cur->_co = Black;grand->_co = Red;}break;}}}_root->_co = Black;//return true;return make_pair(newnode, true);}void Inorder(){_Inorder(_root);}bool IsBalance(){return _IsBalance();}Node* Find(const K& key){//KeyOfValue kot;Node* cur = _root;while (cur){//if (cur->_data.first < key)if (kot(cur->_data) < key){cur = cur->_right;}//else if (cur->_data.first > key)else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;}//求最左节点Node* LeftMost(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return cur;}//求最右节点Node* RightMost(){Node* cur = _root;while (cur && cur->_right){cur = cur->_right;}return cur;}private:bool _IsBalance(){if (!_root) return true;if (_root->_co == Red){cout << "根节点为红色" << endl;return false;}int BlackSum = 0;Node* cur = _root;while (cur){if (cur->_co == Black) BlackSum++;cur = cur->_left;}return _Check(_root, 0, BlackSum);}bool _Check(Node* root, int Blacknum, int BlackSum){if (!root){if (Blacknum != BlackSum){cout << "某条路径黑色节点的数量不相等" << endl;return false;}return true;}if (root->_co == Black){++Blacknum;}if (root->_co == Red && root->_parent && root->_parent->_co == Red){cout << "存在连续的红色节点" << endl;return false;}return _Check(root->_left, Blacknum, BlackSum) && _Check(root->_right, Blacknum, BlackSum);}//直接创建一个全局变量,递归遍历的话消耗太高//注意把前面我们更改的删除//KeyOfValue kot;void _Inorder(Node* root){if (!root){return;}//这里我们也要更改一下_Inorder(root->_left);//cout << root->_data.first << ":" << endl;cout <<kot(root->_data) <<" ";_Inorder(root->_right);}void _RotateR(Node* parent){Node* SubL = parent->_left;Node* SubLR = SubL->_right;parent->_left = SubLR;if (SubLR) SubLR->_parent = parent;Node* pparent = parent->_parent;SubL->_right = parent;parent->_parent = SubL;if (_root == parent){_root = SubL;SubL->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = SubL;}else{pparent->_right = SubL;}SubL->_parent = pparent;}}void _RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent; //将subR的左指针指向parentNode* pparent = parent->_parent;parent->_parent = subR;//将parent的父指针指向subRif (subRL)subRL->_parent = parent;if (_root == parent) //判断parent是否是头节点{_root = subR;subR->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}subR->_parent = pparent;}}KeyOfValue kot;Node* _root = nullptr;};
}

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

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

相关文章

Vue基本使用(一)

&#x1f4d1;前言 本文主要是【Vue】——Vue基本使用的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&#x…

基于单片机红外测温(mlx90614)-原理图-PCB设计+源程序

一、系统方案 1、本设计采用52单片机作为主控器。 2、mlx90614采集温度&#xff0c;液晶显示温度值。 3、按键设置温度上下限。 4、实际测量温度小于下限或者大于上限&#xff0c;蜂鸣器报警&#xff0c;大于上限&#xff0c;风扇启动&#xff0c;低于下限&#xff0c;风扇停止…

函数式接口

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 咱们今天讨论下函数式接…

MySQL -DDL 及表类型

DDL 创建数据库 CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [, create_specification] ...] create_specification:[DEFAULT] CHARACTER SET charset_name [DEFAULT] COLLATE collation_name 1.CHARACTER SET&#xff1a…

WPF中DataGrid解析

效果如图&#xff1a; 代码如下&#xff1a; <DataGrid Grid.Row"1" x:Name"dataGrid" ItemsSource"{Binding DataList}" AutoGenerateColumns"False"SelectedItem"{Binding SelectedItem,UpdateSourceTriggerPropertyChange…

Lesson 08 string类 (上)

C&#xff1a;渴望力量吗&#xff0c;少年&#xff1f; 文章目录 一、STL1. 概念2. STL的六大组件3. STL的重要性 二、string类的介绍与使用1. 介绍2. 使用&#xff08;1&#xff09;string类对象的常见构造&#xff08;2&#xff09;string类对象的容量操作&#xff08;3&…

【vue脚手架配置代理+github用户搜索案例+vue项目中常用的发送Ajax请求的库+slot插槽】

vue脚手架配置代理github用户搜索案例vue项目中常用的发送Ajax请求的库slot插槽 1 vue脚手架配置代理2 github用户搜索案例2.1 静态列表2.2 列表展示2.3 完善案例 3 vue项目中常用的发送Ajax请求的库3.1 xhr3.2 jQuery3.3 axios3.4 fetch3.5 vue-resource 4 slot 插槽4.1 效果4…

缺省参数的声明和定义

首先&#xff0c;函数缺省参数不能同时出现在声明和定义中&#xff0c;如出现则报错&#xff1a; 声明和定义中同时出现缺省参数 ctrlb&#xff0c;编译报错&#xff0c;提示 “test"&#xff1a;重定义默认参数&#xff1a;参数1 编译报错 当函数的声明和定义中都出现…

Nodejs+Vue校园餐厅外卖订餐点餐系统 PHP高校食堂 微信小程序_0u4hl 多商家

对于校园订餐小程序将是又一个传统管理到智能化信息管理的改革&#xff0c;对于传统的校园订餐管理&#xff0c;所包括的信息内容比较多&#xff0c;对于用户想要对这些数据进行管理维护需要花费很大的时间信息&#xff0c;而且对于数据的存储比较麻烦&#xff0c;想要查找某一…

小程序开发中SSL证书的重要作用

随着互联网技术的发展&#xff0c;越来越多的企业和个人开始开发自己的小程序来满足各种需求。然而&#xff0c;在这个过程中&#xff0c;安全性和稳定性成为了开发者必须关注的重点之一。为了保障用户的隐私安全和体验效果&#xff0c;越来越多的小程序开发者开始采用SSL证书进…

SpringBoot : ch12 多模块配置YAML文件

前言 当您使用SpringBoot框架进行项目开发时&#xff0c;通常需要配置一些参数和属性。在实际开发中&#xff0c;可能需要将这些配置参数分成多个不同的YAML文件&#xff0c;并将它们组织到不同的模块中。这样可以方便管理和维护配置文件&#xff0c;并且可以避免配置文件的冲…

界限与不动产测绘乙级申请条件

整理一期关于测绘资质界限与不动产测绘乙级资质的申请要求 测绘资质是由测绘资质主管部门自然资源部制定的 想要了解标准、正规的申请条件&#xff0c;可以到当地省份的政务网搜索测绘资质办理相关标准&#xff08;例如下图&#xff09; 1、通用标准 http://gi.mnr.gov.cn/20…