关联容器和顺序容器有着根本的不一样:关联容器中的元素是按关键字来保存和访问的。与之相对,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。算法
虽然关联容器的不少行为与顺序容器相同,但其不一样之处反映了关键字的做用。数组
关联容器支持高效的关键字查找和访问。两个主要的关联容器类型是map和set。map中的元素是一些关键字-值对:关键字起到索引的做用,值则表示与索引相关联的数据。set中每一个元素包含一个关键字;set支持高效的关键字查询操做——检查一个给定关键字是否在set中。例如,在某些文本处理过程当中,能够用一个set来保存想要忽略的单词。字典则是一个很好的使用map的例子:能够将单词做为关键字,将单词释义做为值。函数
标准库提供8个关联容器,以下表所示。这8个容器间的不一样体如今三个维度上:每一个容器(1)或者是一个set,或者是一个map;(2)或者要求不重复的关键字,或者容许重复关键字;(3)按顺序保存元素,或无序保存。容许重复关键字的容器的名字中都包含单词multi;不保存关键字按顺序存储的容器的名字都以单词unordered开头。所以一个unordered_multi_set是一个容许重复关键字,元素无序保存的集合,而一个set则是一个要求不重复关键字,有序存储的集合。无序容器使用哈希函数来组织元素。性能
类型map和multimap定义在头文件map中;set和multiset定义在头文件set中;无序容器则定义在头文件unordered_map和unordered_set中。spa
关联容器类型 |
按关键字有序保存元素指针 map 关联数组:保存关键字-值对code set 关键字即值,即只保存关键字的容器对象 multimap 关键字可重复出现的mapblog multiset 关键字可重复出现的set排序 无序容器 unordered_map 用哈希函数组织的map unordered_set 用哈希函数组织的set unordered_multimap 哈希组织的map;关键字能够重复出现 unordered_multiset 哈希组织的set;关键字能够重复出现 |
关联容器不支持顺序容器的位置相关的操做,例如push_front或push_back。缘由是关联容器中元素是根据关键字存储的,这些操做对关联容器没有意义。并且,关联容器也不支持构造函数或插入操做这些接受一个元素值和一个数量值的操做。
除了顺序容器相同的操做以外,关联容器还支持一些顺序容器不支持的操做和类型别名。此外,无序容器还提供了一些用来调整哈希性能的操做。
关联容器的迭代器都是双向的。
定义关联容器
当定义一个map时,必须既指明关键字类型又指明值类型。而定义一个set时,只需指明关键字类型,由于set中没有值。每一个关联容器都定义了一个默认构造函数,它建立一个指定类型的空容器,咱们也能够将关联容器初始化为另外一个同类型容器的拷贝,或是从一个值范围来初始化关联容器,只要这些值能够转化为容器中所需类型就能够。在新标准下,咱们也能够对关联容器进行值初始化:
map<string,size_t> word_count ; //空容器
//列表初始化
set<string> exclude={"the","but","and","or","an","a","The","But","And","Or","An","A"};
//三个元素;authors将姓映射到名
map<string,string> autors={ {"Joyce","James"},
{"Austen","Jane"},
{"Dickens","Charles"}};
与以往同样,初始化器必须能转换为容器中元素的类型。对于set,元素类型就是关键字类型。
当初始化一个map时,必须提供关键字类型和值类型,咱们将每一个关键字-值对包围在花括号中:
{key,value}
来指出它们一块儿构成了map中的一个元素。在每一个花括号中,关键字是第一个元素,值是第二个。所以,anthors将姓映射到名,初始化它包括三个元素。
初始化multimap或multiset
一个map或set中的关键字必须是惟一的,即,对于一个给定的关键字,只能有一个元素的关键字等于它。容器multimap和multiset没有此限制,它们都容许多个元素具备相同的关键字。
关键字类型的要求
关联容器对其关键字类型有一些限制。对于无序容器中关键字的要求,咱们后面介绍。对于有序容器——map、multimap、set以及multiset,关键字类型必须定义元素比较的方法。默认状况下,标准库使用关键字类型的<运算符来比较两个关键字。在集合类型中,关键字类型就是元素类型,在映射类型中,关键字类型是元素的第一部分的类型。所以,word_count的关键字类型是string。相似的,exclude的关键字类型也是string。
有序容器的关键字类型
能够向一个算法提供咱们本身定义的比较操做,与之相似,也能够提供本身定义的操做来代替关键字上的<运算符。所提供的操做必须在关键字类型上定义一个严格弱序。能够将严格弱序看做“小于等于”,虽然实际定义的操做多是一个复杂的函数。不管咱们怎样定义比较函数,它必须具有以下基本性质:
若是两个关键字是等价的(即,任何一个都不“小于等于”另外一个),那么容器将它们视做相等来处理。当用做map关键字时,只能有一个元素与这个关键字关联,咱们能够用二者中任意一个来访问对应的值。
使用关键字的比较函数
用来组织一个容器中元素的操做的类型也是容器类型的一部分。为了指定使用自定义的操做,必须在定义关联容器类型时提供此操做的类型。如前所述,用尖括号指出要定义哪一种类型的容器,自定义的操做类型必须在尖括号中紧跟这元素类型给出。
在尖括号中出现的每一个类型,就仅仅是一个类型而已。当咱们建立一个容器(对象)时,才会以构造函数参数的形式提供真正的比较操做(其类型必须与在尖括号中指定的类型相吻合)。
例如,咱们不能直接定义一个Sales_data的multiset,由于Sales_data没有<运算符。可是,能够用定义的compareIsbn函数来定义一个multiset。此函数在Sales_data对象的ISBN成员上定义了一个严格弱序。函数compareIsbn应该像下面这样定义
bool compareIsbn(const Sales_data &lhs,const Sales_data &rhs)
{
return lhs.isbn()<rhs.isbn();
}
为了使用本身定义的操做,在定义multiset时咱们必须提供两个类型。关键字类型Sales_data,以及比较操做类型——应该是一种函数指针类型,能够指向compareIsbn。当定义此容器类型的对象时,须要提供想要使用的操做的指针。在本例中,咱们提供一个指向compareIsbn的指针:
//bookstore中多条记录能够有相同的ISBN
//bookstore中的元素以ISBN的顺序进行排序
multiset<Sales_data,decltype(compareIsbn)*> bookstore(compareIsbn);
此处,咱们使用decltype来指出自定义操做的类型。记住,当用decltype来得到一个函数指针类型时,必须加上一个*来指出咱们要使用一个给定函数类型的指针。用compareIsbn来初始化bookstore对象,这表示当咱们向bookstore添加元素时,经过compareIsbn来为这些元素排序。即,bookstore中的元素将按它们的ISBN成员的值排序。能够用compareIsbn代替&compareIsbn做为构造函数的参数,由于当咱们使用一个函数的名字时,在须要的状况下它会自动转化为一个指针。固然,使用&compareIsbn的效果也是同样的。
pair类型
在介绍关联容器操做以前,咱们须要了解名为pair的标准库类型,它定义在头文件utility中。
一个pair保存两个数据成员。相似容器,pair是一个用来生成特定类型的模板。当建立一个pair时,咱们必须提供两个类型名,pair的数据成员将具备对应的类型。两个类型不要求同样:
pair<string,string> anon; //保存两个string
pair<string,size_t> word_count; //保存一个string和一个size_t
pair<string,vector<int>> line; //保存string和vector<int>
pair的默认构造函数对数据成员进行值初始化。所以,anon是一个包含两个空string的pair,line保存一个空string和一个空vector。word_count中的size_t 成员值为0,而string成员被初始化为空vector。
咱们也能够为每一个成员提供初始化器:
pair<string,string> author{"James","Joyce"};
这条语句建立了一个名为author的pair,两个成员被初始化为"James"和"Joyce"。
与其余标准库类型不一样,pair的数据成员是public的。两个成员分别命名为first和second。咱们用普通的成员访问符号来访问它们,例如
cout<<w.first<<" occurs "<<w.second<<endl;
此处,w 是指向某个元素的引用。map的元素是pair。在这条语句中,咱们首先打印关键字——元素的first成员,接着打印计数器——second成员。标准库只定义了有限的几个pair操做,下表列出了这些操做:
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的类型推断出来 p.first 返回p的名为first的(公有)数据成员 p.second 返回p的名为second的(公有)数据成员 p1 relop p2 关系运算符(<、>、<=、>=)按字典序定义;例如,当p1.first<p2.first或!(p2.first<p1.first)&&p1.second<p2.second成立时,p1<p2为 true。关系运算符利用元素的<运算符来实现 p1==p2 当first和second成员分别相等时,两个pair相等。相等性判断利用元素的==运算符实现 p1!=p2 |
建立pair对象的函数
想象有一个函数须要返回一个pair。在新标准下,咱们能够对返回在进行列表初始化
pair<string,int> process(vector<string> &v) { //处理v if(!v.empty()) return {v.back(),v.back().size()); // 列表初始化 else return pair<string,int>(); //隐式构造返回值 }
在较早的C++版本中,不容许用花括号包围的初始化器来返回pair这种类型的对象,必须显示构造返回值:
if(!v.empty())
return pair<string,int>(v.back(),v.back().size());
咱们还能够用make_pair来生成pair对象,pair的两个类型来自于make_pair的参数:
if(!v.empty())
return make_pair(v.back(),v.back().size());