秒懂C++之set、map的封装

news2024/11/14 1:28:50

fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

 

红黑树的泛型编程

改变比较方式:仿函数

迭代器模拟实现

++运算符重载

begin/end

!=/==运算符重载

测试

const

Find

[ ] 运算符重载

全部代码

RBTree.h

Mymap.h

Myset.h

test.cpp


红黑树的泛型编程

既然我们要实现set,map的封装那肯定要用到我们前面所学的红黑树为底层~

不过set是k模型,map是kv模型,难道我们要专门写两份不同模型的红黑树供它们使用吗?

让我们来看看在源码中它们是如何解决这一问题的~

在set源码中底层红黑树也设置了两个参数~不过这两个参数其实都是Key没啥区别

而map源码中底层红黑树第一个参数为key,第二个参数为pair,这就有点奇怪了,红黑树设置kv模型第一个参数为pair就行了,为什么这里有两个参数?

这其实是一种泛型编程的写法,目的就是减少重复冗余代码。 首先红黑树第一个参数统一为key,第二个参数是根据set与map传递的参数而变化~

如果set使用红黑树,那红黑树就是<k,k>模型,value值就是key~

如果是map使用红黑树,那么红黑树就是<k,pair<k,v>>模型,value值就是pair<k,v>~

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

// set<k>->RBTree<K, K>
// map<k,pair<k,v>>->RBTree<K, pair<K, V>>
template<class K, class T>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	

private:
	Node* _root = nullptr;
};
#pragma once
//Myset.h
#include"RBTree.h"


namespace lj
{
	template<class K>
	class set
	{

	private:
		RBTree<K, K> _t;
	};
}
#pragma once
//Mymap.h
#include"RBTree.h"


namespace lj
{
	template<class K,class V>
	class map
	{

	 private:
		RBTree<K,  pair<k,v>> _t;
	};
}

改变比较方式:仿函数

 以下是源代码中pair的比较方式~但我们不想实现value的比较,我们只想实现key的比较~

其实我们也不用特意去修改运算符>,<,我们修改获取_data数据的方式就行了~

pair小于如果是比较key与value的话,那么我们在获取data的时候只让它拿到key就好了~

定义一个内部类,作用为set参数的data只能获取key,map参数的data只能获取key~

迭代器模拟实现

++运算符重载

当右子树为空时,it++有指向其父节点的,也有指向其爷爷节点的,只能说最终会指向其祖先节点的某一个~那我们要如何表示这个呢?

当右子树为空作为条件时,我们先看向节点15,作为其父节点的左子树访问完后就应该访问父节点了~应该二叉搜索树为中序遍历——左子树,根,右子树

我们再来看向节点6,作为其父节点1的右子树,当把节点6访问完也就意味着包括节点1在内的其所有子树都访问完了,即节点8的左子树访问完了,下一步就该访问节点8~

所以当其右子树为空时,我们就去找哪个祖先节点的孩子节点为左节点,就比如节点15,它是祖先节点的左节点吗?——是的,那么我们的下一个节点就是其父亲。

再看节点6,它是目前父节点的左孩子吗?——不是,那么继续向上遍历,节点1是其父节点8的左孩子吗?——是的,那么我们的下一个节点就是节点1的父亲~

template<class T>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T> Self;

	Node* _node;

	RBTreeIterator(Node* node)
		:_node(node)
	{}

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

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

	Self& operator++()
	{
		//如果其右子树不为空
		if (_node->_right)
		{
			// 右子树的最左节点
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}

			_node = subLeft;
		}
		else
		{
			// 一直找该节点恰好是父亲左节点的关系
			Node* cur = _node;
			Node* parent = cur->_parent;
			//只要孩子还是父亲右节点的关系,就一直循环继续找
			//特殊判断,当最后一个节点++时应该指向空
			while (parent&&cur == parent->_right)
			{
				//向上寻找
				cur = parent;
				parent = cur->_parent;
			}
			//找到了,把父亲节点作为下一个节点
			_node = parent;
		}

		return *this;
	}
};

关于封装迭代器(使得指针++自定义)不熟悉的友友可以去看这篇文章:秒懂C++之List-CSDN博客

begin/end

iterator begin()
	{
		// 求最左节点
		Node* subLeft = _root;
		//若为空树则返回空节点
		while (subLeft&&subLeft->_left)
		{
			subLeft = subLeft->_left;
		}

		return iterator(subLeft);
	}

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

!=/==运算符重载

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

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

测试

当我们部署好迭代器后,开始来检验一下成果~

const

对const迭代器不了解的友友可以参考这篇文章:秒懂C++之List-CSDN博客

前我们还面临一个问题,key是不支持修改的~而我们现在只能通过降低传参权限去限制~

其实我们也可以封装一个const类型的迭代器~

不过const迭代器了解即可,毕竟正常迭代器就够用了~

ps:map一般不需要const迭代器,因为它需要修改pair里面的value,而set只有key,所以不能被修改~

Find

iterator Find(const K& key)
	{
		whok kot;
		Node* cur = _root;
		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();

	}

这就是为什么我们红黑树第一个参数统一设置成key的意义,我们用Find函数单纯就是寻找key,如果用第二个参数的话从data里面还得挑出key反而麻烦~

当然啦,如果你就是想在第二个参数的data找也可以~ 

不影响~

[ ] 运算符重载

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

		

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

要实现[ ] 运算符重载就必须得对insert的返回值进行修改~

全部代码

RBTree.h

//RBTree.h
#pragma once
#include<vector>
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 Ptr, class Ref>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ptr,Ref> Self;

	Node* _node;

	RBTreeIterator(Node* node)
		:_node(node)
	{}

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

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

	Self& operator++()
	{
		//如果其右子树不为空
		if (_node->_right)
		{
			// 右子树的最左节点
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}

			_node = subLeft;
		}
		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;
	}

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


// set->RBTree<K, K>
// map->RBTree<K, pair<K, V>>
template<class K, class T, class whok>
class RBTree
{
	typedef RBTreeNode<T> Node;
	public:
	
	typedef RBTreeIterator<T, T*, T&> iterator;
	typedef RBTreeIterator<T, const T*, const T&> const_iterator;

	const_iterator begin() const
	{
		// 求最左节点
		Node* subLeft = _root;
		//若为空树则返回空节点
		while (subLeft && subLeft->_left)
		{
			subLeft = subLeft->_left;
		}

		return const_iterator(subLeft);
	}

	iterator begin()
	{
		// 求最左节点
		Node* subLeft = _root;
		//若为空树则返回空节点
		while (subLeft&&subLeft->_left)
		{
			subLeft = subLeft->_left;
		}

		return iterator(subLeft);
	}

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

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

	iterator Find(const T& data)
	{
		whok kot;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))
			{
				cur = cur->_left;
			}
			else
			{
				return iterator(cur);
			}
		}

		return end();

	}


	pair<iterator, bool>  Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			//根节点为黑色
			_root->_col = BLACK;
			//return true;
			return make_pair(iterator(_root), true);
		}
		whok 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 false;
				return make_pair(iterator(cur), false);
			}
		}
		// 准备插入,节点为红色
		cur = new Node(data);
		//标记最开始准备插入的cur,避免cur在向上遍历时变色移动,这时候的迭代器虽然还是叫cur,但已经不是原来的节点了
		Node* newnode = cur;

		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
		//若插入成功后其父节点为黑,那么并不会触发连续红色,不需要修改
		//若插入成功后其父节点为红,触发连续红色,需要修改
		while (parent && parent->_col == RED)
		{
			//记录g节点
			Node* grandfather = parent->_parent;
			//记录u节点
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//开始分类情况
				//情况一:u节点存在且为红
				if (uncle && uncle->_col == RED)
				{
					//p/g/u变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					//把g当作cur继续处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二:u节点不存在或u节点存在且为黑
				else
				{
					//情况二需要按照旋转情况进行分类
					//右旋情况,以g为旋转点
					if (cur == parent->_left)
					{
						//       g
						//    p     u
						// c 

						//右旋
						RotateR(grandfather);
						//p/g节点变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//左右旋,p为旋转点后以g为旋转点
					else if (cur == parent->_right)
					{
						//       g
						//    p     u
						//		 c 

						//左旋
						RotateL(parent);
						//右旋
						RotateR(grandfather);
						//cur/g节点变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					//旋转处理完毕,不用再向上处理,直接退出
					break;
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				//开始分类情况
				//情况一:u节点存在且为红
				if (uncle && uncle->_col == RED)
				{
					//p/g/u变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					//把g当作cur继续处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二:u节点不存在或u节点存在且为黑
				else
				{
					//情况二需要按照旋转情况进行分类
					//左旋情况,以g为旋转点
					if (cur == parent->_right)
					{
						//       g
						//    u     p
						//			   c

						//左旋
						RotateL(grandfather);
						//p/g节点变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//右左旋,p为旋转点后以g为旋转点
					else if (cur == parent->_left)
					{
						//       g
						//    u     p
						//		 c 

						//右旋
						RotateR(parent);
						//左旋
						RotateL(grandfather);
						//cur/g节点变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					//旋转处理完毕,不用再向上处理,直接退出
					break;
				}
			

			}
					
		}
		//插入调整结束后我们统一让根节点变为黑色
		_root->_col = BLACK;
		//插入成功~
		//return true;
		//把最开始指向插入节点的迭代器返回
		return make_pair(iterator(newnode), true);
	}

	void RotateL(Node* parent)
	{
		//先完成左单旋基本规则
		//移动三个主要节点
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		parent->_right = subRL;
		subR->_left = parent;

		//再更新_parent
		if (subRL)
		{
			//为空就不用给了
			subRL->_parent = parent;
		}
		Node* ppnode = parent->_parent;
		parent->_parent = subR;
		//若subR左旋后成为根节点
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		//若subR左旋后不会成为根节点
		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;
		subL->_right = parent;

		//再更新_parent
		if (subLR)
		{
			//为空就不用给了
			subLR->_parent = parent;
		}
		Node* ppnode = parent->_parent;
		parent->_parent = subL;
		//若subL右旋后成为根节点
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		//若subL右旋后不会成为根节点
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << endl;
		_InOrder(root->_right);
	}

	void InOrder()
	{
		_InOrder(_root);
	}

	bool Check(Node* cur,int RedBlackNum,int BlackNum)
	{
		
		if (cur == nullptr)
		{
			if (RedBlackNum != BlackNum)
			{
				cout << "黑色节点不足" << endl;
				return false;
			}
			cout << BlackNum << endl;
			return true;
		}
			
		//检查是否存在连续的红色节点
		if (cur->_col == RED && cur->_parent->_col == RED)
		{
			cout << cur->_kv.first << "存在连续的红色节点" << endl;
			return false;
		}
		if (cur->_col == BLACK)
		{
			BlackNum++;
		}

		return Check(cur->_left,RedBlackNum, BlackNum)
			&& Check(cur->_right, RedBlackNum, BlackNum);
	}

	bool IsBalance()
	{	
		//检查根节点是否为黑色
		if (_root && _root->_col == RED)
			return false;
		//检查路径黑节点
		int RedBlackNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				RedBlackNum++;
			}
			cur = cur->_left;
		}
		return Check(_root, RedBlackNum, 0);
	}

	private:
	Node* _root = nullptr;
};






Mymap.h

#pragma once
//Mymap.h
#include"RBTree.h"


namespace lj
{
	template<class K,class V>
	class map
	{
		struct mapk
		{
			const K& operator()(const pair<K, V>& kv)
			{
				//封装一个只获取pair中key值的内部类
				return kv.first;
			}
		};
	public:
		typedef typename RBTree< K, pair<const K, V>, mapk>::iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, mapk>::const_iterator const_iterator;

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

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

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

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

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

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}
	 private:
		//使用底层红黑树封装时:
		RBTree<K,  pair<const K,V>, mapk> _t;
	};
	void maptest1()
	{
		map<int, int> m;
		int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
		for (auto e : a)
		{
			m.insert(make_pair(e, e));
		}
		map<int, int>::iterator it = m.begin();
		while (it != m.end())
		{
			cout << it->first << " "<< it->second<<endl;
			++it;
		}
		cout << endl;
	}
	void maptest2()
	{
		set< int> s;
		int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
		for (auto e : a)
		{
			s.insert(e);
		}
		set<int>::iterator it = s.begin();
		while (it != s.end())
		{
			//*it += 100;
			if (it == s.find(1))
			{
				cout << "找到了" << endl;
				cout << *it << endl;
				break;
			}
			else
			{
				cout << "找不到" << endl;
				break;
			}
			//cout << *it << endl;
			++it;
		}
		cout << endl;
	}
	void maptest3()
	{
		string arr[] = { "哈哈", "嘻嘻", "嘿嘿", "呃呃", "呃呃", "嘻嘻","嘿嘿", "嘿嘿", "哈哈", "嘿嘿", "呃呃", "哈哈" };
		map<string, int> countMap;
		for (auto& e : arr)
		{
			/*if (e == "ݮ")
			{
				int i = 0;
			}*/

			countMap[e]++;
		}

		for (auto& kv : countMap)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
		cout << endl;
	}
}

Myset.h

#pragma once
//Myset.h
#include"RBTree.h"


namespace lj
{
	template<class K>
	class set
	{
		struct setk
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K,const K, setk>::iterator iterator;
		typedef typename RBTree<K, const K, setk>::const_iterator const_iterator;
		iterator begin()
		{
			return _t.begin();
		}

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

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

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

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

		/*bool insert(const K& key)
		{
			return _t.Insert(key);
		}*/
		pair<iterator, bool> insert(const K& key)
		{
			return _t.Insert(key);
		}
		
	private:
		//使用底层红黑树封装时:
		RBTree<K, const K, setk> _t;
	};
	void settest1()
	{
		set< int> s;
		int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
		for (auto e : a)
		{
			s.insert(e);
		}
		set<int>::iterator it = s.begin();
		while (it != s.end())
		{
			//*it += 100;
			if (it == s.find(11))
			{
				cout << "找到了"<<endl;
				cout << *it << endl;
				break;
			}
			else
			{
				cout << "找不到" << endl;
				break;
			}
			//cout << *it << endl;
			++it;
		}
		cout << endl;
	}
}

test.cpp

//test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include <map>
#include <set>

#include "MySet.h"
#include "Mymap.h "


int main()
{
	//lj::maptest1();
	//lj::maptest2();
	lj::maptest3();

	//lj::settest1();


	return 0;
}

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

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

相关文章

LLaMa系列模型详解(原理介绍、代码解读):LLaMa_llama模型

LLaMA详解 LLaMA&#xff08;Large Language Model Meta AI&#xff09;是由Meta&#xff08;前身为Facebook&#xff09;开发的一种大规模语言模型&#xff0c;旨在提高自然语言处理&#xff08;NLP&#xff09;任务的性能。LLaMA基于变换器&#xff08;Transformer&#xff…

只需5分钟!手把手教你安装StableDiffusion,开启AI图像生成新纪元

在这个数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;技术已经深入到我们生活的每一个角落。 特别是在图像生成领域&#xff0c;AI技术的应用不仅极大地提高了创作效率&#xff0c;更开启了艺术创作的新纪元。 今天&#xff0c;我们将聚焦于StableDiffusion这一先…

【数据结构】关于二叉搜索树,你知道如何实现增删模拟吗???(超详解)

前言&#xff1a; &#x1f31f;&#x1f31f;Hello家人们&#xff0c;这期讲解二叉搜索树内部实现基础功能模拟&#xff0c;希望能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;http://t.csdnimg.cn/rfYFW &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a…

从需求到交付:动态敏捷如何确保每一行代码都物超所值

动态敏捷方法论在软件开发中的应用 在当今快速变化的商业环境中&#xff0c;软件开发团队面临着不断变化的需求和市场挑战。传统的瀑布式开发模型已无法满足现代软件开发的灵活性和响应速度需求。动态敏捷&#xff08;Dynamic Agility&#xff09;作为一种新兴的开发方法论&…

introsort的快排跑排序OJ代码

introsort的快排跑排序OJ代码 introsort是introspective sort采⽤了缩写&#xff0c;他的名字其实表达了他的实现思路&#xff0c;他的思路就是进⾏⾃ 我侦测和反省&#xff0c;快排递归深度太深&#xff08;sgi stl中使⽤的是深度为2倍排序元素数量的对数值&#xff09;那就说…

《黑神话:悟空》在未来市场的应用与代码案例分析

作者主页: 知孤云出岫 目录 作者主页:**《黑神话&#xff1a;悟空》在未来市场的应用与代码案例分析****一、引言****二、市场应用场景分析****1. 数据驱动的市场决策****2. 游戏内经济系统的智能优化****3. 个性化推荐系统与用户体验提升** **三、市场推广与用户扩展策略***…

十一:C语言-操作符详解

1.了解二进制 其实二进制&#xff1b;八进制&#xff1b;十进制和十六进制都是数值的不同表示形式而已 二进制&#xff1a;基数为2&#xff0c;由0和1两个数字组成&#xff0c;逢2进1。八进制&#xff1a;基数为8&#xff0c;由0~7八个数字组成&#xff0c;逢8进1。十进制&am…

猫头虎 分享:Python库 SymPy 的简介、安装、用法详解入门教程 ‍

猫头虎 分享&#xff1a;Python库 SymPy 的简介、安装、用法详解入门教程 &#x1f431;‍&#x1f464; 今天猫头虎带您 深入了解 Python库 SymPy&#xff0c;这是一个强大且广泛应用于符号数学计算的库。最近有粉丝问猫哥&#xff1a;如何利用 SymPy 进行数学公式的符号化处…

【Maps JavaScript API】基础地图的创建与实现详解

文章目录 一、概述1. Google Maps JavaScript API 简介2. Simple Map 示例概述 二、创建一个基础地图1. 引入 Google Maps JavaScript API2. 初始化地图(1) 定义地图的 HTML 容器(2) 编写 JavaScript 代码初始化地图 3. 将地图集成到网页中 三、代码分析与关键点1. 地图中心点的…

32 增加系统调用(1)

系统调用在 数据手册中的描述 这是在 GDT 中的描述符 这个系统调用 segment selector 指向的时 内核的代码段。因为系统调用需要的权限比较高。 offset 指的时 在内核代码中的具体的函数的地址。

SQL Server 查询语句中,对索引列做CONVERT的影响

通常&#xff0c;在做SQL Server查询语句优化的时候&#xff0c;如果发现语句对索引列做了函数计算&#xff0c;都会建议改写&#xff0c;将计算的逻辑转移到筛选条件列上。但这种对索引列的计算&#xff0c;有时却会带来一些额外的好处。请看以下的例子&#xff1a; --测试数…

【Linux开发板pip安装库时报错解决】Error 28:No space left on device报错需要更换库的安装路径

之前在Linux开发板上尝试运行pytorch框架&#xff0c;但是需要安装torch和torchvision的库&#xff0c;很奇怪的是我按照之前pip3 install torch -i http://pypi.douban.com/simple --trusted-host pypi.douban.com的安装方式却出现了以下的报错&#xff1a; 系统报错提示说No …

R 语言学习教程,从入门到精通,R 绘图饼图(23)

1、R 绘图 条形图 条形图&#xff0c;也称为柱状图条形图&#xff0c;是一种以长方形的长度为变量的统计图表。 条形图可以是水平或垂直的&#xff0c;每个长方形可以有不同的颜色。 R 语言使用 barplot() 函数来创建条形图&#xff0c;格式如下&#xff1a; barplot(H,xlab,…

FastAPI+React18开发通用后台管理系统用户功能实战

最近开发了一个React18的后台管理系统&#xff0c;登录界面如下&#xff1a; 如果登录成功了则提示并跳转到首页&#xff1a; 点击注销按钮则提示退出系统成功&#xff1a; 没有登录就访问首页则提示请先登录。 这些功能是怎么实现的呢&#xff1f; 先看看登录功能使用…

JNA实践之Java模拟C结构体、结构体指针、结构体数组

目录 1 JNA模拟C结构体1.1 结构体本身作参数1.2 结构体指针作参数1.3 结构体内部嵌套结构体(结构体本身作参数)1.4 结构体指针作参数 2 结构体中嵌套结构体数组2.1 用作输入2.2 用作输出 3 结构体数组作参数典型错误1--内存不连续典型错误2--误用ByValue 4 Java映射C中char[]类…

scrapy--json结构数据-存储

免责声明:本文仅做演示与分享... 目录 基于命令存储的解析方法: settings.py blibli.py 基于管道存储的解析方法: 1-在爬虫文件中进行数据解析 2-在items.py定义相关属性 3-在 爬虫文件中 把 解析的数据存储封装到item类型对象中 4-把item类型对象提交给管道 5-在管道文件中…

软件设计之MySQL(6)

软件设计之MySQL(6) 此篇应在JavaSE之后进行学习: 路线图推荐&#xff1a; 【Java学习路线-极速版】【Java架构师技术图谱】 Navicat可以在软件管家下载 使用navicat连接mysql数据库创建数据库、表、转储sql文件&#xff0c;导入sql数据 MySQL数据库入门到大牛&#xff0c;my…

【吊打面试官系列-Memcached面试题】memcached 能接受的 key 的最大长度是多少?

大家好&#xff0c;我是锋哥。今天分享关于 【memcached 能接受的 key 的最大长度是多少&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; memcached 能接受的 key 的最大长度是多少&#xff1f; key 的最大长度是 250 个字符。需要注意的是&#xff0c;250 是 m…

KEIL中分散加载文件基础知识

一、分散加载文件基本概念 1、分散加载文件&#xff1a;&#xff08;即scatter file 后缀为.scf&#xff09;是一个文本文件&#xff0c;通过编写一个分散加载文件来指定ARM连接器在生成映像文件时如何分配RO,RW,ZI等数据的存放地址。如果不用分散加载文件指定&#xff0c;那么…

区域形态学demo发布

demo实现了halcon中threshold、connection、fill_up、union、difference、intersection、dilation、erosion、opening、closing等算子功能&#xff0c;区域使用行程编码表示。目前可选择的结构元有圆形、矩形、十字&#xff08;实际接口没有限制&#xff09;&#xff0c;所有结…