zoukankan      html  css  js  c++  java
  • c++primer笔记十一、关联容器

    与顺序容器的根本不同:关联容器中的元素是按关键字来保存和访问的,而顺序容器是按位置保存的
    关联容器支持高效的查找和访问
    主要的有map和set

    一共有8种,主要分类依据:
    1、set或map
    2、是否允许重复关键字,multi
    3、按顺序保存或无序,unordered_

    使用关联容器

    map:关联数组
    set:集合

    使用map

    例:单词计数

    //统计每个单词在输入中出现的次数
    map<string, size_t> word_count;
    string word;
    while (cin >> word)
        ++word_count[word];
    for (const auto &w : word_count)
        cout << w.first << " occurs " << w.second
             << ((w.second > 1) ? " times" : " time") << endl;
    

    从map中提取一个元素时,得到的是pair

    使用set

    例:对上一个程序中的常见单词进行忽略,可以用set保存忽略单词

    map<string, size_t> word_count;
    set<string> exclude = { "The", "But", "And", "Or", "An", "A",
                            "the", "but", "and", "or", "an", "a"};
    string word;
    while(cin >> word)
        if (exclude.find(word) == exclude.end())
            ++word_count[word];
    

    关联容器概述

    关联容器不支持位置相关的操作,都支持一些其他操作。
    还提供用来调整哈希性能的操作。
    迭代器都是双向的

    定义关联容器

    map<string, size_t> word_count;     //空容器
    //列表初始化
    set<string> exclude = { "The", "But", "And", "Or", "An", "A",
                            "the", "but", "and", "or", "an", "a"};
    //map初始化
    mat<string, string> authors = { {"joyce", "james"},{"Austem", "Jane"}};
    

    把关键字-值包围在花括号中

    multimap 或multiset

    允许拥有重复的关键字

    关键字类型的要求

    关键字类型必须定义元素比较的方法,默认使用<

    可以定义自己的操作来带起关键字上的<运算符,必须定义一个严格弱序("小于等于")

    例:为了使用自己定义的操作,必须提供两个类型:Sakes_data以及比较操作类型

    multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);
    
    pair类型

    定义在utility中,一个pair保存两个数据成员

    pair<string, string> anon;      //保存两个string
    

    pair的默认构造函数对数据成员进行值初始化,也可以提供初始化器

    pair<string, string> author{"James", "Joyce"};
    

    pair的数据成员是public的,分别为first和second
    其它操作见表11.2

    创建pair对象的函数

    可以用花括号

    pair<string, int> process(vector<string> &v)
    {
        if(!v.empty())
            return {v.back(), v.back().size());
        else
            return pair<string, int>();
    }
    

    老版本不能用花括号,必须显式构造

    return pair<string, int>(v.back(), v.back().size());
    

    或者用make_pair

    return make_pair(v.back(), v.back().size());
    

    关联容器操作

    关联容器额外的类型别名

    key_type        此容器类型的关键字类型
    mapped_type     每个关键字关联的类型;只适用于map
    value_type      对于set,于key_type相同
                    对于map,为pair<const key_type, mapped_type>
    

    这些pair关键字部分是const的

    set<string>::value_type v1;         //v1是string
    set<string>::key_type v2;           //v2是string
    map<string, int>::value_type v3;    //V3是pair<const string, int>
    map<string, int>::key_type v4;      //v4是string
    map<string, int>::mapped_type v5;   //v5是int
    

    关联容器迭代器

    map的value_type是一个pair。只能改变值不能改变关键字

    map_it->first = "new";  //错误,关键字是const
    ++map_it->second;       //正确,元素可以改
    
    set的迭代器是const的

    iterator和const_iterator都是只读的

    遍历关联容器

    map和set都支持begin和end
    例:打印单词技术结果

    auto map_it = word_count.cbegin();
    while (map_it != word_count.cend())
    {
        cout << map_it->first << " occurs"
             << map_it->second << "times" << endl;
        ++map_it;
    }
    

    map、multimap、set、multiset的迭代器按关键字升序遍历元素

    关联容器和算法

    关联容器可用于只读取的算法,修改或重排的不行。set是const的,map中的pair第一个成员是const的。
    使用时要么把它当作一个源序列,要么当作一个目的位置。

    添加元素

    使用insert添加元素,如果插入一个已存在元素对容器也没有任何影响

    向map添加元素

    有4个方法

    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)); //自动构造合适的pair
    

    其它操作见表11.4

    检测insert的返回值

    insert(或emplace)的返回值依赖于容器类型和参数。
    对于不重复的容器,添加单一元素的insert返回一个pair,first是迭代器,指向给定关键字的元素;second是bool值,指出是插入成功还是以及存在于容器中。
    例:计数单词

    map<string, size_t> word_count;
    string word;
    while (cin >> word){
        auto ret = word_count.insert({word,1});
        if (!ret.second)
            ++ret.first->second;
    }
    
    展开递增语句
    ++((ret.first) -> second);
    
    向multiset或multimap添加元素

    允许重复关键字的容器,接受单个元素的insert操作返回一个指向新元素的迭代器。不需要返回bool

    删除元素

    通过erase删除,有3个版本。可以传递迭代器或迭代器对表示的范围来删除元素
    关联容器可以接受一个key_type参数,删除所有匹配给定关键字的元素,返回实际删除的元素的数量。

    map的下标元素

    map和unordered_map都有下标运算符和at函数,set不支持下标。
    下标接受索引,但如果下标不存在,会创建一个元素并插入到map中,关联值将进行值初始化。

    word_count["Anna"] = 1; //插入一个关键字值对并用1初始化

    at则是访问关键字为k的元素,如果不存在抛出异常

    访问元素

    对于不允许重复的容器find和count没区别
    允许重复的容器count会统计次数,如果不需要计数用find

    set<int> iset = {0,1,2,3,4,5,6,7,8,9};
    iset.find(1);           //返回key==1的元素的迭代器
    iset.find(11);          //返回iset.end(0)
    iset.count(1);          //返回1
    iset.count(11);         //返回0
    

    map还有其它操作

    c.lower_bound(k);       //指向第一个关键字不小于k的元素
    c.upper_bound(k);       //指向第一个关键字大于k的元素
    c.equal_range(k);       //返回一个迭代器pair,表示关键字等于k的元素的范围。若k不存在则两个成员都是c.end();
    
    对map使用find代替下标操作

    下标一个副作用:如果关键字不存在会创建一个
    如果只想知道一个给定的关键字是否在map中,而不想改变map,用find

    if (word_count.find("foobar") == word_count.end())
        cout << "foobar is not in the map" << endl;
    
    在multimap或multiset中查找

    一个容器中可能有很多元素会有给定的关键字,则这些元素会相邻存储。
    例:找一个特定作者的所有著作

    string search_item("Alain de Botton");
    auto entries = authors.count(search_item);  //返回元素数量
    auto iter = authors.find(search_item);      //返回第一本书
    while(entries)
    {
        cout << iter->second << endl;
        ++iter;
        --entries;
    }
    

    先用count获取数量,再用find获得第一个迭代器,进行循环

    一种面向迭代器的解决方法

    用lower_bound和upper_bound来解决
    如果关键字在容器,返回第一个元素迭代器和最后一个迭代器之后的位置
    如果关键字不在,都返回一个不影响排序的关键字插入位置
    如果查找的元素具有最多的关键字,upper_bound返回尾后迭代器。如果关键字不存在且大于容器中的任何关键字,则lower_bound也是尾后迭代器

    //重写上面
    for (auto beg = authors.lower_bound(search_item),
              end = authors.upper_bound(search_item);
         beg != end; ++beg)
         cout << beg->second << endl
    
    equal_range

    此函数接受一个关键字,返回一个迭代器pair。
    若关键字存在,则第一个迭代器指向关键字匹配元素,第二个迭代器指向最后一个匹配元素之后的位置。
    若不存在则都返回指向可以插入的位置

    //重写
    for (auto pos = authors.equal_range(search_item);
         pos.first != pos.second; ++pos.first)
         cout << pos.first->second << endl;
    
    一个单词转换的map程序

    给定一个string,根据对应表,转换成另一个string。
    程序使用三个函数,word_transform管理整个过程,接受两个ifstream:一个绑定到单词转换文件,一个绑定到我们要转换内容的映射。
    buildMap读取转换规则文件,并创建一个map,保存每个单词到其转换内容的映射。
    transform接受一个string,如果存在转换规则,返回转换后的内容

    void word_transform(ifsteam &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;
        }
    }
    
    map<string, string> buildMap(ifsteam &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<sting, string> &m)
    {
        auto map_it = m.find(s);
        if (map_it != m.cend())
            return map_it->second;
        else
            return s;
    }
    

    11.4 无序容器

    unordered_map、unordered_set
    新标准定义了4个无序关联容器,使用哈希函数和关键字类型得==运算符
    无序容器也能用有序容器相同得操作如(find、insert)。

    管理桶

    无序容器在存储上组织为一组桶,每个桶保存零或多个元素。使用哈希函数将元素映射到桶。
    访问时先计算元素得哈希值,将一个具有特定哈希值得所有元素都保存在同一个桶中。如果允许重复关键字,所有具有相同关键字得元素也都会在同一个桶中。
    无序容器得性能依赖于哈希函数得质量和桶得数量和大小。

    理性情况下,每个特定的值都映射到唯一的桶,但是将不同的关键字映射到相同的桶也是允许的。
    桶的管理操作见表11.8

    无序容器对关键字的要求

    无序容器用==来比较,使用hash<key_tpye>类型的对象来生成每个元素的哈希值。
    标准库为内置类型都定义了hash,可以直接定义关键字是内置类型或string或智能指针的无序容器。
    不能直接定义自定义类型的无序容器,必须提供自己的hash模板。

    //定义哈希
    size_t hasher(const Sales_data &sd)
    {
        return hash<string>()(sd.isbn());
    }
    //定义比较
    bool eqOp(const Sales_data &lhs, const Sales_data &rhs)
    {
        return lhs.isbn() == rhs.isbn();
    }
    
    //定义一个unordered_multiset
    using SD_multiset = unordered_multiset<Sales_data, decltype(hasher)*, decltype(eqOp)*>;
    SD_multiset bookstore(42,hasher,eqOp);
    
    //如果类定义了==运算符,则可以只重载哈希函数
    unordered_set<Foo, decltype(FooHash)*> fooSet(10, FooHash);
    
  • 相关阅读:
    对Java课程的感想
    OO第二阶段总结
    OO第一作业周期(前四周)总结
    实验7 流类库和输入输出
    实验6 类的继承和多态
    实验5 类和对象3
    实验4 类与对象2
    实验3 类和对象
    实验2
    实验1
  • 原文地址:https://www.cnblogs.com/aqq2828/p/14204740.html
Copyright © 2011-2022 走看看