C++中map和multimap容器深入学习,轻松掌握
引言
💡 作者简介:专注分享高性能服务器后台开发技术知识,涵盖多个领域,包括C/C++、Linux、网络协议、设计模式、中间件、云原生、数据库、分布式架构等。目标是通过理论与代码实践的结合,让世界上看似难以掌握的技术变得易于理解与掌握。
💡 公众号:Lion 莱恩呀。
👉
🎖️ CSDN实力新星、专家博主,阿里云博客专家、华为云云享专家
👉
🔔 专栏介绍:从零到c++精通的学习之路。内容包括C++基础编程、中级编程、高级编程;掌握各个知识点。
👉
🔔 专栏地址:C++从零开始到精通
👉
🔔 博客主页:https://blog.csdn.net/Long_xu
一、简介
在C++中,map
和multimap
容器是非常重要的数据结构,它们提供了一种键值对的映射关系,可以高效地组织和访问数据。map
容器中的每个元素都包含一个键和一个值,而multimap
容器允许键重复。
这两种容器在实际项目中广泛应用,特别适合需要快速查找和插入元素的场景。其底层实现采用了红黑树等高效的数据结构,map
和multimap
容器在处理大量数据时具有良好的性能表现。它们也提供了丰富的操作方法和函数,可以轻松地对容器中的数据进行插入、删除、查找和遍历操作。
深入理解map
和multimap
容器的使用方法和性能特点可以设计高效的数据结构和算法,提高程序的性能和可维护性。
二、map和multimap容器的基本概念
map
的特性是,所有元素都会根据元素的键值自动排序。map
所有的元素都是pair
,同时拥有实值和键值,pair
的第一元素被视为键值,第二元素被视为实值,map
不允许两个元素有相同的键值。我们可以通过map
的迭代器改变map
的键值吗?答案是不行,因为 map
的键值关系到 map
元素的排列规则,任意改变map
键值将会严重破坏map
数据组织。如果想要修改元素的实值,那么是可以的。
map
和list
拥有相同的某些性质,当对它的容器元素进行新增操作或者删除操作时,操作之前的所有迭代器,在操作完成之后依然有效,当然被删除的那个元素的迭代器必然是个例外。
map
容器提供了快速的查找、插入和删除操作,时间复杂度为O(log n)
。
multimap
与map
类似,也是键-值
对的容器,不同之处在于它允许重复的键。multimap
容器同样是有序的,使用红黑树来组织数据,提供了快速的查找、插入和删除操作,时间复杂度为O(log n)
。
map
容器:每个元素都是键值-实值成对存储,自动根据键值排序,键值不能重复,不能修改。
multimap
和map
的操作类似,唯一区别 multimap 键值可重复 。map
和multimap
都是以红黑树为底层实现机制。
三、map和multimap容器的基本操作
3.1、常用的接口函数API
(1)构造函数:
// 默认构造
map<Key, T> m; //默认构造一个空的map,键的类型为Key,值的类型为T。
multimap<Key, T> mm;//默认构造一个空的multimap,键的类型为Key,值的类型为T。
// 带有比较函数参数的构造函数:
map< Key, T, Compare> m (const Compare& comp); //通过指定比较函数comp来构造map。
multimap< Key, T, Compare> mm (const Compare& comp);//通过指定比较函数comp来构造multimap。
// 区间构造函数:
map< Key, T> m (InputIterator first, InputIterator last); //使用迭代器范围[first, last)内的元素来构造map。
multimap< Key, T> mm (InputIterator first, InputIterator last) //使用迭代器范围[first, last)内的元素来构造multimap。
//拷贝构造函数:
map< Key, T> m (const map& x); //拷贝构造一个map,其元素和x相同。
multimap< Key, T> mm (const multimap& x); //拷贝构造一个multimap,其元素和x相同。
//移动构造函数(C++11引入):
map< Key, T> m (map&& x); //移动构造一个map,取得x的资源但留下x为空。
multimap< Key, T> mm (multimap&& x);//移动构造一个multimap,取得x的资源但留下x为空。
(2)赋值操作:
// 赋值运算符:
map& operator=(const map& x); //赋值运算符重载,用x的内容替换当前map的内容。
multimap& operator=(const multimap& x); //赋值运算符重载,用x的内容替换当前multimap的内容。
// swap函数:
void swap(map& x); //交换当前map和map x的内容。
void swap(multimap& x); //交换当前multimap和multimap x的内容。
(3)大小操作:
// size函数:
size_type size() const; //返回map或multimap中元素的个数。
// empty函数:
bool empty() const; //如果map或multimap为空,则返回true,否则返回false。
(4)插入数据元素操作:
// insert函数:
pair<iterator, bool> insert(const value_type& val); //在map中插入val,返回一个pair,第一个元素是一个迭代器,指向新插入的元素或者已存在的相同键的元素,第二个元素是一个bool值,表示插入是否成功。
iterator insert(iterator position, const value_type& val); //在position位置插入val,并返回插入元素的迭代器。
// emplace函数:使用传入的参数构造元素并插入map中,返回一个pair,
// 第一个元素是一个迭代器,指向新插入的元素或者已存在的相同键的元素,第二个元素是一个bool值,表示插入是否成功。
template <class... Args> pair<iterator, bool> emplace(Args&&... args);
// insert_or_assign函数(C++17及以上版本):如果k存在,则将其关联的值替换为obj,如果不存在则创建一个新的键值对,并返回一个pair,
// 第一个元素是一个迭代器,指向新插入或修改的元素,第二个元素是一个bool值,表示是插入还是修改操作。
- template <class M> pair<iterator, bool> insert_or_assign(const key_type& k, M&& obj);
(5)删除操作:
// erase函数:
iterator erase(iterator position); //删除指向位置position的元素,并返回指向被删元素之后的下一个元素的迭代器。
size_type erase(const key_type& k); //删除所有键为k的元素,并返回被删除的元素的数量。
iterator erase(iterator first, iterator last); //删除[first, last)范围内的所有元素,并返回指向被删元素之后的下一个元素的迭代器。
// clear函数:删除map或multimap中的所有元素。
void clear();
(6)查找操作:
// find函数:
iterator find(const key_type& k); //查找键为k的元素,并返回指向该元素的迭代器,如果未找到则返回指向尾部的迭代器。
const_iterator find(const key_type& k) const; //在const map或multimap中查找键为k的元素,并返回指向该元素的const迭代器。
// count函数:返回map或multimap中键等于k的元素的个数。
size_type count(const key_type& k) const;
// lower_bound函数:返回一个迭代器,指向第一个不小于k的元素。
iterator lower_bound(const key_type& k);
// upper_bound函数:返回一个迭代器,指向第一个大于k的元素。
iterator upper_bound(const key_type& k);
// equal_range函数:返回一个pair,包含两个迭代器,第一个迭代器指向键值等于k的第一个元素,第二个迭代器指向键值大于k的第一个元素。
pair<iterator, iterator> equal_range(const key_type& k);
(7)访问元素:
operator[]
:通过键访问对应的值。at(key)
:通过键访问对应的值,如果不存在会抛出out_of_range
异常。find(key)
:查找键为key
的元素,返回指向该元素的迭代器。
3.2、使用示例
(1)创建和初始化map
和multimap
容器:
#include <iostream>
#include <map>
#include <unordered_map>
int main() {
// 创建并初始化map容器
std::map<int, std::string> myMap = {
{1, "One"},
{2, "Two"},
{3, "Three"}
};
// 创建并初始化multimap容器
std::multimap<int, std::string> myMultimap = {
{1, "Apple"},
{2, "Banana"},
{2, "Orange"}
};
// 输出map容器内容
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
// 输出multimap容器内容
for (const auto& pair : myMultimap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
(2)插入和删除元素:
#include <iostream>
#include <map>
#include <unordered_map>
int main() {
// 创建并初始化map容器
std::map<int, std::string> myMap = {
{1, "One"},
{2, "Two"},
{3, "Three"}
};
// 插入元素到map中
myMap.insert(std::make_pair(4, "Four"));
myMap[5] = "Five"; // 或者使用下标操作符[]
// 输出map容器内容
std::cout << "Map after insertions:" << std::endl;
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
// 删除元素从map中
myMap.erase(2); // 删除键为2的元素
// 输出map容器内容
std::cout << "\nMap after deletion:" << std::endl;
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
// 创建并初始化multimap容器
std::multimap<int, std::string> myMultimap = {
{1, "Apple"},
{2, "Banana"},
{2, "Orange"}
};
// 插入元素到multimap中
myMultimap.insert(std::make_pair(3, "Peach"));
// 输出multimap容器内容
std::cout << "\nMultimap after insertions:" << std::endl;
for (const auto& pair : myMultimap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
// 删除元素从multimap中
// 删除multimap中所有键为2的元素
auto it = myMultimap.find(2);
myMultimap.erase(it, myMultimap.end());
// 输出multimap容器内容
std::cout << "\nMultimap after deletion:" << std::endl;
for (const auto& pair : myMultimap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
(3)查找和访问元素:
#include <iostream>
#include <map>
#include <unordered_map>
int main() {
// 创建并初始化map容器
std::map<int, std::string> myMap = {
{1, "One"},
{2, "Two"},
{3, "Three"}
};
// 查找和访问map中的元素
// 使用find函数查找键为2的元素
auto it = myMap.find(2);
if (it != myMap.end()) {
std::cout << "Element with key 2 found: " << it->second << std::endl;
} else {
std::cout << "Element with key 2 not found" << std::endl;
}
// 修改元素的值
myMap[1] = "One (updated)";
// 输出map容器内容
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
// 创建并初始化multimap容器
std::multimap<int, std::string> myMultimap = {
{1, "Apple"},
{2, "Banana"},
{2, "Orange"}
};
// 查找和访问multimap中的元素
// 使用equal_range函数查找键为2的元素
auto range = myMultimap.equal_range(2);
for (auto it = range.first; it != range.second; ++it) {
std::cout << "Element with key 2 found: " << it->second << std::endl;
}
return 0;
}
(4)遍历容器的所有元素:
#include <iostream>
#include <map>
#include <unordered_map>
int main() {
// 创建并初始化map容器
std::map<int, std::string> myMap = {
{1, "One"},
{2, "Two"},
{3, "Three"}
};
// 遍历map容器的所有元素
std::cout << "Iterating through map:" << std::endl;
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
// 创建并初始化multimap容器
std::multimap<int, std::string> myMultimap = {
{1, "Apple"},
{2, "Banana"},
{2, "Orange"}
};
// 遍历multimap容器的所有元素
std::cout << "\nIterating through multimap:" << std::endl;
for (const auto& pair : myMultimap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
3.3、性能分析
map
和multimap
都是关联容器,它们都基于红黑树实现。它们之间的主要区别在于:
map
中的每个键都是唯一的,而multimap
允许多个具有相同键的元素存在。multimap
中的元素在树中是按键值排序的,而map
中的元素则按照键的比较函数进行排序。
从性能上来说,map
通常比multimap
具有更好的性能,因为map
中的键是唯一的,可以更快地进行查找和访问操作。
如何选择:
- 要保持键的唯一性,应该选择
map
。 - 要允许多个具有相同键的元素存在,那么就应该选择
multimap
。
四、map和multimap容器的高级操作
(1)自定义比较函数和排序:在默认情况下,map
和multimap
容器使用元素的键值进行排序,但是有时候要根据不同的标准进行排序,这就需要自定义比较函数。
自定义比较函数需要满足严格弱顺序(Strict Weak Ordering)的要求:
- 反对称性:如果 a 不小于 b 且 b 不小于 a,则 a 等于 b。
- 传递性:如果 a 小于 b 且 b 小于 c,则 a 也小于 c。
- 可以相等:两个元素可以相等。
示例:
#include <iostream>
#include <map>
#include <string>
struct CustomCompare {
bool operator() (const std::string& a, const std::string& b) const {
// 根据字符串长度进行排序
return a.length() < b.length();
}
};
int main() {
std::map<std::string, int, CustomCompare> myMap = {
{"one", 1},
{"three", 3},
{"two", 2}
};
for (const auto& pair : myMap) {
std::cout << pair.first << " : " << pair.second << std::endl;
}
return 0;
}
(2)处理重复键(multimap):遍历查找特定键的所有值。
std::multimap<int, std::string> myMultimap;
myMultimap.insert({1, "one"});
myMultimap.insert({2, "two"});
myMultimap.insert({1, "first"}); // 在键为1的位置再插入一个值
int keyToFind = 1;
auto range = myMultimap.equal_range(keyToFind);
for (auto it = range.first; it != range.second; ++it) {
std::cout << it->first << " : " << it->second << std::endl;
}
(3)对map和multimap容器中的数据进行排序:
-
对
map
容器进行元素值排序:可以将map
中的键值对元素复制到一个vector
中,然后使用自定义的比较函数对vector
进行排序:std::map<int, std::string> myMap = { {2, "two"}, {1, "one"}, {3, "three"} }; std::vector<std::pair<int, std::string>> vec(myMap.begin(), myMap.end()); std::sort(vec.begin(), vec.end(), [](const std::pair<int, std::string>& a, const std::pair<int, std::string>& b) { return a.second < b.second; });
-
对
multimap
容器进行元素值排序:由于multimap
允许重复的键值,我们可以使用相似的方法将multimap
中的元素复制到一个vector
中,并使用自定义的比较函数对vector
进行排序:std::multimap<int, std::string> myMultiMap = { {2, "two"}, {1, "one"}, {3, "three"}, {2, "second"} }; std::vector<std::pair<int, std::string>> vec(myMultiMap.begin(), myMultiMap.end()); std::sort(vec.begin(), vec.end(), [](const std::pair<int, std::string>& a, const std::pair<int, std::string>& b) { return a.second < b.second; });
这样就可以使用自定义的比较函数对 map
和 multimap
容器中的元素值进行排序。在需要按照元素值而不是键值进行排序时,这些方法都比较有用。
五、代码实践
(1)map
容器的使用。
#include <iostream>
#include <map>
#include <utility>
#include <string>
using namespace std;
class Person
{
friend void test01();
friend void printAll(map<int,Person> &m);
private:
int num;
string name;
float score;
public:
Person(){}
Person(int num,string name,float score){
this->num = num;
this->name = name;
this->score = score;
}
};
void printAll(map<int,Person> &m)
{
for(auto &[key,value]:m)
{
cout<<value.name<<","<<value.num<<","<<value.score<<endl;
}
}
void test01(){
map<int,Person> m;
//方式1
m.insert(pair<int,Person> (103,Person(103,"Lion",100.1)));
// 方式二(推荐)
m.insert(make_pair(102,Person(102,"Lion2",100.1)));
// 方式三
m.insert(map<int,Person>::value_type(101,Person(101,"Lion3",100.1)));
// 方式四(危险)
m[100]=Person(100,"FLY",100.1);
printAll(m);
// 演示方式四的危险性。
cout<<"***********************"<<endl;
cout<<m[107].name<<","<<m[107].num<<","<<m[107].score<<endl;
printAll(m);
}
int main(int argc, char *argv[]){
test01();
return 0;
}
输出:
FLY,100,100.1
Lion3,101,100.1
Lion2,102,100.1
Lion,103,100.1
***********************
,0,0
FLY,100,100.1
Lion3,101,100.1
Lion2,102,100.1
Lion,103,100.1
,0,0
(2)multimap
的示例。
#include <iostream>
#include <map>
#include <string>
int main() {
// 创建一个 multimap 容器,键是 int 型,值是 string 型
std::multimap<int, std::string> myMultiMap;
// 向 multimap 中插入元素
myMultiMap.insert(std::make_pair(1, "apple"));
myMultiMap.insert(std::make_pair(2, "banana"));
myMultiMap.insert(std::make_pair(3, "apple")); // 重复的键值
// 遍历 multimap 并打印所有元素
std::cout << "All elements in the multimap:" << std::endl;
for (auto it = myMultiMap.begin(); it != myMultiMap.end(); ++it) {
std::cout << it->first << " : " << it->second << std::endl;
}
// 查找特定键值对应的所有元素
int keyToFind = 1;
auto range = myMultiMap.equal_range(keyToFind);
std::cout << "All elements with key " << keyToFind << ":" << std::endl;
for (auto it = range.first; it != range.second; ++it) {
std::cout << it->first << " : " << it->second << std::endl;
}
return 0;
}
六、总结
map和multimap是C++标准库中非常重要且常用的容器类型,它们都是关联式容器(Associative Containers),用于实现键值对映射。
-
map和multimap容器使用红黑树实现,因此能够在O(log n)的时间复杂度内进行查找和检索操作,这使得它们非常适合于需要快速查找特定键值的应用场景。
-
这两种容器中的元素默认按照键值进行排序,map要求键值唯一,而multimap允许键值重复。这使得它们非常适合于建立有序的键值对映射,使得对数据的处理更加方便。
-
由于其快速查找和自动排序的特性,map和multimap容器可以应用于各种类型的问题,如数据存储、索引管理、关联规则等。
-
map和multimap容器提供了各种方法来插入、删除和修改元素,同时它们还提供了丰富的迭代器接口和算法,对数据的操作更加灵活和方便。