文章目录
map容器的基本性质
作为关联式容器的一种,map容器存储的都是pair对象,也就是用pair类模板创建的键值对。其中各个键值对的键和值可以是任意数据类型,包括基本输出类型、结构体或自定义的类型。通常情况下,map容器中存储的各个键值对一般使用string字符串作为键的类型。
在使用map容器存储多个键值对时,该容器会根据各个键值对的键的大小,按照既定的规则进行排序。
默认情况下,map容器选用std::less<T>排序规则(其中T表示键的数据类型),会根据键的大小对所有键值做升序排序。
当然,根据实际形况需要,也可以手动指定map容器的排序规则,选用STL标准库中提供的其他排序规则(例如std::greater<T>),也可以自定义排序规则。
另外,使用map容器存储的各个键值对时,键的值既不能重复也不能被修改。也就是说,map容器中存储的各个键值对不仅键的值独一无二,键的类型也会用const修饰,意味着只要键值对被存储到map容器中,其键的值将不能在做任何修改。
前面提到map容器存储的都是pair类型的键值对元素,更确切的说是pair<const K, T>类型(其中K和T分别表示键和值的数据类型)的键值对元素。
STL —— map的用法
map容器定义在<map>头文件中,并位于std命名空间中,如果想要使用map容器,应该引入头文件:
#include<map>
map容器的模板定义如下:
template < class Key, // 指定键(key)的类型
class T, // 指定值(value)的类型
class Compare = less<Key>, // 指定排序规则
class Alloc = allocator<pair<const Key,T> > // 指定分配器对象的类型
> class map;
其中后2个参数都设有默认值,大多数时候只需设置前2个参数的值,有时候会用到第3个参数,最后一个参数几乎不会用到。
创建map容器
map容器的模板类中包含多种构造函数,因此创建map容器的方式也有多种。
1)调用map容器类的默认构造函数map(),创建一个空的map容器
map<K,V> myMap; //创建一个名为myMap的空map对象,其键和值的类型分别为K和V,例如:
map<string, int> myMap;
通过这个方式创建的map容器,初始状态下是空的,即没有存储任何键值对。
2)当然也可以在创建map容器的同时进行初始化
map<string, int> myMap{{"penny",1},{"leonard",2}};
经过上述操作,myMap在初始状态下,就包含2个键值对。
当然,map容器中存储的键值对,其本质都是pair类模板创建的pair对象。因此,下面的代码也可以创建出一样的myMap容器:
map<string, int> myMap{make_pair("penny", 1),make_pair("leonard", 2)};
3)拷贝(复制)构造函数
map<K, V> myMap1(myMap); //利用myMap创建一个名为myMap1的新容器,其键和值的元素类型均相同。
>map<string, int> myMap{{"penny",1},{"leonard",2}};
>map<string, int> myMap1(myMap);
4)获取已建 map 容器中指定区域内的键值对,创建并初始化新的 map 容器。
map<string, int> myMap{make_pair("penny", 1),make_pair("leonard", 2)};
map<string, int> myMap1(++myMap.begin(), myMap.end());
执行完上述代码后,myMap1容器中包含的键值对为{“penny”,1}。
在以上几种创建map容器的基础上,也可以手动修改map容器的排序规则。
默认情况下,map容器调用std::less<T>规则,根据容器内各键值对的键的大小,对所有的键值进行升序排序。
因此,下面两行代码是等价的:
map<string, int> myMap{{"penny",1},{"leonard",2}};
map<string, int, std::less<std::string>> myMap{{"penny",1},{"leonard",2}};
//若程序中已经默认指定了std命名空间,故也可以省略std::
若想要元素降序排序,则:
map<string, int, std::greater<std::string>> myMap{{"leonard",2},{"penny",1}};
执行完该语句,myMap中的键值对排列顺序为:{“penny”,1},{“leonard”,2}
容器的大小
map容器中提供了count()、max_size()、size()分别用于统计元素的个数、求容器的最大存储容量和容器大小。
此外,还可以用empty()判断当前map容器是否为空。
1)count(key),统计键的值为key的元素的个数,由于map中没有重复的元素,因此其计算结果只有0和1,而multimap会有多个值。
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3},{"howard",4}};
int num = myMap.count("penny");
cout<<num<<endl; //1
2)max_size()用于求容器最大存储量。通常与机器本身的限制有关。
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3},{"howard",4}};
cout<<myMap.max_size()<<endl; //256204778801521550
3)size()用于统计当前容器的大小,也就是容器中键值对的个数。
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3},{"howard",4}};
cout<<myMap.size()<<endl; //4
4)empty()用于判断当前map容器是否为空。
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3},{"howard",4}};
cout<<myMap.empty()<<endl; //0
map容器中键值对的访问与遍历
无论是序列式容器还是关联式容器,要想实现遍历操作,就必须要用到该类型容器的迭代器。
C++ STL标准库为map容器配备的是双向迭代器,这意味着map容器迭代器只能行++p、p++、–p、p–、*p操作,并且迭代器之间只能使用==或者!=运算符进行比较。
map容器迭代器中成员方法:
成员方法 | 功能 |
---|---|
begin() | 返回指向容器中第一个(已排好序的第一个)键值对的双向迭代器。 |
end() | 返回指向容器中最后一个元素(已排好序的最后一个)所在位置的后一个位置的双向迭代器。 |
rbegin() | 返回指向容器中最后一个(已排好序的最后一个)元素的反向双向迭代器。 |
rend() | 返回指向容器中第一个(已排好序的第一个)元素所在位置的前一个位置的反向双向迭代器。 |
cbegin() | 和begin()功能相同,只不过在其基础上,增加了const属性,不能用于修改容器内储存的键值对。 |
cend() | 和end()功能相同,只不过在其基础上,增加了const属性,不能用于修改容器内储存的键值对。 |
crbegin() | 和rbegin()功能相同,只不过在其基础上,增加了const属性,不能用于修改容器内储存的键值对。 |
crend() | 和rend()功能相同,只不过在其基础上,增加了const属性,不能用于修改容器内储存的键值对。 |
find(key) | 在map容器中查找键为key的键值对,若成功找到,则返回指向该键值对的双向迭代器;若未找到,则返回和end()方法一样的迭代器。 |
lower_bound(key) | 返回一个指向当前map容器中第一个大于或等于key的键值对的双向迭代器。 |
upper_bound(key) | 返回一个指向当前map容器中第一个大于key的键值对的双向迭代器。 |
equal_range(key) | 返回一个pair对象(包含2个双向迭代器),其中pair.first和lower_bound()方法的返回值等价,pair.second和upper_bound()方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为key的键值对(map容器键值对唯一,因此该返回最多包含一个键值对)。 |
关于begin() 、end()、rbegin()、rend()函数的功能示意图:
图中Ei表示的是pair类对象,即键值对。
遍历map容器
以begin()/end()组合为例,遍历map容器:
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
map<string, int> myMap{{"penny",1},{"leonard",2},{"sheldon",3}};
for(auto iter = myMap.begin(); iter != myMap.end(); iter++){
cout<<iter->first<<" "<<iter->second<<endl;
}
return 0;
}
执行结果:
也可以用其他组合,例如cbegin()/cend()、rbegin()/rend()等遍历map容器。
find(key)查找指定key值的键值对
find()方法可以查找指定key值的键值对,如果成功找到则返回一个指向该键值对的双向迭代器;反之,其功能和end()方法相同。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3}};
auto iter = myMap.find("sheldon");
//从iter开始,遍历map容器
for(; iter != myMap.end(); iter++){
cout<<iter->first<<" "<<iter->second<<endl;
}
return 0;
}
执行结果:由上一个遍历可知,要查找的键值对排序在最后一个,故从最后一个开始遍历map会得到以下结果:
lower_bound(key)与upper_bound(key)
lower_bound(key)返回的是指向第一个键大于或等于(不小于)key的键值对的迭代器。
upper_bound(key)返回的是指向第一个键大于key的键值对的迭代器。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3}};
//找到第一个键的值不小于"penny"的键值对
auto iter = myMap.lower_bound("penny");
cout<<iter->first<<" "<<iter->second<<endl;
//找到第一个键的值大于"penny"的键值对
iter = myMap.upper_bound("penny");
cout<<iter->first<<" "<<iter->second<<endl;
return 0;
}
执行结果:
equal_range(key)
equal_range(key)方法可以看作是lower_bound(key)和upper_bound(key)的结合体,该方法会返回一个pair对象,其中的2个元素都是迭代器类型,其中pair.first实际上就是lower_bound(key)的返回值,而pair.second则等同于upper_bound(key)的返回值。
显然,equal_range(key)方法表示的一个范围,位于此范围中的键值对,其键的值都为key,而map中的键都是唯一的,故返回的范围内最多只有一个键值对。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3}};
//创建一个pair对象,来接收equal_range()的返回值
pair<map<string,int>::iterator, map<string,int>::iterator> myPair;
myPair = myMap.equal_range("leonard");
for(auto iter = myPair.first; iter!=myPair.second; iter++){
cout<<iter->first<<" "<<iter->second<<endl;
}
return 0;
}
执行结果:
map获取键对应的值
map容器的类模板中提供了以下2种方法,可以直接获取容器指定键对应的值。
[ ]运算符
1)map类模板中对[ ]运算符进行了重载,类似于借助数组下标可以直接访问数组中的元素,通过指定的键,可以获取该map容器中该键对应的值。例如:
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3}};
int value = myMap["penny"];
cout<<value<<endl; //1
return 0;
}
注意,只有当map容器中包含该指定键的键值对时,借助重载的[ ]运算符才能成功获取该键对应的值;否则,若当前map容器中没有包含该指定键的键值对,则此时使用[ ]运算符将不再是访问容器中的元素,而变成了向该map容器中增添一个键值对。
其中,该键值对的键用[ ]运算符中指定的键,其对应的值取决于map容器中规定键值对中值的数据类型,如果是基本数据类型,则值为0;如果是string类型,其值为" ",即空字符串,也就是说使用该类型的默认值作为键值对的值。
at()成员方法
2)at()成员方法也可以获取指定键对应的值。和通过[ ]运算符获取方法不同的是:如果在当前容器查找失败,该方法不会向容器中添加新的键值对,而是直接抛出out_of_range异常。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3}};
int value = myMap.at("penny");
cout<<value<<endl;
value = myMap.at("howard");
cout<<value<<endl;
return 0;
}
执行结果为:
find()成员方法
3)此外,还可以使用上面的find()成员方法间接实现此目的。
和上面两种方法不同的是,该方法返回一个迭代器,如果查找成功,该迭代器指向查找到的键值对;反之,则指向map容器中最后一个键值对之后的位置,即和end()成功方法返回的迭代器一样。
4)最后,也可以采用遍历整个map容器的方法,找到包含指定键的键值对,进而获取该键对应的值。
map插入数据
[ ]运算符
1)前面提到过一次,C++ STL类模板中对[ ]运算符进行了重载,即根据使用场景的不同,可以实现不同的操作。可以使用[ ]运算符类似数组添加元素那样添加键值对。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3}};
myMap["howard"] = 4;
for(auto iter = myMap.begin(); iter != myMap.end(); iter++){
cout<<iter->first<<" "<<iter->second<<endl;
}
return 0;
}
执行结果:
insert()成员方法
2)map类模板中提供了insert()成员方法,该方法专门用来向map容器中插入新的键值对。
注意:这里的“插入”指的是insert()方法可以将新的键值对插入到map容器中的指定位置,但这与map容器会自动对存储的键值对进行排序并不冲突。当使用insert()方法向map容器的指定位置插入新键值对时,其底层会先将新键值对插入到容器的指定位置,如果其破坏了map容器的有序性,该容器会对新键值的位置进行调整。
从C++ 11标准后,insert()成员方法的用法大致有以下4种。
2.1)无需指定插入位置,直接将键值对添加到map容器中。
//1.引用传递一个键值对
pair<iterator,bool> insert (const value_type& val);
//2.以右值引用的方式传递键值对
tempalte<class P>
pair<iterator,bool> insert(P&&val);
val参数表示键值对变量。
该方法返回一个pair对象,其中pair.first表示一个迭代器,pair.second为一个bool类型变量。
- 如果成功插入val,则该迭代器指向新插入的val,bool的值为true。
- 如果插入val失败,则表明当前map容器种存在和val的键相同的键值对p,此时返回的迭代器指向p,bool的值为false。
以上两种语法的区别在于传递参数的方式不同,无论是局部定义还是全局定义的键值对变量,都采用普通引用传递的方式;而对于临时的键值对变量,则以右值引用的方式传参。
具体实例:
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
//创建一个空map容器
map<string,int> myMap;
//创建一个键值对变量
pair<string,int> myPair = {"penny", 1};
//创建一个接收Insert()方法返回值的pair对象
pair<map<string,int>::iterator, bool> ret;
//1.插入myPair,由于myPair并不是临时变量,因此会用第一种方式传参
ret = myMap.insert(myPair);
cout<<ret.first->first<<" "<<ret.first->second<<", "<<ret.second<<endl;
//2.以右值引用的方式传递临时的键值对变量
ret = myMap.insert({"sheldon",2});
cout<<ret.first->first<<" "<<ret.first->second<<", "<<ret.second<<endl;
//插入失败的情况
ret = myMap.insert({"penny",2});
cout<<ret.first->first<<" "<<ret.first->second<<", "<<ret.second<<endl;
return 0;
}
执行结果:
总共执行了3次插入操作,成功2次,失败了1次。
2.2)insert()方法向map容器的指定位置插入新的键值对。
//1.普通引用的方式传递val参数
iterator insert(const_iterator position, const value_type& val);
//2.以右值引用的方式传递val键值对参数
template <class P>
iterator insert (const_iterator position, P&& val);
val为要插入的键值对变量。
和2.1)不同的是,这里insert()方法返回的是迭代器,而不是pair对象。
- 如果插入成功,返回一个指向map容器中已插入键值对的迭代器。
- 如果插入失败,同样会返回一个迭代器,该迭代器指向map容器中和val具有相同键的那个键值对。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
//创建一个空map容器
map<string,int> myMap;
//创建一个键值对变量
pair<string,int> myPair = {"penny", 1};
//指定要插入的位置
map<string,int>::iterator it = myMap.begin();
//1.向it位置以普通引用的方式插入myPair
auto iter1 = myMap.insert(it,myPair);
cout<<iter1->first<<" "<<iter1->second<<endl;
//2.向it位置以右值引用的方式插入临时的键值对
auto iter2 = myMap.insert(it,pair<string,int>("sheldon",2));
cout<<iter2->first<<" "<<iter2->second<<endl;
//插入失败的情况
auto iter3 = myMap.insert(it,pair<string,int>("penny",2));
cout<<iter3->first<<" "<<iter3->second<<endl;
return 0;
}
执行结果:
再次强调,即使指定了新键值对的插入位置,map容器仍会对存储的键值对进行排序。也就是说,最终决定新插入的键值对在map容器中位置的,是键值对中键的值。
2.3)insert()方法还支持向当前map容器中插入其它map容器指定区域内的所有键值对。
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
其中first和last都是迭代器,组合起来<first, last>可以表示某map容器中的指定区域。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
//创建一个map容器
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3}};
//创建一个空map容器
map<string,int> copyMap;
//指定插入区域
map<string,int>::iterator first = ++myMap.begin();
map<string,int>::iterator last = myMap.end();
//将<first,last>区域内的键值对插入到copyMap中
copyMap.insert(first,last);
//遍历输出copyMap中的键值对
for(auto iter = copyMap.begin(); iter != copyMap.end(); iter++){
cout<<iter->first<<" "<<iter->second<<endl;
}
return 0;
}
执行结果:
2.4)除了上一种格式外,insert()方法还允许一次向map容器中插入多个键值对。
void insert ({val1, val2, ...});
其中,vali 都表示的是键值对变量。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
//创建一个map容器
map<string,int> myMap;
myMap.insert({{"penny",1},{"leonard",2},{"sheldon",3}});
//遍历输出myMap中的键值对
for(auto iter = myMap.begin(); iter != myMap.end(); iter++){
cout<<iter->first<<" "<<iter->second<<endl;
}
return 0;
}
执行结果:
emplace()和emplace_hint()成员函数
C++ STL map类模板中还提供了emplace()和emplace_hint()成员函数,也可以实现向map容器中插入新的键值对。
值得一提的是,和insert()方法相比,emplace()和emplace_hint()方法的使用要简单的多,而且,对于实现相同的插入操作,这两种方法都比比insert()方法的效率高。
1) emplace()
template <class... Args>
pair<iterator,bool> emplace (Args&&... args);
参数 (Args&&… args) 指的是,这里只需要将创建新键值对所需的数据作为参数直接传入即可,此方法可以自行利用这些数据构建出指定的键值对。
该方法的返回值是一个pair对象,其中pair.first为一个迭代器,pair.second为一个bool类型变量:
- 当键值对成功插入到map容器中时,其返回的迭代器指向该新插入的键值对,同时bool变量的值为true
- 当插入失败时,则表明map容器中存在具有相同键的键值对,此时返回的迭代器指向具有相同键的键值对,同时bool变量的值为false。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
//创建一个空map容器
map<string, int> myMap;
pair<map<string,int>::iterator, bool> ret;
//插入键值对
ret = myMap.emplace("penny",1);
cout<<"1."<<ret.first->first<<" "<<ret.first->second<<", "<<ret.second<<endl;
ret = myMap.emplace("sheldon",2);
cout<<"2."<<ret.first->first<<" "<<ret.first->second<<", "<<ret.second<<endl;
//插入失败
ret = myMap.emplace("penny",2);
cout<<"3."<<ret.first->first<<" "<<ret.first->second<<", "<<ret.second<<endl;
return 0;
}
执行结果:
2)emplace_hint()
template <class... Args>
iterator emplace_hint (const_iterator position, Args&&... args);
emplace_hint()方法的功能和emplace()类似,但又有不同之处:
- 该方法不仅要传入创建键值对所需要的数据,还需要传入一个迭代器作为第一个参数,指明要插入的位置,新键值对会插入到该迭代器指向的键值对的前面。
- 该方法的返回值是一个迭代器,而不再是pair对象。当成功插入新键值对时,返回的迭代器指向新插入的键值对;反之,如果插入失败,则表明map容器中有相同键的键值对,返回的迭代器就指向这个键值对。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
//创建一个空map容器
map<string, int> myMap;
map<string,int>::iterator iter;
//插入键值对
iter = myMap.emplace_hint(myMap.begin(),"penny",1);
cout<<"1."<<iter->first<<" "<<iter->second<<endl;
iter = myMap.emplace_hint(myMap.begin(),"sheldon",2);
cout<<"2."<<iter->first<<" "<<iter->second<<endl;
//插入失败
iter = myMap.emplace_hint(myMap.begin(),"penny",2);
cout<<"3."<<iter->first<<" "<<iter->second<<endl;
return 0;
}
执行结果:
需要注意,和insert()方法一样,虽然emplace_hint()方法指定了插入键值对的位置,但map容器为了保持存储键值对的有序状态,可能会移动其位置。
map删除数据
与插入元素相对应,map也提供了删除容器中元素的一些方法:erase()和clear()。
erase()
erase()有3种适用于不同情况的语法格式。
1)根据要删除的键值对位于容器中的位置,实现删除该键值对。
//删除map容器种指定位置的键值对
iterator erase(const_iterator position);
其中,position为迭代器,指向要删除的键值对。
该方法会返回一个iterator迭代器,其指向的是删除键值对之后的那个键值对(如果是删除最后一个键值对,那么该方法也会返回迭代器指向的最后一个键值对之后的位置)。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
//创建并初始化map容器
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3},{"howard",4}};
cout<<myMap.size()<<endl; //输出键值对个数
//遍历map容器
for(auto iter = myMap.begin(); iter != myMap.end(); iter++){
cout<<iter->first<<","<<iter->second<<" ";
}
cout<<endl;
//创建一个指向要删除键值对的迭代器
map<string,int>::iterator iter = ++myMap.begin(); //指向第二个键值对
//执行删除操作
map<string,int>::iterator ret = myMap.erase(iter);
cout<<myMap.size()<<endl; //输出键值对个数
//输出erase()方法返回的迭代器指向的键值对
cout<<ret->first<<" "<<ret->second<<endl;
return 0;
}
执行结果:
2)传入要删除目标键值对的键的值,该方法会自行根据指定的键找到目标键值对,并将其删除。
//删除map容器种键为k的键值对
size_type erase(const key_type& k);
其中,参数k为要删除的键值对的键的值。
该方法的返回值为成功删除的键值对的个数。由于map容器种每个键值对的键都是独一无二的,故其返回值最大为1.
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
//创建并初始化map容器
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3},{"howard",4}};
cout<<myMap.size()<<endl; //输出键值对个数
//执行删除操作,删除myMap容器种键为"penny"的键值对
int num = myMap.erase("penny");
cout<<myMap.size()<<endl; //输出键值对个数
//输出erase()方法的返回值
cout<<"num= "<<num<<endl;
return 0;
}
执行结果:
3)在某些情况下,可能需要删除某个指定区域内的所有键值对,也可以通过erase()方法实现。
//删除 map 容器中位于 [first,last) 区域内的所有键值对
iterator erase (const_iterator first, const_iterator last);
其中参数first和last都是迭代器,[first, last)就表示map容器种的某个范围,该方法会删除此范围内的所有键值对。
返回值为一个迭代器,其指向删除范围之后的第一个键值对。如果该范围之后,不再有任何键值对,则返回的迭代器指向map容器最后一个键值对之后的位置。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
//创建并初始化map容器
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3},{"howard",4}};
cout<<myMap.size()<<endl; //输出键值对个数
//指定范围
map<string,int>::iterator first = myMap.begin();
map<string,int>::iterator last = --myMap.end();
//删除指定范围内的容器
map<string,int>::iterator ret = myMap.erase(first,last);
cout<<myMap.size()<<endl;
cout<<ret->first<<" "<<ret->second<<endl;
return 0;
}
执行结果:
clear()
如果想删除map容器中存储的所有键值对,可以使用clear()成员方法。
void clear();
该方法不需要传入任何参数,也不需要接收任何返回值。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
//创建并初始化map容器
map<string,int> myMap{{"penny",1},{"leonard",2},{"sheldon",3},{"howard",4}};
cout<<myMap.size()<<endl; //输出键值对个数
//删除myMap中所有元素
myMap.clear();
cout<<myMap.size()<<endl;
return 0;
}
执行结果: