c++ map,set封装

map 是一个 kv 结构, set 是 k结构。
我们前面模拟实现了 红黑树,但是我们实现的红黑树把 kv 结构写死了,怎么样才能用泛型编程的思想来实现map和set呢
我们先简单看一下原码中是怎么实现的

1.原码实现逻辑

我们打开这里的 stl_set.h
在这里插入图片描述
通过我们前面学习红黑树,我们看到这里的 rb_tree 的名字 就是一棵纯正的红黑树
我们打开 tree.h 的头文件去找这个 rb_tree
在这里插入图片描述
在这里插入图片描述

这里用 true 和 false 来表示 red 和 black,和我们的枚举有区别,但是没影响,只要能判断红黑即可。
这里map 和 set 都使用了红黑树的代码
接下来开始我们的重头戏
在这里插入图片描述
我们实现节点是一个 struct 就实现了,但是这里还用到了继承,并且我们的红黑树传入了两个模版参数 K, V ,但是这里的节点却只有一个 Value 的模版参数,我们接着往下看
在这里插入图片描述
我们看到,源代码实现的红黑树传入参数有点多,而且下这个整体的封装过程挺麻烦的。
在这里插入图片描述

首先,他的 __rb_tree_node 只传入了一个 Value 的模版参数。但是 map 是 kv 模型,只传入了一个 Value ,怎么能让 map 满足两个类型呢?
我们打开 stl_set.h ,虽然这里重命名了,但是 key_type 和 value_type 的原本类型都是 Key 类型
在这里插入图片描述
我们把这里联系起来
在这里插入图片描述
那这样我们猜测,map 中应该也有一个 key_type 和 value_type ,且这两个类型不同
在这里插入图片描述
这里我们能看到,key_type 和 value_type 的原本类型不同,且 value_type 还是用 pair<const Key, T> 来实现的
在这里插入图片描述
我们先简单理解一下,这里通过一个模版参数 value_type 来确定是哪种类型,如果传入的只是一个 value_type,就是 k 模型,如果传入的参数是键值对 pair<const Key, T>,那这里就是 kv 模型
这里就通过传入参数,使 map 和 set 可以使用同一段代码,实现了泛型编程的思想

2. 模拟实现

set

namespace xsz1
{
	template<class K>
	class set
	{
	private:
		RBTree<K, K> _t;
	}
}

map

namespace xsz2
{
	template<class K, class T>
	class map
	{
	private:
		RBTree<K, pair<K, T>> _t;
	}
}

这里我们先把 K 设置为可修改的。
在这里插入图片描述
我们这里的写法比源代码简化了很多,因为只是模拟实现,不是完全实现,所以只是实现基本功能。
上面的 map 和 set 写完后,我们还需要修改 红黑树的基本代码

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	//pair<K, V> _kv;
	Colour _col;
	T _data;
	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_col(RED)
	{}
};
template<class K, class T>
class RBTree
{
public:
	typedef RBTreeNode<T> Node;
	//...
};

2.1. insert

但是还有一个问题需要处理,第二参数怎么比较大小?
如果是 set 比较大小很简单,都是 key,直接比较,那么 map 的键值对呢
在这里插入图片描述
我们看到,键值对是可以比较的,但是这个比较大小和我们想要的不一样,这里的比较大小,会先去比较 first,在 first 相同的情况下,会去比较 second,但是我们只需要比较 first,所以这里我们还需要单独处理一下
但是要怎么处理才能让 set 和 map 都适合这种用法呢

while(cur)
{
	if(cur->_data <data)
	{
		parent = cur;
		cur = cur->-right;
	}
	else if(cur->_data >data)
	{
		parent = cur;
		cur = cur->_left;
	}
	else
	{
		return false
	}
}

我们能不能用类型对比的方式?

if(cur->_data.type == K)
{
	//...
}

如果 data 的类型和传入的第一模版参数类型相同,我们就直接比,如果传入的类型是 pair 类型,我们再对比 data.first
但是这样写肯定不行,其实 c 语言中有一种写法能支持这种思路
typeid.name()

if(typeif(cur->_data).name == K)
{
	//...
}

typeif.name 可以把传入的内容转化成字符串,可以按照这个思路写下去,但是这样写不太符合我们的泛型编程的思想。
还有一个方法

template<class K, class T>
class map
{
public:
	struct MapKeyofT
	{
		const K& operator()(const K& key)
		{
			return key.first;
		}
	};
private:
	RBTree<K, pair<K, T>, MapKeyofT> _t;
};
template<class K>
class set
{
public:
	struct SetKeyofT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
private:
	RBTree<K, K, SetKeyofT> _t;
template<class K, class T, class KeyofT>
class RBTree
{
	//....
	KeyofT kot;
	kot(cur->_data <kot < kot(data));
}

我们在 set 和 map 中实现两个仿函数,这两个仿函数在调用时会返回该对象的 value 。
如果是 set 调用,会返回 key,如果是 map 调用,会返回 键值对中的 first。
我们把这两个类作为模版参数传入 RBTree 中,在 RBTree 中,我们只需要去调用这个 kot 即可,kot 会把set , map 中需要比较的部分返回供我们比较,也就不需要我们自己去考虑怎么处理了。

2.2. 迭代器

2.2.1. 红黑树迭代器

红黑树,树形结构,和之前学的链表的迭代器很像。

template<class T, class KeyofT>
struct __TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T, KeyofT> Self;
	Node* _node;

	__TreeIterator(Node* node)
		:_node(node)
	{}
	T& operator*()
	{
		return _node->_data;
	}
	T* operator->()
	{
		return &_node->_data;
	}
}

我们先把最基础的部分写出来。
上面这些都没什么问题,迭代器支持 ++,那么我们的迭代器怎么写才能支持++呢?
拿这棵树研究研究
在这里插入图片描述

一般情况下,会从 begin() 到 end()
begin() 的位置很好确认,就是 _root 的最左节点
在这里插入图片描述
观察后,我们发现,如果当前节点不存在孩子节点时,会去找节点是父亲左节点的父亲节点节点。
在这里插入图片描述
比如当 it== 5 或者 it == 10 时,应该会去找 节点是父亲节点左孩子节点的那个父亲节点
在这里插入图片描述

比如这里如果 it 是 9 ,先去找 10 ,然后走到10 的右孩子节点,此时要向上走,10这棵树已经走完了,那么就去找 10 的父节点。it 在 10 的右孩子节点时,不满足 节点是父亲左节点的条件,接着往上找,10 是 11 的父亲节点的左节点,所以 _node = 11,开始下一步。
如果节点的右子树不为空,那就去右子树中找最左节点。
这句比较好理解,和 begin() 位置一样,直接找最左节点。
我们在实现 ++ 前,先把 begin() 和 end() 实现了

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

这里的 begin 和 end 函数都比较好理解
begin 最开始要指向最小的节点,所以最开始就是 cur 找最左节点即可
但是 end 要注意,并不是指向最右的节点,按照我们之前对vector 那些迭代器的理解,end 返回的是最后一个节点后面位置的位置,但是当遍历到最大节点时,树中是没有下一个节点的,所以我们默认直接返回 nullptr(按照上面++ 的逻辑,最后会指向 _root 的父节点,但是 _root 本身就是空节点,所以直接用 nullptr 返回即可)。
下面开始实现 ++ 的操作

Self& operator++()
{
	if(_node->_right)
	{
		Node* cur = _node->_right;
		while(cur && cur->_left)
		{
			cur = cur->_left;
		}
		_node = cur;
	}
	else
	{}
	return *this;
}

右不为空的情况下,去找右子树的最左节点
右为空的情况下,去找节点是父节左的那个父亲节点

Self& operator++()
{
	if(_node->_right)
	{
		Node* cur = _node->_right;
		while(cur && cur->_left)
		{
			cur = cur->_left;
		}
		_node = cur;
	}
	else
	{
		Node* cur = _node;
		Node* parent = cur->_parent;
		while(parent && cur == parent->_right)
		{
			cur = parent;
			parent = cur->_parent;
		}
		_node = parent;
	}
	return *this;
}

这样,我们的 ++ 逻辑就写完了
接下来是 != 操作

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

这里注意是节点指针进行比较,不是对比节点的值,或者是传入的 s 对象。
减减操作和加加操作相反。
++:
右树存在找右树最左节点
右树不存在,找节点是父亲左的那个父亲节点
–:
可以把情况想的极端一点,++是从最左节点遍历到最右节点,–是从最右节点遍历到最左节点,也就是说这俩操作是个完全对称的。
左树存在找最右节点
左树不存在,找节点是父亲右的那个父亲节点

        Self& operator--()
        {
                if (_node->_left)
                {
                        Node* cur = _node->_left;
                        while (cur->_right)
                        {
                                cur = cur->_right;
                        }
                        _node = cur;
                }
                else
                {
                        Node* cur = _node;
                        Node* parent = cur->_parent;
                        while (parent && cur == parent->_left)
                        {
                                cur = parent;
                                parent = cur->_parent;
                        }
                        _node = parent;
                }
                return *this;
        }

在这里插入图片描述

2.2.2. set,map封装迭代器

红黑树的迭代器写完了,接下来要在 set 中也实现迭代器,因为我们要通过 set 的接口去使用红黑树。

template<class K>
class set
{
public:
	//..
	typedef RBTree<K, K, SetKeyofT>::iterataor iterator;
	iterator begin()
	{
		return _t.begin();
	}
	iterator end()
	{
		return _t.end()
	}
private:
	RBTree<K, K, SetKeyofT> _t;
}

既然迭代器写好了,那么我们来试试吧

void test_set()
{
        xsz1::set<int> s;
        s.insert(4);
        s.insert(1);
        s.insert(2);
        s.insert(3);
        s.insert(2);
        xsz1::set<int>::iterator it = s.begin();
        while (it != s.end())
        {
                cout << *it << "  ";
                ++it;
        }
        cout << endl;
}

当我们运行这段代码后,我们会惊奇的发现
在这里插入图片描述
他崩了
注意这里显示出来的问题,iterator 不是类型,我们明明 typedef 了 红黑树的iterator, 为什么这里说 iterator 不是类型呢?
这里要注意,域作用访问限定符

typedef RBTree<K, K, SetKeyofT>::iterataor iterator;

一般除了这里会用到 这个操作符,我们在声明 其类中的静态成员变量也会用到这个操作符,因为我们的红黑树代码是用模版来实现的 ,所以这段代码在预编译的时候并不存在红黑树的代码(实例化后才会出现,想要实例化也是在运行期间),而预编译时访问到了这段代码,编译器就不知道你是想给 iterator 这个静态成员变量重命名,还是给这个类型命重命名,所以会报错。
因此如果想通过这里,我们必须强制说明一下

typedef typename  RBTree<K, K, SetKeyofT>::iterataor iterator;

现在可以开测我们的代码了
在这里插入图片描述
我们看到,通过迭代器,随机插入的数据正向输出了。
迭代器实现了,范围 for 也就随便用。
然后是 map 的迭代器,与 set 同理

template<class K>
class set
{
public:
	//..
	typedef RBTree<K, pair<K, T>, MapKeyofT>::iterataor iterator;
	iterator begin()
	{
		return _t.begin();
	}
	iterator end()
	{
		return _t.end()
	}
private:
	RBTree<K, pair<K, T>, MapKeyofT> _t;
}

我们运行之后
在这里插入图片描述
完全没什么问题。
虽然 set,map 大题功能已经实现,但不是还存在一个很严重的问题。
如果我们去修改 迭代器位置的值

(*it) = 100;

在这里插入图片描述
我们看到,修改后的数据并不会自动去调整,而且最小的值被修改成了 100,我们的红黑树结构也遭到了破坏。
所以接下来我们要想办法,如何修改才能使key 值不可修改。

2.2.3. key 值不可修改

想要解决这个问题,我们先去库里看看大佬的思路。
set.h
在这里插入图片描述
set 只含有一个 key,在这里 iterator 被处理为 const_iterator,我们去看看 map 是怎么处理的
在这里插入图片描述
map并的处理方式和 set 完全不一样。
set 只含有一个值,这个key值本身就不能被修改,所以这里的 const_iterator 就能保证其中的 key 值不会被修改。
但是 map 中含有 key 和 value,map 中的 key 值不能被修改,但是 value 值是可以被修改的,所以 map 就不能那么暴力的直接全部修改为 const_iterator。
这里 set 和 map 的实现逻辑就不太一样了。
首先,我们先把insert 的返回值修改一下,库里面的返回值是键值对,这里我们也返回键值对

pair<iterator, bool> insert(const T& data)
{
	//...
}

红黑树的 insert 修改后,要注意 map.h 和 set.h 中的 insert 的返回值也要修改。

pair<iterator, bool> insert(const T& data)
{
	return _t.insert(data);
}

因为 set 需要用到const_iterator 所以我们先在红黑树中把 const_iterator 实现了
这里和我们前面实现 list 的const_iterator 的做法一样,我们在迭代器的模版参数中传入 Ref(reference参考值) 和 Ptr(指针), 当我们调用 const_iterator 时,就给 迭代器传入 const T& 和 const T*,如果我们要用 iterator, 我们就传入 T& 和 T*

template<class T, class Ref, class Ptr, class KeyofT>
struct __TreeIterator
{
	//...
}
template<class K, class T, class KeyofT>
class RBTree
{
public:
	typedef __TreeIterator<T, T&, T*, KeyofT>  iterator;
	typedef __TreeIterator<T, const T&, const T*, KeyofT> const_iterator;
	//...
}

const_iteraor 写好后,我们就开始操作 set

typedef typename RBTree<K, K, SetKeyofT>::const_iterator iterator ;
typedef typename RBTree<K, K, SetKeyofT>::const_iterator iterator;
pair<iterator, bool> insert(const K& key)
{
	return _t.insert(key);
}
iterator begin()const
{
	return _t.begin();
}
iterator end()const
{
	return _t.end()
}

把这里写好之后,我们尝试修改 set 中的值
在这里插入图片描述
这个时候,这里就会报错,我们不拿这里处理之后,运行代码,我们发现
在这里插入图片描述
还是报错了。
这里的问题就比较复杂了,
先看我们 红黑树 和 set 中 insert 中的返回值

pair<iterator, bool> insert(const T& data)
{
	return make_pair(iterator(cur), true);
}
//set.h
pair<iterator , bool> insert(const K& key)
{
	return _t.insert(key);
}

这里和 pair 的特性相关
在这里插入图片描述
我们知道,pair 的牛逼之处在于它的拷贝,他不需要我们一定要传入相同类型的值才能拷贝构造,而是只要 传入参数 pair 中的 第一第二参数能构造 被传入 的 pair 中的第一第二参数,就能完成 pair 的拷贝构造

 void func(pair<string, const int> p1)
 {
 	//..
 }
 int main()
 {
 	pair<const char*, int> p;
 	func(p);
 	return 0;
 }

在这里插入图片描述这里是没有问题的,因为 const char* 能构造 string, int 能构造 const int,所以 p 就能去构造 p1。
现在回到上面的问题,为什么这里会报错。
我们先清楚一件事,set 的 insert 返回值pair 中的类型iterator,对红黑树来说是 const_iterator 类型。
红黑树返回的类型是 pair<iterator, bool> 类型,
这里的 iterator 构造出来的类型 是 __TreeIterator<T, Ref, Ptr, KeyofT> 的类型
但是我们set 接收到这个类型后·,再返回 pair<const_iterator, bool> 类,型这里的 返回类型 是
RBTree<K, K, SetKeyofT>::const_iterator 类型,也就是
__TreeIterator<K, T, const T&, const T*, KeyofT>。
红黑树的 insert 返回的 pair 中 的迭代器没有 const T 的类型,但是 从 set 的 insert 返回的 pair 的迭代器中含有 const T 类型,pair 能做到 模版参数只要参数类型能构造就行,但是 我们模拟实现的迭代器是不支持自我完善的,也就是说 __TreeIterator<T, Ref, Ptr, KeyofT> 不能去构造 __TreeIterator<K, T, const T&, const T*, KeyofT> 类型 。
解决方法
所以我们可以考虑,不要使用它的默认拷贝构造,我们自己去写一个拷贝构造函数
除了这个方法,还有个更简单的方法
既然 pair 不需要传入和目标相同的模版参数,只要类型能构造就行,那么我们管目标是什么迭代器,我们直接把节点给set,set拿到节点爱怎么构造不关我红黑树的事

pair<Node*, bool> insert(const T& datd)
{
	//...
	return make_pair(cur, true);
	//...
}

在这里插入图片描述
此时我们的代码就能正常运行了,set 拿到节点,就要去自己用节点去构造 const_iterator。
这里要注意 const_iterator 和 const iterator 不是一个东西,const iterator只能保证这个迭代器不能修改,也就不能进行++,–等操作, 但是是可以修改 iterator 内的值。而 const_iterator 是我们自己实现的可以进行 ++,–等操作的类,只是这个类中的成员都用 const 进行修饰,成员不能修改。

然后就是 map 了,map 因为迭代器不那样暴力处理,我们想既然 key 不能修改,但是 value 可以修改,那么把 所有返回和 K 相关的地方,都返回 const K 应该就行了吧。

        template<class K, class T>
        class map
        {
        public:
                struct MapKeyofT
                {
                        const K& operator()(const pair<K, T>& key)
                        {
                                return key.first;
                        }

                };
                typedef        typename RBTree<K, pair<const K, T>, MapKeyofT>::iterator iterator;
                typedef        typename RBTree<const K, const pair<const K, T>, MapKeyofT>::iterator const_iterator;
                pair<iterator, bool> insert(const pair<const K, T>& key)
                {
                        return _t.insert(key);
                }
                iterator begin()
                {
                        return _t.begin();
                }
                iterator end()
                {
                        return _t.end();
                }
                T& operator[](const K& key)
                {
                        pair<iterator, bool> ret = insert(make_pair(key, T()));
                        return (ret.first)->second;
                }
        private:
                RBTree<K, pair<const K, T>, MapKeyofT> _t;
        };

在这里插入图片描述
大概逻辑写完后,我们看到 map 中的 第一参数不能修改,第二参数可以修改。
在这里插入图片描述

2.2.4. operator[]

既然我们上面把迭代器整体都玩完了,那么再写一个 [] 操作助助兴。
在这里插入图片描述
这里的解析在 map_set 学习的那节,想要了解这个怎么操作的可以去那里了解,这里主要进行实现

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

注意这个操作要完成的事,如果 [] 位置不存在值则是插入,如果存在值则返回其中的 value

void test_map2()
{
        string arr[] = { "香蕉", "甜瓜", "苹果", "西瓜","香蕉", "甜瓜", "苹果", "西瓜", "香蕉", "甜瓜", "苹果", "西瓜", "香蕉", "甜瓜", "苹果", "西瓜", "香蕉", "甜瓜", "苹果", "西瓜" };
        xsz2::map<string, int> m;
        for (auto& e : arr)
        {
                m[e]++;;
        }
        for (auto& e : m)
        {
                cout << e.first << "  " << e.second << "    ";
        }
        cout << endl;
}

在这里插入图片描述
这样我们的 [] 操作节完成了。
最后一个问题,我们在 RBTree 中传入了 模版参数 K,但是我们这里全程没用到过这个类型,我们需要 key 时直接使用了 KeyofT,这个 K 模版参数真的有必要吗?
有,因为我们这里主要实现的是 insert 操作,set,map中含有其他操作,比如 find,如果要实现find 操作

iterator find(const k& key)
{
	//...
}

可以说我们在简单的模拟实现这里没用上,但是绝对不能说他没有用。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Win10子系统wsl开机自启动jar包(nginx、redis、apache2、ssh等)

一. 需求背景 最近在研究nextcloud项目&#xff0c;选择了Win10子系统方案&#xff0c;而win10子系统wsl是通过接口实现的&#xff0c;很多linux上的常规操作在wsl上无法实现&#xff0c;比如配置开机自启动jar包以及其他依赖程序。只能通过windows系统重启时&#xff0c;自动执…

数据可视化训练第四天(模拟投掷筛子并且统计频次)

投掷一个筛子 import matplotlib.pyplot as plt from random import randint import numpy as npclass Die:"""模拟投掷筛子"""def __init__(self,num_sides6):self.num_sidesnum_sidesdef roll(self):return randint(1,self.num_sides)num1000…

day05-面向对象内存原理和数组

day05 面向对象内存原理和数组 我们在之前已经学习过创建对象了,那么在底层中他是如何运行的。 1.对象内存图 1.1 Java 内存分配 Java 程序在运行时&#xff0c;需要在内存中分配空间。为了提高运算效率&#xff0c;就对空间进行了不同区域的划分&#xff0c;因为每一片区域…

蓝桥杯-地宫取宝

X 国王有一个地宫宝库&#xff0c;是 nm 个格子的矩阵&#xff0c;每个格子放一件宝贝&#xff0c;每个宝贝贴着价值标签。 地宫的入口在左上角&#xff0c;出口在右下角。 小明被带到地宫的入口&#xff0c;国王要求他只能向右或向下行走。 走过某个格子时&#xff0c;如果那个…

睿尔曼机械臂ROS控制

下载git工程 git clone https://github.com/RealManRobot/rm_robot.git安装配置 catkin build rm_msgs source devel/setup.bash catkin build source setup.bash这里注意&#xff0c;如果采用setup.sh多半不会成功&#xff0c;必须要source setup.bash文件&#xff0c;ros才…

数据分析思维——数据埋点笔记,以电商为例

数据埋点 数据分析前提是有数据&#xff0c;数据从哪里来&#xff0c;要选择采集哪些数据都需要考虑。如某些app上的商品推荐&#xff0c;是基于哪些信息来预判的呢&#xff1f;因此作为数据分析师有必要系统的了解用户行为到用户数据的整个过程 何为数据埋点 每当用户在客户端…

JeeSite V5.7.0 发布,Java快速开发平台,Vite5、多项重构重磅升级

JeeSite V5.7.0 发布&#xff0c;Java快速开发平台&#xff0c;Vite5、多项重构重磅升级 升级内容 新增 参数配置 IP 地址黑白名单过滤器动态参数 新增 侧边栏是否展开第一个菜单的开关 first-open 新增 AesTypeHandler 处理字段数据加密解密或脱敏 新增 JsonTypeHandler …

FANUC机器人坐标系的分类和简介

1、概述 坐标系是为了确定机器人的位置和姿势而在机器人或空间上定义的位置指标系统&#xff0c;坐标系分为关节坐标系和直角坐标系&#xff0c;直角坐标系遵循右手定则&#xff0c;而关节坐标系则是以机器人每个轴所转动的角度来表示机器人当前的位置。 2、坐标系的分类及简…

2024 年最新本地、云服务器安装部署 miniconda 环境详细教程(更新中)

Anaconda 概述 Anaconda 是专门为了方便使用 Python 进行数据科学研究而建立的一组软件包&#xff0c;涵盖了数据科学领域常见的 Python 库&#xff0c;并且自带了专门用来解决软件环境依赖问题的 conda 包管理系统。主要是提供了包管理与环境管理的功能&#xff0c;可以很方便…

嵌入式全栈开发学习笔记---C语言笔试复习大全15

目录 指针运算 笔试题17 思考&#xff1a;*px、*px和(*px)的区别&#xff01; 笔试题18 补充命令8&#xff1a;“cd ..”退回到上一级目录 补充命令9&#xff1a;“man 3 函数名”可以查看库函数的原型 const 修饰指针是什么意思&#xff1f;&#xff08;笔试重点&#…

用hMailServer+roundcubemail+宝塔安装配置一个自己的邮箱服务

用hMailServerroundcubemail安装配置一个自己的邮箱服务 1、准备工具与资料&#xff1a; 云服务器一台 基础配置就行 2核4G。域名一个 以下用lizipro.cn示例。hMailServer安装包roundcubemail安装包异常处理插件补丁&#xff1a; libmysql.zip 2、hMailServer服务安装&#…

QToolButton的特殊使用

QToolButton的特殊使用 介绍通过QSS取消点击时的凹陷效果点击时的凹陷效果通过QSS取消点击时的凹陷效果 介绍 该篇文章记录QToolButton使用过程中的特殊用法。 通过QSS取消点击时的凹陷效果 点击时的凹陷效果 通过QSS取消点击时的凹陷效果 #include <QToolButton> #i…

加密与CA证书

文章目录 加密与CA证书http协议是不安全的使用对称秘钥进行数据加密非对称秘钥加密CA证书应用补充 加密与CA证书 CA 证书是什么&#xff0c;证书的目的是什么 首先明确一点&#xff0c;CA证书是数字时代中确保身份和数据安全的重要工具&#xff0c;为用户提供了安心、便捷和可…

齿轮端面倒棱刀具设计及模拟,记录一下

最近&#xff0c;我深陷在一项复杂且繁琐的任务中&#xff0c;几乎快要被其折磨得近乎疯狂。然而&#xff0c;经过一番努力&#xff0c;我终于迎来了曙光&#xff0c;成功完成了齿轮端面倒棱刀具加工的计算模拟。 这项任务&#xff0c;犹如一场旷日持久的战斗&#xff0c;每一…

小程序获取手机号,用户昵称,头像

一、手机号 在微信小程序中&#xff0c;获取用户手机号也需要用户的明确授权。你可以使用 button 组件的 open-type 属性设置为 getPhoneNumber 来实现这个功能。当用户点击这个按钮时&#xff0c;会弹出一个对话框请求用户的授权。如果用户同意&#xff0c;你可以在 bindgetp…

03.Linux文件操作

1.操作系统与Linux io框架 1.1 io与操作系统 1.1.1 io概念 io 描述的是硬件设备之间的数据交互&#xff0c;分为输⼊ (input) 与输出 (output)。 输⼊&#xff1a;应⽤程序从其他设备获取数据 (read) 暂存到内存设备中&#xff1b;输出&#xff1a;应⽤程序将内存暂存的数据…

数据链路层(详细版)【02】

接 数据链路层&#xff08;详细版&#xff09;【01】 文章目录 四、以太网MAC层&#xff08;一&#xff09;MAC地址组成&#xff08;1&#xff09;48位MAC地址格式&#xff08;2&#xff09;单播地址 & 多播地址 & 广播地址&#xff08;3&#xff09;全球管理 & 本…

如何优雅简单地写 Controller 层代码?

本篇就来介绍一下&#xff0c;如何写好一个 controller &#xff0c;让你的接口变的更加优雅&#xff01; 一个完整的后端请求由 4 部分组成&#xff1a; 接口地址&#xff08;也就是 URL 地址&#xff09; 请求方式&#xff08;一般就是 get、set&#xff0c;当然还有 put、…