zoukankan      html  css  js  c++  java
  • 关联型容器

    关联型容器

    一、概述

    关联性容器支持高效的关键字查找和訪问。当中元素是按关键字来保存和訪问的,主要关联型容器类型是map和set。map中的一些元素是pair<key, value>,即键值对,关键字起到索引的作用,值则表示与索引关联的数据。set中每一个元素仅仅包括一个关键字;set支持高效的关键字查询操作——检查一个给定关键字是否在set中。字典是使用map非常好的样例;

    标准库提供8个关联容器,如图所看到的。

    image

    不同之处体如今三个维度:

    1.或者是map,或者是set

    2.或者是有序。或者是无序

    3.或者是同意反复,或者不同意反复

    map和multimap定义在头文件map中;

    set和multiset定义在set文件里。

    无序容器定义在unordered_map和unordered_set中

    二、定义关联容器

    定义map时。必须之名关键字类型和值类型。而定义set时。仅仅需指明关键字类型;

    定义空容器:map<string, size_t> word;

    列表初始化(C++11):

    map<string, size_t> m;
    map<string, size_t> n{{"tubin", 1}, {"tufang", 2}, {"tianlan", 3}};
    map<string, size_t> p(n.begin(), n.end());
    for (const auto & w : p) {
        cout << w.first << " " << w.second << endl;
    }

    当初始化map时。必须提供关键字类型和值类型,用{}花括号扩起来;

    三、关键字类型的要求

    3.1 关键字类型

    关联型容器对关键字类型有些限制。

    对于有序容器,关键字类型必须定义元素比較的方法。

    默认情况下,标准库使用关键字类型的<运算符来比較两个关键字。

    能够提供自己定义的操作取代关键字的<运算符,仅仅是该操作必须在关键字类型上定义一个严格弱序。(<=)

    在实际编程中,假设一个类型定义了“行为正常”的<运算符,则它能够用作关键字类型。

    3.2使用关键字类型的比較函数

    为了指定使用自己定义的操作。必须在定义关联容器时提供比較操作的类型。

    自己定义操作类型必须在尖括号里紧跟着元素类型给出。

    方法1

    //自己定义比較函数
    bool compare_size(const string &a, const string &b) {
        return a.size() < b.size();
    }
    //指定比較函数
    map<string, size_t, decltype(compare_size)*> word(compare_size);

    decltype指出自己定义操作的类型。但当它作用于函数时得到的并不是是函数指针,而是函数类型,因此必须加上*
    再用compare_size来显示初始化word对象。此时将会调用map的explicit带參构造。

    方法2

    //定义
    template <typename T>
    struct Longer : public binary_function<T, T, bool> {
        bool operator() (const T& x, const T& y) const {
            return x.size() > y.size();
        }
    };
    
    //使用
    map<string, size_t, Longer<string>> word;
    

    看过STL源代码之后,能够通过自己定义一个函数。继承自标准库的模版,就能够得到可配接(adaptable)的函数对象了。

    3.3 简单应用

    能够定义vector::iterator 到 int 的map,可是不能定义list::iterator 到 int 的map.

    由于vector中迭代器即为原生指针。支持<运算符,但list的迭代器不支持<运算符,由于不是连续存储的。比較大小没有意义;

    四、pair类型

    pair标准库类型,定义在utility中。一个pair保存两个数据成员。相似容器,pair是一个用来省城特定类型的模版。创建pair时必须提供两个类型名,两者不要求一样。

    创建pair

    //使用默认构造
    pair<string, string> p1;
    pair<string, size_t> p2;
    pair<string, vector<int> p3;
    //提供初始化器
    pair<string, string> author{"my", "mht"};
    //make_pair();
    pair<string, string> p = make_pair("al", "bd");
    //显示构造
    p3 = pair<string, vector<int>>();
    

    使用pair

    pair的数据成员是public的,两个成员分别命名为first和second。

    用对象訪问:cout << author.first << author.second;

    用指针訪问:cout << w->first << w->second;

    五、简单使用关联容器

    ex1:单词计数程序 map

    //自己定义推断符号
    map<string, size_t, Longer<string>> word_count;
    string record;
    while (cin >> record) {
        ++word_count[record];
    }
    for ( const auto & w : word_count) {
        cout << w.first << ": " << w.second
        << (w.second > 1 ? "times": "time") << endl;
    }

    ex2:忽略常见单词的计数程序 map set

    /用set保存想忽略的单词,在统计前推断
    map<string, size_t> word_count;
    set<string> exclude = {"the", "The", "and", "And", "a", "A", "or", "Or"};
    
    string word;
    while (cin >> word) {
        //不在exclude中
        if (exclude.find(word) == exclude.end()) {
            ++word_count[word];
        }
    }
    
    for (const auto& w : word_count) {
        cout << w.first << ": " << w.second
                << (w.second > 1 ?

    " times" : " time") << endl; }

    六、关联容器操作

    key_type 容器的关键字类型
    mapped_type 每一个关键字关联的类型,仅仅适用于map
    value_type 对于set,与key_type 同样;对于map,是pair

    6.1 关联容器迭代器

    迭代器
    解引用一个迭代器得到一个容器的value_type值的引用。
    当中,set的迭代器是const的,value_type即为key_type,是const的
    对于map,value_type的first成员是key_type, 也是const的。
    容器和算法
    我们通常不正确关联容器使用泛型算法。关键字是const这一特性意味着不能使用mutable 算法。仅仅应用于制度的算法;但这类型的算法都要搜索序列,不能进行高速查找。

    因此对于关联性容器使用泛型搜索算法。差点儿总是个坏主意。应该使用关联性容器专用的成员。

    (见后文)
    实际应用
    在实际应用中。假设要使用算法,要么当作一个源序列,要么当一个目的位置。

    6.2 加入元素

    四种方法加入元素

    map<string, size_t> m;
    m.insert({"tubin", 1});
    m.insert(make_pair("tufang", 2));
    m.insert(pair<string, size_t>("lanlan", 3));
    m.insert(map<string, size_t>::value_type("tiantian", 4));

    返回值
    1.对于不包括反复关键字的容器,加入元素返回一个pair<iterator, bool>,bool标识插入成功与否。
    2.对于包括反复关键字的容器,加入元素一定能成功,因此返回的是指向新元素的iterator

    重写单词计数程序

    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保存返回值pair<iterator, bool>
    ret.first 为iterator,指向value_type<key_type, mapped_type>
    ret.first->second 为值mapped_type
    ++ret.first->second 递增此值

    6.3 删除元素

    标准库定义函数例如以下:
    image

    erase(k):返回实际删除的元素的数量;
    对于不反复容器。erase返回的值总是0(不存在) or 1(存在);
    对于反复容器。erase返回的值可能大于1;

    6.4 map的下标操作

    特定容器提供
    map和unordered_map提供下标[ ]运算符和相应at函数;
    set不提供,由于没有相关联的值;
    multi-不提供;由于下标索引到的结果可能有多个。

    特性
    下标操作接受一个索引(关键字),获取与之相关联的值。但主要特性是。假设没有该索引,则创建一个元素并插入到map中。关联值将进行值初始化。
    因此,仅仅能对非const版本号提供下标操作

    at操作
    訪问关键字为k的元素,带有參数检查;假设k不存在,则抛出异常;

    返回值
    通常情况下,解引用一个迭代器返回类型与下标运算返回类型一致;但对于map则不然
    对map。下标操作返回一个mapped_type对象; 解引用迭代器。得到value_type(pair)对象.

    总结
    下标和at操作仅仅指针对于非const的map和unordered_map

    6.5 訪问元素

    标准库定义例如以下查找元素的操作
    image

    对于不同意反复容器。count和find没差别;
    对于同意反复容器,count还要统计个数;

    因此。对于count。find,[]
    假设指向知道给定关键字是否在map中。而不想改变map,则用find;否则用[]
    假设不想计数。则用find。否则用count

    在multimap,multiset中查找元素的三种方法
    ex:在给定一个从作者到著作题目的映射。打印一个特定作者的全部著作,有三种方法

    1 count + find

    multimap<string, string> m{{"my", "tb"}, {"my", "tm"}, {"my", "zfb"}, {"mht", "QQ"}, {"mht", "wx"}};
    string item = "my";
    
    auto entries = m.count(item);   //统计个数,假设没有返回0
    auto iter = m.find(item);       //找到第一个。假设没有返回end()
    
    while (entries) {
        cout << iter->second << endl;
        ++iter;
        --entries;
    }

    2 lower_bound + upper_bound
    lower_bound:返回迭代器指向第一个具有给定关键字的元素。
    upper_bound:返回的迭代器指向最后一个匹配给定关键字的元素之后的位置;
    假设关键字不存在。则lower_bound和upper_bound会返回相等的迭代器;(有可能都是尾后迭代器)

    for (auto b = m.lower_bound(item), e = m.upper_bound(item);
        b != e; ++b) {
        cout << b->second << endl;
    }

    3 equal_range
    equal_range返回的是pair,当中两个成员各自是调用的lower_bound和upper_bound得到的迭代器。因此,此方法和上面方法没有本质的差别;唯一的差别是:不须要定义局部变量来保存元素范围;

    for (auto p = m.equal_range(item); p.first != p.second; ++p.first) {
        cout << p.first->second << endl;
    }

    7 无序容器

    新标准定义了4个无序容器。这些容器不是用比較运算符来组织元素,而是是哟过一个哈希函数和关键字类型的==运算符。

    在关键字无明显的序的关系时,无序容器是非常实用的。

    7.1使用无序容器

    无序容器的使用和相应的有序容器的使用全然同样。唯一的差别就是内容是无序的。不能指望输出的结果是有序的。
    通常,能够用一个无序容器替换相应的有序容器,反之亦然。

    7.2 管理桶(bucket)

    无序容器在存储上组织为一组桶,每一个桶保存0或多个元素。

    (SGI STL源代码中哈希表採用分离链接法实现)。


    对于同样的參数。哈希函数必须总是产生同样的鸡诶过。

    7.3 对关键字类型的要求

    1 默认情况哎。无序容器使用关键字类型==运算符来比較元素。他们还使用一个hash类型的对象来生成每一个元素的哈希值。


    2 标准库为内置类型(包括指针类型)。string和智能指针类型定义了hash模版,因此我们能够直接定义相相应的无序容器。
    3 可是我们不能直接定义关键字类型为自己定义类型的无序容器。除非我们提供自己的hash模版版本号。

    7.4 自己定义类型容器

    1 自己定义
    相似于自己定义map关键字类型比較函数一样,我们也自己定义函数取代==运算符合哈希函数

    //自己定义函数
    size_t hasher(const A& s) {
        return hash<string>()(s.name());
    }
    
    bool equal_A(const A& a1, const A& a2) {
        return a1.name() == a2.name();
    }
    
    //使用这些函数来定义一个unordered_set
    unordered_set<A, decltype(hasher)*, decltype(equal_A)*> usa1(43, hasher, equal_A);
    
    //简化
    //类型定义
    using SD_Set = unordered_set<A, decltype(hasher)*, decltype(equal_A)*>;
    
    //定义unordered_set
    SD_Set s(43, hasher, equal_A);
    

    2 模版特例化
    对于内置类型直接定义:unordered_set<int> us;

    为了让自己定义数据类型也能用默认方式,必须定义hash模版的一个特例版本号。
    一个特例化hash类必须定义:
    1.一个重载的调用运算符,接受一个容器关键字类型的对象。返回size_t
    2.两个类型成员,result_type和argument_type,分别为调用运算符()的返回类型和參数类型
    3.默认构造和拷贝赋值运算符(可隐式定义)

    唯一复杂的地方就是须要在原模版定义所在的命名空间中特例化。

    详细定义例如以下:

    //假设A为自己定义类型
    class A {
    public:
        string name() const { return name_; }
    private:
        int size_;
        string name_;
    };
    
    //为A类型特例化hash模版
    namespace std {//打开命名空间
        template<>//表示正在定义一个特例化版本号。模版參数为A
        struct hash<A>
        {
            typedef size_t result_type;
            typedef A argument_type;
            size_t operator()(const A& s) const;
        };
    
        size_t hash<A>::operator()(const A &s) const {
            return hash<string>()(s.name());
        }
    } //关闭命名空间。注意,此处无分号。
    
    //定义A类型的无序容器
    unordered_set<A> usa;
  • 相关阅读:
    Intel 10nm全系曝光!巨头也跑不动了
    国产智能手机占据半壁江山却依旧难掩三大尴尬事实
    顺势而为,HTML发展与UI组件设计进化
    怎么让引导不再是无用小透明?
    C++BUILDER的文件操作
    C++ Builder获取系统文件的路径
    C++Builder 常用String
    C++ 判断是否为邮箱格式
    AnsiString, String, char,char
    BCB如何编写,调用动态链接库DLL
  • 原文地址:https://www.cnblogs.com/llguanli/p/8377404.html
Copyright © 2011-2022 走看看