【C++】map 容器的使用

文章目录

  • 上文链接
  • 一、参考文档
  • [二、pair 类型](#二、pair 类型)
    • [1. pair 介绍](#1. pair 介绍)
    • [2. 创建与初始化](#2. 创建与初始化)
    • [3. 访问成员](#3. 访问成员)
  • [三、map 介绍](#三、map 介绍)
  • [四、map 构造函数与迭代器](#四、map 构造函数与迭代器)
    • [1. 无参默认构造](#1. 无参默认构造)
    • [2. 列表构造](#2. 列表构造)
    • [3. 迭代器区间构造](#3. 迭代器区间构造)
    • [4. 拷贝构造](#4. 拷贝构造)
    • [5. 迭代器](#5. 迭代器)
  • [五、map 的增删查改](#五、map 的增删查改)
    • [1. insert 插入数据](#1. insert 插入数据)
    • [2. erase 删除数据](#2. erase 删除数据)
    • [3. find 与 count](#3. find 与 count)
    • [4. lower_bound 与 upper_bound](#4. lower_bound 与 upper_bound)
    • [5. [ ] 实现插入与修改](#5. [ ] 实现插入与修改)
  • [六、multimap 与 map 的区别](#六、multimap 与 map 的区别)

上文链接

一、参考文档


二、pair 类型

1. pair 介绍

pair 是 STL 中一个重要的模板类,它可以用于将两个不同类型的值组合成一个单一对象。它定义在头文件 <utility> 中(<map> 等头文件会间接包含它)。

有的时候函数想要返回两个值的时候,我们就可以把这两个值放在一个 pair 中储存并返回。pair 的底层是一个结构体 struct,其包含两个数据成员:

  • first:pair 中的第一个数据。
  • second:pair 中的第二个数据。

这两个成员是公有的,可直接访问和修改。并且这两个数据的类型可以是任意的,比如:pair<int, string>pair<float, vector<bool>> 等。


2. 创建与初始化

cpp 复制代码
// 方式1:默认构造函数(值初始化)
std::pair<int, double> p1;  // p1.first=0, p1.second=0.0

// 方式2:直接初始化
std::pair<std::string, int> p2("Alice", 25);

// 方式3:使用 make_pair(自动类型推导)
auto p3 = std::make_pair(3.14, "Pi");

// 方式4:C++11统一初始化
std::pair<int, char> p4 = { 65, 'A' };

3. 访问成员

cpp 复制代码
std::pair<int, std::string> student(101, "Bob");

// 直接访问公有成员
int id = student.first;             // 101
std::string name = student.second;  // "Bob"

// 修改值
student.second = "Robert"; 

三、map 介绍

STL 中的 map 容器是一个非常重要的关联式容器,它以键值对 (key-value) 的形式存储元素。并且它的底层是一棵红黑树,也就是一个平衡的二叉搜索树,内部的默认比较规则是按照键 (key) 的升序进行的。

map 中存储的每一个键值对是一个 pair 类型:std::pair<const Key, T>Key 是键的类型,T 是值的类型。比如:<"apple", 苹果><"banana", "香蕉">

除此之外,map 中的每一个键只能有一个,如果一个键已经存在了,再插入一个相同的键就会失败。


四、map 构造函数与迭代器

注:下面是 map 类中一些类型的原型

cpp 复制代码
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type,mapped_type>

1. 无参默认构造

cpp 复制代码
// empty 无参默认构造 
explicit map (const key_compare& comp = key_compare(),
			  const allocator_type& alloc = allocator_type());
cpp 复制代码
// 示例
map<string, int> m1;

2. 列表构造

cpp 复制代码
// initializer list 列表构造 
map (initializer_list<value_type> il,
cpp 复制代码
// 示例
map<string, int> score = 
{
	{"张三", 121},
	{"李四", 127},
	{"王五", 120}
};

for (auto& e : score2) cout << e.first << ":" << e.second << endl;
cpp 复制代码
// 输出
李四:127
王五:120
张三:121

注:上面的输出结果的顺序是按照键的字典序排升序时候的结果。


3. 迭代器区间构造

cpp 复制代码
// range 迭代器区间构造 
template <class InputIterator>
map (InputIterator first, InputIterator last,
 	 const key_compare& comp = key_compare(),
	 const allocator_type& = allocator_type());
cpp 复制代码
// 示例
map<string, int> score = 
{
	{"张三", 121},
	{"李四", 127},
	{"王五", 120}
};

map<string, int> score2(score.begin(), score.end());

for (auto& e : score2) cout << e.first << ":" << e.second << endl;
cpp 复制代码
// 输出
李四:127
王五:120
张三:121

4. 拷贝构造

cpp 复制代码
// copy 拷⻉构造 
map (const map& x);
cpp 复制代码
// 示例
map<string, double> m1({ { "pi", 3.14 }, {"e", 2.718} });
map<string, double> m2(m1);

for (auto& e : score2) cout << e.first << ":" << e.second << endl;
cpp 复制代码
// 输出
e:2.718
pi:3.14

5. 迭代器

cpp 复制代码
// 迭代器是⼀个双向迭代器 
iterator -> a bidirectional iterator to const value_type

// 正向迭代器 
iterator begin();
iterator end();

// 反向迭代器 
reverse_iterator rbegin();
reverse_iterator rend();

五、map 的增删查改

1. insert 插入数据

  • 单个数据插入,如果 key 已经存在则插入失败,key 存在但 value 不相等也会插入失败。
cpp 复制代码
pair<iterator,bool> insert (const value_type& val);

这个函数的返回值是一个 pair 类型,分为两种情况:

  • 如果插入成功,返回 <插入的该数据对应的迭代器, true>
  • 如果插入失败,返回 <已经存在的该数据对应的迭代器, false>
cpp 复制代码
// 示例
map<string, string> dict = 
{
	{"left", "左边"}, 
	{"right", "右边"},
	{"insert", "插入"},
	{ "string", "字符串" } 
};

auto it = dict.begin();
while (it != dict.end())
{
	// cout << (*it).first << ":"<< (*it).second << endl;
	cout << it->first << ":" << it->second << endl;
	++it;
}
cout << endl;

// insert 插入 pair 对象的 4 种⽅式,对比之下,最后一种最方便 
pair<string, string> kv1("first", "第一个");
dict.insert(kv1);
dict.insert(pair<string, string>("second", "第二个"));
dict.insert(make_pair("sort", "排序"));
dict.insert({ "auto", "自动的" });

// "left" 已经存在,插入失败 
dict.insert({ "left", "左边;剩余" });

for (const auto& e : dict) cout << e.first << ":" << e.second << endl;
cout << endl;
cpp 复制代码
// 输出
insert:插入
left:左边
right:右边
string:字符串

auto:自动的
first:第一个
insert:插入
left:左边
right:右边
second:第二个
sort:排序
string:字符串

  • 列表插入,已经在容器中存在的不会插入
cpp 复制代码
void insert (initializer_list<value_type> il);
cpp 复制代码
// 示例
map<string, int> inventory = 
{
    {"Apple", 50},
    {"Banana", 30}
};

inventory.insert(
{
    {"Orange", 40},
    {"Grapes", 60}
});

  • 迭代器区间插入,已经在容器中存在的不会插入。
cpp 复制代码
void insert (InputIterator first, InputIterator last);
cpp 复制代码
// 示例
map<string, int> inventory = 
{
    {"Apple", 50},
    {"Banana", 30}
};

std::map<std::string, int> discountItems = 
{
    {"Banana", 10},    // 重复键,不会插入
    {"Watermelon", 5}
};
    
inventory.insert(discountItems.begin(), discountItems.end());

2. erase 删除数据

  • 删除元素 kk 不存在返回 0,存在则返回 1。
cpp 复制代码
size_type erase (const key_type& k);
  • 删除一个迭代器位置的值,返回被删除位置下一个位置的迭代器。
cpp 复制代码
iterator erase (const_iterator position);
  • 删除一段迭代器区间的值。
cpp 复制代码
iterator erase (const_iterator first, const_iterator last);

示例:

cpp 复制代码
map<string, string> dict =
{
	{"left", "左边"},
	{"right", "右边"},
	{"insert", "插入"},
	{ "string", "字符串" },
	{ "auto", "自动的" }
};

for (const auto& e : dict) cout << e.first << ":" << e.second << endl;
cout << endl;

dict.erase("right");
for (const auto& e : dict) cout << e.first << ":" << e.second << endl;
cout << endl;

auto it1 = dict.find("string");
dict.erase(it1);
for (const auto& e : dict) cout << e.first << ":" << e.second << endl;
cout << endl;

auto it2 = dict.find("insert");
dict.erase(it2, dict.end());
for (const auto& e : dict) cout << e.first << ":" << e.second << endl;
cout << endl;
cpp 复制代码
auto:自动的
insert:插入
left:左边
right:右边
string:字符串

auto:自动的
insert:插入
left:左边
string:字符串

auto:自动的
insert:插入
left:左边

auto:自动的

3. find 与 count

  • find:查找键 k,找到了返回对应的迭代器,没有找到返回 end()
cpp 复制代码
iterator find (const key_type& k);
cpp 复制代码
// 示例
map<string, string> dict =
{
	{"left", "左边"},
	{"right", "右边"},
	{"insert", "插入"},
	{ "string", "字符串" }
};

string s;
cin >> s;

auto it = dict.find(s);
if (it != dict.end())
{
	dict.erase(it);
	cout << "删除成功" << endl;
	for (const auto& e : dict) cout << e.first << ":" << e.second << endl;
}
else
{
	cout << s << "不存在" << endl;
}
cpp 复制代码
// 输入1
right
// 输出1
删除成功
insert:插入
left:左边
string:字符串
    
// 输入2
auto
// 输出2
auto不存在

  • count:查找键 k,返回其个数。
cpp 复制代码
size_type count (const key_type& k) const;

4. lower_bound 与 upper_bound

  • lower_bound:返回大于等于 k 位置的迭代器。
  • upper_bound:返回大于 k 位置的迭代器。
cpp 复制代码
iterator lower_bound (const key_type& k);
const_iterator lower_bound (const key_type& k) const;

iterator upper_bound (const key_type& k);
const_iterator upper_bound (const key_type& k) const;
cpp 复制代码
// 示例
map<char, int> m =
{
	{ 'a', 20 },
	{ 'b', 15 },
	{ 'c', 30 },
	{ 'd', 50 },
	{ 'e', 40 }
};

auto it_l = m.lower_bound('b');
auto it_u = m.upper_bound('d');

cout << it_l->first << ":" << it_l->second << endl;
cout << it_u->first << ":" << it_u->second << endl;
cpp 复制代码
// 输出
b:15
e:40

5. [ ] 实现插入与修改

在 map 中是不允许修改 key 的,因为 map 的底层是一棵二叉搜索树,它是按照 key 来进行排序的,修改了 key 就破坏了搜索树的结构。但是我们可以修改 key 对应的 value,如何修改?。

其中一种修改的方式时通过迭代器,用迭代器遍历或者用 find 返回 key 所在的 iterator 进行修改,map 还有一个非常重要的修改接口 operator[],但是 operator[] 不仅可以持修改对应的 value,还支持插入数据和查找数据,所以他是一个多功能复合接口。

cpp 复制代码
map<string, int> price
{
	{"苹果", 20},
	{"香蕉", 30},
	{"橙子", 15},
	{"西瓜", 35}
};

for (const auto& e : price) cout << e.first << ":" << e.second << endl;
cout << endl;

// 修改已经存在的数据
price["苹果"] += 5;  // 修改
cout << "苹果:" << price["苹果"] << endl << endl;

// 如果不存在,则会自动插入。若没有赋值,则插入后的对应值为该类型的默认值,如: int 为 0,double 为 0.0
price["芒果"];  // 插入
for (const auto& e : price) cout << e.first << ":" << e.second << endl;
cout << endl;

price["葡萄"] = 30;  // 插入 + 修改
for (const auto& e : price) cout << e.first << ":" << e.second << endl;
cpp 复制代码
// 输出
橙子:15
苹果:20
西瓜:35
香蕉:30

苹果:25

橙子:15
芒果:0
苹果:25
西瓜:35
香蕉:30

橙子:15
芒果:0
苹果:25
葡萄:30
西瓜:35
香蕉:30

六、multimap 与 map 的区别

multimap 和 map 的使用基本是一样的,主要区别点在于 multimap 支持关键值 key 的重复,那么 insert/find/count/erase 都围绕着支持关键值 key 重复有所差异,这里跟 set 和 multiset 完全一样。比如 find时,有多个 key,返回中序遍历的第一个 key。其次就是 multimap 不支持 []