c++——map和set的封装

news2024/11/24 3:46:43

注:该封装基于前面博客已实现红黑树,map和set封装并不难,主要还是对红黑树的理解


目录

一. 改造红黑树

1. 改变节点的定义,使用更高维度的泛型

2. 红黑树追加迭代器的实现

1. 红黑树迭代器的构造函数和基本框架

2. begin()和end()的实现

3. operator*和operator->的实现

4. operator++和operator--的实现

5. operator!=和operator==的实现

3. 对RBTree类进行改变

1. 改造insert

2. 增加find的实现

4. 改造后的RBTree树代码展示

二. 封装set

三. 封装map


一. 改造红黑树

颜色定义不更改,但是由于我们是使用一颗红黑树同时来作为封装set和map的底层结构,因此对数据类型需要进行处理,这里参考stl源码给出的解决方式是使用更高维度的泛型,因此我们也定义成更高维度的泛型,即数据类型这里我们使用T来接受va类型。

即我们不再使用KV的形式接受数据,而是用T来接受数据,T决定红黑树存什么数据。

这里和节点定义一样,我们使用T来接受数据,T决定红黑树存什么数据,对于set来说RBTree<K, V>传进来的是K,对于map来说RBTree<K, pair<K, V>>传进来的是pair类型。

虽然用T能解决传进来不同的数据类型,但是由于我们需要取K,对于set来说K就是K,但是,对于map来说是pair的第一个参数,所以由于无法确定T是K还是pair类型,无法准确去取出来T中的类型。

根据官方库可以发现再引进来一个模板参数KeyOfT,用来取出T中的类型,而这个KeOfT传给RBTree的,查看set和map后源码后发现KeyOfT其实是一个仿函数,用来提取T中类型的K的,因此我们就知道RBTree应该有三个模板参数,分别是K,T和KeyOfT

1. 改变节点的定义,使用更高维度的泛型

有了以上推论,我们可以对节点定义进行改变

//改变红黑节点的定义,使用更高维度的泛型
template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	color _col;

	//构造函数
	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_col(RED)
	{}
};

这里使用T来标识数据类型,因为节点可能是K类型的(set传过来的),也可能是pair类型的(map传过来的)

2. 红黑树追加迭代器的实现

由于map和set都需要用到迭代器,但是两者的迭代器实际上都是封装了红黑树的迭代器,因此我们要先实现红黑树的迭代器,这里迭代器的实现和链表极其相似,因此,也是使用三个模板参数,分别代表普通类型,引用类型,指针类型,库里的实现是采用了哨兵卫的头节点的方式,但是这里并不这样做

1. 红黑树迭代器的构造函数和基本框架

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)
	{}

	operator*()
	{}

	operator->()
	{}

	operator++()
	{}

	operator++(int)
	{}

	operator--()
	{}

	operator--(int)
	{}

	operator!=(const Self& s) const
	{}

	operator==(const Self& s) const
	{}

};

和链表迭代器的实现基本一致,将迭代器的基本功能实现

2. begin()和end()的实现

由于迭代器访问的顺序是按照二叉平衡搜索树的中序的顺序访问的,因此我们也不能坏这个规则。

按中序访问的思路,左子树->根->右子树:

begin应该返回中序的第一个元素,即需要去找二叉平衡搜索树的最左节点:

    iterator Begin()
	{
		Node* subleft = _root;
		while (subleft && subleft->_left)
		{
			subleft = subleft->_left;
		}

		return iterator(subleft);
	}

由于迭代器是自己实现的类,因此应该用迭代器创建对象并返回

 end()应该返回的是nullptr,因为最后遍历完成后应该是遍历到最右节点的nullptr节点

    iterator End()
	{
		return iterator(nullptr);
	}

直接用迭代器创建一个对象并传nullptr

3. operator*和operator->的实现

和链表一样,operator*返回节点中的数据operator->返回节点数据的地址(原因已在链表迭代器实现已解释,不做过多赘述)

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

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

4. operator++和operator--的实现

由于begin(),是按照中序的方式访问的,++可以分为两种情况,当右子树不存在为空时,找孩子是祖先的左的那个祖先节点,当右子树存在不为空时,找右子树的最左节点

有如下图红黑树:

一开始begin()迭代器应该位于节点为1处,当我们要++遍历这颗红黑树时:

由于1的右子树不为空,找右子树的最左节点,最左节点为空,则++以后访问到是6,由于6的右子树为空,再++应该去找孩子是祖先的左的那个祖先节点,此时找到就是节点为8的祖先节点,此时发现,这就已经按中序的方式运行起来了

++迭代器的实现方式如下代码:

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

			_node = parent;
		}
		else
		{
			Node* subleft = _node->_right;
			while (subleft->_left)
			{
				subleft = subleft->_left;
			}

			_node = subleft;
		}

		return *this;
	}

	Self operator++(int)
	{
		Self tmp(*this);
		++(*this);

		return tmp;
	}

由于begin(),是按照中序的方式访问的,--可以分为两种情况,当左子树不存在为空时,找孩子是祖先的右的那个祖先节点,当左子树存在不为空时,找左子树的最右节点

以下图红黑树为例:

假设此时迭代器已经遍历到节点为11的地方:

当我们要--遍历红黑树时,由于11的左子树不存在为空,往上找孩子是祖先的右的祖先节点,我发现11就是8的右,于是迭代器访问的是节点8的祖先节点,再次--,此时节点8的左子树存在不为空,找左子树的最右节点,此时节点6是1的最右节点,因此找到的节点是节点6,此时,发现已经是在按中序的方式遍历了

--的迭代器的实现代码如下:

    Self& operator--()
	{
		if (_node->_left == nullptr)
		{
			// 找祖先里面,孩子是父亲
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}
		else
		{
			// 左子树的最右节点
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}

			_node = subRight;
		}

		return *this;
	}

	Self operator--(int)
	{
		Self tmp(*this);

		--(*this);

		return tmp;
	}

5. operator!=和operator==的实现

比两个对象中的节点即可

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

	bool operator==(const Self& s) const
	{
		return _node == s->_node;
	}

至此迭代器的基本实现已完成

3. 对RBTree类进行改变

由于模板参数的改变和迭代器的实现,使RBTree的实现更加完整,需要对RBTree类进行改变。

1. 改造insert

首先就是对insert进行改变,有了迭代器的实现,我们应该和库里面的一样,返回的是pair<iterator, bool>而不是bool,以及在比较的时候应该使用KeyOfT这个仿函数模板参数来接受K,修改完成代码如下:

    pair<iterator, bool> Insert(const T& data)
	{
		// 1、搜索树的规则插入
		// 2、看是否违反平衡规则,如果违反就需要处理:旋转
		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)
		{
			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(iterator(cur), true);
			}
		}

		cur = new Node(data);
		Node* newnode = cur;
		cur->_col = RED;  
		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		cur->_parent = parent;

		// 存在连续红色节点
		while (parent && parent->_col == RED)
		{
			Node* grandfater = parent->_parent;
			assert(grandfater);

			if (grandfater->_left == parent)
			{
				Node* uncle = grandfater->_right;
				// 情况一:
				if (uncle && uncle->_col == RED) // 叔叔存在且为红
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}
				else // 叔叔不存在 或者 叔叔存在且为黑
				{
					if (cur == parent->_left) // 单旋
					{
						//     g
						//   p
						// c
						RotateR(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else // 双旋
					{
						//     g
						//   p
						//     c 
						RotateL(parent);
						RotateR(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
			else //(grandfater->_right == parent)
			{
				Node* uncle = grandfater->_left;
				// 情况一:
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)
					{
						// g
						//   p
						//     c 
						RotateL(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else // 双旋
					{
						// g
						//   p
						// c
						RotateR(parent);
						RotateL(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

		return make_pair(iterator(newnode), true);//创建并返回iterator对象
	}

相比原来的insert对返回值做了修改,并用KeyOfT仿函数创建对象来接受其中的K,来进行比较

2. 增加find的实现

利用KeyOfT提取T中的K来进行比较找想要找的元素,找到返回迭代器元素位置的迭代器否则返回end(),具体代码实现如下:

    iterator 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 iterator(cur);
			}
		}

		return End();
	}

4. 改造后的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)
		:_data(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _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;
	}
	// 休息17:00
	Self& operator++()
	{
		if (_node->_right == nullptr)
		{
			// 找祖先里面,孩子是父亲左的那个
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && parent->_right == cur)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}
		else
		{
			// 右子树的最左节点
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}

			_node = subLeft;
		}
		
		return *this;
	}

	Self operator++(int)
	{
		Self tmp(*this);
		
		++(*this);

		return tmp;
	}

	Self& operator--()
	{
		if (_node->_left == nullptr)
		{
			// 找祖先里面,孩子是父亲
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}
		else
		{
			// 左子树的最右节点
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}

			_node = subRight;
		}

		return *this;
	}

	Self operator--(int)
	{
		Self tmp(*this);

		--(*this);

		return tmp;
	}

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

	bool operator==(const Self& s) const
	{
		return _node == s->_node;
	}
};

// T决定红黑树存什么数据
// set  RBTree<K, K>
// map  RBTree<K, pair<K, V>>
// KeyOfT -> 支持取出T对象中key的仿函数
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*> const_iterator;

	// 构造 拷贝构造 赋值 和析构 跟搜索树实现方式是一样的

	iterator Begin()
	{
		Node* subLeft = _root;
		while (subLeft && subLeft->_left)
		{
			subLeft = subLeft->_left;
		}

		return iterator(subLeft);
	}

	iterator End()
	{
		return iterator(nullptr);
	}

	const_iterator Begin() const
	{
		Node* subLeft = _root;
		while (subLeft && subLeft->_left)
		{
			subLeft = subLeft->_left;
		}

		return const_iterator(subLeft);
	}

	const_iterator End() const
	{
		return const_iterator(nullptr);
	}

	pair<iterator, bool> Insert(const T& data)
	{
		// 1、搜索树的规则插入
		// 2、看是否违反平衡规则,如果违反就需要处理:旋转
		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)
		{
			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(iterator(cur), true);
			}
		}

		cur = new Node(data);
		Node* newnode = cur;
		cur->_col = RED;  
		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		cur->_parent = parent;

		// 存在连续红色节点
		while (parent && parent->_col == RED)
		{
			Node* grandfater = parent->_parent;
			assert(grandfater);

			if (grandfater->_left == parent)
			{
				Node* uncle = grandfater->_right;
				// 情况一:
				if (uncle && uncle->_col == RED) // 叔叔存在且为红
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}
				else // 叔叔不存在 或者 叔叔存在且为黑
				{
					if (cur == parent->_left) // 单旋
					{
						//     g
						//   p
						// c
						RotateR(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else // 双旋
					{
						//     g
						//   p
						//     c 
						RotateL(parent);
						RotateR(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
			else //(grandfater->_right == parent)
			{
				Node* uncle = grandfater->_left;
				// 情况一:
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)
					{
						// g
						//   p
						//     c 
						RotateL(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else // 双旋
					{
						// g
						//   p
						// c
						RotateR(parent);
						RotateL(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

		return make_pair(iterator(newnode), true);
	}

	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 (parent == _root)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == ppNode->_left)
			{
				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;
		}
	}

	iterator 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 iterator(cur);
			}
		}

		return End();
	}

private:
	Node* _root = nullptr;
};

二. 封装set

set的底层结构就是红黑树,因此在set中直接封装一棵红黑树,然后将其接口包装下即可

由于在RBTree中传了KeyOfT来获取K,因此我们在set中需要实现一个仿函数来获取set的K

    template<class K>
	class set
	{
		//给红黑树获取K
		struct SetkeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

其他都是包装RBTree的接口即可

注意:

由于set的Key是不能改变的,所以set的iterator应该用const_iterator包装。

但是会进一步导致insert函数在返回时,由于RBTree的iterator里的参数没有被const修饰是普通迭代器,但是set的iterator是const_iterator,就会出现iterator向const_iterator的转化,这是两个封装后毫无关系的类型,无法直接转化,因此我们需要先提取出来返回值稍作修改后重新封装,因此insert应该做出如下改造:

pair<iterator, bool> insert(const K& key)
{
    //pair<typename RBTree<K, K, SetkeyOfT>::iterator, bool> ret = _t.Insert(key);
	auto ret = _t.Insert(key);
	return pair<iterator, bool>(iterator(ret.first._node), ret.second);
}

注意:

这里和list实现迭代器一样,当我们typedef迭代器时,会出现内嵌类型的问题,需要使用到typename关键字

set具体封装的实现代码:

#include "RBTree.h"

namespace mystl
{
	template<class K>
	class set
	{
		//给红黑树获取K
		struct SetkeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:

		typedef typename RBTree<K, K, SetkeyOfT>::const_iterator iterator;
		typedef typename RBTree<K, K, SetkeyOfT>::const_iterator const_iterator;

		//set不能修改,只提供const版本
		iterator begin() const
		{
			return _t.Begin();
		}

		iterator end() const
		{
			return _t.End();
		}

		pair<iterator, bool> insert(const K& key)
		{
			//pair<typename RBTree<K, K, SetkeyOfT>::iterator, bool> ret = _t.Insert(key);
			auto ret = _t.Insert(key);
			return pair<iterator, bool>(iterator(ret.first._node), ret.second);
		}

		iterator find(const K& key)
		{
			return _t.find(key);
		}

	private:
		RBTree<K, K, SetkeyOfT> _t;
	};
}

三. 封装map

这里需要的注意事项和set,唯一不同的是这里insert没有set的问题,因为map的iterator就是RBTree的普通迭代器封装的,因map的V是可以更改的,map和set的K是不能更改的,所以map的普通迭代器应该封装成普通的迭代器供V改变,因此,insert可以直接返回封装的insert的返回值。

map实现了operator[],这里也可以实现,直接封装RBTree的insert,返回值是RBTree返回值的V的引用供修改即可

实现代码如下:

    V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));

			return ret.first->second;
		}

其他直接封装RBTree的接口即可,j具体实现代码如下:
 

#include "RBTree.h"

namespace mystl
{
	template<class K, class V>
	class map
	{
		//给红黑树获取K
		struct MapkeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};

	public:

		typedef typename RBTree<K, pair<K, V>, MapkeyOfT>::iterator iterator;
		typedef typename RBTree<K, pair<K, V>, MapkeyOfT>::const_iterator const_iterator;

		//map可以修改都提供
		iterator begin()
		{
			return _t.Begin();
		}

		iterator end()
		{
			return _t.End();
		}

		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _t.Insert(kv);
		}

		iterator find(const K& key)
		{
			return _t.find(key);
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));

			return ret.first->second;
		}

	private:
		RBTree<K, pair<K, V>, MapkeyOfT>  _t;
	};
}

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

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

相关文章

2.4、编码与调制

2.4、编码与调制 在计算机网络中。计算机需要处理和传输用户的文字&#xff0c;图片&#xff0c;音频和视频。它们可以统称为消息。 数据是运送消息的实体。 计算机中的网卡将比特 000 和 111&#xff0c;变换成相应的电信号发送到网线。 也就是说&#xff0c;信号是数据的…

[附源码]java毕业设计网络学习平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

LeetCode力扣刷题——更加复杂的数据结构

更加复杂的数据结构 一、引言 目前为止&#xff0c;我们接触了大量的数据结构&#xff0c;包括利用指针实现的三剑客和 C 自带的 STL 等。 对于一些题目&#xff0c;我们不仅需要利用多个数据结果解决问题&#xff0c;还需要把这些数据结构进行嵌套和联 动&#xff0c;进行更为…

五.STM32F030C8T6 MCU开发之RTC模块基础例程

五.STM32F030C8T6 MCU开发之RTC模块基础例程 文章目录五.STM32F030C8T6 MCU开发之RTC模块基础例程0.总体功能概述1.RTC硬件介绍1.1日历的功能1.2 闹钟输出1.3 入侵检测1.4 时间戳事件检测2.RTC软件配置2.1.RTC 模块初始化配置2.2 RTC 开始时间配置2.2.1RTC 年月日 时分秒配置2.…

_linux 进程间通信(管道)

文章目录1. 进程间通信目的2. 进程间通信发展3. 进程间通信分类4. 管道1. 进程间通信目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程资源共享&#xff1a;多个进程之间共享同样的资源。通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息&#x…

【English】十大词性之介词

介词 文章目录介词前言一、方位介词1.1 某地1.2 里里外外1.3 上上下下1.4 前前后后1.5 ....中间1.6 ...穿越1.7 ...树上1.8 在...墙上1.9 旁边(距离远近区分)二、时间介词三、方式介词四、易混淆介词4.1 制成4.2 交通工具4.3 除了总结前言 介词是表示名词、代词与句子中其它词…

02Java线程模型

1. 操作系统线程 无论使用何种编程语言编写多线程程序&#xff0c;最终都是通过调用操作系统的线程来执行任务。线程是CPU调度的最小执行单元。 线程有多种实现方式&#xff0c;常见的有&#xff1a;内核线程、用户线程、混合线程。 不同线程模型的主要区别在于线程的调度方不…

【Ubuntu】配置ubuntu网络

配置ubuntu网络 一、三种虚拟网络介绍二、 配置ubuntu系统使用桥接模式连接外网三、通过NAT模式让ubuntu系统连接外网四、常见问题1.解决ubuntu系统没有网络图标一、三种虚拟网络介绍 VMnet0 : 桥接模式,选中桥接模式之后,可以将VMnet0桥接到对应的物理网卡之上, 默认选中自…

uniapp公共新闻模块components案例

uniapp公共新闻模块components案例 简介&#xff1a;本文使用uniapp的公共新闻模块讲解components案例。 效果展示&#xff1a; 第一步 创建公共模块 第二步 编写组件 <template><view class"newsbox"><view class"pic"><ima…

动态路由协议 OSPF 工作过程 之 状态机维度

状态机 &#xff1a; # 什么是状态机呢 &#xff1f; 状态机 &#xff1a; 就是 OSPF 路由间的邻居关系所在的不同阶段 不同的关系 就是 不同的状态机 OSPF 的状态机 &#xff1a; # 我们用 思科 的PPT 来介绍 OSPF 的状态机 # 里面所有黄颜色方框里 标定的就是 状态机…

Json格式API调试,taobao1688pinduoduo商品详情测试接口

Json常用类型 任何支持的类型都可以通过 JSON 来表示&#xff0c;例如字符串、数字、对象、数组等。但是对象和数组是比较特殊且常用的两种类型。 对象&#xff1a;对象在 JS 中是使用花括号包裹 {} 起来的内容&#xff0c;数据结构为 {key1&#xff1a;value1, key2&#xf…

JavaScript:生成器函数

在介绍生成器函数之前先了解一下ES6的一个关键字&#xff0c;名为yield yield关键字&#xff0c;可以让代码在其出现的地方暂停执行&#xff0c;它只能在生成器函数内部使用。 生成器函数 生成器函数的语法比较简单在普通函数声明的时候在函数名前面添加一个*即可&#xff0…

openxr runtime Monado 源码解析 源码分析:Prober设备发现和管理 system device HMD target instance

monado系列文章索引汇总&#xff1a;openxr runtime Monado 源码解析 源码分析&#xff1a;源码编译 准备工作说明 hello_xr解读openxr runtime Monado 源码解析 源码分析&#xff1a;整体介绍 模块架构 模块作用 进程 线程模型 整体流程openxr runtime Monado 源码解析 源码分…

计算机网络4小时速成:物理层,功能特性,通信系统模型,分类,调制,曼彻斯特编码,信噪比,香农定理,复用技术,同轴电缆,中继器

计算机网络4小时速成&#xff1a;物理层&#xff0c;功能特性&#xff0c;通信系统模型&#xff0c;分类&#xff0c;调制&#xff0c;曼彻斯特编码&#xff0c;信噪比&#xff0c;香农定理&#xff0c;复用技术&#xff0c;同轴电缆&#xff0c;中继器 2022找工作是学历、能力…

[附源码]计算机毕业设计JAVA基于jsp的网上点餐系统

[附源码]计算机毕业设计JAVA基于jsp的网上点餐系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM m…

Proxy 代理对象使用详解即原理总结

Proxy 代理对象使用详解即原理总结 Proxy简单介绍 ECMAscript 6新增的代理可以给目标对象定义一个关联的代理对象&#xff0c;而这个代理对象可以作为抽象的目标对象来使用&#xff0c;在对目标对象的各种操作影响目标对象之前&#xff0c;可以在代理对象中对这些操作加以控制…

【python与数据分析】Matplotlib数据可视化

目录 前言 一、数据可视化库matplotlib 1.综述 2.pyplot基础语法 &#xff08;1&#xff09;创建画布与创建子图 &#xff08;2&#xff09;添加画布内容 &#xff08;3&#xff09;保存与展示图形 &#xff08;4&#xff09;设置pyplot的动态rc参数 二、绘制折线图 …

Python面向对象总结一

还记得第一次接触Python是在刚刚进入大学的第一个学期&#xff0c;之后就没有再使用过python。虽然基本语法&#xff0c;内置函数等没有忘记&#xff0c;但最近的学习中&#xff0c;一直在用python、matlab混合编程&#xff0c;零零散散的知识点、模模糊糊的记忆&#xff0c;有…

5G无线技术基础自学系列 | 传统无线网络架构

素材来源&#xff1a;《5G无线网络规划与优化》 一边学习一边整理内容&#xff0c;并与大家分享&#xff0c;侵权即删&#xff0c;谢谢支持&#xff01; 附上汇总贴&#xff1a;5G无线技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 在4G网络中&#xff0c; 无线侧基本完成…

Anaconda配置镜像源

目录 一、首先生成 Anaconda的 .condarc配置文件&#xff1a; 二、命令行方式配置镜像源 三、可视化界面的方式配置镜像源 一、首先生成 Anaconda的 .condarc配置文件&#xff1a; 打开 Anaconda prompt&#xff0c;敲命令&#xff1a; conda config 查看 .condarc 中的源…