【C++学习】map和set的封装

news2024/9/30 23:34:33

🐱作者:一只大喵咪1201
🐱专栏:《C++学习》
🔥格言:你只管努力,剩下的交给时间!
图

map和set的封装

  • 🍉map和set中的红黑树
    • 🍌set中的键值和map中的键值
  • 🍉红黑树的迭代器实现
    • 🍌迭代器++
    • 🍌迭代器--
    • 🍌迭代器条件判断(==和!=)
  • 🍉map和set对迭代器的封装
  • 🍉map的operator[]
    • 🍌普通迭代器和const迭代器的转换
  • 🍉multimap和multiset
  • 🍉总结

🍉map和set中的红黑树

我们在学习红黑树的时候,实现的是KV模型,节点中存放的是键值对pair。

  • 而set中的节点只存放一个key值,map中的节点存放的是键值对。
  • 但是map和set却使用的是同一颗红黑树。

这到底是怎么实现的呢?怎么做到一会儿是键值,一会又是键值对的呢?

我们来看一下STL库中是如何实现的:

图

  • map和set中都既有key值,又有数据类型,map中的数据类型是键值对pair<const Key, T>,而set中的数据类型也是key值。
  • STL模板中,红黑树中的数据类型只有一个。

无论是map还是set,底层封装的都是红黑树,区别在于给红黑树实例化的是什么类型的模板参数。

  • map给红黑树传的模板参数是键值对pair<const Key, T>。
  • set给红黑树传的模板参数是键值Key。

对于红黑树而言,它是不知道接收到的第二个模板参数value是什么类型的,它只能推演

所以set对应的红黑树中的数据类型就是一个key值,而map对应的红黑树中的数据类型就是一个键值对。

接下来就是对我们实现的红黑树进行改造:

图

首先就是对节点进行改造,将原本的KV键值对数据类型改成T,像STL中一样,只有一个,让编译器自己去推演这个数据类型是key值还是键值对。

图
红黑树中也不再用键值对去构建新节点,而是使用那一个数据类型T。

图

  • set中,向红黑树传的模板参数是<K,K>,第二个K传给节点,作为节点是数据类型。
  • map中,向红黑树传的模板参数是<K,pair<const K,T>>键值对,第二个参数键值对pair<const K, T>传给节点,作为节点的数据类型。

现在有一个问题,给红黑树传模板参数时,第一个参数K类型的作用是什么?节点中存放的数据第二个参数。

  • 对于insert来说,set和map中都可以不要第一个参数K,因为第二个参数中就有K,可以用来比较。
  • 但是对于find接口来说,它需要的只是K。
  • set中第二个参数也是K,所以第一个K也可以省略。
  • map中第二个参数是一个键值对,如果省略了第一K后,红黑树中只有一个键值对类型,在使用find的时候,无法确定拿到first的数据类型,此时就需要第一个模板参数K来确定find的类型了。

虽然set中可以不需要第一个模板参数K,但是map不可以,因它两使用的一个红黑树,所以为了统一,第一个模板参数K不能省略

🍌set中的键值和map中的键值

站在红黑树的角度,并不知道它接收到的模板参数value是来自set中的键值还是map中的键值对。

在插入节点进行比较时:

  • set:cur->data 与 data进行比较,插入节点中的key值直接和树中的key值比较大小,决定插入左还是右即可。
  • map:cur->data.first 与 data.first进行比较,插入节点中的键值对的first和树种键值对的first比较,决定插入左还是右。

既然使用的是模板,是泛型编程,那么在比较处到底该写成map和set中的哪种比较方式呢?要知道set中的data不是键值对,是没有first的,而map中的data直接比较又不符合我们的要求。

pair提供的比较方式,first和second是都要看的:

  • 当first不相等时first的比较结果就是最终结果。
  • 当first相等时,要再看second的比较结果。

而红黑书中新节点和书中节点比较只要看key值,也就是键值对中的first,而不看second。

此时我们也不能自己重新定义键值对的比较方式,因为人家库中已经有了,我们无法再重载一个函数名,返回值,参数都相同的比较方式。

为了能够在红黑树中使用统一的比较方式,这里采用仿函数的方式:

图

在set和map中各定义一个仿函数,专门用来获取key值的,并且将这个仿函数当作模板参数传给红黑树。

  • set中存放的数据本身就是key,所以获取key时有点多此一举,但是为了和红黑树的结构以及map的结构统一,也需要写一个。
  • map中存放的数据是键值对,所以仿函数返回的是键值对中的first,依次来获取到key值。

图

  • 在插入函数inset中,创建仿函数对象koft。
  • 在需要进行键值key比较的位置,使用仿函数koft获取键值进行比较,然后决定插入左边函数右边。

使用仿函数的方法,压根就不用关心比较的是键值还是键值对,因为set和map都会给红黑树传它自己获取键值的仿函数,最终比较的都是键值

图

  • set和map中的插入直接复用红黑树中的插入即可。
  • 但是set中插入的是一个键值,而map中插入的是一个键值对。

使用仿函数去取键值这一思路非常值得借鉴

🍉红黑树的迭代器实现

map和set的迭代器可以参考list的迭代器,红黑树也是通过指针来链接的。

template <class T>
struct _RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef _RBTreeIterator<T> Self;
	Node* _node;
	//构造函数
	_RBTreeIterator(Node* node)
		:_node(node)
	{}
};

最基本的迭代如上面所示代码,迭代器中只有一个成员变量,那就是节点node。

下面就是逐渐完善迭代器支持的功能了,比如解引用,++,–等操作。

解引用和箭头:

图

这里和链表是一样的,就不再详细讲解了。

  • 解引用返回的是节点中的数据data,箭头返回的是节点中数据的data的地址。

🍌迭代器++

set和map迭代器的++按照中序遍历的顺序进行加加的。所以要时刻铭记中序遍历的顺序:左子树 根 右子树

右子树存在:

图

假设现在it在根节点,如上图所示。

  • 当++it以后,it指向的是右子树中的最左节点,如上图所示。
//++it
	Self& operator++()
	{
		//右子树存在
		if (_node->_right)
		{
			//寻找右子树中的最左节点
			Node* MinLeft = _node->_right;
			while (MinLeft->_left)
			{
				MinLeft = MinLeft->_left;
			}
			_node = MinLeft;//找到最左边的最小节点
		}
		return *this;
	}
  • 将当前it指向节点的有子节点开始,一直寻找最左节点。
  • 找到后,让it指向最左节点。

右子树不存在:

图

it处于上图所示位置,位于子树的最右边,当++it后,it会指向哪呢?it的右子树为空,肯定不能像上面那样找右子树最左边的节点。

  • it是parent的右子树,说明父节点parent已经被访问过了,所以还需要继续向上走。
  • parent又是grandfather的右子树,说明祖父节点grandfather也被访问过了,所以还需要继续向上走。
  • grandfather是它父节点的左子树,按照中序遍历的顺序,grandfather的父节点还没有被访问,所以it应该指向这里,也就是grandfather->parent节点。

当it右子树不存在时,++it后,it指向的是it所在子树是左子树的最近祖宗节点

图

  • cur是从it开始向上跟新的,cur更新的同时,它的parent也在更新。
  • 当cur是parent是左子树时,让it指向parent即可。

那如果it是最后一个节点呢?

图

  • 当it指向的是红黑树最右边的节点时,再++it后,it应该指向最后一个节点的下一个节点。
  • 但是红黑树最后一个节点的下一个节点并没有,所以我们让it指向nullptr。
//右子树不存在
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			//寻找it是左子树的最近祖宗节点
			while (parent && cur == parent->_right)
			{
				//cur和parent同时更新
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
  • 在代码中,无论是找到了++it后的位置,还是it是最后一个节点,都会跳出循环,将it指向跳出循环的parent即可。

再来实现一下后置++:

图

后置++和前置++的唯一不同就是返回的是++之前的位置,其他操作都一样,所以在改变it指向的位置之前,需要提前记录下要返回的it。

  • 后置++返回类型不能用引用,因为记录位置的临时变量会销毁。

🍌迭代器–

迭代器减减的逻辑和加加是相反的,所以它的顺序应该是:右子树 根 左子树

左子树存在:

图
当左子树存在时,it减减后,应该指向的是左子树最右边的节点,如上图所示。

//--it
	Self& operator--()
	{
		//左子树存在
		if (_node->_left)
		{
			//寻找左子树最右边节点
			Node* MaxRight = _node->_left;
			while (MaxRight->_right)
			{
				MaxRight = MaxRight->_right;
			}
			_node = MaxRight;//it指向左子树最右节点
		}
		return *this;
	}

只是逻辑和++相反,本喵就不详细解释了。

左子树不存在:
图

  • it是左子树,说明它的根节点就已经被访问过来,所以需要继续向上。
  • 当找到it所在子树是右子树的最近祖宗时,将it指向这个祖宗节点。

因为是–,逻辑相反,所以此时减减it后,it指向it所在子树是右子树的最近祖宗节点,同样,当it指向是第一个节点时,减减it会指向空节点。

//左子树不存在
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}

再实现一下后置–:

//it--
	Self operator--(int)
	{
		//记录返回节点
		Self ret = Self(_node);
		//左子树存在
		if (_node->_left)
		{
			//寻找左子树最右边节点
			Node* MaxRight = _node->_left;
			while (MaxRight->_right)
			{
				MaxRight = MaxRight->_right;
			}
			_node = MaxRight;//it指向左子树最右节点
		}
		//左子树不存在
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return ret;
	}

同样,需要返回的是减减之前的节点,所以需要先记录下来。

🍌迭代器条件判断(==和!=)

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

无论是普通对象还是const对象,都可以调用const版本,并且仅仅是进行比较,所以只有const版本的就够用了。

  • 比较的内容是两个迭代器指向节点是否相同,而不是节点中的值。

迭代器写好之后,还要在红黑书中封装它,因为我们都是通过红黑树来使用的。

图
红黑树提供两种版本的迭代器共map和set使用。

  • 为了达到迭代器区间是左闭右开[begin,end),迭代器的起始位置一定是红黑树最左边的节点,也就是第一个,结束位置这里设为空节点。
  • 当是一颗空树时,起始位置也是空。

图
为了实现左闭右开,在STL库中采用上图所示方式,在根节点之前再加一个哨兵位头节点。

  • end指向的是哨兵位头节点,红黑树最左边的节点直接和这个头节点相连。
  • 红黑树的根也可以通过哨兵位头节点找到,从而进入树中。

有兴趣的小伙伴可以自行去尝试一下这种方式,本喵就不在这里介绍了。

🍉map和set对迭代器的封装

底层的迭代器做好了,下一步就需要把它封装到set和map中:

图

  • 我们知道,红黑树以及AVL树等二叉搜索树是不支持修改节点中的key值的,因为会破坏树的结构。
  • set中节点中存放的是key值,所以必然不能被修改,所以它的iterator和const_iterator的底层都是迭代器的const版本,保证set中的数据不能被修改。
  • map中节点存放的是键值对,键值对中的key值不可以被修改,但是另一个值可以被修改。键值对的first的类型是const K类型,已经保证了key值不会被修改。所以map提供了普通和const两种迭代器,而且它们的底层也是不一样的,供不同清来使用。

在使用typedef的时候,还需要注意一个点:

typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;

必须得加关键字typename

  • 当模板类没有进行实例化时,它就是一张图纸,在编译的时候并不参与编译。
  • 因为域作用限定符::的存在,编译器在处理这条语句的时候,可能会将::后的iterator当作静态变量处理,参与编译。
  • 所以就需要加关键字typename来告诉编译器这是一个模板类型,暂时不参与编译。

图
像这种已经实例化后的模板类就不用再加typename去表面自己是一个类型了。

tu
此时我们就可以使用实现的迭代器进行打印了,如上图所示,无论是set和map,都符合我们的预期。

🍉map的operator[]

map有一个特有的[],可以实现查找,插入,修改三个功能,下面来实现一下。

//operator[]
		T& operator[](const K& key)
		{
			//返回键值对,一个是插入数据所在位置的迭代器
			//一个是bool值,原本存在返回false,不存在则插入并返回true
			pair<iterator, bool> ret = _t.insert(make_pair(key,T()));
			return ret.first->second;
		}

代码看起来很简单,但是存在问题

图
继续用之前的代码来统计水果个数.

图
运行时出现的第一个错误就是上图所示的错误.

  • 我们在operator中,使用insert返回的是一个键值对<iterator,bool>,而红黑树底层中的insert返回的是一个bool值.

所以需要对红黑树底层中的inset做修改:

图

  • 键值对<iterator, bool>:迭代器是新插入节点的迭代器,bool值表示插入是否成功.
  • 如果新节点原本就存在,则返回原本存在节点的迭代器,并且插入失败,返回false.
  • 如果新节点不存在,则插入,返回新插入节点在树中的迭代器,并且返回true.

图
红黑树底层的inset已经被修改了,set和map中的insert也需要被修改,如上图所示.

🍌普通迭代器和const迭代器的转换

图
但是此时又出现了一个问题,出现在set里面,如上图所示.

  • 红黑树的insert返回的是<iterator, bool>,迭代器是普通迭代器.
  • 但是set中的迭代器为了防止key值被修改,iterator和const_iterator都封装的是红黑树的const_iterator.

所以会报无法从普通迭代器转换到const迭代器的错误.

补充知识:

  • 类型之间的转换不是任意类型都可以进行转换的,只有强相关的类型才能相互转换.
  • 比如不同类型的指针变量,它们就可以相互转换,因为不管怎么转换还是指针.
  • 再比如整形家族的不同类型,再怎么转换也还是整数.

普通迭代器和const迭代器是完全不同的两个类型,不能相互转换.

那这个问题怎么处理呢?

图
在迭代器类模板中作上图所示处理就可以解决了,这一点非常的秒.

  • 在set中,红黑树底层的insert返回键值对中的迭代器是普通迭代器.
  • 而set中insert返回键值对中的迭代器是const迭代器.
  • set中调用insert相当于用红黑树底层的普通迭代器构造set中的const迭代器.
  • 迭代器中的拷贝构造函数就是名副其实的构造函数,它的形参是用const修饰的普通迭代器(因为上面typedef了iterator),此时接收来自红黑树底层的普通迭代器是在权限缩小,是被允许的.
  • 再用接收到的普通迭代器的值去构造const迭代器,虽然不能直接转换,但是普通迭代器的值const迭代器还是可以使用的.

解释:

  • const iteraror的本质是:const _RBTreeIterator<T, T&, T*>
  • const_iterator的本质是:_RBTreeIterator<T, const T&, const T*>

const所在位置是完全不一样的,所以它们是完全不同的两个类型.

  • 如果是在map中,它的insert返回的迭代器就是普通迭代器,此时迭代器中的拷贝构造函数就是拷贝构造,拷贝从红黑树底层insert返回来的迭代器到map的迭代器.
typedef __RBTreeIterator<T, T&, T*> iterator;

如果没有这个重命名,拷贝构造函数的形参类型就只能用Self,而

typedef __RBTreeIterator<T, Ref, Ptr> iterator;

当set使用insert的时候,Ref和Ptr就已经被推演成了const T& 和 const T*,是一个const迭代器,所以在接收红黑树底层的普通迭代器时也会发生类型无法转换的错误.

  • 而使用了重命名后的iterator,无论是set还是map调用它们自己的insert,拷贝构造的形参都是普通迭代器,都可以正常接收红黑底层的普通迭代器.

拷贝构造函数在将普通迭代器转换成const迭代器的时候,充电的是构造函数的角色.

图
此时便可以成功统计出水果的个数了.

🍉multimap和multiset

multimap和multiset和与map和set的唯一区别就是运行出现重复的节点,这一点在学习使用的时候就详细讲解过.

图
在源码中,红黑树底层的插入方式有两种,如上图所示.map和set就使用的insert_unique,而multimap和multiset使用的就是insert_equal.

那么允许重复插入时,相等的key应该插入左边还是右边呢?其实都一样:

图

  • 无论是插入左边还是右边,都会发生旋转,旋转之后就都一样了.

至于具体的代码实现本喵就不再写了,有兴趣的小伙伴可以自己去尝试.

🍉总结

再说一次,模拟实现容器并不是为了造更好的轮子,而是为了更好的了解这个容器已经一些好的方法.
比如这篇文章中,使用仿函数获取key值进行比较,中序遍历顺序指针的移动,已经使用构造函数将普通迭代器转换成const迭代器的方法,都值得我们学习借鉴.

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

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

相关文章

CTF杂项提纲

CTF的杂项是涉及编码&#xff0c;图片隐写&#xff0c;音频隐写&#xff0c;压缩包分析的方向&#xff0c;本文对MISC的知识点做了一个简单列举 常见编码 ASCII 0-9,48-57 A-Z 65-90 a-z 97-122 URL url编码又叫百分号编码&#xff0c;是统一资源定位的编码方式 base16/…

ModStartCMS v6.2.0 VIP权益配置功能,界面UI优化升级

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议&#xff0c;免费且不限制商业使用。 功能特性 丰富的模块市…

【初识数据库】数据库简介及MySQL安装

目录 数据库基本概念&#xff1a; 主流的关系型数据库&#xff1a; 下载并安装数据库 我们选择的是&#xff1a;MySQL Community Server 8.0.26 卸载老版本MySQL 数据库基本概念&#xff1a; 数据库&#xff1a;是数据的仓库&#xff0c;存储数据的地方 数据库管理系统&am…

RK3568平台开发系列讲解(调试篇)debugfs 分析手段

🚀返回专栏总目录 文章目录 一、enable debugfs二、debugfs API三、使用示例沉淀、分享、成长,让自己和他人都能有所收获!😄 📢Linux 上有一些典型的问题分析手段,从这些基本的分析方法入手,你可以一步步判断出问题根因。这些分析手段,可以简单地归纳为下图: 从这…

进程虚拟地址空间的划分

进程虚拟地址空间划分是操作系统中的一个核心概念&#xff0c;它决定了进程可以访问的内存范围和方式。在本文中&#xff0c;我们将介绍进程虚拟地址空间的划分方法和各个部分的作用。 进程虚拟地址空间的划分方法 在操作系统中&#xff0c;每个进程都有自己的虚拟地址空间&am…

到底还是留不住!库克“中国行”后火速变脸,3000亿产能转向印度?

以国家的层面来制衡一家科技企业&#xff0c;这种事或许只有老美干得出来。当看到华为即将崛起之时&#xff0c;美一意孤行&#xff0c;修改了大量关乎芯片和商贸的政策。层层围堵之下&#xff0c;华为如今的市场份额大幅缩水&#xff0c;腾出来的高端市场基本被苹果占据。相关…

2023年数据挖掘与知识发现国际会议(DMKD 2023) | IOP JPCS独立出版

会议简介 Brief Introduction 2023年数据挖掘与知识发现国际会议(DMKD 2023) 会议时间&#xff1a;2023年6月24日-26日 召开地点&#xff1a;中国重庆 大会官网&#xff1a;DMKD 2023-2023 International Conference on Data Mining and Knowledge Discovery 由重庆邮电大学、重…

找工作吗?50道Python面试题集锦【附答案】

嗨害大家好鸭&#xff01;我是爱摸鱼的芝士~ 希望能够帮助你在今年的求职面试中脱颖而出&#xff0c; 找到一份高薪工作~ 这些面试题涉及Python基础知识、Python编程、数据分析以及Python函数库等多个方面。 提前预祝给这篇文章点赞收藏的友友们~ 拿到心中最满意的那一份OF…

PHP快速入门13-MySQL数据库与Redis操作

文章目录前言一、PHP连接MySQL1.1 建立数据库链接1.2 插入数据1.3 更新数据1.4 删除数据1.5 查询数据并输出结果1.6 查询数据并返回结果1.7 获取查询结果的行数1.8 获取查询结果的列数1.9 获取查询结果的字段名1.10 获取查询结果的字段类型1.11 获取上一次操作影响的行数1.12 开…

PsExec流量分析

PsExec远程连接服务器 psexec是sysinternals提供的众多windows工具中的一个&#xff0c;这款工具的初衷是帮助管理员管理大量的机器的&#xff0c;后来被攻击者用来做横向渗透。 下载地址&#xff1a; https://docs.microsoft.com/en-us/sysinternals/downloads/psexec使用Ps…

d2l 里面GRU与Lstm实现

此二者的本质都是对rnn进行改良&#xff1a;关注当前多还是关注之前多。 在此详细讲一下。 目录 1.GRU门循环控制单元 1.1理论&#xff1a; 1.2初始化参数 1.3定义网络 1.4训练命令行 1.5简洁实现 2.Lstm长短期记忆网络 2.1理论 2.2加载参数 2.3定义lstm计算 2.4定义…

iTOP4412开发板Qt程序打包和部署

因为我们要把写好的程序发给用户来用&#xff0c;写好的源码也不方便给别人看&#xff0c;所以要把程序进行打包部署。 步骤一&#xff1a;点击左下角的电脑图标将 Debug 模式切换到 Release 模式。 release 模式&#xff1a;发布版本&#xff0c;不对源代码进行调试&#xff…

微信小程序:表格中更改输入框的值,实时获取表格全部数据,点击按钮更改数据库指定项数据

样例&#xff1a; 样式展示 数据库中原始第一条数据 修改表格第一行的数量&#xff1a; 数据库结果 核心代码 wxml ①wx:for:执行循环将数组数据展示出来 ②在某一单元格加上input样式 ③在input中绑定&#xff1a;文本框改变事件&#xff0c;并且绑定data-index便于知道…

网络编程,IO流

网络编程 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备&#xff0c;通过通信线路连接起来&#xff0c;在网络操作系统&#xff0c;网络管理软件及网络通信协议的管理和协调下&#xff0c;实现资源共享和信息传递的计算机系统。 1.网络通信的要素 通信…

程序环境和预处理(下)——“C”

各位CSDN的uu们你们好呀&#xff0c;今天小雅兰的内容是程序环境和预处理的下篇知识点&#xff0c;那么&#xff0c;这篇博客写完后&#xff0c;C语言的知识点就到这里就结束啦&#xff0c;后续会专注于刷题和读书&#xff0c;也是关于C语言的&#xff0c;会写一些数据结构和C的…

本地Linux服务器安装宝塔面板,并内网穿透实现公网远程登录

文章目录前言1. 安装宝塔2. 安装cpolar内网穿透3. 远程访问宝塔4. 固定http地址5. 配置二级子域名6. 测试访问二级子域名转发自CSDN远程穿透的文章&#xff1a;Linux安装宝塔&#xff0c;并实现公网远程登录宝塔面板【内网穿透】 前言 宝塔面板作为建站运维工具&#xff0c;它…

尚融宝17-用户身份认证的三种模式

目录 1、单一服务器模式 2、SSO&#xff08;Single Sign On&#xff09;模式 3、Token模式 1、单一服务器模式 即只有一个服务器&#xff0c;用户通过输入账户和密码&#xff0c;提交表单后服务器拿到前端发送过来的数据查询数据库是否存在该用户&#xff0c;其一般流程如下…

【分享】体验微软Bing在线绘图功能

哈喽&#xff0c;大家好&#xff0c;我是木易巷~ 木易巷体验了一下子微软Bing在线绘图功能&#xff0c;快来看看吧~ 简单介绍 New Bing 不了解或者没有注册New Bing的小伙伴可以看看这一篇&#xff1a; 【教程】你现在还不知道微软的New Bing&#xff1f;你out了&#xff0…

【NestJs】使用MySQL关联查询

上一篇文章介绍了NestJs使用MySQL创建多个实体&#xff0c;接下来讲到的则是实体创建完毕之后&#xff0c;开始进行查询。里面可能涉及到nestjs使用语法&#xff0c;要是不知道的小伙伴可以先行了解&#xff0c;也可以模仿写&#xff0c;后面我会继续出nestjs的教程。也欢迎大家…

SpringMVC的基本使用-------基本注解RequestMapping、基本数据类型绑定、参数绑定、POJO类型绑定

SpringMVC的三层架构和MVC SpringMVC简介 三层架构概述&#xff1a; 一种是 C/S 架构&#xff0c;也就是客户端/服务器&#xff0c;另一种是 B/S 架构&#xff0c;也就是浏览器服务器。在 JavaEE 开发中&#xff0c;几乎全都是基于 B/S 架构的开发。那么在 B/S 架构中&#…