本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录前一修订版后一修订版 | 前一修订版 | ||
cs:programming:cpp:cpp_primer:11_acontainers [2024/01/14 13:46] – 移除 - 外部编辑 (未知日期) 127.0.0.1 | cs:programming:cpp:cpp_primer:11_acontainers [2024/01/14 13:47] (当前版本) – ↷ 链接因页面移动而自动修正 codinghare | ||
---|---|---|---|
行 1: | 行 1: | ||
+ | | ||
+ | C++ Primer 笔记 第十一章\\ | ||
+ | ---- | ||
+ | **关联容器**(// | ||
+ | 关联容器拥有两种类型:// | ||
+ | * Map 中的元素成对出现,包括一个 //Key// 与其对应的存储内容 | ||
+ | * Set 中的 //Key// 就是元素 | ||
+ | 从应用上来讲,需要快速查找指定内容的情形可以使用 // | ||
+ | STL 提供的关联容器分为 8 种,主要以三个方面来划分: | ||
+ | * 是 set 或 map | ||
+ | * Key 是否唯一 | ||
+ | * 是否**按顺序**存储元素(Key 是不是有序) | ||
+ | 允许重复 Key 的关联容器以 '' | ||
+ | <code cpp> | ||
+ | unordered_multmap | ||
+ | </ | ||
+ | 指的是一个// | ||
+ | \\ \\ | ||
+ | {{ cs: | ||
+ | ==关联容器的头文件== | ||
+ | * '' | ||
+ | * '' | ||
+ | * 对应的 // | ||
+ | ====使用关联容器==== | ||
+ | |||
+ | //Map// 用于存储一系列 // | ||
+ | <code cpp> | ||
+ | person[name]; | ||
+ | </ | ||
+ | //Set// 用于存储一系列的 key。 Key 本身即为 //Set// 的元素。// | ||
+ | ===使用 Map=== | ||
+ | 一个比较经典的用法使用 //Map// 检测某个词输入了多少次:\\ \\ | ||
+ | 首先,// | ||
+ | <code cpp> | ||
+ | map< | ||
+ | //other def | ||
+ | string word; | ||
+ | </ | ||
+ | 其次,map可以通过 '' | ||
+ | <code cpp> | ||
+ | while(cin >> word) | ||
+ | ++word_count[word]; | ||
+ | </ | ||
+ | 需要注意的是,当通过新的 key(word)创建 pair 的时候, value 的初始值为 '' | ||
+ | 当输入完成以后,使用循环将信息打印出来。// | ||
+ | <code cpp> | ||
+ | for(const auto &w : word_count) | ||
+ | cout << w.first << " occurs " | ||
+ | << w.second << ((w.second > 1) ? " | ||
+ | << | ||
+ | </ | ||
+ | 打印 pair 的时候需要分开打印。 '' | ||
+ | ===使用 Set=== | ||
+ | Set 常用于**过滤关键词**。比如下面的例子,我们将使用 //Set// 定义屏蔽词列表,使 //Map// 只统计未被屏蔽的关键词输入次数。\\ \\ | ||
+ | 首先定义 // | ||
+ | <code cpp> | ||
+ | set< | ||
+ | " | ||
+ | //other def | ||
+ | map< | ||
+ | string word; | ||
+ | </ | ||
+ | 接下来使用循环输入数据。这里我们通过两个 set 成员 //find()// 与 //end()// 实现条件的判断: | ||
+ | <code cpp> | ||
+ | while (cin >> word) | ||
+ | if(exclude.find(word) == exclude.end()) | ||
+ | ++word_count[word]; | ||
+ | </ | ||
+ | //find()// 会接收一个 Key,并在调用它的对象中寻找是否有匹配的 Key。如果找到,返回指向该 Key 的迭代器;如果没有找到,则返回 // | ||
+ | ====关联容器概述==== | ||
+ | 关联容器与顺序容器都支持一般的容器操作 (见** [[cs: | ||
+ | * 由于关联容器基于 Key 实现存储,因此不支持**基于位移的顺序容器操作**,比如 // | ||
+ | * 关联容器不支持构造、插入 pair 类型数据的操作。 | ||
+ | 除此之外,C++ 还提供了一些关联容器专属的操作和 type alias,并且,针对 // | ||
+ | <WRAP center round info 100%> | ||
+ | 关联容器使用**双向迭代器**。 | ||
+ | </ | ||
+ | ===定义关联容器=== | ||
+ | //Map// 与 //Set// 初始化只要求 initializer 的**类型**满足要求即可。// | ||
+ | <code cpp> | ||
+ | //default initialization | ||
+ | map< | ||
+ | set< | ||
+ | |||
+ | //list initialization | ||
+ | map< | ||
+ | {" | ||
+ | {" | ||
+ | |||
+ | set< | ||
+ | " | ||
+ | </ | ||
+ | 上例中,// | ||
+ | <code cpp> | ||
+ | {key, value} | ||
+ | </ | ||
+ | ==定义 multimap 或 multiset== | ||
+ | 与 //Map// 和 //Set// 不同,在 // | ||
+ | <code cpp> | ||
+ | // define a vector with 20 elements, holding two copies of each number from 0 to 9 | ||
+ | vector< | ||
+ | for (vector< | ||
+ | ivec.push_back(i); | ||
+ | ivec.push_back(i); | ||
+ | } | ||
+ | // iset holds unique elements from ivec; miset holds all 20 elements | ||
+ | set< | ||
+ | multiset< | ||
+ | cout << ivec.size() << endl; // prints 20 | ||
+ | cout << iset.size() << endl; // prints 10 | ||
+ | cout << miset.size() << endl; // prints 20 | ||
+ | cout << miset.size() << endl; // prints 20 | ||
+ | </ | ||
+ | 由于 set 中的 key 是唯一的,因此其元素要比 multiset 少一半。 | ||
+ | ===对 Key 的要求=== | ||
+ | 有序关联容器使用 Key 对元素进行排序。因此,有序关联容器的 Key 需要**定义关系运算**(该要求与排序算法要求的可调用对象相同)。关联容器默认的比较操作为 ''<'' | ||
+ | |||
+ | ==有序关联容器中对 Key 的类型要求== | ||
+ | 与在排序算法中自定义比较策略类似,我们也可以按自定义的规则对 Key 进行排序。该规则需要需要 Key 类型满足 //Strict weak ordering// 的标准: | ||
+ | <code cpp> | ||
+ | // two keys cannot both be "less than" each other | ||
+ | 1) if F(key1, key2) == true, then F(key1, key2) == false | ||
+ | |||
+ | // k1 "less than" k2 and k2 "" | ||
+ | 2) if F(key1, key2) == true, F(key2, key3) ==true, then F(key1, key3) == true | ||
+ | |||
+ | // | ||
+ | 3) if F(key1, key2) == false && F(key2, key1) == false, then key1 == key2 | ||
+ | 4) if key1 == key2 && key2 == key3, then key1 == key3 | ||
+ | </ | ||
+ | 可以看出'' | ||
+ | 当在 map 中使用 key 的时候,若两个 key 等价,则只允许**有一个** value 与这些 key 相关,且通过这些 key 访问到的都是该相关的 value。 | ||
+ | <WRAP center round tip 100%> | ||
+ | 从实现上来说,为 key 类型定义一个行为正常的 $<$ 关系运算是非常重要的。 | ||
+ | </ | ||
+ | **Refs**: | ||
+ | * [[https:// | ||
+ | * [[https:// | ||
+ | |||
+ | ==为容器元素的排序指定比较策略== | ||
+ | 先明确几个理解: | ||
+ | * 用于组织容器内元素的操作(比如排序),**也属于容器类型的一部分**,需要在声明的时候在三角括号内写上 | ||
+ | * 该类型的操作通常通过函数来实现 | ||
+ | * 传递该类型则通过函数指针来实现 | ||
+ | 默认情况下,有序关联容器通过 ''<'' | ||
+ | <code cpp> | ||
+ | //The comparison must meet the requirements of the Stirct Weak Ordering. | ||
+ | bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) { | ||
+ | return lhs.isbn() < rhs.isbn(); | ||
+ | } | ||
+ | </ | ||
+ | 我们通过**函数指针**将其作为类型参数传递给 set。需要注意的是: | ||
+ | * 传递函数类型时,一般通过函数指针传递。 | ||
+ | * 普通列表项目如果此时使用 '' | ||
+ | * 由于 ISBN 相同的书可能存在多个交易记录,因此使用 // | ||
+ | * // | ||
+ | <code cpp> | ||
+ | // bookstore can have several transactions with the same ISBN | ||
+ | // elements in bookstore will be in ISBN order | ||
+ | multiset< | ||
+ | bookstore(compareIsbn); | ||
+ | </ | ||
+ | |||
+ | 代表策略的函数 '' | ||
+ | 更多 multiset 的构造函数用法:[[https:// | ||
+ | ===Pair 类型=== | ||
+ | //Pair// 类型是一种 STL 定义的**模板**类型,定义于 ''< | ||
+ | <code cpp> | ||
+ | pair< | ||
+ | pair< | ||
+ | pair< | ||
+ | </ | ||
+ | //Pair// 的默认构造函数为其成员提供**值初始化**;同时,也可以通过提供初始值初始化 pair: | ||
+ | <code cpp> | ||
+ | pair< | ||
+ | </ | ||
+ | //Pair// 的两个数据成员均为 '' | ||
+ | < | ||
+ | cout << w.first << w.second << endl; | ||
+ | </ | ||
+ | 一些 pair 类型相关的操作: | ||
+ | \\ \\ {{ cs: | ||
+ | |||
+ | ==返回 pair 类型== | ||
+ | 当需要以 //Pair// 类型做为返回值的时候,有几种方法可以使用: | ||
+ | * list intialize the return value(C++ 11) | ||
+ | <code cpp> | ||
+ | pair< | ||
+ | process(vector< | ||
+ | { | ||
+ | // process v | ||
+ | if (!v.empty()) | ||
+ | | ||
+ | else | ||
+ | | ||
+ | } | ||
+ | </ | ||
+ | * 构造并返回 pair (早期版本) | ||
+ | <code cpp> | ||
+ | if (!v.empty()) | ||
+ | return pair< | ||
+ | </ | ||
+ | * 使用 '' | ||
+ | <code cpp> | ||
+ | if (!v.empty()) | ||
+ | return make_pair(v.back(), | ||
+ | </ | ||
+ | ====关联容器的操作==== | ||
+ | 关联容器还定义了一些属于自己的类型: | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * 在 set 中表示 '' | ||
+ | * 在 map 中表示 '' | ||
+ | 由于 //map// 的元素是以 // | ||
+ | <code cpp> | ||
+ | set< | ||
+ | set< | ||
+ | map< | ||
+ | map< | ||
+ | map< | ||
+ | </ | ||
+ | type 的写法与顺序容器中的自定义类型一致,需要组合 scope 操作符使用: | ||
+ | <code cpp> | ||
+ | map< | ||
+ | </ | ||
+ | ===关联容器的迭代器=== | ||
+ | 对关联容器迭代器的解引用运算会返回指向 // | ||
+ | <code cpp> | ||
+ | // get an iterator to an element in word_count | ||
+ | auto map_it = word_count.begin(); | ||
+ | // *map_it is a reference to a pair< | ||
+ | cout << map_it-> | ||
+ | cout << " " << map_it-> | ||
+ | map_it-> | ||
+ | ++map_it-> | ||
+ | </ | ||
+ | 需要注意的是,迭代器只能修改 Map 中元素的 value。 | ||
+ | ==set 的迭代器都是只读的== | ||
+ | 无论 set 的迭代器是 non-const 还是 const,我们都只能通过该迭代器进行**只读**操作。这是因为 set 中的 key 依然是**不可更改**的: | ||
+ | <code cpp> | ||
+ | set< | ||
+ | set< | ||
+ | if (set_it != iset.end()) { | ||
+ | *set_it = 42; // error: keys in a set are read-only | ||
+ | cout << *set_it << endl; // ok: can read the key | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | ==使用迭代器对关联容器进行遍历== | ||
+ | //Map// 与 //Set// 也提供了 '' | ||
+ | <code cpp> | ||
+ | / get an iterator positioned on the first element | ||
+ | auto map_it = word_count.cbegin(); | ||
+ | // compare the current iterator to the off-the-end iterator | ||
+ | while (map_it != word_count.cend()) { | ||
+ | // dereference the iterator to print the element key--value pairs | ||
+ | cout << map_it-> | ||
+ | << | ||
+ | ++map_it; // increment the iterator to denote the next element | ||
+ | } | ||
+ | </ | ||
+ | 上面的打印结果会根据字母表排序。实际上,使用迭代器遍历 map / set,得出的排序会按照 key 的**升序**来排列。 | ||
+ | ==关联容器与泛型算法== | ||
+ | **不推荐**对关联容器使用泛型算法: | ||
+ | * 由于 key 是 const,要求写的泛型算法无法在关联容器上使用。 | ||
+ | * 泛型算法的读取与查找是序列性的,并不适用于关联容器的数据结构。 | ||
+ | 如果非要使用: | ||
+ | * 将关联容器当做源序列,比如拷贝关联容器的内容到别的序列中 | ||
+ | * 使用 Inserter 的方式对关联容器进行算法的应用 | ||
+ | ===添加元素=== | ||
+ | 关联容器可以通过成员 //insert// 和 //emplace// 向容器中添加元素。相关的操作如下: | ||
+ | \\ \\ < | ||
+ | 需要注意的是,// | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | set< | ||
+ | set2.insert(ivec.cbegin(), | ||
+ | set2.insert({1, | ||
+ | </ | ||
+ | ==向 map 中添加元素== | ||
+ | 只要是 pair 类型的元素,都可以向 map 里添加。添加方式有 4 种: | ||
+ | * 初始化列表 | ||
+ | * 使用 '' | ||
+ | * 显式的使用 argument_list 构造元素 | ||
+ | * 使用 // | ||
+ | <code cpp> | ||
+ | // four ways to add word to word_count | ||
+ | word_count.insert({word, | ||
+ | word_count.insert(make_pair(word, | ||
+ | word_count.insert(pair< | ||
+ | word_count.insert(map< | ||
+ | </ | ||
+ | |||
+ | ==测试 insert 的返回值== | ||
+ | 对于像 //Map// 与 //Set// 这样 Key 是独一无二的容器,// | ||
+ | 该 bool 返回值也可以用于计算输出词的频率: | ||
+ | <code cpp> | ||
+ | // more verbose way to count number of times each word occurs in the input | ||
+ | map< | ||
+ | string word; | ||
+ | while (cin >> word) { | ||
+ | // inserts an element with key equal to word and value 1; | ||
+ | // if word is already in word_count, insert does nothing | ||
+ | auto ret = word_count.insert({word, | ||
+ | if (!ret.second) | ||
+ | ++ret.first-> | ||
+ | } | ||
+ | </ | ||
+ | 总的逻辑是,如果插入操作返回的 bool 数为假,则证明该词已经被插入过了(出现过了),因此增加一次该词的累积次数。 | ||
+ | \\ \\ <color # | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | 这里的迭代器的类型是基于 map 类型的。本例中的迭代器类型是: | ||
+ | <code cpp> | ||
+ | map< | ||
+ | </ | ||
+ | ==向 multimap / multiset 中添加元素== | ||
+ | multimap / multiset 的元素添加不受 key 的限制;也就是说,一个 key 可以对应多个 value。很多应用可以基于这样的结构实现:比如某个作者写了很多本书,则作者可以作为 key 值,他写的书可以做为 value。通过对 multimap 进行元素添加,我们可以把作者与他的书关联起来: | ||
+ | <code cpp> | ||
+ | multimap< | ||
+ | // adds the first element with the key Barth, John | ||
+ | authors.insert({" | ||
+ | // ok: adds the second element with the key Barth, John | ||
+ | authors.insert({" | ||
+ | </ | ||
+ | 该插入操作返回一个迭代器,指向**最新添加的元素**。 | ||
+ | ===删除元素=== | ||
+ | 关联容器使用 //erase// 成员删除元素。// | ||
+ | \\ \\ < | ||
+ | 使用 // | ||
+ | <code cpp> | ||
+ | // erase on a key returns the number of elements removed | ||
+ | if (word_count.erase(removal_word)) | ||
+ | cout << "ok: " << removal_word << " removed\n"; | ||
+ | else cout << "oops: " << removal_word << " not found!\n"; | ||
+ | </ | ||
+ | 当操作容器具有唯一的 key 时,该返回值永远是 '' | ||
+ | <code cpp> | ||
+ | auto cnt = authors.erase(" | ||
+ | </ | ||
+ | ===使用下标访问 map=== | ||
+ | 关系容器中,只有 map 接受下标 '' | ||
+ | \\ \\ < | ||
+ | |||
+ | //Set// 不接受该操作,因为 set 中只存在 Key 值;// | ||
+ | * 通过下标访问 map 中**不存在**的元素时,会直接**创建**一个对应 key 的新元素。该元素的值会被**值初始化**。 | ||
+ | * 生成新元素的操作是 // | ||
+ | <code cpp> | ||
+ | map <string, size_t> word_count; // empty map | ||
+ | // insert a value-initialized element with key Anna; then assign 1 to its value | ||
+ | word_count[" | ||
+ | </ | ||
+ | ==如何使用下标访问的返回值== | ||
+ | 需要注意的是:然而在 map 中,解引用与下标操作的返回值是**不同**的: | ||
+ | * 通过下标访问 map ,返回的值是 // | ||
+ | * 对 map 迭代器解引用,返回的值是 // | ||
+ | 除此之外,使用下标访问的返回值是 // | ||
+ | <code cpp> | ||
+ | cout << word_count[" | ||
+ | ++word_count[" | ||
+ | cout << word_count[" | ||
+ | </ | ||
+ | <WRAP center round tip 100%> | ||
+ | 下标操作会添加不存在的元素。如果只是想查看容器是否包含指定元素,请不要使用下标访问该元素。 | ||
+ | </ | ||
+ | ===访问元素=== | ||
+ | 关联容器提供了如下的方式供使用者访问元素: | ||
+ | \\ \\ < | ||
+ | 如何使用上述的操作取决于应用对象,比如: | ||
+ | * //find// 主要用于确认容器内是否存在主要元素 | ||
+ | * //count// 在 key 对应元素不唯一的情况下可以做更多事情 | ||
+ | 一些例子: | ||
+ | <code cpp> | ||
+ | set< | ||
+ | iset.find(1); | ||
+ | iset.find(11); | ||
+ | iset.count(1); | ||
+ | iset.count(11); | ||
+ | </ | ||
+ | ==使用 find 取代下标== | ||
+ | //find// 成员访问不存在的元素时,不会将其添加到 map。如果只想确认 map 中是否存在某个 key,使用 //find//: | ||
+ | <code cpp> | ||
+ | if (word_count.find(" | ||
+ | cout << " | ||
+ | </ | ||
+ | ==multimap / set 中的元素寻找== | ||
+ | 以 // | ||
+ | - 定义要查找的 key, 也就是作者的名字 | ||
+ | - 定义要搜寻的次数,也就是作者著作的数量,可以使用 // | ||
+ | - 该元素在容器中所处的位置,使用 //find// 成员即可获得指向第一本书籍位置的迭代器 | ||
+ | - 使用搜寻的次数做为循环,循环中: | ||
+ | - 打印当前的书籍 | ||
+ | - 将迭代器自增移动,指向下一本书籍 | ||
+ | - 将搜寻次数自减,意味着一本书籍已经搜索完成 | ||
+ | <code cpp> | ||
+ | string author_name(" | ||
+ | auto entries = authors.count(author_name); | ||
+ | auto iter = authors.find(author_name); | ||
+ | |||
+ | while(entries) { | ||
+ | cout << iter-> | ||
+ | ++iter; //to the next book | ||
+ | --entries; | ||
+ | } | ||
+ | </ | ||
+ | < | ||
+ | 上述循环可以通过两个成员的返回结果来实现: | ||
+ | * // | ||
+ | * // | ||
+ | 利用上述成员的功能,如果使用当前的 key,也就是作者的名字作为 k 值,就可以建立遍历作者所有书籍的循环: | ||
+ | < | ||
+ | curr_iter = lower_bound(curr_key); | ||
+ | next_iter = upper_bound(curr_key); | ||
+ | </ | ||
+ | \\ < | ||
+ | 因此,实现代码可以写成: | ||
+ | <code cpp> | ||
+ | auto curr_book = authors.lower_bound(author_name); | ||
+ | auto next_book = authors.upper_bound(author_name); | ||
+ | |||
+ | for (curr_book; curr_book != next_book; ++curr_book) | ||
+ | cout << curr_book-> | ||
+ | </ | ||
+ | 如果查询不到该作者,则两个成员都会返回指向 // | ||
+ | <color # | ||
+ | 比上述版本更简便的写法是使用 // | ||
+ | <code cpp> | ||
+ | for (auto pos = equal_range(author_name).first; | ||
+ | pos != equal_range(author_name).second; | ||
+ | cout << pos-> | ||
+ | } | ||
+ | </ | ||
+ | ===实例:使用 Map 实现词转换=== | ||
+ | 词的转换是一个很好的 Map 应用。它利用了 Map 元素中, Key-value 的一一对应性质,使用 value 中的长词汇来替换检测到的 key 中的缩写。比如 key 值 为 '' | ||
+ | 一个简单的实现方案大致分为三部分: | ||
+ | * // | ||
+ | * // | ||
+ | * // | ||
+ | < | ||
+ | ==wordTransform 函数的实现== | ||
+ | // | ||
+ | * 接收 map 数据,并调用 // | ||
+ | * 接收 input 的数据,并对数据进行拆分;将拆分好的数据交由 tansform 处理,并格式化输出打印结果: | ||
+ | <code cpp> | ||
+ | void wordTransform(ifstream & | ||
+ | { | ||
+ | //build the tranform map | ||
+ | auto transMap = buildMap(mapData); | ||
+ | string line; | ||
+ | |||
+ | while(getline(inputData, | ||
+ | { | ||
+ | std:: | ||
+ | //output the result by word | ||
+ | for(string word; wordStream >> word; ) | ||
+ | cout << transform(word, | ||
+ | cout << endl; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | 这里通过了 '' | ||
+ | <WRAP center round tip 100%> | ||
+ | 个人意见:书中对于空格的判定是多余的,正常输出即可打印出正确结果。 | ||
+ | </ | ||
+ | |||
+ | ==buildMap 函数的实现== | ||
+ | // | ||
+ | * 文本数据的单位为 '' | ||
+ | * 替换的时候需要检测 value 的值是否合法。本程序的主要目的是将缩写替换为全写,这里设定小于 '' | ||
+ | * 在第二阶段读取 value 的时候,会将中间的空格一并读取;因此输出的时候需要使用 '' | ||
+ | <code cpp> | ||
+ | const map< | ||
+ | { | ||
+ | map< | ||
+ | string abbr; | ||
+ | string fullName; | ||
+ | |||
+ | //reading data | ||
+ | while(mapData >> abbr && getline(mapData, | ||
+ | { | ||
+ | if(fullName.size() > 1) // check that there is a transformation | ||
+ | transMap[abbr] = fullName.substr(1); | ||
+ | // | ||
+ | else | ||
+ | throw std:: | ||
+ | } | ||
+ | return transMap; | ||
+ | } | ||
+ | </ | ||
+ | 还有一点需要注意的是,本程序使用了下标的方法添加元素到 Map。 如果 key(缩写) 重名,则其对应的 value (全写) 将会被**覆盖**掉。如果希望**保留**旧的规则,可以替换使用 // | ||
+ | ==transform 函数的实现== | ||
+ | // | ||
+ | <code cpp> | ||
+ | const string& | ||
+ | transform(const string &s, const map< | ||
+ | { | ||
+ | auto mapIter = mss.find(s); | ||
+ | //if a transformable word ' | ||
+ | //return its fullname | ||
+ | if(mapIter != mss.cend()) | ||
+ | return mapIter-> | ||
+ | else | ||
+ | return s; // otherwise return the original unchanged | ||
+ | } | ||
+ | </ | ||
+ | 这里对不在 map 中的词做了不修改的处理。 | ||
+ | ====无序关联容器==== | ||
+ | 新标准中定义了四种无序关联容器。这些关联容器使用 '' | ||
+ | * 当 key 不需要**有序**的情况下 | ||
+ | * 当 key 的**排序代价很高**的情况下 | ||
+ | 但总的说来,尽管通过哈希函数来管理元素可以提供更好的平均性能,但对哈希函数的设计、测试与修改给我们带来了更多的工作量。因此,默认情况下,使用有序关联容器往往拥有更好的表现。 | ||
+ | <WRAP center round tip 100%> | ||
+ | 如果 Key 的无序是固有的,或者通过哈希函数管理元素可以解决性能的问题,使用无序关联容器。 | ||
+ | </ | ||
+ | ===使用无序容器=== | ||
+ | 除了与哈希管理相关的成员,无序容器提供了与有序容器基本上一样的操作。不过因为其无序性,因此在输出的**顺序**上会与有序容器的输出有差别。 | ||
+ | ==桶的管理== | ||
+ | 无序容器通过一系列的**桶**(// | ||
+ | \\ \\ | ||
+ | 当容器允许一个 key 对应多个元素的时候,所有与某个 key 相关的元素都会放到同一个桶中。因此,无序容器的性能依赖如两个要求: | ||
+ | * 哈希函数是如何设计的 | ||
+ | * 桶里的元素有多少 | ||
+ | 当使用相同的 key 调用哈希函数的时候,哈希函数会确保返回同样的值。虽然在理想状态下,哈希函数应该为每一个元素都分配一个独立的哈希值;但由于容器允许 key 与 value 存在一对多的关系,因此很可能存在将同 key 下的 value 放入同一个桶中的情况。由于**对单个桶中的元素查找是通过顺序查找的方式来进行的**,这带来了一个问题:尽管通过 key 找元素对应的桶依然很快,但**桶内的元素过多,会严重影响无序容器的性能**。 | ||
+ | \\ \\ | ||
+ | 无序容器提供了如下的成员对桶进行管理: | ||
+ | \\ \\ < | ||
+ | ==无序容器对 key 的要求== | ||
+ | 默认情况下,无序容器使用 '' | ||
+ | 不过,该模板函数并不能直接对自定义类型使用。计算自定义类型的哈希值有两种方法: | ||
+ | * 创建自定义的哈希模板(之后的内容) | ||
+ | * 使用哈希函数计算自定义类型中的某个受其支持的成员,并使用该成员来做为自定义类型的比较标准。 | ||
+ | 拿之前提到的 // | ||
+ | <code cpp> | ||
+ | template< | ||
+ | class Key, //element type | ||
+ | class Hash = std:: | ||
+ | class KeyEqual = std:: | ||
+ | class Allocator = std:: | ||
+ | > class unordered_multiset; | ||
+ | </ | ||
+ | 本例中,我们使用 '' | ||
+ | <code cpp> | ||
+ | size_t hasher(const Sales_data &sd) | ||
+ | { | ||
+ | return hash< | ||
+ | } | ||
+ | |||
+ | bool eqOp(const Sales_data &lhs, const Sales_data &rhs) | ||
+ | { | ||
+ | return lhs.isbn() == rhs.isbn(); | ||
+ | } | ||
+ | </ | ||
+ | 因此,我们的 multiset 可以定义为: | ||
+ | <code cpp> | ||
+ | std:: | ||
+ | </ | ||
+ | 当然,最好使用 alias 简化写法: | ||
+ | <code cpp> | ||
+ | using SdMultiset = std:: | ||
+ | SdMultiset bookstore(42, | ||
+ | </ | ||
+ | 这里使用了函数指针作为传递的参数,与之前有序容器中使用自定义的 compareIsbn 函数用法相同。同时,我们为 '' | ||
+ | |||
+ | \\ \\ <color # | ||
+ | 上例使用的该构造函数进行初始化。 | ||
+ | <code cpp> | ||
+ | unordered_multiset ( size_type n, | ||
+ | const hasher& hf = hasher(), | ||
+ | const key_equal& | ||
+ | const allocator_type& | ||
+ | </ | ||
+ | 其他构造方式可以参考下面的引用。\\ \\ | ||
+ | **Ref: | ||
+ | 如果类中已经定义了 '' | ||
+ | <code cpp> | ||
+ | // use FooHash to generate the hash code; Foo must have an == operator | ||
+ | unordered_set< | ||
+ | </ | ||