• 【C++】《C++ Primer 》第十一章


    第十一章 关联容器

    • 关联容器和顺序容器的不同:关联容器中的元素时按照关键字来保存和访问的。
    • 关联容器支持通过关键字来高效地查找和读取元素,基本的关联容器类型是 map和 set。
    • 类型 mapmultimap 定义在 头文件map 中;set 和 multimap 定义在 头文件set 中。 无序容器则定义在 unordered_map 和 unordered_set 中。
    容器类型 解释
    有序存储
    map 关键数组:保存关键字-值对
    set 关键字即值,即只保存关键字的容器
    multimap 支持同一个键多次出现的map
    multiset 支持同一个键多次出现的set
    无序存储
    unordered_map 用哈希函数组织的map
    unordered_set 用哈希函数组织的set
    unordered_multimap 哈希组织的map,关键字可以重复出现
    unordered_multiset 哈希组织的set,关键字可以重复出现

    一、使用关联容器

    1. 使用map

    // 单词计数程序
    map<string, size_t> word_cnt;
    string word;
    while(cin >> word) {
        ++word_cnt[word];
    }
    for(const auto &w: word_cnt) {
        cout << w.first << " occurs " << w.second << ((w.second > 1) ? "times": "time") << endl;
    }
    

    2. 使用set

    // 单词计数程序
    map<string, size_t> word_cnt;
    set<string> exclude = {"The", "But", "And", "Or", "An"};
    
    string word;
    while(cin >> word) {
        if(exclude.find(word) == exclude.end()) {
            ++word_cnt[word];
        }
    }
    

    二、关联容器概述

    • 关联容器的迭代器都是双向的。

    1. 定义关联容器

    • 需要指定元素类型。
    • 列表初始化:
      • map:map<string, int> word_count = {{"a", 1}, {"b", 2}};
      • set:set exclude = {"the", "a"};

    2. 关键字类型的要求

    • 对于有序容器,关键字类型必须定义元素比较的方法。默认是<。
    • 如果想传递一个比较的函数,可以这样定义:multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);
    // 可以定义一个vector<int>::iterator到int的map吗?
    // 答:可以。因为vector的迭代器支持比较操作。
    
    // 那list<int>::iterator到int的map呢?
    // 答:不可以。因为list的元素不是连续存储,其迭代器不支持比较操作。
    

    3. pair类型

    • 头文件utility 中定义。
    • 一个pair保存两个数据成员,两个类型不要求一样。
    • pair操作
    操作 解释
    pair<T1, T2> p; p是一个pair,两个类型分别是T1和T2的成员都进行了值初始化。
    pair<T1, T2> p(v1, v2); first和second分别用v1和v2进行初始化。
    pair<T1, T2>p = {v1, v2}; 同上。
    make_pair(v1, v2); pair的类型从v1和v2的类型推断出来。
    p.first 返回p的名为first的数据成员。
    p.second 返回p的名为second的数据成员。
    p1 relop p2 运算关系符按字典序定义。
    p1 == p2 必须两对元素两两相等。
    p1 != p2 同上。

    三、关联容器操作

    • 关联容器额外的类型别名
    类型别名 解释
    key_type 此容器类型的关键字类型。
    mapped_type 每个关键字关联的类型,只适用于map
    value_type 对于map,是pair<const key_type, mapped_type>; 对于set,和key_type相同。

    1. 关联容器迭代器

    • 解引用一个关联容器迭代器时,会得到一个类型为容器的value_type的值的引用。
    • 注意:一个map的value_type是一个pair,可以改变pair的值,但是不能改变关键字成员的值。
    • set的迭代器是const的。可以用一个set迭代器来读取元素的值,但不能修改。
    • 遍历关联容器:使用begin和end,遍历map、multimap、set、multiset时,迭代器按关键字升序遍历元素。
    • 通常不对关联容器使用泛型算法。关键字是const这一特性意味着不能将关联容器传递给修改或重排容器元素的算法,因为这类算法需要向元素写入值,而set类型中的元素是const的,map中的元素是pair,其第一个成员是const的。
    • 实际开发中,真要用一个关联容器使用算法,要么是将它当作一个源序列,要么当作一个目的位置

    2. 添加元素

    • 关联容器 insert 操作
    操作 解释
    c.insert(v) c.emplace(args) v是value_type类型的对象;args用来构造一个元素。 对于map和set,只有元素的关键字不存在c中才插入或构造元素。函数返回一个pair,包含一个迭代器,指向具有指定关键字的元素,以及一个指示插入是否成功的bool值。对于multimap和multiset则会插入范围中的每个元素。
    c.insert(b, e) c.insert(il) b和e是迭代器,表示一个 c::value_type 类型值的范围;il是这种值的花括号列表。函数返回void。对于 map和set,只插入关键字不在c中的元素。
    c.insert(p, v) c.emplace(p, args) 类似insert(v),但将迭代器p作为一个提示,指出从哪里开始搜索新元素应该存储的位置。返回一个迭代器,指向具有给定关键字的元素。
    • 向 set 添加元素
    vector<int> vec{1,244,642,72,3,6,3,6,1};
    set<int>set2;
    set2.insert(vec.cbegin(), vec.cend());   // set2现有6个元素
    set2.insert({5, 8, 5, 8});  // set2现有8个元素
    
    • 向 map 添加元素
    // 四种插入方法
    word_count.insert({word, 1});   // 推荐使用
    word_count.insert(make_pair(word, 1));  // 推荐使用
    word_count.insert(pair<string, size_t>(word, 1));
    word_count.insert(map<string, size_t>::value_type (word, 1));
    

    3. 删除元素

    • 从关联容器中删除元素
    操作 解释
    c.erase(k) 从c中删除每个关键字为k的元素。返回一个size_type值,指出删除的元素的数量。
    c.erase(p) 从c中删除迭代器p指定的元素。p必须指向c中一个真实元素,不能等于c.end()。返回一个指向p之后元素的迭代器,若p指向c中的尾元素,则返回c.end()。
    c.erase(b, e) 删除迭代器对b和e所表示范围中的元素。返回e。

    4. map的下标操作

    • 只支持map和unordered_map的下标操作。
    • 由于下标运算符可能插入一个新元素,只可以对非const的map使用下标操作。
    • 对一个map使用下标操作,其行为与数组或vector上的下标操作有很大不同,使用一个不在容器中的关键字作为下标,会添加一个具有此关键字的元素到map中。
    • map和unordered_map的下标操作
    操作 解释
    c[k] 返回关键字为k的元素;如果k不在c中,添加一个关键字为k的元素,对其值初始化。
    c.at(k) 访问关键字为k的元素,带参数检查;若k不存在在c中,抛出一个out_of_range异常。

    5. 访问/查找元素

    • lower_bound和upper_bound不适用于无序容器。
    • 下标 和 at 操作只适用于非const的map和unordered_map。
    • 如果只关心一个特定元素是否在容器中,可能find是最佳选择。对于不允许重复的容器,可能使用find和count没有什么区别。对于允许重复的容器,count还会做更多事,如果元素在容器中,它还会统计次数。如果不需要计数,最好使用find。
    • 在一个关联容器中查找元素
    操作 解释
    c.find(k) 返回一个迭代器,指向第一个关键字为k的元素,若k不在容器中,则返回尾后迭代器。
    c.count(k) 返回关键字等于k的元素的数量。对于不允许重复关键字的容器,返回值永远是0或1。
    c.lower_bound(k) 返回一个迭代器,指向第一个关键字不小于k的元素。
    c.upper_bound(k) 返回一个迭代器,指向第一个关键字大于k的元素。
    c.equal_range(k) 返回一个迭代器pair,表示关键字等于k的元素的范围。若k不存在,pair的两个成员均等于c.end()。
    • 如果只想知道一个关键字是否在map中时,推荐使用find代替下标操作,因为下标操作可能会引发意想不到的问题。

    6. 一个综合的例子

    给定一个string,将它转换为另一个string。程序的输入是两个文件。第一个文件保存一些规则,用来转换第二个文件中的文本。每条规则由两部分组成:一个可能出现在输入文件中的单词和一个替换它的短语。

    // 给定一个string,将它转换为另一个string。程序的输入是两个文件。第一个文件保存一些规则,用来转换第二个文件中的文本。
    // 每条规则由两部分组成:一个可能出现在输入文件中的单词和一个替换它的短语。
    
    #include <iostream>
    #include <fstream>
    #include <sstream>
    #include <string>
    #include <map>
    //#include <algorithm>
    
    using namespace std;
    
    // 转换映射
    map<string, string> buildMap(ifstream &map_file) {
        map<string, string> trans_map;
        string key;
        string value;
    
        while(map_file >> key && getline(map_file, value)) {
            if(value.size() > 1)
                trans_map[key] = value.substr(1);
            else
                throw runtime_error("no rule for " + key);
        }
        return trans_map;
    }
    
    // 生成转换文本
    const string & transform(const string &s, const map<string, string> &m) {
        auto map_it = m.find(s);
        if(map_it != m.cend())
            return map_it->second;
        else
            return s;
    }
    
    // 单词转换
    void word_transform(ifstream &map_file, ifstream &input) {
        auto trans_map = buildMap(map_file);
        string text;
        while(getline(input, text)) {
            istringstream stream(text);
            string word;
            bool firstWord = true;
            while(stream >> word) {
                if(firstWord)
                    firstWord = false;
                else
                    cout << " ";
    
                cout << transform(word, trans_map);
            }
            cout << endl;
        }
    }
    
    int main() {
        ifstream map_file_in("../ch11/map_file.txt");
        ifstream input_in("../ch11/input.txt");
    
        word_transform(map_file_in, input_in);
    
        return 0;
    }
    
    

    四、无序容器

    • C++11定义了4个无序关联容器,它们不是使用比较运算符来组织元素,而是使用一个 哈希函数关键字类型的==运算符
    • 如果关键字类型固有就是无序的,或者性能测试发现问题可以用哈希计数解决,就可以使用无序容器。
    • 无序容器在存储上组织为一组桶,每个桶保存零个或多个元素。无序容器使用一个哈希函数将元素映射到桶。所以,无序容器的性能依赖于哈希函数的质量桶的数量和大小
    • 无序容器管理操作:允许查询容器的状态以及必要时强制容器进行重组。
    操作 解释
    桶接口
    c.bucket_count() 正在使用的桶的数目
    c.max_bucket_count() 容器能容纳的最多的桶的数目
    c.bucket_size(n) 第n个桶中有多少个元素
    c.bucket(k) 关键字为k的元素在哪个桶中
    桶迭代
    local_iterator 可以用来访问桶中元素的迭代器类型
    const_local_iterator 桶迭代器的const版本
    c.begin(n),c.end(n) 桶n的首元素迭代器
    c.cbegin(n),c.cend(n) 与前两个函数类似,但返回const_local_iterator
    哈希策略
    c.load_factor() 每个桶的平均元素数量,返回float值
    c.max_load_factor() c试图维护的平均比桶大小,返回float值。c会在需要时添加新的桶,以使得load_factor<=max_load_factor
    c.rehash(n) 重组存储,使得bucket_count>=n,且bucket_count>size/max_load_factor
    c.reverse(n) 重组存储,使得c可以保存n个元素且不必rehash
  • 相关阅读:
    iOS 笔记-AFN使用中的遇到的问题
    iOS 笔记-incompatible pointer types initializing 'NSMutableString *' with an expression of type 'NSString *'警告处理
    iOS 笔记-SRT视频字幕的解析与同步
    iOS-笔记 字符编码
    iOS-笔记 设置导航栏的样式
    iOS 笔记-自定义的导航栏按钮
    iOS 笔记-删除插件的方法
    iOS 笔记-关于用户交互的那些事
    iOS BUG整理--[__NSCFNumber length]: unrecognized selector sent to instance 崩溃解决
    iOS BUG整理-Data argument not used by format string的警告处理
  • 原文地址:https://www.cnblogs.com/parzulpan/p/13396290.html
Copyright © 2020-2023  润新知