C++primer-learning-notes-chapter11

200 阅读8分钟

关联容器

关联容器类型

//按顺序存储	
map	//关键数组:保存关键字-值对
set	//关键字即值,即只保存关键字的容器
multimap	//支持同一个键多次出现的map
multiset	//支持同一个键多次出现的set
//无序集合	
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<<w.second<<((w.second>1) ? "times" : "time")<<endl;

使用set

map<string,size_t> word_count;
set<string> exclude = {"the","but","and"};
string word;
while(cin>>word){
    if(exclude.find(word) == exclude.end())  //find调用返回一个迭代器,关键字在set中,迭代器指向该关键字,否则,返回尾后迭代器。
        ++word_count[word];
}

定义关联容器

map<string,size_t> word_count;
set<string> exclude = {};
map<string,string> = {{"",""},{"",""},{"",""}};

//初始化multimap和multiset
vector<int> ivec;
for(vector<int>::size_type i = 0;i!=10;++i){
    ivec.push_back(i);
    ivec.push_back(i);
}
set<int> iset(ivec.cbegin(),ivec.end());
multiset<int> miset(ivec.cbegin(),ivec.cend());
cout<<ivec.size()<<endl;
cout<<iset.size()<<endl;
cout<<miset.size()<<endl;

关键字类型的要求

  • 对于有序容器,关键字类型必须定义元素比较的方法。默认是<。
  • 使用关键字类型的比较函数。如不能定义Sales_data的multiset,因为Sales_data没有<运算符。
bool compareIsbn(const Sales_data& lhs,const Sales_data& rhs){
    return lhs.isbn() < rhs.isbn();
}
multiset<Sales_data,decltype(compareIsbn)*> bookstore(compareIsbn);//第二个实际是指针。

pair类型 头文件utility中

pair<string,string> anon;
pair<string,size_t> word_count;
pair<string,vector<int>> line;

pair<T1, T2> p;	//p是一个pair,两个类型分别是T1和T2的成员都进行了值初始化。
pair<T1, T2> p(v1, v2);	//first和second分别用v1和v2进行初始化。
pair<T1, T2>p = {v1, v2};	//等价于`p(v1, v2)
make_pair(v1, v2);	//pair的类型从v1和v2的类型推断出来。
p.first	//返回p的名为first的数据成员。
p.second	//返回p的名为second的数据成员。
p1 relop p2	//运算关系符按字典序定义。
p1 == p2	//必须两对元素两两相等
p1 != p2	//同上

//创建pair对象的函数
pair<string,int> process(vector<string> &v){
    if(!v.empty())
        return {v.back(),v.back().size()};
    else
        return pair<string,int>();  //空pair
}

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

关联容器操作

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

//set类型key_type和value_type一样
map<string,int>::value_type v3; //v3是一个pair<const string,int>

关联容器迭代器

  • 解引用一个关联容器迭代器时,会得到一个类型为容器的value_type的值的引用,对map而言,value_type是一个pair类型,first成员保存const的关键字,second成员保存值。
auto map_it = word_count.begin();
//*map_it是指向一个pair<const string,size_t>的引用
cout<<map_it->first;
cout<<map_it->second;
++map_it->second;

//set的迭代器是const的
set<int> iset = {};
set<int>::iterator set_it = iset.begin();
if(set_it!=iset.end()){
    *set_it = 43;  //wrong
    cout<<*set_it<<endl;
}

//遍历关联容器
auto map_it = word_count.cbegin();
while(map_it != word_count.cend()){
    cout<<map_it->first<<map_it->second<<endl;
    ++map_it;//迭代器按关键字升序遍历元素
}

添加元素

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

set.insert(ivec.cbegin(),ivec.cend());

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));

检测insert返回值

  • 对不包含重复关键字的容器,添加单一元素的insert和emplace返回一个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->解引用此迭代器,提取map中的元素,元素也是一个pair
pair<map<string,size_t>::iterator,bool> ret = word_count.insert(make_pair(word,1));

//multimap
multimap<string,string> authors;
authors.insert({"dd","cc"});
authors.insert({"dd","bb"});
//返回一个指向新元素的迭代器且无须返回一个bool值。

删除元素

if(word_count.erase(removal_word))
    cout<<removal_word<<endl;
else
    cout<<removal_word<<endl;
//对于保存不重复关键字的容器,erase返回值总是0或1,返回值为0,表明想要删除的元素并不在容器中,对允许重复关键字的容器,删除元素的数量可能大于1。

auto cnt = authors.erase("dd"); //cnt = 2

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

map的下标操作

map<string,size_t> word_count;
word_count["Anna"] = 1;

c[k]	//返回关键字为k的元素;如果k不在c中,添加一个关键字为k的元素,对其值初始化。
c.at(k)	//访问关键字为k的元素,带参数检查;若k不存在在c中,抛出一个out_of_range异常。

//下标操作获得mapped_type对象,返回的是一个左值,可读可写
//而解引用map迭代器得到value_type对象
cout<<word_count["Anna"];  //1
++word_count["Anna"];
cout<<word_count["Anna"];//2

访问元素

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("name");
auto entries = authors.count(search_item);
auto iter = authors.find(search_item);
while(entries){
    cout<<iter->second<<endl;
    ++iter;
    --entries;
}

//一种不同的面向迭代器的解决方法
for(auto beg=authors.lower_bound(search_item),end=authors.upper_bound(search_item);beg!=end;++beg)
    cout<<beg->second<<endl;
//如果lower_bound和upper_bound返回相同的迭代器,则给定关键字不在容器中。

//equal_range函数
for(auto pos=authors.equal_range(search_item);pos.first!=pos.second;++pos.first){
    cout<<pos.first->second<<endl;
}
//pos.first等价于beg 
//pos.second等价于end

单词转换程序

where r u
y dont u send me a pic
k thk l8r
转换为
where are you
why dont you send me a picture
okay? thanks! later

建立转换映射

map<string,string> buildMap(ifstream& 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];  //getline不会跳过前导空格
        else
            throw runtime_error("no rule for" + key);
    }
    return trans_map;
}

生成转换文本

const string& transform(const string& s,const map<string,string>& m){
    auto map_it = m.find(s);
    if(map_it != m.cend())
        return map_it->second;
    else
        return s;
}

单词转换程序

void word_transform(ifstream& map_file,ifstream& input){
    auto trans_map = buildMap(map_file);
    string text;
    while(getline(input,text)){
        isstringstream stream(text);
        string word;
        bool firstword = true;
        while(stream>>word){
            if(firstword)
                firstword = false;
            else
                cout<<" ";
            cout<<transform(word,trans_map);
        }
        cout<<endl;
    }
}

无序容器

  • 有序容器使用比较运算符来组织元素;无序容器使用哈希函数和关键字类型的==运算符。
  • 理论上哈希技术可以获得更好的性能。
  • 无序容器在存储上组织为一组桶(bucket),每个桶保存零个或多个元素。无序容器使用一个哈希函数将元素映射到桶。
unordered_map<string,size_t> word_count;
stirng word;
while(cin>>word)
    ++word_count[word];
for(const auto& w : word_count)
    cout<<w.first<<w.second<<endl;

无序容器管理操作:

桶接口	
c.bucket_count()	//正在使用的桶的数目
c.max_bucket_count()	//容器能容纳的最多的桶的数目
c.bucket_size(n)	//第n个桶中有多少个元素
c.bucket(k)	//关键字为k的元素在哪个桶中

桶迭代	
local_iterator	//可以用来访问桶中元素的迭代器类型
const_local_iterator	//桶迭代器的const版本
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.reverse(n)	//重组存储,使得c可以保存n个元素且不必rehash。
size_t hasher(const Sales_data& sd){
    return hash<string>()(sd.isbn());
}
//hasher函数使用标准库hash类型对象计算ISBN成员的哈希值,该hash类型建立在string类型之上
bool eqOp(const Sales_data& lhs,const Sales_data& rhs){
    return lhs.isbn() == rhs.isbn();
}
//eqOp函数通过比较ISBN号来比较两个Sales_data.

//我们用这些函数来定义一个unordered_multiset
using SD_multiset = unordered_multiset<Sales_data,decltype(hasher)*,decltype(eqOp)*>;
SD_multiset bookstore(42,hasher,eqOp);

//如果类定义了==运算符,则可以只重载哈希函数
//使用FooHash生成哈希值;Foo必须有==运算符
unordered_set<Foo,decltype(FooHash)*>fooSet(10,FooHash);