C++STL之map、set的使用和模拟实现

news2024/12/28 19:05:33

绪论​:
“我这个人走得很慢,但是我从不后退。——亚伯拉罕·林肯”,本章是接上一章搜索二叉树中红黑树的后续文章,若没有看过强烈建议观看,否则后面模拟实现部分很看懂其代码原理。本章主要讲了map、set是如何使用的,以及map、set如何底层实现相较于前面的章节本章更加考验你的逻辑能力来进行封装,还能帮助对大体框架的有更加好的了解。下一章将进入哈希表的学习,同样也是STL中非常重要且难以学习的章节敬请期待(早关注不迷路!)。话不多说安全带系好,发车啦(建议电脑观看)。


1.set

1.1set的概念

set其底层就是红黑树,所以其很多特性也和红黑树一样也就表明了他就是一个二叉搜索树。set看起来只是存着value,但是set是关联式容器容器内部的元素都是pair<key,value>构成的键值对,所以set的value元素其实就是pair<value,value>它两个元素是相同的且只存了一个值value,但我们在使用的时候并不用管底层只需要插入正常的value值即可,并且在set中所有元素值value都是唯一的不能重复出现在set中不能修改元素的值,但可以正常增删元素,同二叉搜索树一样他可以通过迭代器遍历出有序对。
set常用来:去重(去掉一个序列的相同值)、排序、查找。

1.2set常使用的方法

  1. set的模板参数:
    在这里插入图片描述

Compare默认缺省为less也就表示其默认是其底层是 x < y,这样如果用迭代器遍历出来就是一个升序。 x < y 返回真进行交换,反之x>y的话返回假(和vector等若干容器中一样)。

  1. set的构造:
    在这里插入图片描述上图123如何使用对应下图123的具体方法:
    在这里插入图片描述源码:
void test1()
{
	set<int> s1;//无参的构造
	int a[] = { 1,2,3,4,5,6 };
	for (auto ch : a)
	{
		s1.insert(ch);//调用插入函数直接放value值即可
	}
	set<int> s2(s1.begin(), s1.end());//迭代器的构造 , 构造出来和s1一样
	set<int> s3(s2);//拷贝构造,拷贝s2
	for (auto k : s1)
	{
		cout << k << " ";
	}
	cout << endl;
	for (auto k : s2)
	{
		cout << k << " ";
	}
	cout << endl;
	for (auto k : s3)
	{
		cout << k << " ";
	}
	cout << endl;
}
  1. set的插入:
    在这里插入图片描述
    下图123对应着上图123的使用方法:
    在这里插入图片描述
    1. 在方法1中的返回值一个pair<iterator,bool>类型,其中iterator指向插入的节点,bool表示true表示不存在该元素插入成功,false表示已经存在该元素则插入失败。
    2. 在方法2中返回的是新插入节点的迭代器。
      源码:
void Print(const set<int>& s1)
{
	for (auto k : s1)
	{
		cout << k << " ";
	}
	cout << endl;
}
void test2()
{
	set<int> s1;
	int a[] = { 1, 2, 3, 4, 5, 6 };
	for (auto ch : a)
	{
		s1.insert(ch);//直接插入value值
	}
	Print(s1);
	
	s1.insert(s1.begin(), 0);//在指定迭代器位置插入
	Print(s1);

	set<int> s2 = { 7,8,9,10 }; //c++11知识暂时不管
	s1.insert(s2.begin(), s2.end());//迭代器区域插入
	Print(s1);
}
  1. set的删除:在这里插入图片描述
    下图123对应着上图123的使用方法:
    在这里插入图片描述
    1. 在2方法中返回的是删除元素的个数(size_type 就是 size_t 无符号整形)
      源码:
void Print(const set<int>& s1)
{
	cout << "P:";
	for (auto k : s1)
	{
		cout << k << " ";
	}
	cout << endl;
}
void test3()
{
	set<int>s1{ 1,2,3,4,5,6,7,8,9,10 };//c++11知识暂时不管
	Print(s1);

	s1.erase(s1.begin());//迭代器指定删除
	Print(s1);

	s1.erase(10);//删除指定元素
	Print(s1);

	s1.erase(s1.begin(), s1.end());//删除一段迭代器区间
	Print(s1);
}
  1. set的查找find、查看个数size、是否为空empty、是否存在count、清空元素clear:
    在这里插入图片描述
    下面通过代码解释:在这里插入图片描述
    1. find,返回迭代器,如找到则返回找到的元素的迭代器,反之返回nullptr的迭代器
    2. size,返回该容器的元素个数
    3. empty,返回0表示非空,返回1表示空
    4. count,返回1表示存在,返回0表示是不存在

源码:

void test4()
{
	set<int>s1{ 1,2,3,4,5,6,7,8,9,10 };//c++11知识暂时不管

	set<int>::iterator it1 = s1.find(8);
	if (it1 != s1.end())
		cout << "find的返回值(指向该元素):" << *it1 << endl;
	else cout << "返回Nullptr" << endl;
	cout << "8值是否存在(返回1表示存在0表示不存在):" << s1.count(8) << endl;
	cout <<"s1的元素个数(返回元素个数):" << s1.size() << " | s1是否为空(0表示非空反之1为空):" << s1.empty() << endl;

	cout << endl;

	s1.clear();//清空元素
	set<int>::iterator it2 = s1.find(8);
	if(it2 != s1.end())
		cout << "find的返回值(指向该元素):" << *it2 << endl;
	else cout << "返回Nullptr" << endl;
	cout << "8值是否存在(返回1表示存在0表示不存在):" << s1.count(8) << endl;
	cout << "s1的元素个数(返回元素个数):" << s1.size() << " | s1是否为空(0表示非空反之1为空):" << s1.empty() << endl;
}
  1. set的交换swap、指定区间的lower_bound、upper_bound:
    在这里插入图片描述

lower_bound和upper_bound是通过指定数值来确定迭代器区间,常用来指定删除数据的区间,如:1 2 3 4 5 6 , lower_bound(2),upper_bound(5),这样取出来的区间lower_bound指向的就是2(取>=value的值),而upper_bound指向的是6(取>value的值),但因为迭代器区间是左闭右开的所以即使删除也不会删到6!
在这里插入图片描述

源码:

void Print(const set<int>& s1)
{
	cout << "P:";
	for (auto k : s1)
	{
		cout << k << " ";
	}
	c
void test5()
{
	set<int>s1{ 1,2,3,4,5,6 };
	set<int>s2{ 7,8,9,10,11,12 };
	Print(s1);
	Print(s2);

	s1.swap(s2);//s1 和 s2 交换
	Print(s1);
	Print(s2);


	cout << "upper_bound:" << *s1.upper_bound(11) << endl;
	s1.erase(s1.lower_bound(8), s1.upper_bound(11));
	Print(s1);

	cout << "upper_bound:" << *s2.upper_bound(5) << endl;
	s2.erase(s2.lower_bound(2), s2.upper_bound(5));
	Print(s2);
}

1.3 multiset的概念和使用

其原理和set几乎一致,只是multiset能存多个相同的值了
注意点:是find查找时是返回第一个遇到的value,count将返回该值存在的个数
其中还要交代一个函数(set中也有不过不够实用)
在这里插入图片描述
返回的是pair<iterator,iterator>,这两个迭代器分别表示的就是value值的lower_bound和upper_bound,这样就能一次性删除所有相同的元素!在这里插入图片描述

void Print(const multiset<int>& s1)
{
	cout << "P:";
	for (auto k : s1)
	{
		cout << k << " ";
	}
	cout << endl;
}
void test6()
{
	multiset<int> s{ 1,1,2,2,2,3,3 };
	Print(s);

	cout << "lower_bound:" << *s.equal_range(2).first << endl;
	cout << "upper_bound:" << *s.equal_range(2).second << endl;
	s.erase(s.equal_range(2).first, s.equal_range(2).second);
	Print(s);
}

2.map

2.1map的概念

map底层也是红黑树,同理也就表明了他就是一个二叉搜索树。map不同于set他的K,V两个值都是有用的,map也是关联式容器,
1. 容器内部的元素都是pair<K,V>构成的一个个键值对
2. 如果是比较大小的话通常是用key值来进行
3. 在map中所有元素的值都是唯一的(指的是key值不能重复)
4. 在map中不能修改元素的值(即是不能修改K值的,只能修改value值)

2.2map常使用的方法

  1. map的模板参数:
    在这里插入图片描述

同样Compare默认缺省为less

  1. map的构造:
    在这里插入图片描述
    对照着set几乎一样,直接通过代码展示了
    在这里插入图片描述
    注意点:
    1. 范围for打印时给kv的是迭代器,迭代器的类型是pair<K,V>所以kv.first指向Key,kv.second指向value
    2. 插入的数据必须是键值对,用make_pair()函数直接构造

源码:

void Print(const map<int,int>& m1)
{
	cout << "P:";
	for (auto kv : m1)
	{
		cout << kv.first << " " << kv.second << " | ";
	}
	cout << endl;
}
void test1()
{
	map<int, int> m1;//无参的构造
	int a[] = { 1,2,3,4,5,6 };
	for (auto ch : a)
	{
		m1.insert(make_pair(ch, ch));//调用插入函数注意插入的是构造出来的pair,通过make_pair(n1,n2),
		//给定两个参数就能直接构造出对应参数类型的pair(n1,n2)
	}
	map<int, int> m2(m1.begin(), m1.end());//迭代器的构造,构造出来和m1一样
	map<int, int> m3(m2);//拷贝构造,拷贝m2
	Print(m1);
	Print(m2);
	Print(m3);
}
  1. set的插入:在这里插入图片描述在这里插入图片描述
    其中插入函数参数的value_type的实际类型是pair<const K,V>
    注意点:
    1. 用make_pair(key,value)构造出K,V类型的pair<K,V>当参数传递进去,或者写成用pair的构造pair<K,V>(key,value)(具体如下)在这里插入图片描述
      在这里插入图片描述
    2. 返回pair<iterator,bool>。
    3. 若返回的iterator,需要注意的是其类型是pair<K,V>型
map<int, int> m1;
m1.insert(make_pair(1,1));//构造出pair(1,1) 或m1.insert(pair<int,int>(1,1));
  1. map的删除:
    在这里插入图片描述
    同理使用迭代器部分是一样的,此处删除时就不用在使用pair了,直接通过确定key即可删除。
    在这里插入图片描述
void test2()
{
	map<int, int> m1;
	int a[] = { 1,2,3};
	for (auto ch : a)
	{
		m1.insert(make_pair(ch, ch));
	}
	Print(m1);
	m1.erase(1);
	Print(m1);
}
  1. map的查找find、查看个数size、是否为空empty、是否存在count、清空元素clear、交换swap:

此处find、count的参数同样用key即可使用、其余函数和set完全一样。
在这里插入图片描述

  1. map的重载operator[](极其重要)

通过代码和底层来解释:在这里插入图片描述
其中我们operator[]的底层是:
(*((this->insert(make_pair(k,mapped_type()))).first)).second
其中能看到他是调用了插入函数,所以[]能实现插入功能
而我们map的插入返回的类型是:pair<iterator,bool>
所以就能简化为:(*pair<iterator,bool>.first).second//此处.的优先级高于*所以是先访问,得到iterator后,再(*iterator).second得到其迭代器的value值,也就是插入时的第二个参数
所以分析上面代码:m1[ch]++;第一次进去的时候插入成功并且返回来他的second++,这样就能不断的插入,即使后面是插入失败也能返回second进行++,所以就能形成计数器。(其中注意点我们虽然没有初始化int,但其实其内部会默认为0开始,也就是上面的mapped_type()他会自动构造出该类型的初始值,int为0,string为"")

2.3 multimap的概念和使用

与multiset一样,都是可以出现重复的值,只是其中没有了map的operator[ ],就不过诉了。

3.map、set的底层实现

3.1红黑树的修改

为了给set、map做底层,我们需要完善一下红黑树在里面主要是

  1. 模板的改变:将原本第二个参数V改成T,T代表的是K,V组成成的键值对pair<K,V>
  2. 添加迭代器以及begin、end函数,让map、set也能用迭代器
  3. 修改插入的返回值:将原本的iterator改成pair<iterator,bool>,(这是STL源码内的设计,也是为了map的[]做准备)

修改后的源码:

#pragma once
#include<iostream>
using namespace std;
enum Color
{
	BLACK,
	RED
};

template<class T>
struct RBTreeNode {

	RBTreeNode<T>* _left = nullptr;
	RBTreeNode<T>* _right = nullptr;
	RBTreeNode<T>* _parent = nullptr;

	T _data;

	Color _col = RED;//默认生成的节点颜色是红色

	RBTreeNode(const T& data)
		:_data(data)
	{}
};

//迭代器
template<class T, class Ptr, class Ref>
struct _TreeIterator
{
	typedef _TreeIterator<T, Ptr, Ref> Self;
	typedef RBTreeNode<T> Node;
	Node* _node;//迭代器的成员变量

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

	Self& operator++()
	{
		Node* cur = _node;
		if (cur->_right)//若右边不为空,则找到其左边的右边节点
		{
			cur = cur->_right;
			while (cur->_left)
			{
				cur = cur->_left;
			}
			_node = cur;
		}
		else
		{
			Node* parent = cur->_parent;
			while (parent && parent->_left != cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

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

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

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

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

};

template<class K, class T, class Compare>
class RBTree
{
	typedef RBTreeNode<T> Node;
	Compare kot;
public:
	
	typedef _TreeIterator<T,T*,T&> iterator;
	typedef _TreeIterator<T,const T*,const T&> const_iterator;

	//typedef typename RBTree<K, pair<K, V>, Mapofkey>::iterator iterator;

	// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
	// 注意:为了简单起见,本次实现红黑树不存储重复性元素
	iterator begin()
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}
	iterator end()
	{
		return iterator(nullptr);//end指向最后数据的后面故为空
	}

	const_iterator begin() const
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return const_iterator(cur);
	}
	const_iterator end() const
	{
		return const_iterator(nullptr);//end指向最后数据的后面故为空
	}
	//此处用Node* 的原因set处的iterator为了防止被修改所以set内的iterator本质还是const_iterator,
	//所以这里用了 Node* 来代替iterator 的返回

	pair<Node*, bool> Insert(const T& data)
	{
		//此处和AVL平衡二叉树的性质一样找到所要插入节点的位置 大的在右 、 小的在左
		Node* parent = nullptr;
		Node* cur = _root;
		
		if (cur == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(_root, true);
		}

		//找到插入的位置!
		while (cur)//当为null时表示此处就是要插入的位置!
		{
			if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return make_pair(nullptr, false);
			}
		}
		//找到位置后,插入
		cur = new Node(data);//建立新节点
		Node* ret = cur;
		//建立链接
		if (kot(parent->_data) > kot(data))
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		//插入时要判断插入后是否会导致不平衡!对于红黑树来说主要问题有
			//1. 不能出现连续的红节点
			//2. 最长路径不超过最短路径的两倍
		//判断是否需要变色/旋转
		// 
		//1.当父亲节点为黑色时,当新增了一个红色节点时就结束插入了
		// 
		//2.当父为红时:
		//	情况一(仅变色即可):当parent为红 grandfather为黑 uncle存在且为黑 插入一个新节点

		while (parent && parent->_col == RED)
		{
			Node* g = parent->_parent;//grandfather
			if (g->_left == parent)
			{
				Node* u = g->_right;//uncle

				if (u && u->_col == RED)//u存在且为红
				{
					//变色即可
					u->_col = parent->_col = BLACK;
					g->_col = RED;

					//向上调整
					cur = g;
					parent = g->_parent;
					//当g 的 父亲为黑时或者为null时停止调整
				}
				else //u不存在或者为黑
				{
					if (cur == parent->_left)//此处u不存在和当插入节点在左边时的情况一样直接右旋加变色即可
					{
						//旋转加变色
						RotateR(g);
						parent->_col = BLACK;
						g->_col = RED;
					}
					else
					{
						//旋转加变色
						RotateL(parent);
						RotateR(g);

						cur->_col = BLACK;
						g->_col = RED;
					}
				}
			}
			else
			{
				Node* u = g->_left;//uncle
				if (u && u->_col == RED)//u存在且为红
				{
					//变色即可
					u->_col = parent->_col = BLACK;
					g->_col = RED;

					//向上调整
					cur = g;
					parent = g->_parent;

					//当g 的 父亲为黑时或者为null时停止调整
				}
				else //u不存在或者为黑
				{
					if (cur == parent->_right)//此处u不存在和当插入节点在左边时的情况一样直接右旋加变色即可
					{
						RotateL(g);
						parent->_col = BLACK;
						g->_col = RED;
					}
					else
					{
						RotateR(parent);
						RotateL(g);

						cur->_col = BLACK;
						g->_col = RED;
					}
				}
			}
		}
		_root->_col = BLACK;
		return make_pair(ret, true);
	}


	

	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}
	// 获取红黑树最左侧节点
	Node* LeftMost()
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_left == nullptr)
			{
				return cur;
			}
			cur = cur->_left;
		}
		return nullptr;
	}
	// 获取红黑树最右侧节点
	Node* RightMost()
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_right == nullptr)
			{
				return cur;
			}
			cur = cur->_right;
		}
		return nullptr;
	}
	//  检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
	//	1.每条路径中的黑色节点个数是否一样
	//	2.最长路径不超过最短路径的两倍
	//	3.不能出现连续的红色节点
	//	4.根节点为黑色
	bool IsValidRBTRee()
	{
		if (_root == nullptr) return true;

		if (_root->_col == RED) return false;

		Node* cur = _root;
		int blackCount = 0;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				blackCount++;
			}
			cur = cur->_left;
		}
		return _IsValidRBTRee(_root, blackCount, 0);
	}

	int Height()
	{
		if (_root == nullptr) return 0;
		return _Height(_root);
	}

	int Size()
	{
		if (_root == nullptr) return 0;

		return _Size(_root);
	}
	//检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
	Node* Find(const K& val)
	{
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) == val)
			{
				return cur;
			}
			else if (kot(cur->_data) > val)
			{
				cur = cur->_left;
			}
			else {
				cur = cur->_right;
			}
		}
		return nullptr;
	}
private:
	int _Size(Node* root)
	{
		if (root == nullptr)return 0;

		return _Size(root->_left) +
			_Size(root->_right) + 1;
	}

	int _Height(Node* root)
	{
		if (root == nullptr)return 0;

		int lefthight = _Height(root->_left);

		int righthight = _Height(root->_right);

		return lefthight > righthight ? lefthight + 1 : righthight + 1;
	}

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

		_Inorder(root->_left);
		cout << root->_data.first << " ";
		_Inorder(root->_right);
	}

	bool _IsValidRBTRee(Node* root, size_t blackCount, size_t pathBlack)
	{
		if (root == nullptr)
		{
			if (blackCount != pathBlack)//当为null时表示该路径已经结束,那么判断改路径的黑色节点(pathblack) 和其他路径的黑色节点(blacCount)是否相同
			{
				return false;
			}
			return true;
		}

		if (root->_col == RED)
		{
			if (root->_left && root->_right && (root->_left->_col == RED || root->_right->_col == RED))
			{
				cout << "有连续的红色节点" << endl;
				return false;
			}
		}
		if (root->_col == BLACK)
		{
			pathBlack++;
		}

		return _IsValidRBTRee(root->_left, blackCount, pathBlack) &&
			_IsValidRBTRee(root->_right, blackCount, pathBlack);
	}

	//	// 为了操作树简单起见:获取根节点
		//Node*& GetRoot();
	void RotateR(Node* parent)
	{
		Node* SubL = parent->_left;//此处就为 cur
		Node* SubLR = SubL->_right;

		//parent的左换成cur的右
		parent->_left = SubLR;
		//把cur的右孩子换成parent
		SubL->_right = parent;

		//注意还要修改其父指针
		Node* Ppnode = parent->_parent;

		parent->_parent = SubL;
		if (SubLR)//cur的右边可能为空
			SubLR->_parent = parent;

		if (_root == parent)//如果parent为根节点,则需要把subR改变成根节点并且其父亲为nullptr
		{
			_root = SubL;
			SubL->_parent = nullptr;
		}
		else
		{
			//同时还要考虑父亲 是祖先的左或右
			if (Ppnode->_left == parent)
			{
				Ppnode->_left = SubL;
			}
			else
			{
				Ppnode->_right = SubL;
			}
			SubL->_parent = Ppnode;
		}
	}

	// 左单旋
	// 同理
	void RotateL(Node* parent)
	{
		Node* SubR = parent->_right;//此处就为 cur
		Node* SubRL = SubR->_left;

		//parent的右换成cur的左
		parent->_right = SubRL;
		//把cur的左孩子换成parent
		SubR->_left = parent;

		Node* Ppnode = parent->_parent;

		//注意 还要修改其父指针

		parent->_parent = SubR;
		if (SubRL)//右边可能为空
			SubRL->_parent = parent;

		if (_root == parent)//如果parent为根节点,则需要把subR改变成根节点并且其父亲为nullptr
		{
			_root = SubR;
			SubR->_parent = nullptr;
		}
		else
		{
			//同时还要考虑父亲 是祖先的左或右
			if (Ppnode->_left == parent)
			{
				Ppnode->_left = SubR;
			}
			else
			{
				Ppnode->_right = SubR;
			}
			SubR->_parent = Ppnode;
		}
	}

private:
	Node* _root = nullptr;
};

为了让set、map能实现迭代器所以还要写好迭代器常用的重载operator*、operator++、operator!=、…

下面是实现过程也就是再将以及写好的红黑树进行再一次的封装,实现set、map所需要的功能。

3.2set的实现

#pragma once
#include"RBTree.h"

template<class K>
class MySet
{
public:

	struct Setofkey
	{
	//自己所写的适配器用来从pair中调出key值
		const K& operator()(const K& key)
		{
			return key;
		}
	};

//通过红黑树的迭代器来重定义生成自己的
//typename的意思是声明他是类型名
	typedef typename RBTree<K, K, Setofkey>::const_iterator iterator;
	typedef typename RBTree<K, K, Setofkey>::const_iterator const_iterator;

	pair<iterator, bool> Insert(const K& key)
	{
		return _rb.Insert(key);
		// pair<Node*, bool> 给 pair<iterator, bool>
		// pair的构造函数: template<class U, class V> pair (const pair<U,V>& pr);
		// 这样就能通过一个pair去构造另外一个pair
		// 所以返回来的pair<Node*,bool> 就会对应给到要返回的pair<iterator,bool>的 iterator 和 bool 来进行构造
		// 这样就能iterator避免内外的不一样,外部的是iterator其实是const_iterator
	}

	pair<iterator, bool> Insert(const K& key)const
	{
		return _rb.Insert(key);//pair<iterator, bool>  
	}

	//普通对象、const对象都能调用
	iterator begin() const
	{
		return _rb.begin();
	}

	iterator end() const
	{
		return _rb.end();
	}

private:
	RBTree<K,K,Setofkey> _rb;
};

3.3map的实现

#pragma once
#include"RBTree.h"

template<class K,class V>
class MyMap
{
public:
//自己所写的适配器用来从pair中调出key值
	struct Mapofkey
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};

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


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

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

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

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

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

	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>,Mapofkey> _rb;
};

本章完。预知后事如何,暂听下回分解。

如果有任何问题欢迎讨论哈!

如果觉得这篇文章对你有所帮助的话点点赞吧!

持续更新大量C++细致内容,早关注不迷路。

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

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

相关文章

qemu搭建arm64 linux kernel环境

一、环境准备 ubuntu 22.04 内核源码&#xff1a;linux-6.6.1 &#xff08;直接上最新版&#xff09; 下载链接&#xff1a;The Linux Kernel Archives 交叉编译工具链&#xff1a; sudo apt-get install gcc-12-aarch64-linux-gnu 具体能用的版本gcc-XX-arch64-linux-gnu…

Demo: 前端生成条形码并打印

前端生成条形码并打印 安装依赖&#xff1a; npm i print-js // 打印 npm i jsbarcode // 生成条形码 <template><div id"printContent" style"display: none;"><div id"elTable"><div class"name">名称&…

SpringBoot + Mybatis 快速入门实战

一.创建Spring项目 出现报错的话&#xff0c;换一个jdk创建&#xff0c;一般java 8 选择Spring Web 测试一下 默认端口8080&#xff0c;但是Vue也需要8080&#xff0c;因此&#xff0c;后端设置一个端口3000 再次测试&#xff0c;成功 此处&#xff0c;如何kill一个端口占…

人生,就是一场断舍离

常言道&#xff1a;“尽人事&#xff0c;听天命。”人世间&#xff0c;除了生死皆是小事&#xff0c;不必过度留恋&#xff0c;不必消耗自己&#xff0c;当你不懂得断舍离&#xff0c;你会发现&#xff0c;人生会痛苦不少。有时候&#xff0c;我们不会被难题所击倒&#xff0c;…

一键部署私有化的思维导图SimpleMindMap

简介 SimpleMindMap 是一个可私有部署的web思维导图工具。它提供了丰富的功能和特性&#xff0c;包含插件化架构、多种结构类型&#xff08;逻辑结构图、思维导图、组织结构图等&#xff09;、节点内容支持文本、图片、图标、超链接等&#xff0c;支持拖拽、导入导出功能、快捷…

cocos creator 调用预设体Prefab中的方法(调用另一个节点的方法)

调用预设体中的方法 通过cc.instantiate(this.star)创建这个预设体实例这个star预设体中添加了一个脚本组件star.ts 获取到这个脚本组件star.getComponent(‘star’).test()&#xff0c;并调用其中的test()方法同理可以用该方式像另一个节点中传值 //星星预设体property(cc.Pr…

最新GPT4.0使用教程,AI绘画-Midjourney绘画,GPT语音对话使用,DALL-E3文生图+思维导图一站式解决

一、前言 ChatGPT3.5、GPT4.0、GPT语音对话、Midjourney绘画&#xff0c;文档对话总结DALL-E3文生图&#xff0c;相信对大家应该不感到陌生吧&#xff1f;简单来说&#xff0c;GPT-4技术比之前的GPT-3.5相对来说更加智能&#xff0c;会根据用户的要求生成多种内容甚至也可以和…

【C++杂货铺】详解类和对象 [中]

博主&#xff1a;代码菌-CSDN博客 专栏&#xff1a;C杂货铺_代码菌的博客-CSDN博客 目录 &#x1f308;前言&#x1f308; &#x1f4c1; 类的6个默认成员函数 &#x1f4c1; 构造函数 &#x1f4c2; 概念 &#x1f4c2; 特性&#xff08;灰常重要&#xff09; &#x1f4c…

用友U8接口-部署和简要说明(1)

概括 本专栏文章目的说明对目前用友U8ERP接口介绍对底层接口二次封装的介绍 说明 过去发布过介绍U8接口文章简介&#xff0c;参考以下链接。 U8接口开发方式 本专栏文章与下面的HTTP接口相辅相成&#xff0c;主要是写给正在使用&#xff0c;或未来使用本套接口的开发人员&am…

Harmony的自定义组件和Page的数据同步

在开发过程中会经常使用自定义组件,就会遇到一个问题,在页面中引入组件后,如何把改变的值传递到自定义组件中呢,这就用到了装饰器,在这是单向传递的,用的装饰器是@State和@Prop @State在page页面中监听数据的变化 @Prop在自定义组件中监听page页面传递过来的变化值,并赋…

关于开展人工智能从业人员“计算机视觉处理设计开发工程师”专项培训(第二期)的通知!

近日我们刚刚结束了《计算机视觉处理设计开发工程师》证书第一期培训&#xff0c;培训效果良好&#xff0c;所有学员均通过工信部统一线上考试&#xff0c;坐等证书了。鉴于学员们的反应我们第二期课程如约而至。 证书出台背景&#xff1a;为进一步贯彻落实中共中央印发《关于深…

VMware中CentOS 7解决网络问题

问题描述 在 VMware 中使用 CentOS 7 中使用 ping www.baidu.com 测试网络是否能正常连接。 出现了未知的名称和服务的问题&#xff1a; 解决方案一 在服务中检查 VMware NAT Service 是否开启 解决方案二 在控制面板中的网络适配器里检查 解决方案三 检查VMware中的网络适…

DAY10_SpringBoot—SpringMVC重定向和转发RestFul风格JSON格式SSM框架整合Ajax-JQuery

目录 1 SpringMVC1.1 重定向和转发1.1.1 转发1.1.2 重定向1.1.3 转发练习1.1.4 重定向练习1.1.5 重定向/转发特点1.1.6 重定向/转发意义 1.2 RestFul风格1.2.1 RestFul入门案例1.2.2 简化业务调用 1.3 JSON1.3.1 JSON介绍1.3.2 JSON格式1.3.2.1 Object格式1.3.2.2 Array格式1.3…

Python第十二章(函数2)

一。变量作用域&#xff1a; 1.局部变量&#xff1a;定义于函数内部&#xff0c;生效在函数&#xff0c;外部不生效 作用&#xff1a;临时保存数据&#xff0c;当函数调用完毕后&#xff0c;则摧毁局部变量 2.全局变量&#xff1a;定义于函数体外面&#xff0c;在函数体外面…

实现元素进入界面的平滑效果

先看效果&#xff1a; 实现思路&#xff1a;获取页面中需要加载动画的节点&#xff0c;用元素的animate()方法创建一个动画对象&#xff0c;并传入两个关键帧&#xff0c;接着使用IntersectionObserverAPI创建观察对象&#xff0c;用于观察元素进入页面。当元素进入界面时&…

sqli.labs靶场(第18~22关)

18、第十八关 经过测试发现User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0加引号报错 这里我们闭合一下试试 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0,127.0.0.1,adm…

漏洞原理linux操作系统的SqlMap工具的使用

漏洞原理linux操作系统的SqlMap工具的使用 Linux操作系统基础操作链接: 1024一篇通俗易懂的liunx命令操作总结(第十课)-CSDN博客 kali的IP地址:192.168.56.1 实操 # kali中使用sqlmap http://192.168.56.1/ sqlmap -u http://192.168.56.1/news/show.php?id46 sqlmap -u …

线程锁多线程的复习

线程 实现方式3种乐观锁&悲观锁线程池线程池总结 进程:是正在运行的程序 线程:是进程中的单个顺序控制流,是一条执行路径 实现方式3种 1.Thread //步骤一:定义一个继承Thread的类 //步骤二:再定义的类中重写run()方法 //步骤三:创建定义类对象 //步骤四:启动线程 class M…

轮廓测长|轮廓仪高精度测量粗糙度

现代工业制造领域中&#xff0c;为什么测量粗糙度至关重要&#xff1f;在现代工业制造领域中&#xff0c;测量粗糙度是一项非常重要的工作。因为粗糙度能够影响到工件的功能性能和外观质量。当我们制造机器零件或工具时&#xff0c;需要确保表面的光洁度能够满足设计要求。过高…

iOS 自动打包 命令之如何获取PROVISIONING_PROFILE

获取描述文件UUID&#xff1a; 首先在Finder中前往/Users/用户名/Library/MobileDevice/Provisioning Profiles路径 然后在Provisioning Profiles文件夹中选择打包需要的描述文件&#xff0c;一般描述文件的文件名都是由UUID组成的&#xff0c;如果文件名不是由UUID组成的&…