C++ Primer记录_第十一章

第十一章 关联容器

  • 关联容器和顺序容器有着根本的不同:关联容器中的元素是按关键字来保存和访问的。
  • 与之相对,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。
  • 两个主要的关联容器mapset
  • 标准库提供8个关联容器。
关联容器类型解释
按关键字有序保存元素
map关联数组;保存关键字-值对
set关键字即值,即只保存关键字的容器
multimap关键字可重复出现的map
multiset关键字可重复出现的set
无序集合
unordered_map用哈希函数组织的map
unordered_set用哈希函数组织的set
unordered_multimap哈希组织的map:关键字可以重复出现
unordered_multiset哈希组织的set:关键字可以重复出现

11.1 使用关联容器

  • map类型通常被称为关联数组,与正常数组类似,不同之处在于其下标不必是整数,通过一个关键字而不是位置来查找值。
  • set就是关键字的简单集合。
  • 使用map。
//统计每个单词在输入中出现的次数
map<string, size_t> word_count; // string到size_t的空map
string word;
while (cin >> word)
{
    ++word_count[word]; //提取word的计数器并将其加1
}
for (const auto &w : word_count) //对map中的每个元素
{
    //打印结果
    cout << w.first << "occurs" << w.second << ((w.second > 1) ? "times" : "time") << endl;
}
  • 使用set。
//统计捡入中每个单词出现的次数
map<string, size_t> word_cout; // string到size_t的空map
set<string> exclude = {"The", "But", "And", "Or", "An", "A", "the", "but", "and", "or", "an", "a"};
string word;
while (cin >> word)
{
    //只统计不在exclude中的单词
    if (exclude.find(word) == exclude.end())
    {
        ++word_count[word]; //荻取并递增word的计数器
    }
}

11.2 关联容器概述

  • 关联容器支持普通容器操作,关联容器不支待顺序容器的位置相关的操作。

11.2.1 定义关联容器

  • 定义一个map时,必须既指明关键字类型又指明值类型。
  • 定义一个set时,只需指明关键字类型。
map<string, size_t> word_cout; //空容器
//列表初始化
set<string> exclude = {"The", "But", "And", "Or", "An", "A", "the", "but", "and", "or", "an", "a"}; //列表初始化
//三个元素;authors将姓映射为名
map<string,string> authors={{"Joyce", "James"},{"Austen", "Jane"),{"Dickens", "Charles"}};
  • 初始化multimap或multiset。
//定义一个有20个元素的vector,保存0到9每个整数的两个拷贝
vector<int> ivec;
for (vector<int>::size_type i = 0; i != 10; ++i)
{
    ivec.push_back(i);
    ivec.push_back(i);
}
// iset包含未自ivec的不重复的元素;miset包含所有20个元素
set<int> iset(ivec.cbegin(),ivec.cend()));
multiset<int> miset((ivec.cbegin()), ivec.cend());

cout << ivec.size()) << endl;//打印出20
cout << iset.size() << endl; //打印出10
cout << miset.size() << endl; //打印出20

11.2.2 关键字类型的要求

  • 对于有序容器map、multimap、set、multiset,关键字类型必须定义元素比较的方法。
  • 有序容器的关键字类型可以向一个算法提供自己定义的比较操作,所提供的操作必须在关键字类上定义一个严格弱序
    • 两个关键字不能同时小于等于对方;如果kl小于等于k2,那么k2绝不小于等于kl。
    • 如果kl小于等于k2,且k2小于等于k3,那么kl必须小于等于k3。
    • 如果存在两个关键字,任何一个都不小于等于另一个,那么我们称这两个关键字是”等价”的。如果kl等价于k2,且k2等价于k3,那么kl必须等价于k3。
  • 不能直接定义一个自定义类型multiset,使用关键字类型的比较函数定义了一个严格弱序
bool comparelsbn(const Sales_data &lhs, const Sales_data &lhs)
{
    return Ihs.isbn() < rhs.isbn();
}
// bookstore中多条记录可以有相同的ISBN
// bookstore中的元素以 ISBN 的顺序进行排列
multiset<Sales_data, decltype(compareisbn) *> bookstore(compareisbn);

11.2.3 pair类型

  • 关于pair标准库类型,定义在utility中。
  • 一个pair保存两个数据成员。
pair<string, string> anon;       //保存两个string
pair<string, size_t> word_count; //保存一个string和一个size_t
pair<string, vector<int>> line;  //保存string和vector<int>
  • 可以为每个成员提供初始化器。
pair<string, string> author{"James", "Joyce"};
  • 打印结果,假设一个单词计数程序的输出语句。
//打印结果
cout << w.first << " occurs " << w.second << ((w.second > 1) ? " times" : " time") << endl;
pair上的操作解释
pair<T1, T2> p;p是一个pair,两个类型分别为T1和T2的成员都进行了值初始化
pair<T1, T2> p(v1, v2)p是一个pair,分别用v1和v2进行初始化
pair<T1, T2>p = {vl, v2} ;同上
make_pair(v1, v2)返回一个用v1和v2初始化的pair,pair的类型从v1和v2的类型推断出来
p.first返回p的名为first的(公有)数据成员
p.second返回p的名为second的(公有)数据成员
pl relop p2关系运算符(<、>、<=、>=)按字典序定义
pl == p2当first和second成员分别相等时,两个pair相等
pl != p2判断利用元素的==运算符实现
  • 创建pair对象的函数。
pair<string, int> process(vector<string> &v)
{
    //处理v
    if (!v.empty())
    {
        return {v.back(), v.back().size()}; //列表初始化
    }
    else
    {
        return pair<string, int>() //隐式构造返回值
    }
}
  • 在较早的C++版本中,不允许用花括号包围的初始化器来返同pair这种类型的对象。
if (!v.empty())
{
    return pair<string, int>{v.back(), v.back().size()}; //列表初始化
}
  • 或者可以用make_pair来生成pair对象。
if (!v.empty())
{
    return make_pair<string, int>(v.back(), v.back().size());
}

11.3 关联容器操作

  • 这些类型表示容器关键字和值的类型。
关联容器额外的类型别名解释
key_type此容器类型的关键字类型
mapped_type每个关键字关联的类型:只适用于map
value_type对于set,与key_type相同,对于map,为pair<const key_type, mapped_type>
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

11.3.1 关联容器迭代器

  • 当解引用一个关联容器迭代器时,会得到一个类型为容器的value_type的值的引用。
//获得指向word_count中一个元素的迭代器
auto map_it = word_count.begin();
//*map_it是指向一个pair<const string,size_t>对象的引用
cout << map_it->first;         //打印此元素的关键字
cout << " " << map_it->second; //打印此元素的值
map_it->first = "new key";     //错误:关键字是const的
++map_it->second;              //正确:我们可以通过迭代器改变元素
  • set的迭代器是const的。
set<int> iset = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
set<int>::iterator set_it = iset.begin();
if (set_it != iset.end())
{
    *set_it = 42;            //错误:set中的关键字是只读的
    cout << *set_it << endl; //正确:可以读关键字
}
  • 遍历关联容器,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; //递增迭代器,移动到下一个元素
}
  • 通常不对关联容器使用泛型算法。

11.3.2 添加元素

  • 关联容器的insert成员向容器中添加一个元素或元素范围。
  • 插入一个已经处在的元素对容器没有任何影响。
vector<int> ivec = {2, 4, 6, 8, 2, 4, 6, 8}; // ivec有8个元素
set<int> set2;                               //空集合
set2.insert(ivec.cbegin(), ivec.cend());     // set2有4个元素
set2.insert({1, 3, 5, 7, 1, 3, 5, 7});       // set2现在有8个元素
  • 对一个map进行insert时,必须元素类型是pair。
//向word_count插入word的4种方法
word_count.insert({word, 1});
word_count.insert(make_pair(word, 1));
word_count.insert(pair<string, size_t>(word, l));
word—count.insert(map<string, size_t>::value_type(word, 1));
关联容器insert操作解释
c.insert(v)v是value_type类型的对象
c.emplace(args)args用来构造一个元素
c.insert(b, e)b和e是迭代器,表示一个c::value_type类型值的范围
c.insert(il)il是这种值的花括号列表
c.insert(p, v)类似insert(v)或emplace(args),但将迭代器p作为一个提示,指出从哪里开始搜索新元素应该存储的位置。
c.emplace(p,args)类似insert(v)或emplace(args),但将迭代器p作为一个提示,指出从哪里开始搜索新元素应该存储的位置。
  • insert(或emplace)返回的值依赖于容器类型和参数。返回一个pair,first成员是一个迭代器,指向具有给定关键字的元素,second成员是一个cool值,指出元素是插入成功还是已经存在于容器中。
  • 使用insert重写单词计数程序。
//统计每个单词在输入中出现次数的一种更烦琐的方法
map<string, size_t> word_count; //从string到size_t的空map
string word;
while (cin >> word)
{
    //插入一个元素,关键字等于word,值为1;
    //若word已在word_count中,insert什么也不做
    auto ret = word_count.insert({word, 1});
    if (!ret.second) // word已在word_count中
    {
        ++ret.first->second; //递增计数器
    }
}
  • 展开递增语句。
    • ret保存insert返回的值,是一个pair。
    • ret.first是pair的第一个成员,是一个map迭代器,指向具有给定关键字的元素。
    • **ret.first->**解引用此迭代器,提取map中的元素,元素也是一个pair。
    • ret.first->secondmap中元素的值部分。
    • ++ret.first->second递增此值。
  • 新标准推出之前,不使用auto。
pair<map<string, size_t>::iterator, bool> ret = word_count.insert(make_pair(word, 1));
  • 向multiset或mutimap添加元素,接受单个元素的insert操作返回一个指向新元素的迭代器。
multimap<string, string> authors;
//插入第一个元素,关键字为Barth, John
authors.insert({"Barth,John", "Sot-Weed Factor"});
//正确:添加笫二个元素,关键字也是Barth, John
authors.insert({"Barth, John", "Lost in the Funhouse"});

11.3.3 删除元素

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

11.3.4 map的下标操作

  • map和unordered_map容器提供了下标运算符和一个对应的at函数。
map <string, size_t> word_count;//empty map 
//插入一个关键字为Anna的元素,关联值进行值初始化,然后将1赋予它 
word_count["Anna"] = 1;
  • 将执行如下操作:
    • 在word_count中搜索关键字为Anna的元素,未找到。
    • 将一个新的关键字-值对插入到word_count中,关键字是一个const string,保存Anna,值进行值初始化,在本例中意味着值为0。
    • 提取出新插入的元素,并将值1赋予它。
  • 由于下标运算符可能插入一个新元素,只可以对非const的map使用下标操作。
map和unordered_map的下标操作解释
c[k]返回关键字为k的元素,如果k不在c中,添加一个关键字为k的元素,对其进行值初始化
c.at(k)访问关键字为k的元素,带参数检查,若k不在c中,抛出一个out_of_range异常
  • 使用下标操作的返回值。
cout << word_count["Anna"]; //用Anna作为下标提取元素,会打印出1
++word_count["Anna"];       //提取元素,将其增1
cout << word_count["Anna"]; //提取元素并打印它,会打印出2

11.3.5 访问元素

  • 关联容器提供多种查找一个指定元素的方法。
set<int> iset = {0, 1, 2 1 3, 4, 5, 6, 7, 8, 9};
iset.find(1);   //返回一个迭代器,指向key == 1的元素
iset.find(11);  //返回一个迭代器,其值等于iset.end()
iset.count(1);  //返回1
iset.count(11); //返回0
在一个关联容器中查找元素的操作解释
lower_bound和upper_bound不适用于无序容器
下标和at操作只适用于非const的map和unordered_map
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。
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;                    //记录已经打印了多少本书
}
  • 一种不同的,面向迭代器的解决方法。
// authors和search_item的定义,与前面的程序一样
// beg和end表示对应此作者的元素的范围
for (auto beg = authors.lower_bound(search_item), end = authors.upper_bound(search_item); beg != end; ++beg)
{
    cout << beg->second << endl; //打印每个题目
}
  • 直接调用equal_range函数的方法。
// authors和search_item的定义,与前面的程序一样
// pos保存迭代器对,表示与关键词匹配的元素范围
for (auto pos = authors.equal_range(search_item); pos.first != pos.second; ++pos.first)
{
    cout << pos.first->second << endl;
}

11.3.6 一个单词转换的map

  • 单词转换程序。
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 << " "; //在单词间打印一个空格
            }
            // transform返回它的第一个参数或其转换之后的形式
            cout << transform(word, trans_map); //打印输出
        }
        cout << endl; //完成一行的转换
    }
}
  • 建立转换映射。
map<string, string> buildMap(ifstream &map_file)
{
    map<string, string> trans_map; //保存转换规则
    string key;
    string value; //替换后的内容
    //读取第一个单词存入key中,行中剩余内容存入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, canst map<string, string> &m)
{
    //实际的转换工作,此部分是程序的核心
    auto map_it = m.find(s);
    //如果单词在转换规则map中
    if (map_it != m.cend())
    {
        return map_it->second; //使用替换短语
    }
    else
    {
        return s; //否则返回原string
    }
}

11.4 无序容器

  • 新标准定义了4个无序关联容器
  • 这些容器不是使用比较运算符来组织元素,而是使用一个哈希函数和关键字类型的==运算符。
  • 通常可以用一个无序容器替换对应的有序容器,但是无序的输出与有序版本不同。
  • 无序容器在存储上组织为一组桶,每个桶保存零个或多个元素,使用一个哈希函数将元素映射到桶。
无序容器管理操作解释
桶接口
c.bucket_count()正在使用的桶的数目
c.max_bucket_count()容器能容纳的最多的桶的数最
c.bucket_size(n)第n个桶中有多少个元素
c.bucket(k)关键字为k的元素在哪个桶中
桶迭代
local_iterator可以用来访问桶中元素的迭代器类型
const local_iterator可以用来访问桶中元素的迭代器类型
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.reserve(n)重组存储,使得c可以保存n个元素且不必rehash
  • 无序容器使用关键字类型的==运算符来比较元素,还使用一个hash<key_type>类型的对象来生成每个元素的哈希值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Flame老唐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值