C++ | set与map的用法指南

前言

        前面我们学习了vector、list等容器,其实他们都属于序列式容器,因为其底层为线性结构;今天我们学习使用的set与map是属于关联式容器,关联式容器更注重于数据检索访问的效率;本文所有的资料均查阅于文档,以下为文档链接;可自行查看;

文档链接

一、set的简介

        在C++中,它是一种集合,是我们前面二叉搜索树的Key模型;其所在的头文件是set;其实这个头文件下,还有一个兄弟容器 ------- multiset,在介绍set是我也会介绍multiset,因为二者其实用法区别并不大,差异我会一 一介绍出来;

C++ | set与map的用法指南_第1张图片

        关于multiset其实与set几乎差不多,唯一区别是,set会去重,而multiset不会;本文主要注重set的讲解,当然,你会使用set,multiset你也自然会使用了;

C++ | set与map的用法指南_第2张图片

        set的类模板如上所示;我们说过它是我们前面学过二叉搜索树的Key模型;因此它的第一个模板参数就是我们的Key的类型;第二个模板参数是比较的方式,需要传入一个仿函数类;第三个参数为内存池相关对象;传默认即可;

C++ | set与map的用法指南_第3张图片

        首先我们查看该类的类型重定义,我们发现该类有key_type与value_type都是第一个模板参数T类型;所以说set是Key模型;然后我们发现set的迭代器是双向迭代器;

二、set相关接口

1、构造相关接口

        这部分我们主要看其构造函数与赋值重载的接口;构造函数接口如下;我们发现有三种不同的构造方式,第一种是无参的默认构造;第二种是迭代器区间构造;第三种是拷贝构造;有了前面的基础,这里的接口几乎看一眼都会使用了; 

C++ | set与map的用法指南_第4张图片

void test_set3()
{
	// 无参构造
	set s1;
	// 迭代器区间构造
	vector v{ 1,2,3,4,5,6 }; // C++11初始化语法
	set s2(v.begin(), v.end());
	// 拷贝构造
	set s3(s2);
}

        赋值重载接口如下;赋值重载对于已存在的对象进行赋值;使用难度几乎没有;

C++ | set与map的用法指南_第5张图片

2、迭代器相关接口

        迭代器真的不愧是STL中六大组件之一,无论是线性结构还是set与map中的树形结构,我们都可以通过迭代器接口来进行访问;使用起来几乎没有区别;这里迭代器为双向迭代器,因此不支持方括号的方式访问,但支持++与--;使用如下;

C++ | set与map的用法指南_第6张图片

void test_set4()
{
	set s1;
	// 这里为了展示迭代器先使用插入接口了
	s1.insert(2);
	s1.insert(3);
	s1.insert(1);
	s1.insert(5);
	s1.insert(2);
	s1.insert(1);
	s1.insert(4);

	set::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	
	// 既然支持迭代器,就肯定有范围for了
	for (auto& e : s1)
	{
		cout << e << " ";
	} 
	cout << endl;
}

C++ | set与map的用法指南_第7张图片

        我们发现对set用迭代器遍历的时候,set对里面的数据完成了去重+排序;我们再试试我们之前说的multiset,代码与结果如下;

void test_set4()
{
	cout << "set: " << endl;
	set s1;
	// 这里为了展示迭代器先使用插入接口了
	s1.insert(2);
	s1.insert(3);
	s1.insert(1);
	s1.insert(5);
	s1.insert(2);
	s1.insert(1);
	s1.insert(4);

	set::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	
	// 既然支持迭代器,就肯定有范围for了
	for (auto& e : s1)
	{
		cout << e << " ";
	} 
	cout << endl;


	cout << "multiset: " << endl;
	multiset ms1(s1.begin(), s1.end());
	ms1.insert(2);
	ms1.insert(3);
	ms1.insert(1);
	ms1.insert(5);
	ms1.insert(2);
	ms1.insert(1);
	ms1.insert(4);
	for (auto& e : ms1)
	{
		cout << e << " ";
	}
	cout << endl;
}

C++ | set与map的用法指南_第8张图片

        我们发现与set不同,multiset只会对数据进行排序,并不具有去重功能; 

3、容量相关接口

        到了这一块就没有什么扩容的概念了;empty是检查set是否为空,size是查看set中元素个数,max_size是查看理论上最多可以放的元素个数,这个接口几乎没什么意义;

C++ | set与map的用法指南_第9张图片

 4、修改相关接口

关于set的修改接口如下所示;以下展示比较经常使用的接口;

C++ | set与map的用法指南_第10张图片

void test_set5()
{
	set s1;
	// 插入某个值
	s1.insert(2);
	s1.insert(5);
	s1.insert(1);
	s1.insert(2); // 插入失败,因为有2这个值了
	s1.erase(1); // 删除某个值
	set s2;
	// 交换两个set
	s1.swap(s2);
	// 清空某个set
	s2.clear();
}

5、其他相关操作 

关于set还有一些特殊化的操作,如下所示;

C++ | set与map的用法指南_第11张图片

void test_set6()
{
	set s1;
	s1.insert(0);
	s1.insert(1);
	s1.insert(3);
	s1.insert(3);
	s1.insert(4);
	// 查找某个key的位置,返回该位置迭代器,若没找到返回s1.end()
	set::iterator it = s1.find(2);
	if (it != s1.end())
	{
		cout << "找到了" << endl;
	}

	// count接口在set中就是查看该元素在不在set中
	cout << s1.count(2) << endl;
}

        这里的count在multiset中是返回指定元素个数;lower_bound函数与upper_bound函数是一个查找函数,一个是返回一个迭代器,该迭代器指向集合容器中的第一个元素,该元素不被认为在值之前。另一个是返回一个迭代器,该迭代器指向集合容器中的第一个元素,该元素被认为是在值之后。equal_range则是返回一个范围的边界,该范围包括集合容器中与 value 等效的所有元素。

三、set的应用场景

        set是一个Key模型,因此我们通常用来解决在不在的问题;例如还是查一篇文章中的单词是否拼写正确,我们可以检查这个单词是否存在字典中;如下代码所示;

void test_set2()
{
	set dict;
	dict.insert("left");
	dict.insert("right");
	dict.insert("area");
	dict.insert("count");
	dict.insert("return");

	string str;
	while (cin >> str)
	{
		set::iterator pos = dict.find(str);
		if (pos != dict.end())
		{
			cout << "在" << endl;
		}
		else
		{
			cout << "不在" << endl;
		}
	}

}

C++ | set与map的用法指南_第12张图片

四、map简介 

        map是我们前面所学二叉搜索树中的一种Key_Value模型,是一种键值对的方式,key与value一 一对应;与set类似,还有一个multimap接口;因为map也是去重的,multimap则是不去重版本;它们的接口使用几乎也无二异;故放在一起讲解;

C++ | set与map的用法指南_第13张图片

        首先我们看模板参数,第一个模板参数是Key的类型,第二个模板参数是Value的类型;第三个参数则是比较的方式;第四个模板参数是内存池相关类;

C++ | set与map的用法指南_第14张图片

        在看map类型的重定义时,我们发现这里与set有很大的差别;它的key_type是第一个模板参数;这里多了一个map_type,是第二个模板参数;而它的value_type则是一个二元组(pair),里面存的是一个键值对;而迭代器是一个双向迭代器;

C++ | set与map的用法指南_第15张图片

        在正式谈map接口之前,我们首先弄清关于pair,pair是一个二元组,具体定义如下;其中有两个成员分别为first与second;

template 
struct pair
{
    typedef T1 first_type;
    typedef T2 second_type;
    T1 first;
    T2 second;

    pair()
        : first(T1())
        , second(T2())
    {}
    pair(const T1& a, const T2& b)
        : first(a)    
        , second(b)
    {}
};

        同时,C++还为我们提供了一个函数来构建pair;我们可以通过这个函数实例化出一个pair对象;

C++ | set与map的用法指南_第16张图片

五、map相关接口

1、构造函数相关接口

        map的构造函数无非也就是那三种构造方式,默认构造,迭代器区间构造,拷贝构造;

C++ | set与map的用法指南_第17张图片

C++ | set与map的用法指南_第18张图片

C++ | set与map的用法指南_第19张图片

 2、迭代器相关接口

        迭代器的使用也没什么不同,演示代码如下;

void test_map1()
{
	map dict;
	dict.insert(make_pair("left", "左边"));
	dict.insert(make_pair("right", "右边"));
	dict.insert(make_pair("count", "计数"));

	map::iterator it = dict.begin();
	while (it != dict.end())
	{
		// 迭代器本质实际上就是指向pair的指针,因此这里我们指定访问的某个具体成员
		cout << it->first << ": " << it->second << endl;
		it++;
	}
	cout << endl;

	// 范围for
	for (auto& e : dict)
	{
		cout << e.first << ": " << e.second << endl;
	}
	cout << endl;
}

3、容量与修改相关接口

        容量接口与前set相同,此处就不做介绍了;

C++ | set与map的用法指南_第20张图片

        主要介绍这里的修改相关接口,这里的insert插入的是value_type类型的数据,也就是我们前面说的pair类型的数据; 

C++ | set与map的用法指南_第21张图片

C++ | set与map的用法指南_第22张图片

        注意,上面的插入数据的接口是一个二元组的pair类型的数据,且pair的第一个数据的类型是一个迭代器,第二个数据的类型是一个bool类型的数据;实际上,第二个pair的第二个成员是记录是否插入成功,若插入数据的key值已存在,则插入失败,第二个参数设置为false,第一个参数设置为对应key的迭代器;若插入数据不存在,则插入新值,然后第一个参数设置为新插入key值得迭代器的位置,第二个参数设置为true; 

void test_map2()
{
	map dict1;
	dict1.insert(make_pair("left", "左边"));
	dict1.insert(make_pair("right", "右边"));
	dict1.insert(make_pair("count", "计数"));

	dict1.insert(make_pair("right", "权力")); // 插入失败

	// 删除指定键值对
	dict1.erase("count");

	// 交换两个map(两棵树的根节点)
	map dict2;
	dict1.swap(dict2);
	// 清空
	dict2.clear();
}

4、方括号访问与其他函数

        关于map的使用,最需要注意的就是这个方括号了;其次我们需要学会使用这个find接口;这里的count接口与set一样,查看一个键值对是否存在,在multimap可以统计某个key在容器中的个数;

C++ | set与map的用法指南_第23张图片

        方括号的返回值的声明如下所示,其实际上调用了insert函数而已;函数体也放在下面了;

C++ | set与map的用法指南_第24张图片

        函数体如下图,可能很多人看到下面的代码头都大了,小编一开始看到这样的代码也是一脸懵;

mapped_type& operator[] (const key_type& k)
{
    return (*((this->insert(make_pair(k,mapped_type()))).first)).second;
}

C++ | set与map的用法指南_第25张图片

        这个知识不就跟我们之前学的插入的返回值串起来了吗?map中的方括号功能十分强大,如下所示;

void test_map3()
{
	map mp1;
	mp1.insert(make_pair("left", "左边"));
	mp1.insert(make_pair("right", "右边"));
	mp1.insert(make_pair("int", "整型"));
	mp1.insert(make_pair("right", "权力")); // 插入失败

	// 我们可以通过方括号代替insert
	mp1["double"] = "浮点型"; // 插入
	cout << mp1["int"] << endl; // 查找
	mp1["right"] = "权力";  // 查找+修改
}

        方括号语法确实也强大,具有多种功能;接下来我们来看看map的查找函数;

C++ | set与map的用法指南_第26张图片

void test_map4()
{
	map mp1;
	mp1.insert(make_pair("left", "左边"));
	mp1.insert(make_pair("right", "右边"));
	mp1.insert(make_pair("int", "整型"));

	map::iterator pos = mp1.find("int");
	if (pos != mp1.end())
	{
		cout << pos->second << endl;
	}
}

        相比起来,还是我们的方括号似乎更加好用;

五、map的应用场景

        map是key_value模型,主要用于键值对的维护,是一种一 一对应的关系;我们可以用来统计某种物品的个数,这里我们在之前的二叉搜索树那里也实现过相同的代码,今天我们用map再实现一次;

void test_map5()
{
	map fruits;
	std::string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "火龙果", "苹果", 
		"西瓜","苹果", "香蕉", "苹果", "香蕉" , "火龙果"};
	// 方法一
	//for (auto& e : arr)
	//{
	//	auto pos = fruits.find(e);
	//	if (pos != fruits.end())
	//	{
	//		pos->second++;
	//	}
	//	else
	//	{
	//		fruits.insert(make_pair(e, 1));
	//	}
	//}
	
	// 方法二
	for (auto& e : arr)
	{
		fruits[e]++;
	}


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

        这里是不是又能体会到方括号功能的强大!同时这里也体现了key_value模型的作用;

你可能感兴趣的:(c++,开发语言)