C++_map_set详解

关联容器的基本介绍

关联容器支持高效的关键字查找和访问。map和set是最主要关联容器。关联容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。C++标准库中提供了8个关联容器:

关联容器的类型
按关键字有序保存元素
map关联数组;保存键值对
set关键字即值,即只保存关键字的容器
multimap关键字可重复出现的map
multiset关键字可重复出现的set
无序集合
unordered_map用哈希函数组织的map
unordered_set用哈希函数组织的set
unordered_multimap哈希组织的map;关键字可重复出现
unordered_multiset哈希组织的set;关键字可重复出现

其中,类型map和multimap定义在头文件map中;set和multiset定义在头文件set中;无序容器则定义在头文件unordered_map和unordered_set中。本文重点讨论map与set

键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key相对应的信息。比如 可以将一个人的名字作为关键字key,将其电话号码作为值value,这样我们就可以通过人名来找到对应的电话号码。

pair类型 

pair是一个标准库类型,它定义在头文件utility中。pair文档

一个pair保存两个数据成员。类似容器,pair是一个用来生成特定类型的模板,下面是pair的原型

// SGI-STL中关于键值对的定义
template <class T1, class T2>
struct pair
{
    typedef T1 first_type;
    typedef T2 second_type;
    
    pair()                            // 构造
        : first(T1())
        , second(T2())
    {}
    pair(const T1& a, const T2& b)    // 拷贝构造
        : first(a)
        , second(b)
    {}

    T1 first;                         // 成员变量
    T2 second;
};

 当创建一个pair时,我们必须提供两个类型名,pair的数据成员将具有对应的类型

// 代码示例
pair<string, string> a;         // 保存两个string
pair<string, int> b;            // 保存一个string和一个int
pair<string, vector<int>> c;    // 保存一个string和vector<int>

 pair的默认构造函数会对数据成员进行值初始化。因此a是一个包含两个空string的pair;b中的int成员值初始化为0,而string成员初始化为空;c保存了一个空string和一个空的vector。

我们也可以为每个成员提供初始化器:

// 创建一个名为object的pair,两个成员分别被初始化为“xxxx”和2
pair<string, int> object {"xxxx", 2};
pair的常见操作
pair<T1, T2> p;p是一个pair,两类型分别为T1和T2的成员进行了 值初始化
pair<T1, T2> p (v1, v2);p是一个成员类型为T1和T2的pair;first和second成员分别用v1,v2初始化
pair<T1, T2> p = {v1, v2};等价于 p (v1, v2)
make_pair(v1, v2);返回一个用v1和v2初始化的pair。pair的类型从v1和v2的类型推断出来

set

set的基本介绍 

  1. set是按照一定次序存储元素的容器
  2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
  3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格的排序准则(默认是小于)进行排序。
  4. set在底层是用二叉搜索树(红黑树)实现的。

set的基本使用

set的构造

函数声明功能介绍
set (const Compare& comp = Compare(), const Allocator&
= Allocator() );
构造空的set

set (InputIterator first, InputIterator last, const Compare&

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

用[first, last)区间中的元素构造set
set ( const set<Key,Compare,Allocator>& x);set的拷贝构造
// set构造
std::set<int> first;                                  // empty set of ints

int myints[]= {10,20,30,40,50};
std::set<int> second (myints,myints+5);               // range
std::set<int> third (second);                         // a copy of second
std::set<int> fourth (second.begin(), second.end());  // iterator ctor.

set的迭代器

虽然set同时定义了iterator和const_iterator类型,但两种类型都只允许只读访问set中的元素,因为set中的关键字是const的。可以用一个set迭代器来读取元素的值,但不能修改

set<int> s = {0,1,2,3,4,5,6,7,8,9};
set<int>::iterator it = s.begin();
if(it != s.end())
{
    //*it = 20;              // 错误,set中的关键字是只读的
    cout << *it << endl;     // 正确,可以读关键字
}

set的修改操作

函数声明功能介绍
pair<iterator,bool> insert (const value_type& k )在set中插入元素k,实际插入的是<k, k>构成的
键值对,如果插入成功,返回<该元素在set中的
位置,true>,如果插入失败,说明k在set中已经
存在,返回<k在set中的位置,false>
void erase ( iterator position )删除set中position位置上的元素
void erase ( iterator first, iterator last )删除set中[first, last)区间中的元素
size_type erase ( const key_type& k )删除set中值为k的元素,返回删除的元素的个数
插入操作

insert操作向容器中添加一个元素或一个元素范围。

vector<int> v = {2,4,6,8,2,4,6,8};
set<int> s;
s.insert(v.begin(), v.end());        // 迭代器
s.insert({1,3,5,7,1,3,5,7});         // 初始化器列表

insert有两个版本,分别接受一对迭代器,或是一个初始化器列表,这两个版本的行为类似对应的构造函数。 

删除操作 

set定义了三个版本的erase,如上表所示。与vector(序列式容器)一样,我们可以通过传递给erase一个迭代器 或者 一个元素范围。这两个版本的erase与对应的序列式容器的操作非常相似:指定元素被删除,函数返回void。

set提供了一个额外的erase操作,此版本删除所有匹配给定关键字的元素(如果存在的话),返回实际删除的元素的数量。就set来说此版本的erase的返回值总是0或者1。若返回0则说明该元素不存在,返回1则说明存在;但是如果在multiset上面,返回值可能会大于1。

set的访问操作

函数说明功能简介
iterator find ( const key_type& k ) const

返回一个迭代器,指向第一个关键字为 k 的元素,

若k不在容器中,则返回尾后迭代器end()

size_type count ( const key_type& k ) const返回关键字等于 k 的元素的数量。
set<int> s = {0,1,2,3,4,5,6,7,8,9};
s.find(1);            // 返回一个迭代器,指向key == 1的元素
s.find(20);           // 返回一个迭代器,其值等于s.end()
s.count(1);           // 返回1
s.count(20);          // 返回0

map

map的基本介绍

  1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
  2. 在map中,键值key通常用于排序和唯一地标识元素,而值value中存储与此键值key关的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:typedef pair<const key, T> value_type;
  3. 在内部,map中的元素总是按照键值key进行比较排序的。
  4. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
  5. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。

map的基本使用

map的构造

函数声明功能介绍
map()构造一个空的map
map (const map& x);拷贝构造

map的迭代器 

注意:map的value_type是一个pair类型,其first成员保存const的关键字,second成员保存值。我们可以改变pair的值,但是不能改变关键字成员的值!!!

map<string, string> m;
// 使用insert插入元素到m
m.insert(pair<string, string>("peach", "桃子"));
m.insert(make_pair("banan", "香蕉"));

map<string, string>::iterator it = m.begin();
// 遍历map
while(it != m.end())
{
    cout << it->first << " " << it->second << endl;// 打印此元素的关键字和值
    //it->first = "apple"                          // 错误,关键字是const
    it->second++;                                  // 正确,可以通过迭代器改变值
}

map的修改操作

函数声明功能简介
pair<iterator,bool> insert (const value_type& k )在map中插入键值对k,注意 k 是一个键值
对,返回值也是键值对:iterator代表新插入
元素的位置,bool代表释放插入成功
void erase ( iterator position )删除map中position位置上的元素
void erase ( iterator first, iterator last )删除map中[first, last)区间中的元素
size_type erase ( const key_type& k )删除map中值为k的元素,返回删除的元素的个数
插入操作

对map进行insert操作时,必须要记住元素类型是一个pair。通常,对于想要插入的数据,并没有一个现成的pair对象。但是可以在insert的参数列表中创建一个pair。

// pair的构造方式
map<string, string> dict;
// 方式一:有名对象
pair<string, string> kv("left", "左边");
dict.insert(kv);
// 方式二:匿名对象
dict.insert(pair < string, string>("right", "右边"));
// 方式三:make_pair函数模板(make_pair帮你构造)
dict.insert(make_pair("up", "上边"));
// 方式四:多参数构造函数-隐式类型转换--中间生成临时对象
pair<string, string> kv1 = { "down", "下边" };
dict.insert({ "down", "下边" });
// 方式五:initializer_list构造
map<string, string> dict = { {"left", "左边"}, {"right", "右边"}, {"up", "上边"} ,{"down", "下边"} };
删除操作

参考set的删除操作

map的访问操作

函数声明功能简介
iterator find ( const key_type& k ) 

返回一个迭代器,指向第一个关键字为 k 的元素,

若k不在容器中,则返回尾后迭代器end()

size_type count ( const key_type& k ) const

返回key为k的键值在map中的个数,注意map中key

是唯一的,所以该函数的返回值要么为0,要么为1,

因此也可以用该函数来检测一个key是否在map中

mapped_type& operator[] (const
key_type& k)
返回key对应的value

ps. 其中find函数与count函数请参见set

再谈insert

前面我们已经了解过insert函数作用和使用场景,下面我们再来谈谈insert的返回值。

        仔细观察insert的函数声明不难发现,其返回值是一个pair。其中pair的first成员是一个迭代器,指向指定关键字的元素;second成员是一个bool值,指出元素是插入成功还是已经存在容器中。如果关键字已在容器中,则insert什么也不做,且其中的bool值为false;如果关键字不存在,元素则被插入到容器中,且bool值为true。

// 单词计数程序
map<string, int> m;
string word;
while(cin >> word)
{
    // 插入一个元素,关键字等于word, 值为1
    // 若word已经在m中,则insert什么也不做
    auto ret = m.insert({word, 1});
    if(!ret.second)                         // word已经在m中
    {
        ++((ret.first)->second);            // 递增计数器
    }
}

上面代码中的if语句检查返回值的bool部分,若为false,则说明插入操作并未发生。

明确了insert函数,下面我们再来谈谈map的下标操作operator[]。

operator[]

map和unordered_map容器提供了下标运算符,简单说,operator[]就是给我一个key,我返回key对应value的引用operator[]文档

上面两张图非常详细的介绍了operator[]的基本原理,希望大家能够理解

// 代码演示
map<string, int> m;
m["kevin"] = 1;
// 将会进行下面的操作
// 在m中搜索关键字为kevin的元素,未找到。
// 将一个新的键值对插入到m中,关键字是一个const string,保存kevin。值进行初始化为0
// 提取出新插入的元素,并赋值为1

对于map的下标操作,其行为与数组或vector上的下标操作很不相同:使用一个不在容器中的关键字作为下标,会添加一个具有此关键字的元素到map中

map的下标操作与其他下标运算符的另一个不同之处就是其返回类型。通常情况下,解引用一个迭代器所返回的类型与下标运算符返回的类型是一样的。但map的下标操作会返回一个key对应value的引用;当解引用map的迭代器时,会得到一个pair对象。

与其他下标运算符相同的是,map的下标运算符返回一个左值。由于返回的是一个左值,所以我们可以读写元素。

cout << m["kevin"];        // 用Kevin作为下标提取元素,会打印出1
++m["kevin"];              // 提取元素,将其增加1
cout << m["kevin"];        // 提取元素并打印,会打印出2
unordered_map与unordered_set有什么区别? 回答: unordered_map和unordered_set都是关联式容器,类似于键值对 (key-value) 的模型。它们的底层实现方式不同,unordered_map使用哈希表作为底层数据结构,而unordered_set也是使用哈希表。unordered_map和unordered_set的区别在于它们存储的类型不同,即unordered_map存储键值对,而unordered_set存储单个元素。此外,unordered_map和unordered_set在功能上也有一些区别。unordered_map提供了以键为索引的查找功能,而unordered_set则提供了判断元素是否存在的功能。从效率上来看,unordered_map和unordered_set的增删查改操作的时间复杂度都是O(1),即常数时间。而mapset的时间复杂度为O(logN),其中N是容器中的元素数量。所以在对效率要求较高的情况下,选择unordered_map和unordered_set会更合适。但是,unordered_map和unordered_set相比于mapset会消耗更多的内存空间。因此,在对数据有排序或者对空间有要求的情况下,选择mapset;而对于对效率有要求的情况,选择unordered_map和unordered_set更合适。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [STL详解(十二)—— unordered_set、unordered_map的介绍及使用](https://blog.csdn.net/chenlong_cxy/article/details/122277348)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [模拟实现unordered_map和unordered_set详解C++)](https://blog.csdn.net/m0_67430750/article/details/124760725)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [unordered_set和unordered_map的使用【STL】](https://blog.csdn.net/m0_63312733/article/details/128000844)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值