关联型容器

关联型容器

一、概述

关联性容器支持高效的关键字查找和訪问。当中元素是按关键字来保存和訪问的,主要关联型容器类型是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;
【推广】 免费学中医,健康全家人
原文地址:https://www.cnblogs.com/llguanli/p/8377404.html