STL之set和map

news2024/11/19 15:34:53

目录

  • 一. 原型
  • 二. 模板参数适配
  • 三. 迭代器
  • 四. 插入函数的修改
  • 四. 代码

一. 原型

  • 简单实现的红黑树
template<class K, class V>
struct RBTreeNode
{
    RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
    
	pair<K, V> _kv;
	Colour _col;
};

template<class K, class V>
struct RBTree
{
    typedef RBTreeNode<K, V> Node;
public:
    bool Insert(const pair<K, V>& kv);
    Node* Find(const K& key); 
private:
	Node* _root = nullptr;
};
  • set
template<class K>
class set
{
private:
    RBTree<K, K> _t;
};
  • map
template<class K, class V>
class map
{
private:
    RBTree<K, V> _t;
};

二. 模板参数适配

  • 红黑树
template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	T _data;
	Colour _col;
};

template<class K, class T, class KeyOfT>
struct RBTree
{
    typedef RBTreeNode<T> Node;
public:
    bool Insert(const T& data);
    Node* Find(const K& key);     
private:
	Node* _root = nullptr;
}
  • set
template<class K>
class set
{
    //仿函数
    struct SetKeyOfT//返回K对象的key
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
private:
    RBTree<K, K, SetKeyOfT> _t;
};
  • map
template<class K, class V>
class map
{
	struct MapKeyOfT//返回pair<K,V>对象的key
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
private:
    RBTree<K, pair<K,V>, MapKeyOfT> _t;
};

在这里插入图片描述

红黑树作为底层结构而言,它不能够区分使用者是set还是map。

class K:表示键值类型

class T:表示存储类型

class KeyOfT:仿函数,用于从T类型中提取K键值

对于set容器,底层存储K类型元素,键值也为K

对于map容器,底层存储<K,V>键值对,键值为K

三. 迭代器

设计迭代器,方便遍历,让用户在使用时不需要在意底层实现。

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

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

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

template<class K, class T, class KeyOfT>
struct RBTree
{
	typedef __RBTreeIterator<T, T&, T*> iterator;
public:
    // 最左节点
    iterator begin()
	{
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}

		return iterator(left);
	}
	// 最后一个节点的下一个位置
	iterator end()
	{
		return iterator(nullptr);
	}
};

在这里插入图片描述

  1. operator++()

步骤:

  • 右子树不为空,++就是找右子树中序第一个(最左节点)
  • 右子树为空,++找孩子不是父亲右的那个祖先
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 (parent && cur == parent->_right)
        {
            cur = cur->_parent;
            parent = parent->_parent;
        }

        _node = parent;
    }

    return *this;
}
  1. operator--()

与++相反,步骤:

  • 左子树不为空,–就是找左子树的最右节点
  • 左子树为空,–找孩子不是父亲的左的那个祖先
Self& operator--()
{
    if (_node->_left)
    {
        // 下一个是左子树的最右节点
        Node* right = _node->_left;
        while (right->_right)
        {
            right = right->_right;
        }

        _node = right;
    }
    else
    {
        // 孩子不是父亲的左的那个祖先
        Node* parent = _node->_parent;
        Node* cur = _node;
        while (parent && cur == parent->_left)
        {
            cur = cur->_parent;
            parent = parent->_parent;
        }

        _node = parent;
    }

    return *this;
}

四. 插入函数的修改

pair<iterator, bool> Insert(const T& data)
{
    KeyOfT kot;//仿函数,获取键值

    if (_root == nullptr)//空树,让新节点为根节点
    {
        _root = new Node(data);
        _root->_col = BLACK;
        return make_pair(iterator(_root), true);;
    }

    //插入新节点
    Node* cur = _root;//当前节点
    Node* parent = nullptr;//cur的父节点
    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), false);;
        }
    }

    cur = new Node(data);
    Node* newnode = cur;//标记新节的

    if (kot(parent->_data) < kot(data))//新节点的键值大于parent
    {
        parent->_right = cur;//放在parent的右边
    }
    else
    {
        parent->_left = cur;//左边
    }
    cur->_parent = parent;

    // 调整
    //(cur最差会遍历到根节点)p为红色
    while (parent && parent->_col == RED)
    {
        Node* grandfater = parent->_parent;//parent的父节点

        // 关键看叔叔
        if (parent == grandfater->_left)//p是g的左孩子
        {
            Node* uncle = grandfater->_right;//叔叔节点

            // 情况一 : u为红,
            if (uncle && uncle->_col == RED)
            {
                // 变色
                parent->_col = uncle->_col = BLACK;
                grandfater->_col = RED;
                // 继续往上处理
                cur = grandfater;
                parent = cur->_parent;
            }
            else// 情况二:u为黑
            {
                if (cur == parent->_left)//c为p的左孩子
                {
                    // 右单旋+变色
                    //     g 
                    //   p   u
                    // c
                    RotateR(grandfater);//右旋
                    parent->_col = BLACK;
                    grandfater->_col = RED;
                }
                else//c为p的右孩子
                {
                    //  左右单旋+变色
                    //     g 
                    //   p   u
                    //     c
                    RotateL(parent);//左旋
                    RotateR(grandfater);//右旋
                    cur->_col = BLACK;
                    grandfater->_col = RED;
                }
                break;//结束
            }
        }
        else // (parent == grandfater->_right) //p是g的右孩子
        {
            Node* uncle = grandfater->_left;//叔叔节点

            // 情况一 : u为红,
            if (uncle && uncle->_col == RED)
            {
                // 变色
                parent->_col = uncle->_col = BLACK;
                grandfater->_col = RED;
                // 继续往上处理
                cur = grandfater;
                parent = cur->_parent;
            }
            else//情况二 : u为黑,
            {
                if (cur == parent->_right)//c为p的右孩子
                {
                    // 左单旋+变色
                    //     g 
                    //   u   p
                    //         c
                    RotateL(grandfater);//左旋
                    parent->_col = BLACK;
                    grandfater->_col = RED;
                }
                else// c为p的左孩子
                {
                    // 情况三:右左单旋+变色
                    //     g 
                    //   u   p
                    //     c
                    RotateR(parent);//右旋
                    RotateL(grandfater);//左旋
                    cur->_col = BLACK;
                    grandfater->_col = RED;
                }
                break;//结束
            }
        }
    }
    _root->_col = BLACK;//根节点颜色为黑色(可能在情况一下被修改)

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

将Insert()的返回值改为pair<iterator, bool>的形式,主要是为了实现map中的operator[]

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

四. 代码

  • 红黑树
#pragma once
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)
	{}

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

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

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

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

	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 (parent && cur == parent->_right)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	Self& operator--()
	{
		if (_node->_left)
		{
			// 下一个是左子树的最右节点
			Node* right = _node->_left;
			while (right->_right)
			{
				right = right->_right;
			}

			_node = right;
		}
		else
		{
			// 孩子不是父亲的左的那个祖先
			Node* parent = _node->_parent;
			Node* cur = _node;
			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}
};

template<class K, class T, class KeyOfT>
struct RBTree
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, T&, T*> iterator;

public:

	iterator begin()
	{
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}

		return iterator(left);
	}

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

	pair<iterator, bool> Insert(const T& data)
	{
		KeyOfT kot;

		if (_root == nullptr)//空树,让新节点为根节点
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true);;
		}

		//插入新节点
		Node* cur = _root;//当前节点
		Node* parent = nullptr;//cur的父节点
		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), false);;
			}
		}

		cur = new Node(data);
		Node* newnode = cur;//标记新节的

		if (kot(parent->_data) < kot(data))//新节点的键值大于parent
		{
			parent->_right = cur;//放在parent的右边
		}
		else
		{
			parent->_left = cur;//左边
		}
		cur->_parent = parent;

		// 调整
		//(cur最差会遍历到根节点)p为红色
		while (parent && parent->_col == RED)
		{
			Node* grandfater = parent->_parent;//parent的父节点

			// 关键看叔叔
			if (parent == grandfater->_left)//p是g的左孩子
			{
				Node* uncle = grandfater->_right;//叔叔节点

				// 情况一 : u为红,
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;
					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}
				else// 情况二:u为黑
				{
					if (cur == parent->_left)//c为p的左孩子
					{
						// 右单旋+变色
						//     g 
						//   p   u
						// c
						RotateR(grandfater);//右旋
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else//c为p的右孩子
					{
						//  左右单旋+变色
						//     g 
						//   p   u
						//     c
						RotateL(parent);//左旋
						RotateR(grandfater);//右旋
						cur->_col = BLACK;
						grandfater->_col = RED;
					}
					break;//结束
				}
			}
			else // (parent == grandfater->_right) //p是g的右孩子
			{
				Node* uncle = grandfater->_left;//叔叔节点

				// 情况一 : u为红,
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;
					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}
				else//情况二 : u为黑,
				{
					if (cur == parent->_right)//c为p的右孩子
					{
						// 左单旋+变色
						//     g 
						//   u   p
						//         c
						RotateL(grandfater);//左旋
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else// c为p的左孩子
					{
						// 情况三:右左单旋+变色
						//     g 
						//   u   p
						//     c
						RotateR(parent);//右旋
						RotateL(grandfater);//左旋
						cur->_col = BLACK;
						grandfater->_col = RED;
					}
					break;//结束
				}
			}
		}
		_root->_col = BLACK;//根节点颜色为黑色(可能在情况一下被修改)

		return make_pair(iterator(newnode), true);
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool IsRBTree()
	{
		if (_root == nullptr)
		{
			return true;
		}

		if (_root->_col == RED)
		{
			cout << "根节点不是黑色" << endl;
			return false;
		}

		int benchmark = 0;// 黑色节点数量基准值

		return PrevCheck(_root, 0, benchmark);
	}

private:
	// 休息21:18继续
	bool PrevCheck(Node* root, int blackNum, int& benchmark)
	{
		if (root == nullptr)//路径走完,到空节点
		{
			//benchark更新为第一条路径上黑色节点的数量
			if (benchmark == 0)
			{
				benchmark = blackNum;
				return true;
			}

			if (blackNum != benchmark)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}
			else
			{
				return true;
			}
		}

		if (root->_col == BLACK)
		{
			++blackNum;
		}

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		return PrevCheck(root->_left, blackNum, benchmark)
			&& PrevCheck(root->_right, blackNum, benchmark);
	}

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

		_InOrder(root->_left);
		cout << kot(root->_data) << ":" << root->_data << endl;
		_InOrder(root->_right);
	}

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

			subL->_parent = ppNode;
		}

	}

private:
	Node* _root = nullptr;
};
  • set
#pragma once

#include "RBTree.h"

template<class K>
class set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	typedef typename RBTree<K, K, SetKeyOfT>::iterator 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;
};
  • map
#pragma once

#include "RBTree.h"

template<class K, class V>
class map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;

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

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

	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<K, V>, MapKeyOfT> _t;
};

🦀🦀观看~~

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

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

相关文章

FPGA_学习_11_IP核_RAM_乒乓操作

本篇博客学习另一个IP核&#xff0c;RAM。 用RAM实现什么功能呢&#xff1f; 实现乒乓操作。 乒乓操作是什么呢&#xff1f; 参考&#xff1a; FPGA中的乒乓操作思想_fpga中乒乓操作的原因_小林家的龙小年的博客-CSDN博客 何为乒乓操作_fanyuandrj的博客-CSDN博客 以下是本人理…

Clion开发STM32之日志模块(参考RT-Thread)

前言 日志对于开发和排错方面有着很重要的影响。通过查看RT-Thread的源码&#xff0c;将日志的打印输出划分到具体的文件和通过宏定义对具体的日志等级进行划分&#xff0c;这样就比较方便。结合此源码的形式将其分离出来&#xff0c;作为自己项目的日志框架进行使用分为日志驱…

crossover软件下载2023最新版虚拟机

在Mac系统中一直存在一个比较令用户们头疼的问题&#xff0c;那就是安装不了想要的Windows软件。如果使用的第一台电脑就是MacBook那接触到的Windows软件想必并不是很多。但我们中的大多数人都是从小先学习了Windows的操作系统&#xff0c;再过渡到Mac系统上的。 那有小伙伴会…

最新基于MATLAB 2023a的机器学习、深度学习实践应用

MATLAB 2023版的深度学习工具箱&#xff0c;提供了完整的工具链&#xff0c;使您能够在一个集成的环境中进行深度学习的建模、训练和部署。与Python相比&#xff0c;MATLAB的语法简洁、易于上手&#xff0c;无需繁琐的配置和安装&#xff0c;能够更快地实现深度学习的任务。 M…

Flink流批一体计算(4):Flink功能模块

目录 Flink功能架构 Flink输入输出 Flink功能架构 Flink是分层架构的分布式计算引擎&#xff0c;每层的实现依赖下层提供的服务&#xff0c;同时提供抽象的接口和服务供上层使用。 Flink 架构可以分为4层&#xff0c;包括Deploy部署层、Core核心层、API层和Library层 部署层…

基于SSM的宠物领养系统的设计与实现

1.引言 动物作为人类的宠物已经有几千年的历史了&#xff0c;尤其是猫和狗因其天性被人类所喜爱和推崇&#xff0c;好多的成语故事、俗语等都及它们有关。但是&#xff0c;近几年来由于生活节奏的加快&#xff0c;人们的压力增大&#xff0c;没有时间和空间去照顾宠物&#xf…

ProGuard 进阶系列(三) Java 类文件解析

书接上文&#xff0c;当我们从用户的配置文件中读取到所有的配置信息后&#xff0c;下一步便是将配置中的指定的类文件进行读取&#xff0c;构建需要混淆的 Java 类文件的语法树。在阅读类文件之前&#xff0c;先来看一下输入输出参数中的内容&#xff0c;我使用的是一个 Andro…

大一下暑期计划 + 2023字节青训营预告直播

目录 &#x1f33c;前言 &#x1f339;后端学习方法 &#x1f333;1&#xff0c;层次 &#x1f333;2&#xff0c;体系 &#x1f333;3&#xff0c;算法和数据结构 &#x1f333;4&#xff0c;总结 &#x1f339;前端学习方法 &#x1f333;基础 &#x1f339;求职中如…

如何在Microsoft Excel中使用LEN函数

如果你曾经想尝试查找一行文本中的字符数&#xff0c;你可以使用Microsoft Excel来查找&#xff0c;这要归功于LEN函数。以下是如何使用它。 一、什么是 LEN 函数 LEN函数是一个简单的计算函数&#xff0c;用于计算给定文本字符串中的所有字符&#xff0c;包括数字、字母、特…

【数据库课设】图书馆资源管理系统 源码+流程图+结构设计(借还图书 逾期罚款 图书管理 读者管理 信息查询)python实现

文章目录 一 实现功能&#xff08;1&#xff09;管理员功能&#xff08;2&#xff09;读者功能 二 数据流图三 概念结构设计四 文件目录五 源码&#xff1a;main.py六 运行截图 一 实现功能 &#xff08;1&#xff09;管理员功能 一个管理员编号对应一个密码&#xff0c;且需…

redis—安装以及可视化

前言 redis 是一种非关系型数据库&#xff0c;什么是非关系型数据库&#xff0c;之前我们在mysql专栏 也有提到过&#xff0c;这边就不再过多的赘述&#xff0c;忘记了的小伙伴可以再次阅读这篇文章 终于明白了数据库的【关系型】与【非关系型】 其实这还是挺重要的&#xff…

ruoyi+vue回显数字的问题,解决方案

在项目中用ruoyi框架和前端vue进行开发&#xff0c; 需求是在前端生成下拉框&#xff0c;下拉框中的内容需要调用后端接口进行数据返回&#xff0c; 现在新增的时候&#xff0c;数据已经返回了&#xff0c;但是再修改的时候&#xff0c;进行回显数据导致前端列表中展示出来的…

城市排水监测系统为城市排水防涝提质增效

城市化进程中&#xff0c;城市排水系统成为城市基础设施建设的重要组成部分。然而&#xff0c;随着气候变化和城市建设规模的扩大&#xff0c;极端天气和内涝风险不断增加&#xff0c;城市的排水系统面临巨大挑战。因此&#xff0c;建立一套智能化城市排水监测系统&#xff0c;…

【python】一些常用的pandas技巧

有了gpt之后&#xff0c;确实很多代码都可以让gpt给改错。嘎嘎香 merge多个dateframe https://stackoverflow.com/questions/44327999/how-to-merge-multiple-dataframes data_net [a,b,c,d] net_merged reduce(lambda left,right: pd.merge(left,right,on[key column],ho…

小程序安全指南:保护用户数据的最佳实践

第一章&#xff1a;引言 近年来&#xff0c;小程序已成为移动应用开发的重要组成部分。它们为用户提供了方便的功能和个性化的体验&#xff0c;然而&#xff0c;与此同时&#xff0c;小程序安全问题也引起了广泛的关注。保护用户数据是开发者应该高度重视的问题。在本指南中&a…

JavaScript ES11新特性

文章目录 导文可选链操作符&#xff08;Optional Chaining Operator&#xff09;空值合并操作符&#xff08;Nullish Coalescing Operator&#xff09;动态 import() 函数BigInt 类型Promise.allSettled() 导文 JavaScript ES11&#xff0c;也被称为ES2020&#xff0c;引入了一…

经纬度、时差知识整理(理解与应用)

经纬度是经度与纬度的合称组成一个坐标系统&#xff0c;称为地理坐标系统&#xff0c;它是一种利用三度空间的球面来定义地球上的空间的球面坐标系统&#xff0c;能够标示地球上的任何一个位置。初一的同学在学地理这门课的时候&#xff0c;一上来很快就会学到经纬度这个概念。…

PC市场寒冬,大众还需要PC吗?

PC市场寒冬&#xff0c;大众还需要PC吗&#xff1f; PC&#xff08;个人电脑&#xff09;市场从2016年智能手机兴起之时便进入下滑态势&#xff0c;到2020年疫情发生后&#xff0c;居家办公、在线教育等需求曾给PC市场带来连续六个季度的增长。⁴ 好景不长&#xff0c;进入202…

mybatisplus分页total总数为0

mybatisplus分页total总数为0 背景&#xff1a;最近初始化新项目时&#xff0c;使用mybatisplus分页功能发现 records 有记录&#xff0c;但是 total 总是为0&#xff0c;于是开启了一顿“知识寻求”之路SpringBoot版本 <parent><groupId>org.springframework.boo…

Makerbase CANable V2.0 固件升级或替换

第1部分 应用软件与固件 应用软件CANable V2.0CANable V1.0cangaroocandleLight/slcan(支持CAN FD)candleLight/slcan/cantactBUSMASTER V3.2.2candleLightcandleLight/pcan/cantactTSMastercandleLightcandleLight/pcan/cantactPCAN-Explorer 5、pcan view不支持pcancantacts…