• C++ map和set


    前言:本文中会对mapsetmultisetmultimap的常用接口进行介绍,对于mapoperator[]运算符重载的原理进行了讲解,其中mapset介绍较为详细,对关联式容器,键值对的概念及使用也做了介绍.

    🌿1. 关联式容器

    根据"数据在容器中的排列"特性,STL容器可分为序列式(sequence)和关联式(associative)两种,比如vectorlistdequeforward_listC++11)等,这些容器统称为序列式容器,因为其底层为线性的数据结构,里面存储的是元素本身.那什么是关联式容器,它与序列式容器有什么区别?

    关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是的键值对,在数据检索时比序列式容器效率更高.

    🥦2. 键值对

    键值对pair)是用来表示具有一一对应关系的一种结构,该结构中一般只****包含两个成员变量keyvalue,key代表键值,value表示与key对应的信息.

    比如:我们现在要建立一个英汉互译的字典,那该字典中就必须有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该单词,在字典中就能找到与其对应的中文含义.

    这是SGI-STL中对于pair的定义

    在这里插入图片描述
    键值对的使用:

    void Test()
    {
    	pair<string, string> pr("moon", "月亮");
        
        //first表示key值,second表示value
    	cout << pr.first << ":" << pr.second << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    🌳3. 树形结构的关联式容器

    根据应用场景的不同,STL总共实现了两种不同结构的关联式容器:树型结构哈希结构树型结构的关联式容器主要有四种mapsetmultimapmultiset,其中multimapmultiset是根据mapset衍生出来的.
    这四种容器共同的特点是:都使用红黑树作为其底层结构.

    此外,SGI STL还提供了一个不在标准规格之列的哈希结构的关联式容器:hash_table(散列表),以及以此hash table为底层实现机制而完成的hash_set(散列集合)、hash_map(散列映射表)、hash_multiset(散列多键集合)、hash_multimap(散列多键映射表).

    🍁4. set

    📗4.1 set的介绍

    首先来看一下set的一些介绍及特性:

    1. set是按照一定次序存储元素的容器,所有元素都会根据元素的键值自动被排序
    2. set的元素不像map那样可以同时拥有实值(key)和键值(value),set元素的键值就是实值,实值就是键值,并且set不允许两个元素有相同的键值.
    3. set中的元素不能修改,但可以进行插入或删除.
    4. set的底层是用红黑树实现的
    5. set拥有与list相同的某些性质:当客户端对它进行元素新增操作(insert)或删除(erase)操作时,操作之前的所有迭代器,在操作完成后都依然有效.

    那么为什么set中的元素不允许修改呢?
    我们先来看一看SGI STL源码中的定义:

    typedef typename rep_type::const_iterator iterator;

    这里的rep_type是这样定义的:

    typedef rb_tree, key_compare, Alloc> rep_type;

    其实rep_type也就是红黑树,这样也就验证了底层是红黑树的说法,那么其实,set所开放的各种操作接口,RB-tree也都提供了,所以几乎所有的set操作行为,都只是在转调RB-tree的接口而已.

    对于set中的元素能不能修改,我们来看看set的迭代器:

    typedef typename rep_type::const_iterator iterator;

    也就是说,set的迭代器其实是复用了RB-treeconst_iterator,所以set中不能修改元素值.

    1. map/multimap不同,map/multimap中存储的是真正的键值对set中只放value,但在底层实际存放的是由构成的键值对。
    2. set中插入元素时,只需要插入value即可,不需要构造键值对。
    3. set中的元素不可以重复(因此可以使用set进行去重)。
    4. 使用set的迭代器遍历set中的元素,可以得到有序序列
    5. set中的元素默认按照小于来比较
    6. set中查找某个元素,时间复杂度为:logN
    7. set中的元素不允许修改
    8. set中的底层使用二叉搜索树(红黑树)来实现

    📗4.2 set的使用

    📖4.2.1 set的模板参数列表

    在这里插入图片描述
    T: set中存放元素的类型,实际在底层存储的键值对。

    Compareset中元素默认按照小于来比较

    Allocset中元素空间的管理方式,使用STL提供的空间配置器管理

    📖4.2.2 set的构造

    在这里插入图片描述

    set (const Compare& comp = Compare(), const Allocator& =Allocator() );

    默认构造(空构造):构造一个空的set

    注意:这里的key_compareallocator_type是因为在STL源码中进行了typedef.

    在这里插入图片描述

    set (InputIterator first, InputIterator last, const Compare&comp = Compare(), const Allocator& = Allocator() );

    用迭代器区间[first,last)进行构造.

    set ( const set& x);

    set的拷贝构造.

    📖4.2.3 set的迭代器

    在这里插入图片描述
    set的迭代器与其他容器的迭代器种类类似,有正向迭代器反向迭代器const迭代器,这里不做太多介绍,下面会写代码演示.

    📖4.2.4 set的容量

    在这里插入图片描述

    set::empty:判断set是否为空,是则返回true,反之返回false.

    在这里插入图片描述
    set::size:返回set中有效元素的个数

    在这里插入图片描述
    set::max_size:返回set中能容纳最大元素的个数

    📖4.2.5 set的修改操作

    在这里插入图片描述
    这里主要介绍inserteraseclearswap操作比较简单,读者可自行了解.

    set::insert

    在这里插入图片描述

    pair insert (const value_type& x )

    功能:在set中插入元素val,实际插入的是构成的键值对,如果插入成功,返回<该元素在set中的位置,true>,如果插入失败,说明xset中已经存在,返回<xset中的位置,false>

    iterator insert (iterator position, const value_type& val);

    功能:在position位置插入val,返回新插入元素的位置.

    void insert (InputIterator first, InputIterator last);

    功能:插入迭代器区间[first,last)的值.

    set::erase
    在这里插入图片描述

    void erase (iterator position);

    功能:删除setposition位置上的元素

    size_type erase (const value_type& val);

    功能:删除set中值为val的元素,返回删除的元素的个数

    void erase (iterator first, iterator last);

    功能:删除set[first, last)区间中的元素

    📖4.2.6 set的其他操作

    在这里插入图片描述
    这里主要介绍findcountlower_boundupper_bound这四个接口.

    set::find

    iterator find (const value_type& val) const;

    功能:在set中查找val,如果找到,返回val所在的位置,反之返回end()

    set::count

    size_type count (const value_type& val) const;

    功能:返回set中值为val的元素的个数,这里要特别强调的是,这个函数的返回值只有两个值:01,因为在set中元素不允许重复,所以要么valset中,且只有一个,要么不在,为0个.

    所以在set中,count方法也可以用来判断元素是否存在.

    int main()
    {
    	set<int> s;
        
        //如果存在返回1,不存在返回0
    	if (s.count(1))
    	{
    		cout << "1存在" << endl;
    	}
    	{
    		cout << "1不在" << endl;
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    set::lower_bound

    iterator lower_bound (const value_type& val) const;

    功能:lower_bound会返回>= val的最小值.

    set::upper_bound

    功能:upper_bound会返回set> val的最小值

    例如,对于这样一个0,1,2,3,4,5,6

    如果调用s.lower_bound(3),就会返回3
    如果调用s.upper_bound(3),就会返回4

    使用这两个函数,我们可以实现一个删除[x,y],闭区间的值.

    void Test()
    {
    	set<int> s;
    	s.insert(5);
    	s.insert(0);
    	s.insert(3);
    	s.insert(1);
    	s.insert(4);
    	s.insert(6);
    	s.insert(2);
    	s.insert(7);
    	
    	//删除[x,y]区域的值
    	int x, y;
    	cin >> x >> y;
    	auto left = s.lower_bound(x);
    	auto right = s.upper_bound(y);
        
        //由于erase方法删除的是[first,end)前闭后开,而upper_bound方法正好返回比y大的那个值,
        //保证了将[x,y]区间内的值删掉
    	s.erase(left, right);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    然后,对于上述set的接口,我们来测试一下:

    void Test01()
    {
    	set<int> s;
    	s.insert(5);
    	s.insert(0);
    	s.insert(3);
    	s.insert(1);
    	s.insert(4);
    	s.insert(6);
    	s.insert(2);
    	s.insert(7);
    	s.insert(2);   //在这里我们重复插入了一个2,但输出时,set中只有一个2,验证set具有去重功能.
    	
        cout << "删除前:" << endl;
        //用范围for遍历set -> 遍历结果有序
    	for (const auto& e : s)
    	{
    		cout << e << " ";
    	}
    	cout << endl;
    
    	cout << "共插入"<<s.size()<<"个元素" << endl;
    
    	if (!s.empty())
    	{
    		cout << "set不为空" << endl;
    	}
    	else
    	{
    		cout << "set为空" << endl;
    	}
    
    	set<int>::iterator pos = s.find(3);
    
    	if (pos != s.end())
    	{
    		s.erase(pos);
    	}
        
        cout<<"删除后:"<<endl;
        //用迭代器遍历set
    	set<int>::iterator it = s.begin();
    	while (it != s.end())
    	{
    		//set不允许修改key
    		//*it = 10; -> 报错
    		cout << *it << " ";
    		++it;
    	}
    	cout << endl;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    运行结果如下:
    在这里插入图片描述

    🍀5. map

    📙5.1 map的介绍

    在这里插入图片描述
    这是文档中对于map的介绍,其中主要介绍这么几点:

    1. map是关联式容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
    2. map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,keyvalue通过成员类型value_type绑定在一起,为其取别名称为pair: typedef pair value_type;
    3. 在内部,map中的元素总是按照键值key进行比较排序的。
    4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)
    5. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value
    6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))

    注意:在map中,键值key通常用于排序和惟一地标识元素,所以在进行insertfinderase等这些操作时,操作对象一般都是key.

    📙5.2 map的使用

    📖5.2.1 map的模板参数

    在这里插入图片描述
    Key: 键值对key的类型

    T键值对value的类型

    Compare: 比较器的类型,map中的元素是按照key来比较的,缺省情况下按照小于来比较,一般情况
    下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则
    (一般情况下按照函数指针或者仿函数来传递)

    Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器
    注意:在使用map时,需要包含头文件

    📖5.2.2 map的构造

    在这里插入图片描述
    默认构造(空构造):

    explicit map (const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());

    其中的key_compareallocator_type为比较器和空间配置器,不需要我们自己传递.
    功能:返回一个空的map

    迭代器区间构造:

    map (InputIterator first, InputIterator last,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());

    拷贝构造:

    map (const map& x);

    这两种构造与上面介绍的set类似,不再细说.

    📖5.2.3 map的迭代器

    在这里插入图片描述

    📖5.2.4 map的元素与容量访问

    在这里插入图片描述
    这三个接口与上面set类似,可以看set中的叙述.

    📖5.2.5 详解map的operator[]运算符重载

    这里着重来看这样一个接口:

    在这里插入图片描述
    也就是map中重载了[]运算符,那么这个运算符如何使用呢?

    假如,我们现在有一些水果,需要统计各个水果的个数,就可以使用这样一段代码:

    #include
    #include
    using namespace std;
    
    int main()
    {
    	string str[] = { "香蕉", "苹果", "梨", "西瓜", "葡萄", "苹果", "西瓜", "香蕉", "香蕉" };
    	map<string, int> countMap;
    
    	for (const auto& e : str)
    	{
    		countMap[e]++;
    	}
    
    	for (const auto& m : countMap)
    	{
    		cout << m.first << ": " << m.second << "个" << " ";
    	}
    	cout << endl;
    
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    运行结果:

    在这里插入图片描述
    我们会发现,我们只使用了countMap[e]++这样一个操作就完成了这个需求,那为什么它可以这样做呢?

    在这里插入图片描述
    这条语句便是operator[]的实现,但这句代码非常不直观,所以我们将它分解一下:

    在这里插入图片描述
    key_type表示的是map中键值对key的类型,mapped_type表示的是map中键值对value的类型.
    所以这个函数的功能是,如果传入的kmap中还不存在,那就在map中插入以这个k值为key构造的键值对pair,然后返回这个键值对的value值,如果已经存在,返回的是已经存在的以k为键值的pair的位置.

    所以对于countMap[e]++;:如果key存在,就++value,如果不存在,创建key然后++value.
    如果只写成countMap[e],那就只插入,不修改.

    📖5.2.6 map的修改操作

    在这里插入图片描述
    map的修改操作接口与set基本没有差别,唯一区别就是map插入的是pair键值对,所以这里对于其他接口不再细说,只说一下map的插入:

    在这里插入图片描述

    对于map的插入操作,首先,我们需要构建一个pair键值对,然后插入到map中,那么构造键值对除了我们在上面讲的方法之外,还有一种是使用pair中的一个方法:

    在这里插入图片描述
    由于函数模板可以进行类型自动推演,所以我们使用这个函数,就不用再显式的写出参数,只需要写我们要插入的值即可:

    具体用法如下:

    //创建一个map
    map<string, string> dict;
    
    //使用先构建pair对象,然后插入
    dict.insert(pair<string, string>("moon","月亮"));
    
    //使用make_pair函数
    dict.insert(make_pair("moon", "月亮"));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    map的一些其他接口:

    在这里插入图片描述
    这些接口也参考set中的讲解.

    map的测试:

    void Test02()
    {
    	map<string, string> dict;
    
    	dict.insert(make_pair("moon", "月亮"));
    	dict.insert(make_pair("color", "颜色"));
    	dict.insert(make_pair("bottle", "瓶子"));
    	dict.insert(make_pair("paper", "纸"));
    	dict.insert(make_pair("seawater", "海水"));
    	dict.insert(make_pair("plank", "木板"));
    
    	string str;   
    	
    	while (cin >> str)
    	{
    		map<string, string>::iterator it = dict.find(str);
    		if (it != dict.end())
    		{
    			cout <<it->first<<":"<< it->second << endl;
    		}
    		else
    		{
    			cout << "未查询到此单词" << endl;
    		}
    	}
        
        cout<<"删除前: "<<endl;
    	for (const auto& e : dict)
    	{
    		cout << e.first << ":" << e.second << endl;
    	}
        
        //删除key为"moon"的元素
        dict.erase("moon");
        cout<<"删除后: "<<endl;
        for (const auto& e : dict)
    	{
    		cout << e.first << ":" << e.second << endl;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    运行结果:
    在这里插入图片描述
    同时可以观察到,map打印的结果是按照key排序的

    🍃6. multiset

    对于multiset的介绍,由于它与set太过类似,所以这里只是简单说下它们之间的区别.
    在这里插入图片描述
    对于multiset,它是对set可以插入重复元素后的衍生版本,所以它与set的唯一区别就是它允许插入重复值,也就是说它只能排序不能去重.

    在这里插入图片描述
    还有对于count这个接口,在set中,它只会返回01,但在multiset中,允许插入重复元素,所以它的返回值就要根据具体的元素个数而定.

    在这里插入图片描述
    对于erase这个接口,在set中调用,会将set中存在的唯一的那个value删掉,但如果在multiset中调用,它会删除所有的val值.

    对于其他的接口以及性质,multisetset并无区别,大家可以参考set中的去使用.
    multiset的底层也是通过红黑树实现的.

    注意:使用multiset时不需要重新包含头文件,包含头文件set即可

    简单测试multiset

    void Test()
    {
    	int arr[] = { 0, 0, 0, 1, 2, 3, 4, 5 };
    	multiset<int> s;
    
    	for (auto& e : arr)
    	{
    		s.insert(e);
    	}
    
    	for (auto& t : s)
    	{
    		cout << t << " ";
    	}
    	cout << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    🌴7. multimap

    对于multimap的介绍,由于它与map太过类似,所以这里只是简单说下它们之间的区别.
    在这里插入图片描述
    multimapmap的唯一不同就是:map中的key是唯一的,而multimapkey是可以重复的.

    multimap中,也有各别接口稍有差别,参考multiset中的描述.

    这里只有一点:在multimap中,没有重载operator[],为什么?

    我们刚才在map说过,operator[]的功能是:如果key不存在,就用key创建pair并插入,如果存在,就返回当前已经存在的元素位置, 可是在multimap中,是允许插入重复key的,如果已经存在多个key,然后再次插入相同的key值,那应该返回哪一个呢?所以这个接口也不能再对单个元素实现计数功能,也就显得没有意义.

    对于其他接口的使用,参考map中的讲解.

  • 相关阅读:
    ⑩⑧【MySQL】InnoDB架构、事务原理、MVCC多版本并发控制
    C++ 构造函数
    【linux】倒计时小程序
    SpringMVC-02 MVC模式介绍
    Git命令meger和rebase命令的用法和区别
    矢量绘图软件 Sketch mac中文版介绍
    Abaqus 泰森插件-voronoi 3D
    心理咨询软件开发系统分析
    第二章 JAVA基础语法
    你还在 Docker 中跑 MySQL?恭喜你,可以下岗了!
  • 原文地址:https://blog.csdn.net/smf12138/article/details/126132563