C++11(10):关联容器

来源:互联网 发布:网络购票实名认证 编辑:程序博客网 时间:2024/05/02 12:19
关键字有序保存元素,
map,关联数组,保存关键字-值对,
set,关键字即值,只保存关键字的容器
multimap,关键字可重复出现
multiset,
无序集合
unordered_map,用哈希函数组织的map
unordered_set,用哈希函数组织的set
unordered_multimap,哈希组织的map;关键字可重复出现
unordered_multiset,哈希组织的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,我们必须指定关键字和值得类型。如果word还未在map中,下表运算符会创建一个新元素,其关键字为word,值为0
从map中提取一个元素时会得到一个pair类对象,first保存的是关键字

//忽略常见单词,用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())//只统计不在exclude中的单词
        ++word_count[word];

关联容器都支持普通容器的基本操作,不支持顺序容器的位置相关的操作,如push_front或push_back。原因是关联容器中的元素是根据关键字存储的,这些操作对关联容器是没有意义的。而且,还不支持构造函数或插入操作这些接受一个元素值和一个数量值得操作
关联容器的迭代器是双向的
每个关联容器都定义了一个默认的构造函数,它创建一个指定类型的空容器。我们也可以将关联容器初始化为另一个同类型容器的拷贝,或是从一个范围来初始化关联容器,只要这些值可以转换为容器所需的类型就可以。
multimap或multiset允许多个元素具有相同的关键字。

对于有序的关联容器我们必须定义比较方法。默认是采用<
我们也可以提供自己的操作,但必须满足严格若需
当两个关键字同时关联了同一个元素,我们可以使用其中任何一个访问该元素
bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)
{
    return lhs.isbn() < rhs.isbn();
}
multiset<Sales_data,decltype(compareIsbn)*>      bookstore(compareIsbn);
这里我们使用decltype 来获取一个函数的指针类型时,必须加上一个*来指明我们要使用一个给定函数的指针。用compareIsbn来初始化bookstore对象,表示当我们想bookstore添加元素时,通过调用compareIsbn来为这些元素排序,可以用compareIsbn来代替&compareIsbn作为构造函数的参数,因为当我们使用一个函数名时,在需要的情况下会自动转换成指针,两者效果是一样的

pair定义在uitlity头文件中。pair上的操作:
pair<T1 , T2> p;   初始化,分别制定成员的类型
pair<T1 , T2> p(v1 , v2);
pair<T1 , T2> p = {v1 , v2};
make_pair(v1,v2)      返回一个用v1和v2初始化的pair。pair的类型是从v1和v2推断出来的
p.first    p.second       共有数据成员 
两个对象之间可以使用关系运算符比较  p1 relop p2   当p1.first <p2.first  或!(p2.first < p2.first) && p1.second<p2.second 成立时p1<p2
p1 == p2    当first和second成员分别相等是,两个pair才相等
p1 != p2       用==判断

想象有一个函数需要返回一个pair。在新标准下,可以对返回值进行列表初始化
pair<string , int> process(vector<string> &v)
{
        if (!v.empty())
                return {v.back(), v.beck().size() }; //列表初始化
        else
                return pair<string , int>(); //隐式构造返回值
}

我们还可以用make_pair来返回
 return make_pair(v.back() , v.back().size());
或是早期方法:
return pair<string , int>(v.back() , v.backe().size());

关联容器额外的类型名:
key_type     关键字的类型
mapped_type     每个关键字关联的类型,只适用于map
value_type         对于set与key_type相同,对于map为pair<const key_type,mapped_type>
set 和 map也有const和非const版本的迭代器
通常不对关联容器使用泛型算法,关键字是const,我们可以用只读算法,如find。但不是以个好主意,不能关键字进行(快速)查找。要用自己定义的成员函数的版本
在实际编程中,如果要对关联容器使用算法,要么是将它作为一个源序列,要么是一个目的位置

插入元素:
c.insert(v)     v是一个value_type类型的对象;args用来构造一个元素
c.emplace(args)       对于map和set, 只有当元素的关键字不在c中时才能插入。函数返回一个pair,包含一个迭代器。指向具有关键字的元素,以及一个指示插入是否成功的bool值。对于multimap和multiset,总会插入给定的元素,并返回一个指向新元素的迭代器
c.insert(b,e)    b,e是迭代器,表示一个c::value_type类型值得范围;il是这种值得初始化列表。函数返回void
c.insert(il);        对于set和map,只有插入关键字不存在的。对于multimap和multiset,则会插入范围中的每个元素
c.insert(p,v)   类似insert(v) (或emplace(args))但用p指出从哪里开始搜索新元素应该存储的位置。返回迭代器,指向给定原件字的元素
c.emplace(p,args)

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

由于下标运算符可能插入一个新元素,我们只可以对非const的map使用下标操作。
使用一个不再容器中的关键在作为下标,会添加一个具有此关键字的元素到map中。
c[k]    
c.at(k)    访问关键字为k的元素,dai参数检查;若k不在c中,抛出一个out_of_range异常。
当对一个map进行下标操作时,会获得一个mapped_type的对象;担当解引用以个map迭代器时,会得到一个value_type对象

在一个关联容器中查找元素的操作:
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()

如果multiset和multimap中有多个元素具有给定关键字。那么这些元素在容器中会相邻存储。
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;
}
当我们遍历一个multimap或multiset时,保证可以得到序列中所有具有给定关键字的元素

如果关键字在容器中,lower_bound返回的迭代器将指向第一个具有给定关键字的元素,upper_bound返回的迭代器将指向最后一个匹配给定关键字的元素之后的位置。用相同的关键字调用lower_bound和upper_bound会得到一个迭代器范围,表示所有具有该关键字的元素范围
这两个迭代器都有可能是尾后迭代器,如果我们查找的元素具有容器中的最大关键字,则此时关键字的upper_bound返回尾后迭代器。如果关键字不存在,且大于容器中任何关键字,则lower_bound返回的也是尾后迭代器。
lower_bound返回的迭代器可能指向一个具有给定关键字的元素,但也可能不指向。如果关键字不在容器中,则lower_bound会返回关键字的第一个安全插入点。
//重写前面的程序
for(auto beg=authors.lower_bound(search_item),end=authors.upper_bound(serch_item);beg!=end;++beg)
        cout<<beg->second<<endl;
如果没有元素与给定的关键字匹配,则lower_bound和upper_bound会返回相等的迭代器,都指向给定关键字的插入点。

用equa_range再次修改我们的程序:
for(auto pos=authors.equal_range(search_item); pos.first != pos.second; ++pos.first)
        cout<<pos.first->second<<endl;
  
无序关联容器使用一个哈希函数和关键字类型的==运算符来组织元素,是无序的,通常使用无序容器更为简单,性能也更好
如果关键字类型固有就是无序的,或者性能测试发现问题可以用哈希技术解决,就可以使用无序容器
除了哈希管理操作之外,无序容器还提供了于有序容器相同的操作(find、insert等),就是用于map。set的函数也能用于unordered_set和unordered_map,同样的无序容器也有multi_版本
//重写单词计数程序,唯一的区别是word_count的类型,得到的结果一样,就是顺序不太一样
unordered_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.secound>1)?" times":" time")<<endl;

无序容器管理操作:
桶接口:
c.bucket_count()      正在使用的桶的个数
c.max_bucket_count()       容器能容纳的最多的桶的数量
c.bucket_size(n)      第n个桶中有多少元素
c.bucket_size(k)       关键字为k的元素在哪个桶中
桶迭代
local_iterator      可以用来访问桶中的元素的迭代器类型
const_local_iterator      桶迭代器的const版本
c.begin(n),c.end(n)      桶n的首元素和尾后迭代器
c.cbegin(n),c.cend(n)     const版本
哈希策略
c.load_factor()       每个桶的平均元素数量,返回float值
c.max_load_factor()        c试图维护的平均桶大小,返回float值。c会在需要时添加新的桶,以使load_factor<=max_load_factor
c.rehash(n)        重组存储,以使得backet_count >= n且backet_count>size/max_load_factor
c.reserve(n)      重组存储。使得c可以保存n个元素且不必rehash

对于无序容器,他们还定义了一个hash<key_type>类型的对象来生成每个元素的哈希值。标准库为内置类型(包括指针)提供了hash模板,还为一些标准库类型,包括string和智能指针定义了hash。因此可以直接定义无序容器
但是,我们不能直接定义关键字类型为自定义类类型的无序容器。于容器不同,不能直接使用哈希模板,而必须提供自己的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();
using SD_multiset = unordered_multiset<Sales_data, decltype(hasher)*, decltype(eqOp)*>
SD_multiset bookstore(42,hasher,eqOp);

当然如果我们的类定义了 == 我们只重载hash函数就行。
0 0
原创粉丝点击