与顺序容器不同的是,关联容器中的元素是按关键字来保存和访问的,而顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的,那么典型的就是我们常见的 map 与 set 数据结构
map 类型通常被称为关联数组
//example1.cpp
#include <iostream>
#include <map>
using namespace std;
int main(int argc, char **argv)
{
<string, size_t> m_map;
mapm_map["a"] = 4;
m_map["b"] = 5;
<< m_map["a"] << endl; // 4
cout << m_map["b"] << endl; // 5
cout return 0;
}
//example2.cpp
<string, size_t> m_map;
map<string> vec = {"aaa", "bbb", "ccc", "aaa"};
vector<string> m_set;
setfor (auto &item : vec)
{
m_set.insert(item);
<< item << endl; // aaa bbb ccc aaa
cout if (m_map.find(item) != m_map.end())
{
m_map[item]++;
}
else
{
m_map[item] = 1;
}
}
for (auto &item : m_map)
{
// aaa 1 bbb 1 ccc 1
<< item.first << " " << item.second << endl;
cout }
//遍历set
for (auto &item : m_set)
{
<< item << endl; // aaa bbb ccc
cout }
当定义一个 map 时,必须指明关键字类型与值类型,而定义 set 只需指明关键字类型
空容器、列表初始化、映射键值对列表初始化
//example3.cpp
//空容器
<string, size_t> map_1;
map//列表初始化
<string> set_1 = {"aaa", "bbb", "ccc", "aaa", "bbbb"};
set// map列表初始化
<string, string> map_2 = {
map{"aaa", "vfvdf"},
{"bbb", "adfdsfs"},
{"ccc", "vfdvdf"}};
multi 的意义就是,map 一个关键词可以存储多个值,set 也可以存储多个相同的值
//example4.cpp
// multiset
<string> vec = {"aaa", "aaa", "bbb", "bbb", "ccc"};
vector<string> m_set(vec.begin(), vec.end());
multisetfor (auto &item : m_set)
{
<< item << " "; // aaa aaa bbb bbb ccc
cout }
<< endl;
cout << m_set.size() << endl; // 5
cout // multimap
<string, string> m_map = {{"aaa", "ccc"}, {"aaa", "vvv"}};
multimapfor (auto &item : m_map)
{
<< item.first << " " << item.second << endl;
cout // aaa ccc
// aaa vvv
}
对于有序容器,map、multimap、set、multiset,关键字类型要求必须定义元素比较方法,默认情况下,标准库使用关键字类型的<运算符来比较两个关键字
算法允许自定义比较操作,与之类似也可以提供自定义的操作代替关键字上的<运算符
比较函数必须满足以下条件
形如
map<keyType,valueType,函数指针类型>name(函数指针值);
set<keyType,函数指针类型>name(函数指针值);
//example5.cpp
struct Person
{
int age;
;
string name};
//关键字比较函数必须满足上面比较函数的条件
bool comparePerson(const Person &a, const Person &b)
{
return a.age < b.age && a.name < b.name;
}
int main(int argc, char **argv)
{
// decltype(comparePerson) * 为函数指针
<Person, int, bool (*)(const Person &a, const Person &b)> m_map(comparePerson);
map// 或者使用decltype
// map<Person, int, decltype(comparePerson) *> m_map(comparePerson);
;
Person person1.age = 19;
person1.name = "gaowanlu";
person1m_map[person1] = 99999;
auto val = m_map[person1];
<< val << endl; // 99999
cout return 0;
}
pair 的标准库类型,定义在 utility 头文件中
一个 piar 保存两个数据成员,类似容器 piar
是一个用来生成特定类型的模板,大致可以认为是一个键值对
//example6.cpp
//空初始化
<string, int> m_pair1;
pair
//列表初始化
<int, string> m_pair2{999, "gaowanlu"};
pair<< m_pair2.first << " " << m_pair2.second << endl; // 999 gaowanlu
cout
<int, string> m_pair4 = {999, "gaowanlu"};
pair<< m_pair4.first << " " << m_pair4.second << endl; // 999 gaowanlu
cout
//构造函数初始化
<string, int> m_pair3("gaowanlu", 888);
pair<< m_pair3.first << " " << m_pair3.second << endl; // gaowanlu 888
cout
//使用make_piar
m_pair4 = make_pair(888, "hello");
//赋值
m_pair4.first = 666;
m_pair4.second = "gaowanlu";
<< m_pair4.first << " " << m_pair4.second << endl; // 666 gaowanlu
cout
// <、>、<=、>=比较pair
<int, int> m_pair5(2, 3);
pair<int, int> m_pair6(4, 3);
pair//当first与second同时满足才返回true
<< (m_pair5 < m_pair6) << endl; // 1
cout << (m_pair5 <= m_pair6) << endl; // 1
cout << (m_pair5 > m_pair6) << endl; // 0
cout << (m_pair5 >= m_pair6) << endl; // 0
cout
// ==比较pair
m_pair5 = make_pair(4, 3);
<< (m_pair5 == m_pair6) << endl; // 1
cout
// !=比较piar
<< (m_pair5 != m_pair6) << endl; // 0 cout
只有 map 类型(unordered_map、unordered_multimap、multimap 和 map)才定义了 mapped_type
//example7.cpp
#include <iostream>
#include <utility>
#include <set>
#include <map>
using namespace std;
int main(int argc, char **argv)
{
// set value_type与key_type
<string>::value_type v1; // td::string
set<string>::key_type v2; // std::string
set// map key_type value_type mapped_type
<string, int>::value_type v3; // std::pair<const std::string, int>
map<string, int>::key_type v4; // std::string
map<string, int>::mapped_type v5; // int
mapreturn 0;
}
重点:set 迭代器解引用返回 const 引用,map 迭代器解引用返回 pair 的引用,但 pair 的 first 类型是 const 的,也就是我们只能用迭代器修改 second 不能修改 first
//example8.cpp
<string> m_set = {"aaa", "bbb", "ccc"};
set<string, int> m_map = {{"aaa", 111}, {"bbb", 222}, {"ccc", 333}};
mapauto set_iter = m_set.begin(); // std::set<std::string>::iterator
while (set_iter != m_set.end())
{
<< *set_iter++ << endl; // aaa bbb ccc
cout // set的迭代器是const的
//*set_iter = "dscs";//*set_iter返回的是 const string&
}
auto map_iter = m_map.begin(); // std::map<std::string, int>::iterator
while (map_iter != m_map.end())
{
// map_iter->first = "dscs"; //因为解引用返回的是 std::pair<const std::string, int> &
->second++;
map_iter<< map_iter->first << " " << map_iter->second << endl;
cout // aaa 112
// bbb 223
// ccc 334
++;
map_iter}
一般不对关联容器使用泛型算法,关键字是 const 着意味着不能使用修改或重排容器元素的算法,因为这类算法要向元素写入值,而 set 的元素是 const 的,map 中元素是 pair 但 first 是 const 的
关联容器只用于只读元素的算法,例如泛型算法 find 查找为顺序查找,而关联容器的特定的 find 则是进行 hash 查找,会比泛型算法的 find 快很多,还可以利用泛型 copy 算法将元素拷贝
//example9.cpp
<string, int> m_map = {{"aaa", 111}, {"bbb", 222}, {"ccc", 333}};
map// copy到vector
<pair<string, int>> vec;
vector(m_map.begin(), m_map.end(), back_inserter(vec));
copy<< vec.size() << endl; // 3
cout // copy到新的map
<string, int> m_map_copy;
map(m_map.begin(), m_map.end(), inserter(m_map_copy, m_map_copy.end()));
copyfor (auto &item : m_map_copy)
{
<< item.first << " " << item.second << endl;
cout // aaa 111
// bbb 222
// ccc 333
}
//example10.cpp
<string, int> m_map;
mapm_map.insert({"hello", 3});
m_map.insert(make_pair("aaa", 6));
m_map.insert(pair<string, int>("bbb", 2));
auto res = m_map.insert(map<string, int>::value_type("ccc", 3));
if (res.second) //插入成功
{
<string, int>::iterator iter = res.first;
map<< iter->first << " " << iter->second << endl; // ccc 3
cout }
m_map.emplace("ddd", 4); //背后使用构造函数int(4)
<< m_map.find("ddd")->second << endl; // 4
cout
<string> m_set;
setm_set.insert("aaa");
m_set.insert({"aaa", "bbb", "ccc"});
m_set.insert(string("ddd"));
m_set.emplace("hello"); //背后调用构造函数string("hello")
<< m_set.size() << endl; // 4 cout
知道二者可以存储多个相同的关键字,insert 返回 void 不像 map 或者 set 一样返回一个 pair
//example11.cpp
<string, int> m_map;
multimap<string> m_set;
multisetm_map.insert({{"aaa", 111}, {"aaa", 222}});
m_set.insert({"aaa", "ccc", "ccc"});
<< m_map.size() << endl; // 2
cout << m_set.size() << endl; // 3 cout
关联容器的 erase 定义了三个版本
//example12.cpp
//使用关键字
<string, int> m_map = {{"aaa", 111}, {"bbb", 222}, {"ccc", 333}};
map<string> m_set = {"aaa", "bbb", "ccc", "ddd"};
set(m_map); //<aaa,111> <bbb,222> <ccc,333>
printMap(m_set); // aaa bbb ccc ddd
printSetsize_t count = m_map.erase("aaa");
<< count << endl; // 1 被删除1个
cout (m_map);
printMap= m_set.erase("ccc");
count << count << endl; // 1
cout (m_set);
printSet
//使用迭代器指定删除的元素位置
m_set.erase(m_set.cbegin());
(m_set); // bbb ddd
printSet
//使用迭代器范围
m_set.erase(m_set.cbegin(), m_set.end());
(m_set); // printSet
map、uordered_map 有下标操作,而 multimap 与 unordered_multimap 没有下标操作,因为一个关键词可对应多个值
//example13.cpp
<string, int> m_map = {{"aaa", 111}, {"bbb", 222}, {"ccc", 333}};
map(m_map); // <aaa,111> <bbb,222> <ccc,333>
printMapm_map["aaa"] = 4;
(m_map); // <aaa,4> <bbb,222> <ccc,333>
printMap
//下标赋值没有的关键字则创建新的元素
m_map["ddd"] = 444;
(m_map); // <aaa,4> <bbb,222> <ccc,333> <ddd,444>
printMap
//如果k不再c内,添加一个关键字为k的元素并对值初始化
<< m_map["eee"] << endl; // 0
cout (m_map);
printMap// <aaa,4> <bbb,222> <ccc,333> <ddd,444> <eee,0>
// at方法有检查机制,如果k不在c内则抛出out_of_range异常
int &val = m_map.at("bbb");
<< val << endl; // 222
cout try
{
m_map.at("fff");
}
catch (std::out_of_range e)
{
// RUNTIME ERROR:: map::at
<< "RUNTIME ERROR:: " << e.what() << endl;
cout }
主要为根据关键字查找元素的操作,在 multimap 与 multiset 中相同关键字的元素总是相邻存放
//example14.cpp
<int, string> m_map{{111, "aaa"}, {222, "bbb"}, {444, "ddd"}, {222, "ccc"}};
multimap(m_map); // <111,aaa> <222,bbb> <222,ccc> <444,ddd>
printMap
// count方法
<< m_map.count(222) << endl; // 2
cout
// find方法
auto res = m_map.find(222);
int size = 0, count = m_map.count(222);
while (size < count && res != m_map.cend())
{
//<222,bbb> <222,ccc>
<< "<" << res->first << "," << res->second << ">" << endl;
cout ++;
res++;
size}
// lower_bound方法
//返回第一个关键字不小于333的迭代器
= m_map.lower_bound(333);
res << "<" << res->first << "," << res->second << ">" << endl; //<444,ddd>
cout
// upper_bound方法
//返回第一个关键字大于100的迭代器
= m_map.upper_bound(100);
res << "<" << res->first << "," << res->second << ">" << endl; //<111,aaa>
cout
// equal_range方法
auto range = m_map.equal_range(222); //返回区间[bengin,end)
= range.first;
res while (res != range.second)
{
<< "<" << res->first << "," << res->second << ">" << endl;
cout //<222,bbb> <222,ccc>
++;
res}
无序是 C++11 的新标准,新标准定义了 4
个无序关联容器,这些容器不是使用比较运算符来组织元素,而是使用哈希函数和关键字类型的==运算符
无序容器在关键字杂乱无章的情况下效率更高
四种无序容器
//example15.cpp
<string, int> m_map = {{"aaa", 111}, {"bbb", 222}, {"ccc", 333}};
unordered_mapfor (auto &item : m_map)
{
//<ccc,333> <bbb,222> <aaa,111>
<< "<" << item.first << "," << item.second << "> ";
cout }
<< endl;
cout //可见存放顺序不是有序的
无序容器的存放原理为,无序容器有多个桶,每个桶存放 0 个或多个元素,先对关键字进行 hash 然后找到对应的桶,在桶内存放要存放的元素,在访问元素时也是同样的原理
//example16.cpp
<string, int> m_map = {{"aaa", 111}, {"bbb", 222}, {"ccc", 333}};
unordered_map// bucket_count 正在使用的桶的数目
<< m_map.bucket_count() << endl; // 3
cout // max_bucket_count 容器能容纳的最多的桶的数量
<< m_map.max_bucket_count() << endl; // 59652323
cout // bucket_size(n) 第n个桶中有多少个元素
for (size_t i = 0; i < m_map.bucket_count(); i++)
{
<< m_map.bucket_size(i) << " "; // 0 2 1
cout }
<< endl; cout
//example17.cpp
<string, int> m_map = {{"aaa", 111}, {"bbb", 222}, {"ccc", 333}};
unordered_map<< m_map.bucket_size(1) << endl; // 2
cout <string, int>::local_iterator iter = m_map.begin(1);
unordered_mapwhile (iter != m_map.cend(1))
{
// bbb 222 aaa 111
<< iter->first << " " << iter->second << endl;
cout // begin返回的为local_iterator即可以利用迭代器修改pair的value
++;
iter}
// cbegin返回的为const_local_iterator不可以利用迭代器修改pair的value
//example18.cpp
<string, int> m_map = {{"aaa", 111}, {"bbb", 222}, {"ccc", 333}};
unordered_map
// load_factor 每个桶的平均元素数量
<< m_map.load_factor() << endl; // 1
cout << (float)m_map.size() / m_map.bucket_count() << endl; // 3
cout
// max_load_factor 即 load_factor的阈值,load_factor<=max_load_factor总成立
<< m_map.max_load_factor() << endl; // 1
cout
// rehash(n) 重组存储 使得bucket_count>=n 且 bucket_count>size/max_load_factor
m_map.rehash(4);
<< m_map.bucket_count() << endl; // 5
cout
// reserve(n) 重组元素,使得容器可保存n个元素,且不rehash也就是不改变hash函数
m_map.reserve(100);
<< m_map.bucket_count() << endl; // 103 cout
当关键字类型为自定义符合类型时,需要定义相关的 hash 函数与等于判断函数
//example19.cpp
class Person
{
public:
int age;
bool operator==(const Person &b) const
{
if (this->age == b.age)
{
return true;
}
return false;
}
};
// hash函数
size_t hasher(const Person &person)
{
return hash<int>()(person.age);
}
//==比较函数
bool equalPerson(const Person &a, const Person &b)
{
return a.age == b.age;
}
int main(int argc, char **argv)
{
using namespace std;
using Person_map = unordered_map<Person, string, decltype(hasher) *, decltype(equalPerson) *>;
//使用Person_multimap 42:桶大小 hasher:哈希函数 equalPerson: 相等性判断函数
(42, hasher, equalPerson);
Person_map persons;
Person person.age = 19;
person.insert({person, "gaowanlu"});
persons<< persons[person] << endl; // gaowanlu
cout
//如果类内定义了==运算符,则可以只重载哈希函数
// unordered_map<Person, string, decltype(hasher) *>;
<Person, string, decltype(hasher) *> persons_overide(42, hasher);
unordered_map.insert(make_pair(person, "gaowanlu"));
persons_overide<< persons[person] << endl;
cout return 0;
}