由红黑树到map/set

文章目录

  • 一.map/set 的封装思路
    • 1.封装思路
    • 2.红黑树节点调整
    • 3.map 和 set 的定义
    • 4.仿函数 KeyOfValue
    • 5.map/set 的插入
  • 二.map/set 迭代器实现
    • 1.迭代器的定义
    • 2.解引用运算符重载
    • 3.成员访问运算符重载
    • 4.(不)等于运算符重载
    • 5.begin() 与 end()
    • 6.++ 运算符重载
    • 7.-- 运算符重载
    • 8.[ ]下标访问运算符重载
  • 三.源码
    • 1.RBTree
    • 2.map.h
    • 3.set.h

预备知识:

一.map/set 的封装思路

在实现了红黑树的部分功能后,我们可以便可以将红黑树作为底层结构来封装map set,其中mapK-Value 模型 ,而 set Key 模型。

我们接下来将使用模板、仿函数和一棵红黑树实现 mapset

1.封装思路

因为 map 存储的是pair ,而 set 存储的是 Key ,所以其解决的根本方向就是:

  • 如果是 map,红黑树中就按照 pairK进行比较,从而插入;

  • 如果是 set,红黑树中就按照 Key 值进行比较,从而插入。

map / set 主动传出待比较的数据,红黑树只用根据数据间关系进行插入即可,不用在乎待比较的数据是何种结构。

2.红黑树节点调整

上文我们实现的红黑树是按照键值对的方式进行存储的,而接下来我们要同时封装 map/set,故不能直接定死存储的结构,所以我们在此进行修改。
将原来的kv 模型改为data模型,data 即是比较的数据内容。

在这里插入图片描述

注意,将 Kv模型改为 data后,插入与查找中比较的代码都要进行更新,稍后会讲解。

3.map 和 set 的定义

mapset 底层都使用的红黑树,所以我们 map/set 的功能就是调用红黑树的成员函数即可。

template<class K, class V>
class Map
{
private:RBTree<K, pair<K, V>> _t;
};template<class K>
class Set
{
private:RBTree<K,K> _t;
};

因为Map有两个模板参数,而 Set 只有一个模板参数。所以当我们使用的一个红黑树实现时,要进行匹配处理。即使 Set 是一个模板参数,在调用红黑树时也要传入两个模板参数。因为第一个模板参数是匹配 Map 满足红黑树的两个模板参数,而第二个模板参数是为了让底层红黑树拿到比较的数据。

为什么Map除了传入pair外,第一个参数直接传入 K,为什么不能省略?

因为Find的存在,mapFind函数是直接按pair中的 K 进行查找的,所以要额外设置该参数。

4.仿函数 KeyOfValue

接下来我们就要将数据取出供红黑树比较了,如果是 map,就按pair中的 K去比较,如果是 set,就按Key比较。

为此我们可以在 mapset 内部定义一个仿函数将其数据取出。

template<class K, class V>
class Map
{//Map-keyofvalue 仿函数struct MapKeyOfvalue{const K& operator()(const std::pair<K, V>& kv){return kv.first;}};
private:RBTree<K, pair<K, V>> _t;
};template<class K>
class Set
{//Set-keyofvalue 仿函数struct SetKeyOfvalue{const K& operator()(const K& key){return key;}};
private:RBTree<K,K> _t;
};

然后我们将其仿函数也作为模板,传入红黑树中,对应的,红黑树要添加一个模板参数来接收该仿函数。

改动代码如下:

在这里插入图片描述
改动这些之后,我们便要将红黑树中比较数据大小的地方进行修改

用仿函数将数据取出,然后进行比较:

//根据模板参数创建仿函数
KeyOfvalue kovalue;
if (!_root)
{_root = new Node(data);_root->_col = BLACK;return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{//比较处————进行改动if (kovalue(cur->_data) > kovalue(data)){parent = cur;cur = cur->_left;}//比较处————进行改动else if (kovalue(cur->_data) < kovalue(data)){parent = cur;cur = cur->_right;}else{return false;}
}
//创建新节点,使用data进行构造
cur = new Node(data);
//比较处————进行改动
if (kovalue(parent->_data) > kovalue(data))
{parent->_left = cur;
}
else
{parent->_right = cur;
}
cur->_parent = parent;

这样,红黑树便可以适配 map/set 的插入了。

5.map/set 的插入

接下来map/set 的插入直接套用红黑树的即可。

代码如下:

//map的插入,插入pair
bool insert(const pair<K, V>& kv)
{return _t.Insert(kv);
}//set的插入,插入key
bool insert(const K& key)
{return _t.Insert(key);
}

二.map/set 迭代器实现

1.迭代器的定义

//		节点数据	引用/const引用  指针/const指针
template <class T,class Ref,class Ptr>
struct __RBTreeIterator
{typedef RBTreeNode<T> Node;typedef __RBTreeIterator<T, Ref, Ptr> self;Node* _node;
public:__RBTreeIterator(Node* node):_node(node){}
}

首先,我们要明确,其实 map/set 只是一层套壳,其中的功能都是由红黑树实现后,再封装到map/set中供我们使用,迭代器也不例外。

2.解引用运算符重载

解引用即返回该节点的存储的数据,主要用于 set 中,返回该数据的引用。

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

3.成员访问运算符重载

成员访问操作符即返回该节点的地址,主要用于 map 中,方便访问 pair 中的first以及second

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

4.(不)等于运算符重载

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

5.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);
}

如果 map/set 中想使用红黑树中的迭代器,我们需要在 map/set 中进行声明。

声明如下:

如果想取一个类模板中的一个类型,要使用 typedname 进行声明。
告诉编译器这是一个类型,并不是一个静态变量

//如果想取一个类模板中的一个类型,要使用 typedname 进行声明。
//告诉编译器这是一个类型,并不是一个静态变量
typedef typename RBTree<K, pair<K, V>, MapKeyOfvalue>::iterator iterator;

6.++ 运算符重载

首先我们需要明确,迭代器++是让当前迭代器指向红黑树中序遍历的下一个节点。

以下图的35节点为例。

  • 当迭代器指向 35 时,进行 ++,指向右子树最左节点,即 40。
  • 当迭代器指向 40 时,进行 ++,右子树为空,指向父节点,即 45。
  • 当迭代器指向 45 时,进行 ++,指向右子树最左节点,即 48。
  • 当迭代器指向 48 时,进行 ++,指向未遍历的父节点,即 50。

在这里插入图片描述
分析上面的情况,发现迭代器 ++ 始终围绕着右子树是否存在进行。

现在我们将其抽象化,分析其规律:

  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;=while (cur == parent->_right){cur = cur->_parent;parent = parent->_parent;//防止最后一个节点寻找祖先导致程序崩溃if (parent == nullptr){break;}}_node = parent;}return *this;
}

需要注意,当 ++ 到最后一个节点的时候。有可能在寻找非父亲右节点的祖先时,父节点一路走到 nullptr 的情况,如图:

在这里插入图片描述
所以在每次 parent 更新时都进行一次判断,即可。

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->_left == cur){cur = cur->_parent;parent = parent->_parent;if (parent == nullptr){break;}}_node = parent;}return *this;
}

8.[ ]下标访问运算符重载

我们来看 map[]下标访问操作符,其中 []返回的是mapped_type(pair) 类型。

在这里插入图片描述

再看 set 中的insert也是返回 pair,虽然很反常,但是官方库中确实是这样书写的。

在这里插入图片描述

因为 set 没有[]运算符重载,所以 set 中不必提供该函数,只用在 map 中提供即可。

首先,我们向 mapinsert 数据 pairpair的第一个参数为用户传入的 key 值,第二个参数则是用户声明的第二个模板参数的默认构造函数(如果是 int,则调用 int的构造函数,如果是string,则默认构造 string)。

pair<iterator, bool> result = insert(make_pair(key, V()));

然后我们返回迭代器指向的 pair 数据中的second

//result.first取出迭代器,使用->运算符重载取出data地址,访问second并返回
return result.first->second;
V& operator[](const K& key)
{pair<iterator, bool> result = insert(make_pair(key, V()));//如果存在,则插入失败//如果不存在,则插入数据//无论是否存在,都返回 second;return result.first->second;
}

三.源码

1.RBTree

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 Ref, class Ptr>
struct __RBTreeIterator
{typedef RBTreeNode<T> Node;typedef __RBTreeIterator<T, Ref, Ptr> Self;Node* _node;__RBTreeIterator(Node* node):_node(node){}// 1、typedef __RBTreeIterator<T, T&, T*> itertaor;  拷贝构造// 2、 typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;//  支持普通迭代器构造const迭代器的构造函数__RBTreeIterator(const __RBTreeIterator<T, T&, T*>& it):_node(it._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){// 1、右不为空,下一个就是右子树的最左节点Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}_node = subLeft;}else{// 2、右为空,沿着到根的路径,找孩子是父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}Self& operator--(){if (_node->_left){// 1、左不为空,找左子树最右节点Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}else{// 2、左为空,孩子是父亲的右的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}
};// 仿函数
template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:~RBTree(){_Destroy(_root);_root = nullptr;}
public:typedef __RBTreeIterator<T, T&, T*> itertaor;typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;itertaor begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return itertaor(cur);}itertaor end(){return itertaor(nullptr);}const_itertaor begin() const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return const_itertaor(cur);}const_itertaor end() const{return const_itertaor(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<itertaor, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(itertaor(_root), true);}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;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(itertaor(cur), false);}}cur = new Node(data);Node* newnode = cur;if (kot(parent->_data) > kot(data)){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;// 情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续往上调整cur = grandfather;parent = cur->_parent;}else // 情况2+3:u不存在/u存在且为黑,旋转+变色{//     g//   p   u// c if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//   p   u//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;//parent->_col = RED;grandfather->_col = RED;}break;}}else // (grandfather->_right == parent){//    g//  u   p//        cNode* uncle = grandfather->_left;// 情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续往上调整cur = grandfather;parent = cur->_parent;}else // 情况2+3:u不存在/u存在且为黑,旋转+变色{//    g//  u   p//        cif (cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{//    g//  u   p//    cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(itertaor(newnode), true);;}bool IsBalance(){if (_root && _root->_col == RED){cout << "根节点颜色是红色" << endl;return false;}int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++benchmark;cur = cur->_left;}// 连续红色节点return _Check(_root, 0, benchmark);}int Height(){return _Height(_root);}private:void _Destroy(Node* root){if (root == nullptr){return;}_Destroy(root->_left);_Destroy(root->_right);delete root;}int _Height(Node* root){if (root == NULL)return 0;int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}bool _Check(Node* root, int blackNum, int benchmark){if (root == nullptr){if (benchmark != blackNum){cout << "某条路径黑色节点的数量不相等" << endl;return false;}return true;}if (root->_col == BLACK){++blackNum;}if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << "存在连续的红色节点" << endl;return false;}return _Check(root->_left, blackNum, benchmark)&& _Check(root->_right, blackNum, benchmark);}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* ppnode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}}private:Node* _root = nullptr;
};

2.map.h

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>::itertaor iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}V& operator[](const K& key){pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));return ret.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;
};void test_map1()
{map<string, string> dict;dict.insert(make_pair("sort", ""));dict.insert(make_pair("string", "ַ"));dict.insert(make_pair("count", ""));dict.insert(make_pair("string", "(ַ)")); // ʧmap<string, string>::iterator it = dict.begin();while (it != dict.end()){cout << it->first << ":" << it->second << endl;/*it->first = "1111";it->second = "111";*/++it;}cout << endl;for (auto& kv : dict){cout << kv.first << ":" << kv.second << endl;}cout << endl;
};

3.set.h

template<class K>
class set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public:typedef typename RBTree<K, K, SetKeyOfT>::const_itertaor iterator;typedef typename RBTree<K, K, SetKeyOfT>::const_itertaor const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}pair<iterator, bool> insert(const K& key){return _t.Insert(key);}
private:RBTree<K, K, SetKeyOfT> _t;
};

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

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

相关文章

CSS学习记录(基础笔记)

CSS简介: CSS 指的是层叠样式表* (Cascading Style Sheets)&#xff0c;主要用于设置HTML页面的文字内容&#xff08;字体、大小、对齐方式&#xff09;&#xff0c;图片的外形&#xff08;边框&#xff09; CSS 描述了如何在屏幕、纸张或其他媒体上显示 HTML 元素 CSS 节省…

接口抓包,Fiddler抓包使用方法总结,入门到精通辅助实战...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 工作原理 Fiddle…

Docker网络模式详解

目录 Docker网络模式 一、Host模式 二、container模式 三、none模式 四、bridge模式 五、Overlay模式 Docker网络模式 安装Docker时会自动创建3个网络&#xff0c;可以使用docker network ls命令列出这些网络。 [rootdocker ~]# docker network ls 我们在使用docker run…

小程序服务器配置多大够用?

​  了解小程序服务器的大小和要求对于确保小程序的高效运行非常重要。下面将介绍小程序服务器的大小和要求&#xff0c;帮助您选择合适的服务器。 服务器费用 服务器费用因服务器类型、配置和带宽等因素而异。一般而言&#xff0c;小型小程序服务器的年费用在500元至2000元之…

服务器返回 413 Request Entity Too Large

问题 上传一个大于1.5M的文件时&#xff0c;报错&#xff1a;413 Request Entity Too Large 使用的配置 1、用的是docker环境&#xff0c;还有一层代理&#xff0c;代理用的镜像是&#xff1a;jwilder/nginx-proxy 2、docker里是有php和nginx 确认配置 docker里的php和ngi…

详解Spring中涉及的技术

注解 介绍&#xff1a; 注解(Annotation)很重要&#xff0c;未来的开发模式都是基于注解的&#xff0c;JPA是基于注解的&#xff0c;Spring2.5以上都是基于注解的&#xff0c;Hibernate3.x以后也是基于注解的&#xff0c;现在的Struts2有一部分也是基于注解的了&#xff0c;注…

c语言——计算两个数值的最小公倍数

//计算两个数值的最小公倍数 //列如&#xff1a;4和6的最小公倍数是12. #include<stdio.h> int main() {int a,b,temp,i;printf("Input a&b:");scanf("%d,%d",&a,&b);if(a<b){tempa;ab;btemp;}for(ia;i>0;i)if(i%a0&&i%b0…

Spring @Scheduled单线程单实例的坑

文章目录 前言背景验证解决方案 前言 在 Java Spring 项目中经常会用 Scheduled 来实现一些定时任务的场景&#xff0c;有必要了解一些它使用时的问题和内部实现机制。本文是偶然间发现的一个问题&#xff0c;刷新了我的认知&#xff0c;分享给大家。 其他相关文章&#xff1…

基于ASP.NET MVC开发的、开源的个人博客系统

推荐一个功能丰富、易于使用和扩展的开源博客&#xff0c;可以轻松地创建和管理自己的博客。 项目简介 基于.Net Framework 4.5开发的、开源博客系统&#xff0c;具有丰富的功能&#xff0c;包括文章发布、分类、标签、评论、订阅、统计等功能&#xff0c;同时也可以根据需要…

【css】css设置表格样式-边框线合并

<style> table, td, th {border: 1px solid black;//设置边框线 }table {width: 100%; }td {text-align: center;//设置文本居中 } </style> </head> <body><table><tr><th>Firstname</th><th>Lastname</th><t…

深入篇【C++】手搓模拟实现list类(详细剖析底层实现原理)模拟实现正反向迭代器【容器适配器模式】

深入篇【C】手搓模拟实现list类(详细剖析底层实现原理&#xff09;&& 模拟实现正反向迭代器【容器适配器模式】 Ⅰ.迭代器实现1.一个模板参数2.两个模板参数3.三个模板参数 Ⅱ.反向迭代器实现1.容器适配器模式 Ⅲ.list模拟实现1.定义结点2.封装结点3.构造/拷贝4.迭代器…

Hyper实现git bash在windows环境下多tab窗口显示

1.电脑上安装有git bash 下载链接&#xff1a;https://gitforwindows.org/ 安装Hyper 下载链接:官网 https://hyper.is/ 或者在百度云盘下载&#xff1a; https://pan.baidu.com/s/1BVjzlK0s4SgAbQgsiK1Eow 提取码&#xff1a;0r1f 设置 打开Hyper&#xff0c;依次点左上角-&g…

MATLAB /Simulink 快速开发STM32(使用st官方工具 STM32-MAT/TARGET),以及开发过程

配置好环境以后就是开发&#xff1a; stm32cube配置芯片&#xff0c;打开matlab添加ioc文件&#xff0c;写处理逻辑&#xff0c;生成代码&#xff0c;下载到板子中去。 配置需要注意事项&#xff1a; STM32CUBEMAX6.5.0 MABLAB2022BkeilV5.2 Matlab生成的代码CTRLB 其中关键的…

Linux--验证命令行上运行的程序的父进程是bash

1.输入以下代码&#xff1a; #include <stdio.h> #include <unistd.h> int main() {printf("hello world: pid: %d, ppid: %d\n",getpid(),getppid());return 0; }2.编译得到可执行程序​​​ 3.运行得到ppid 4.输入指令 ps axj | head -1 &&am…

【网络基础进阶之路】设计网络划分的实战详解

PS&#xff1a;本要求基于华为的eNSP模拟软件进行 具体要求&#xff1a; 完成步骤&#xff1a; 1、对192.168.1.0/24进行子网划分 2、对每一个路由器进行IP的配置 3、开始静态路由的书写&#xff0c;在写之前&#xff0c;我们可以先对每一个路由器写一条通向右边的缺省路由&…

C高级-day2

思维导图 #!/bin/bash echo "$(head -n 5 /etc/group | tail -1)" mkdir /home/ubuntu/copy cd /home/ubuntu/copy cp /etc/shadow test chown root test chmod o-r,o-w,o-x test#include <myhead.h> //递归实现&#xff0c;输入一个数&#xff0c;输出这个数的…

【设计模式——学习笔记】23种设计模式——模板方法模式Template Method(原理讲解+应用场景介绍+案例介绍+Java代码实现)

文章目录 介绍基本介绍使用说明应用场景登场角色 案例实现案例一问题介绍实现模板方法模式的钩子方法 案例二实现 模板方法模式在IOC的源码分析总结思考思考一思考二 文章说明 介绍 基本介绍 模板方法模式&#xff0c;又叫模板模式&#xff0c;在一个抽象类中定义了一个执行它…

详解AMQP协议

目录 1.概述 1.1.简介 1.2.抽象模型 2.spring中的amqp 2.1.spring amqp 2.2.spring boot amqp 1.概述 1.1.简介 AMQP&#xff0c;Advanced Message Queuing Protocol&#xff0c;高级消息队列协议。 百度百科上的介绍&#xff1a; 一个提供统一消息服务的应用层标准高…

忘掉MacType吧,TtfAutoHint手工删除ttc、ttf字体的hinting,微软雅黑字体更显平滑

Windows的ClearType渲染字体方式&#xff0c;结合臭名昭著的hinting技术使微软雅黑字体备受争议&#xff0c;正所谓&#xff1a;成也hinting&#xff0c;败也hinting。 首先什么是hinting&#xff1f; Hinting 这个词一直都没有中文名称&#xff0c;我用粤语将它音译为“牵挺”…

数据管理基础知识

数据管理原则 数据管理与其他形式的资产管理的共同特征&#xff0c;涉及了解组织拥有哪些数据以及可以使用这些数据完成哪些工作&#xff0c;然后确定如何最好的使用数据资产来实现组织目标与其他流程一样&#xff0c;他必须平衡战略和运营需求&#xff0c;通过遵循一套原则&a…