用红黑树封装mapset【C++】

news2024/11/16 3:24:43

目录

前言

一,定义

二,完善set&map比较 

三,迭代器实现

operator++

operator--

operator[]

四,发现问题

解决

修改一: set封装层面

修改二:正向迭代器修改

下期预告: 哈希!!

结语


前言

我们在上篇文章红黑树——插入底层实现【C++】面试重灾区!!-CSDN博客

在上篇文章我们实现了红黑树的插入实现,同时也知道map与set底层一般是红黑树实现,那么本篇问文章我们来尝试封装map&set。

注意:建议完整学完本节,代码是逐步完善的。

首先我们从STL源码中寻找线索

还是比较明显的,红黑树的底层更加适合Map,但STL想让set复用map的红黑树,所以让set做出了一些 “ 妥协 ”:

 参数传递方向:

一,定义

根据STL源代码的提示,我们能初步形成框架

namespace my_map
{
	template <class K, class V>
	class map
	{
		RB_Tree<K, pair<const K, V>> _t;

	public:
		bool insert(const pair<const K, V>& p)
		{
			return _t.insert(p);
		}

	};
}
namespace my_set
{
	template <class K>
	class set
	{
		RB_Tree<K, K> _t;

	public:
		bool insert(const K& p)
		{
			return _t.insert(p);
		}
	};
}

同时我们要对红黑树的定义进行修改:

template <class T>
struct RBT_Data
{
	T _data;
	RBT_Data<T>* left = nullptr;
	RBT_Data<T>* right = nullptr;
	RBT_Data<T>* parent = nullptr;
	color _col;  // 颜色

	RBT_Data(const T& p)
		:_data(p)
		,_col(RED) //与其修改黑色路径数量,不如违反红子孩子都为黑的原则来的轻松。
	{}
};

template <class K, class V>
class RB_Tree 
{
	typedef RBT_Data<V> RBT_Data;
	RBT_Data* root = nullptr;
....

二,完善set&map比较 

从结果上来看,set&map 竟然运行成功。这是为什么呢??  没有了固定的  cur->_kv.first map应该是无法访问pair里面的数据,那又是如何进行比较大小,形成排序的呢??

答案在pair里面

pair类里面重载了比较大小的符号,但都是first,second之间进行比较,我们的排序需要依照Key值进行判断。那如何解决??

通过构建仿函数

set与map形成各自的仿函数,这样set就能适配map。

三,迭代器实现

基础框架,基本符号" * ", " -> " , " != "的重载。

这里以set封装为例 

// 在set封装上的处理
namespace my_set
{
	template <class K>
	class set
	{
		typedef  RB_Tree_Iterator<K, K&, K*> iterator;
        typedef  RB_Tree_Iterator<K, const K&, const K*> const_iterator;
		struct KeyofT
		{
			const K& operator()(const K& data)
			{
				return data;
			}
		};

		RB_Tree<K, K, KeyofT> _t;
	public:
		bool insert(const K& p)
		{
			return _t.insert(p);
		}

		iterator begin()
		{
			return _t.begin();
		}

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

// 迭代器类处理
template <class T, class Ref, class Ptr>
class RB_Tree_Iterator
{
	typedef RBT_Data<T>  RBT_Data;
	typedef RB_Tree_Iterator<T, Ref, Ptr> Seft;

	RBT_Data* it_root ;

public:
	RB_Tree_Iterator( RBT_Data* _node = nullptr)
	    :it_root(_node)
	{}

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

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

// 在RB_Tree上的处理
template <class K, class V, class KeyofT>
class RB_Tree
{
	typedef RB_Tree_Iterator<V, V&, V*> iterator;
	typedef RB_Tree_Iterator<V, const V&, const V*> const_iterator;
.......

	iterator begin()
	{
		RBT_Data* node = root;
		while (node && node->left)
		{
			node = node->left;	
		}
		return iterator(node);
	}
	
	// end跟以往的不同,end表示最后一个数据的下一个数据,也就是nullptr,实际距离是根的父节点。
	iterator end() 
	{
		return iterator(nullptr);
	}

	const const_iterator begin() const
	{
		RBT_Data* node = root;
		while (node && node->left)
		{
			node = node->left;
		}
		return const_iterator(node);
	}

	const const_iterator end() const
	{
		return const_iterator(nullptr);
	}
....

  

但我们还差一个极其重要的字符 “ ++ ”的实现,由于这个比较特殊我们分开单独讲。

operator++

思想:中序遍历来访问各个数据。比较难的是如何利用红黑树的三叉链,来实现中序遍历。

   Seft& operator++()
	{
		if (it_root && it_root->right)
		{
			// 假设右孩子存在
			RBT_Data* subleft = it_root->right;
			while (subleft->left)
			{
				subleft = subleft->left;
			}
			it_root = subleft;
		}
		else //如果没有右孩子,就向上回溯
		{
			RBT_Data* cur = it_root;
			RBT_Data* par = it_root->parent;
			while (par && par->right == cur)
			{
				cur = par;
				par = par->parent;
			}
				it_root = par;
		}
		return *this;
	}

operator--

 " -- "与 “ ++ ”两符号重载,本质上没多大改变,就是从原来的 左 ->  根  -> 右   =>    右 -> 根 -> 左

Seft& operator--()
	{
		if (it_root && it_root->right)
		{
			// 假设左侧存在
			RBT_Data* subright = it_root->left;
			while (subright->right)
			{
				subright = subright->right;
			}
			it_root = subright;
		}
		else //如果左侧没有
		{
			RBT_Data* cur = it_root;
			RBT_Data* par = it_root->parent;
			while (par && par->left == cur)
			{
				cur = par;
				par = par->parent;
			}
			it_root = par;
		}
		return *this;
	}

operator[]

这是STL对operator[] 的使用, 这需要我们对insert()进行返回值的修改,我们需要返回一个pair类型,里面放着pair<iterator, bool>,方便我们修改value的值。

这里我给一个案例进行修改:insert返回value的引用

后面的返回值处理成pair返回类似。

然后就是map与set的封装层,我们要如何使用呢??代码如下:

//  set     
    K& operator[](const K& key)
    {
	pair<iterator, bool> ret = _t.insert(key);
	if (ret.second)
		return ret.first.it_root->_data;
	cout << "插入失败" << endl;
		}
// map
V& operator[](const K& key)
{
   pair<iterator, bool> ret = _t.insert(make_pair(key, V()));
   if (ret.second)
     return ret.first->second;
   cout << "插入失败" << endl;
}

四,发现问题

 我们运行这段测试代码,我们会发现,set里面的数据被修改,但这是不被允许的。

    my_set::set<int> st;
	st[1];
	st[2];
	st[3];
	st[4];

	for (auto& e : st)
	{
		cout << e << " ";
	}

	for (auto& e : st)
	{
		e = 1;
		cout << e << " ";
	}

原因分析:下图是普通对象set获取迭代器参数的传递

解决

对于set的普通对象,如果是上面的代码,我们无法在适配map的前提下,保证set的value不可修改。解决思路:我们让set的普通迭代器类型底层是const迭代器。但其中的细节还是非常巧妙,我们通过下图讲解特殊之初吧。

修改一: set封装层面

修改二:正向迭代器修改

好啦,map与set封装精华就到这里了。

map_set.h

namespace my_set
{
	template <class K>
	class set
	{
		struct KeyofT
		{
			const K& operator()(const K& data)
			{
				return data;
			}
		};
		typedef  typename RB_Tree<K, K, KeyofT>::const_iterator iterator;  //直接用,const对象的迭代器充当普通迭代器
		typedef  typename RB_Tree<K, K, KeyofT>::const_iterator const_iterator;

		RB_Tree<K, K, KeyofT> _t;
	public:
		pair<iterator, bool> insert(const K& p)
		{
			return _t.insert(p);
		}

		K& operator[](const K& key)
		{
			pair<iterator, bool> ret = _t.insert(key);
			if (ret.second)
				return ret.first.it_root->_data;
			cout << "插入失败" << endl;
		}

		iterator begin()
		{
			return _t.begin();
		}

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

		const_iterator begin() const
		{
			return _t.begin();
		}

		const_iterator end() const
		{
			return _t.end();
		}
	};
}

namespace my_map
{
	template <class K, class V>
	class map
	{
		typedef  RB_Tree_Iterator<pair<const K, V>, pair<const K, V>&, pair<const K, V>*> iterator;
		typedef  RB_Tree_Iterator<pair<const K, V>, const pair<const K, V>&, const pair<const K, V>*> const_iterator;
		struct KeyofT
		{
			const K& operator()(const pair<const K, V>& data)
			{
				return data.first;
			}
		};

		RB_Tree<K, pair<const K, V>, KeyofT> _t;

	public:
		pair<iterator, bool> insert(const pair<const K, V>& p)
		{
			return _t.insert(p);
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _t.insert(make_pair(key, V()));
			if (ret.second)
				return ret.first->second;
			cout << "插入失败" << endl;
		}
		iterator begin()
		{
			return _t.begin();
		}

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

		const_iterator begin() const
		{
			return _t.begin();
		}

		const_iterator end() const
		{
			return _t.end();
		}
	};
}

 

RB_Tree.h

#include <iostream>
#include<string>
#include <assert.h>
#include <utility>
using namespace std;

enum color
{
	RED,
	BLACK
};


template <class T>
struct RBT_Data
{
	T _data;
	RBT_Data<T>* left = nullptr;
	RBT_Data<T>* right = nullptr;
	RBT_Data<T>* parent = nullptr;
	color _col;  // 颜色

	RBT_Data(const T& p)
		:_data(p)
		, _col(RED) //与其修改黑色路径数量,不如违反红子孩子都为黑的原则来的轻松。
	{}
};

template <class T, class Ref, class Ptr>
class RB_Tree_Iterator
{
	typedef RBT_Data<T>  RBT_Data;
	typedef RB_Tree_Iterator<T, Ref, Ptr> Self;
	typedef RB_Tree_Iterator<T, T&, T*> iterator;  // 跟上面的不同,这个iterator只以T为准,这样在set const的对象时,不会出现
	                                               // 两个const 的情况

public:
	RBT_Data* it_root ;

	RB_Tree_Iterator(RBT_Data* _node = nullptr)
		:it_root(_node)
	{}

	RB_Tree_Iterator(const iterator& it)
		:it_root(it.it_root)
	{}

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

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

	// node* 地址也算不同罢了
	bool operator!= (const Self& node)
	{
		return node.it_root != it_root;
	}

	Self& operator++()
	{
		if (it_root && it_root->right)
		{
			// 假设右侧存在
			RBT_Data* subleft = it_root->right;
			while (subleft->left)
			{
				subleft = subleft->left;
			}
			it_root = subleft;
		}
		else //如果右侧没有
		{
			RBT_Data* cur = it_root;
			RBT_Data* par = it_root->parent;
			while (par && par->right == cur)
			{
				cur = par;
				par = par->parent;
			}
			it_root = par;
		}
		return *this;
	}


	Self& operator--()
	{
		if (it_root && it_root->right)
		{
			// 假设左侧存在
			RBT_Data* subright = it_root->left;
			while (subright->right)
			{
				subright = subright->right;
			}
			it_root = subright;
		}
		else //如果左侧没有
		{
			RBT_Data* cur = it_root;
			RBT_Data* par = it_root->parent;
			while (par && par->left == cur)
			{
				cur = par;
				par = par->parent;
			}
			it_root = par;
		}
		return *this;
	}

};


template <class K, class V, class KeyofT>
class RB_Tree
{
public:
	typedef RBT_Data<V> RBT_Data;
	typedef RB_Tree_Iterator<V, V&, V*> iterator;
	typedef RB_Tree_Iterator<V, const V&, const V*> const_iterator;

	// 这个也是按照后序遍历方式进行析构
	void Destroy(RBT_Data* node)
	{
		if (node == nullptr)
			return;
		Destroy(node->left);
		Destroy(node->right);
		delete(node);
	}

	~RB_Tree()
	{
		if (root)
		{
			Destroy(root);
			root = nullptr;
		}
	}

	iterator begin()
	{
		RBT_Data* node = root;
		while (node && node->left)
		{
			node = node->left;
		}
		return iterator(node);
	}

	// end跟以往的不同,end表示最后一个数据的下一个数据,也就是nullptr
	iterator end()
	{
		return iterator(nullptr);
	}


	const_iterator begin() const
	{
		RBT_Data* node = root;
		while (node && node->left)
		{
			node = node->left;
		}
		return const_iterator(node);
	}

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

	void Print()
	{
		_Print(root);
	}

	bool IsBalance()
	{
		if (root && root->_col == RED)
		{
			return false;
		}

		int BlackNum = 0;
		// 还差一个最长路径
		int standard = 0;
		RBT_Data* cur = root;
		while (cur)
		{
			if (cur->_col == BLACK)
				standard++;
			cur = cur->left;
		}
		return _IsBalance(root->left, BlackNum, standard) && _IsBalance(root->right, BlackNum, standard);
	}

	pair<iterator, bool>  insert(const V& p)
	{
		RBT_Data* new_a_d = new RBT_Data(p);
		if (!root)
		{
			root = new_a_d;
			root->_col = BLACK;
			return make_pair(iterator(new_a_d), true);
		}
		else
		{
			RBT_Data* cur = root;
			RBT_Data* parent = nullptr;
			KeyofT Kt;
			while (cur)
			{
				if (Kt(p) < Kt(cur->_data))
				{
					parent = cur;
					cur = cur->left;
				}
				else if (Kt(p) > Kt(cur->_data))
				{
					parent = cur;
					cur = cur->right;
				}
				else
				{
					delete(new_a_d); // 插入失败,删除新建结点
					return make_pair(iterator(root), false);
				}
			}

			if (p < parent->_data)
			{
				parent->left = new_a_d;
			}
			else
			{
				parent->right = new_a_d;
			}
			new_a_d->parent = parent;

			// 调整颜色
			cur = new_a_d;
			RBT_Data* par = cur->parent;
			if (cur == root)
			{
				cur->_col = BLACK;
			}

			while (par && par->_col == RED)
			{
				RBT_Data* gf = par->parent;
				RBT_Data* uncle = nullptr;
				if (gf && par == gf->right)
				{
					uncle = gf->left;
				}
				else if (gf && par == gf->left)
				{
					uncle = gf->right;
				}
				else
				{
					assert(false);
				}


				if (uncle && uncle->_col == RED)// 有u且为红
				{
					gf->_col = RED;
					uncle->_col = BLACK;
					par->_col = BLACK;

					cur = gf;  // 切换为祖先,进入循环向上
					par = cur->parent;
				}
				else if (!uncle ||
					(uncle && uncle->_col == BLACK))
				{   // 情况2 + 3,判断,是否是折线还是直线
					if (gf->left == par && par->left == cur)
					{  // 右单选
						RotateR(gf);
					}
					else if (gf->right == par && par->right == cur)
					{  // 左单旋
						RotateL(gf);
					}
					else if (gf->left == par && par->right == cur)
					{  // 需要左双旋
						RotateLR(gf);
					}
					else if (gf->right == par && par->left == cur)
					{  // 需要右双旋
						RotateRL(gf);
					}
					else
					{
						assert(false);
					}
					break;
				}
				else
				{
					assert(false);
				}
			}

			if (root->_col == RED)
			{
				root->_col = BLACK;
			}
			return make_pair(iterator(new_a_d), true);
		}
	}

private:
		RBT_Data* root = nullptr;

		void RotateL(RBT_Data* parent)
		{
			assert(parent->right);
			RBT_Data* par = parent;
			RBT_Data* par_R = par->right;
			RBT_Data* par_RL = par->right->left;
			RBT_Data* ppnode = par->parent;

			par->right = par_RL;
			if (par_RL)
				par_RL->parent = par;

			par_R->left = par;
			par->parent = par_R;
			par_R->parent = ppnode;

			if (!ppnode)
			{
				root = par_R;
			}
			else if (ppnode->left == par)
			{
				ppnode->left = par_R;
			}
			else
			{
				ppnode->right = par_R;
			}

			par->_col = RED;
			par_R->_col = BLACK;
		}

		void RotateR(RBT_Data* parent)
		{
			assert(parent->left);
			RBT_Data* par = parent;
			RBT_Data* par_L = par->left;
			RBT_Data* par_LR = par->left->right;
			RBT_Data* ppnode = par->parent;

			par->left = par_LR;
			if (par_LR)
				par_LR->parent = par;

			par_L->right = par;
			par->parent = par_L;
			par_L->parent = ppnode;

			if (!ppnode)
			{
				root = par_L;
			}
			else
			{
				if (ppnode->left == par)
				{
					ppnode->left = par_L;
				}
				else
				{
					ppnode->right = par_L;
				}
			}

			par->_col = RED;
			par_L->_col = BLACK;
		}

		void RotateLR(RBT_Data* parent)
		{
			assert(parent->left);
			RBT_Data* par = parent;
			RBT_Data* par_L = par->left;
			RBT_Data* par_LR = par->left->right;

			RotateL(par_L);
			RotateR(par);

			par_LR->_col = BLACK;
			par_L->_col = BLACK;
		}

		void RotateRL(RBT_Data* parent)
		{
			assert(parent->right);
			RBT_Data* par = parent;
			RBT_Data* par_R = par->right;
			RBT_Data* par_RL = par->right->left;

			RotateR(par_R);
			RotateL(par);

			par_RL->_col = BLACK;
			par_R->_col = BLACK;
		}

		void _Print(const RBT_Data* root)
		{
			if (root == nullptr)
			{
				// cout << "[]";
				return;
			}
			_Print(root->left);
			cout << KeyofT(root->_data) << " ";
			_Print(root->right);
		}

		bool _IsBalance(const RBT_Data* cur, int BlackNum, int standard)
		{
			if (cur == nullptr)
			{
				return true;
			}
			if (cur->_col == BLACK)
				BlackNum++;

			if (cur->_col == RED && cur->_col == cur->parent->_col)
			{
				return false;
			}

			return  _IsBalance(cur->left, BlackNum, standard) && _IsBalance(cur->right, BlackNum, standard);
		}
};

下期预告: 哈希!!

 

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

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

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

相关文章

DataxWeb安装部署及使用--真香警告

DataxWeb安装部署及使用–真香警告 文章目录 1.Datax简介1.1 Datax是什么&#xff1f;1.2 Datax的架构1.3 设计理念1.4 DataX3.0框架设计1.5 DataX3.0插件体系1.6 DataX3.0核心架构1.6.1 核心模块介绍1.6.2 DataX调度流程 2.DataxWeb简介2.1 DataxWeb是什么&#xff1f;2.2 Dat…

在Ubuntu上安装Redis并学习使用get、set和keys命令

目录 安装Redis切换到root用户搜索redis相关软件包安装redis修改配置文件重启服务器使用redis客户端连接服务器 get与set命令keys 安装Redis 我们在Ubuntu20.04上进行Redis的安装 切换到root用户 使用su命令&#xff1a; 在终端中&#xff0c;输入su并按回车键。然后输入roo…

【产品应用】一体化伺服电机在焊接设备中的应用

随着制造业的不断发展&#xff0c;焊接设备在许多领域都得到了广泛应用&#xff0c;如汽车制造、机械加工、钢结构等领域。为了提高焊接设备的性能和效率&#xff0c;许多厂家开始采用一体化伺服电机作为焊接设备的主要驱动部件。本文将介绍一体化伺服电机在焊接设备中的应用背…

Leetcode—485.最大连续1的个数【简单】

2023每日刷题&#xff08;十五&#xff09; Leetcode—485.最大连续1的个数 实现代码 int findMaxConsecutiveOnes(int* nums, int numsSize){int max 0;int i;int flag 0;int cnt 0;for(i 0; i < numsSize; i) {if(nums[i] 1) {if(flag 0) {flag 1;cnt 1;} else {…

python 实时读取文件数据生成折线图——Matplotlib

有时&#xff0c;为了方便看数据的变化情况&#xff0c;需要画一个动态图来看整体的变化情况。主要就是用Matplotlib库。 效果演示&#xff1a; 代码如下&#xff1a; import matplotlib.pyplot as plt import pandas as pdfilename data.log# 创建空的 DataFrame 对象 df …

开心打地鼠,Android小游戏开发

A. 项目描述 “开心打地鼠”是一款非常有趣的游戏&#xff0c;它能够帮助人们放松身心&#xff0c;同时也能够锻炼人们的智力。 “开心打地鼠”这款游戏的玩法非常简单&#xff0c;玩家需要在规定的时间内点击屏幕上出现的地鼠&#xff0c;每次点击都可以得到一定的分数。但是…

kafka动态认证 自定义认证 安全认证-亲测成功

kafka动态认证 自定义认证 安全认证-亲测成功 背景 Kafka默认是没有安全机制的&#xff0c;一直在裸奔。用户认证功能&#xff0c;是一个成熟组件不可或缺的功能。在0.9版本以前kafka是没有用户认证模块的&#xff08;或者说只有SSL&#xff09;&#xff0c;好在kafka0.9版本…

基于CMFB余弦调制滤波器组的频谱响应matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1、CMFB余弦调制滤波器组原理 4.2、CMFB调制过程 4.3、CMFB特点 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ......................…

【Linux】 unzip 命令使用

Unzip 是一个在 Unix 或类 Unix 系统&#xff08;例如 Linux&#xff09;中常用的命令行工具&#xff0c;用于解压缩由 ZIP 压缩算法创建的压缩文件。这个工具是开源的&#xff0c;并且通常在大多数的 Linux 发行版中默认安装。 语法 su [选项] [-] [USER [参数]...] unzip命令…

题号1575 C.难度排名 (并查集知识点)

题目&#xff1a; 样例1&#xff1a; 输入 1 4 3 1 4 2 4 3 4 输出 No 样例2&#xff1a; 输入 1 4 2 1 3 2 3 输出 Yes 思路&#xff1a; 这题&#xff0c;有两种情况是由矛盾的。 第一种情况&#xff1a;当前题号存在大于两个题号的相连&#xff0c;情况是矛盾的&#x…

python之pip常用指令

文章目录 pip show xxx 查看是否安装该 module

Git客户端软件 Tower mac中文版特点说明

Tower mac是一款Mac OS X系统上的Git客户端软件&#xff0c;它提供了丰富的功能和工具&#xff0c;帮助用户更加方便地管理和使用Git版本控制系统。 Tower mac软件特点 1. 界面友好&#xff1a;Tower的界面友好&#xff0c;使用户能够轻松地掌握软件的使用方法。 2. 多种Git操…

Numpy数值计算Numpy 进阶在线闯关_头歌实践教学平台

Numpy数值计算进阶 第1关 Numpy 广播第2关 Numpy 高级索引第3关 Numpy 迭代数组 第1关 Numpy 广播 任务描述 本关任务&#xff1a;给定两个不同形状的数组&#xff0c;求出他们的和。 编程要求 首先用 arange() 生成一个数组&#xff0c;然后用 reshape() 方法&#xff0c;将数…

Java入门篇 之 逻辑控制(练习题篇)

博主碎碎念: 练习题是需要大家自己打的请在自己尝试后再看答案哦&#xff1b; 个人认为&#xff0c;只要自己努力在将来的某一天一定会看到回报&#xff0c;在看这篇博客的你&#xff0c;不就是在努力吗&#xff0c;所以啊&#xff0c;不要放弃&#xff0c;路上必定坎坷&#x…

Windows 11 PowerShell 安装 jq 命令

本心、输入输出、结果 文章目录 Windows 11 PowerShell 安装 jq 命令前言jq 命令简介基本语法案例 Windows 11 PowerShell 安装 jq 命令使用 jq 格式化 curl 输出的 json弘扬爱国精神 Windows 11 PowerShell 安装 jq 命令 编辑&#xff1a;简简单单 Online zuozuo 地址&#xf…

微型计算机组成原理

1、微型计算机组成 一个传统微型计算机硬件组成如下图 CPU通过地址线、数据线和控制信号线组成的本地总线&#xff08;内部总线&#xff09;与系统其他部分进行数据通信。 地址线用于提供内存或I/O设备的地址&#xff0c;即指明需要读/写数据的具体位置&#xff1b;数据线用…

项目实战之安装依赖npm install

文章目录 nvmdeasync包和node-gyp报错deasync包node-gyp报错 前言&#xff1a;有些人看着还活着其实已经凉了好一会儿了。 初拿到项目 初拿到项目肯定是先看配置 package.json的啦&#xff0c;看看都需要安装什么依赖&#xff0c;然后 npm install,OK结束 皆大欢喜。 ————…

OMV 介绍及安装

# Time: 2023/11/02 #Author: Xiaohong # 运行电脑: Lenovo X201I (Intel(R) Core(TM) i3 CPU M 370 2.40GHz) # 功能: OMV 介绍及安装 导图 若OMV6 安装Extras 插件失败&#xff0c;可以参考 OMV6 安装Extras 插件失败的解决方法

LV.12 D15 WDT实验 学习笔记

一、WDT简介 WDT Watch Dog Timer即看门狗定时器&#xff0c;其主要作用是当发生软件故障时可产生复位信号使SOC复位&#xff0c;其本质是一个计数器 工作原理 CPU正常工作时&#xff0c;需要定时往看门狗计数器里刷新一个比较大的值&#xff0c;来保证看门狗计数器中的值不会…

网络安全进阶学习第二十一课——XML介绍

文章目录 一、XML简介二、XML文档结构1、XML文档结构包括2、XML树结构 三、XML语法1、声明信息&#xff0c;用于描述xml的版本和编码方式2、XML有且只有一个根元素3、成对标签&#xff08;即标签必须关闭&#xff0c;html可以不关闭也能运行&#xff09;4、区分大小写5、不可交…