C++ primer学习笔记——第十一章 关联容器

关联容器和顺序容器有着根本的不同:关联容器的元素是按关键字来保存和访问的。与之相对,顺序容器中的元素是按他们在容器中的位置来顺序保存和访问的

关联容器类型
按关键字有序保存元素
map 关联数组:保存关键字-值对
set 只保存关键字的容器
multimap 关键字可重复出现的map
multiset 关键字可重复出现的set
无序集合
unordered_map 用哈希函数组织的map
unordered_set 用哈希函数组织的set
unordered_multimap 用哈希函数组织的map:关键字可重复出现
unordered_multiset 用哈希函数组织的set:关键字可重复出现

一、使用关联容器

使用map

map是关键字-值对的集合,例如:姓名-电话号码。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)  
    cout<<w.first<<" occurs "<<w.second
        <<((w.second>1)?"times":"time")<<endl;

while循环中,如果word还未在map中,下标运算符会创建一个新元素,其关键字为word,值为0.不管元素是否是新创建的,我们都将其值加1.

从map中提取一个元素时,会得到一个pair类型的对象。简单来说,pair是一个模板类型,保存两个名为first和second的(公有)数据成员。

使用set

//统计输入中每个单词出现的次数
map<string,size_t> word_count;
set<string> exclude={"The","But","And","Or","An","A"};
string word;
while(cin>>word)
    //只统计不再exclude中的单词
    if(exclude.find(word)==exclude.end())
        ++word_count[word];

二、关联容器概述

关联容器都支持普通容器操作。关联容器不支持顺序容器的位置相关的操作,例如push_back或push_front。而且,关联容器也不支持构造函数或插入操作这些接受一个元素值和一个数量值的操作。

关联容器的迭代器都是双向的。

1、定义关联容器

空容器+拷贝初始化+范围初始化+值初始化

容器multimap和multiset允许多个元素具有相同的关键字

2、关键字类型的要求

对于有序容器——map、multimap、set、multiset,关键字类型必须定义元素比较的方式。默认情况下,标准库使用关键字类型的<运算来比较两个关键字。在集合类型中,关键字类型就是元素类型;在映射类型中,关键字类型是元素的第一部分的类型。

传递给排序算法的可调用对象必须满足与关联容器中关键字一样的类型要求。

我们可以自定义操作来代替关键字上的<操作,但所提供的操作必须在关键字类型上定义一个严格弱序

在实际编程中,重要的是,如果一个类型定义了“行为正常”的<运算符,则它可以用作关键字类型

使用关键字类型的比较函数

当关键字类型使用自己定义的操作时,必须在定义关联容器类型是提供此操作的类型。即,用尖括号指出要定义哪种类型的容器,自定义的操作必须在尖括号中紧跟着元素类型给出。

bool  compareIsbn(const Sales_data &lhs,const Sales_data &rhs)
{
    return lhs.isbn()<rhs.isbn;
}

//bookstore中的元素以ISBN的顺序进行排列
//操作类型应该是一种函数指针类型
multiset<Sales_data,decltype(compareIsbn)*>
    bookstore(compareIsbn);   

3、pair类型

pair类型定义在头文件utility中

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==p2  
p1!=p2  

创建pair对象的函数

pair<string,int>
process(vector<string> &v)
{
    if(!v.empty())
        //以下三种创建pair的方法等价
        return {v.back(),v.back().size()}; //列表初始化
        return pair<string,int>(v.back(),v.back().size());
        return make_pair(v.back(),v.back().size());
    else
        return pair<string,int>(); //隐式构造函数返回值
}

三、关联容器操作

关联容器额外的类型别名
key_type 此容器类型的关键字类型
mapped_type 每个关键字关联的类型;只适用于map
value_type

对于set,与key_type相同

对于map,为pair<const key_type,mapped_type>

1、关联容器迭代器

当解引用一个关联容器迭代器时,我们会得到一个类型为容器的value_type的值的引用

必须记住,一个map的value_type是一个pair,我们可以改变pair的值,但是不能改变关键字成员的值

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中的关键字也是const的,可以用一个set迭代器来读取元素的值,但不能修改。

遍历关联容器

当使用一个迭代器遍历一个map、multimap、set或multiset时,迭代器按关键字升序遍历元素

关联容器和算法

我们通常不对关联容器使用泛型算法。因为关键字是const这一特性使得不能对容器使用修改或重排的算法,另外,关联容器的元素不能通过他们的关键字进行(快速)查找,因此对其使用泛型搜索算法是个坏主意

在实际编程中,如果我们真的要对一个关联容器使用算法,要么将它当做一个源序列,要么当做一个目的位置

2、添加元素

关联容器的insert操作
c.insert(v)

对于map和set,只有当元素的关键字不在c中才插入(构造)元素。函数返回一个pair,包含一个迭代器,指向具有指定关键字的元素,以及一个指示插入是否成功的bool值。

对于multimap和multiset,总会插入(构造)给定元素,并返回一个指向新元素的迭代器

c.emplace(args)
c.insert(b,e)

函数返回void

对于map和set,只有当元素的关键字不在c中才插入(构造)元素。对于multimap和multiset,总会插入(构造)范围中的每个元素

c.insert(il)
c.insert(p,v) 将元素插入指定元素,返回一个迭代器,指向具有给定关键字的元素
c.emplace(p,args)

向map添加元素

//向word_count插入word的4种方法
word_count.insert({word,1});
word_count.insert(pair<string,int>(word,1));
word_count.insert(make_pair(word,1));
word_count.insert(map<string,size_t>::value_type(word,1));

检测insert的返回值

如果关键字已在容器中,则insert什么事情也不用做,且返回值中的bool部分为false;如果关键字不存在,则元素被插入容器中,且bool值为true。

//统计每个单词在输入中出现次数的一种更繁琐的方法
map<string,size_t> word_count;
string word;
while(cin>>word)
{
    //ret->first返回迭代器指向的元素,为一pair, ++ret->first.second递增其计数器
    auto ret=word_count.insert({word,1});
    if(!ret->second)
          ++ret->first.second;  
    
}

3、删除元素

从关联容器删除元素
c.erase(k) 删除每个关键字为k的元素。返回一个size_type值,指出删除的元素的数量
c.erase(p) 删除迭代器p指定的元素,返回一个指向p之后元素的迭代器
c.erase(b,e) 删除迭代器b和e表示的范围中的元素。返回e

4、map的下标操作

map和unordered_map的下标操作
c[k] 返回关键字为k的元素;如果k不在
c.at(k) 访问关键字为k的元素,带参数检查;若k不在c中,抛出一个out_of_range异常

set类型不支持下标,因为set中没有与关键字相关联的“值”。

multimap和undered_maltimap不支持下标操作,因为这些容器中可能有多个值与一个关键字相关联。

由于下标运算符可能插入一个新元素,我们只可以对非const的map使用下标操作

使用下标操作的返回值

与vector和string不同,map的下标运算符返回的类型与解引用map迭代器得到的类型不同:

对一个map进行下标操作时,会获得一个mapped_type对象,但当解引用一个map迭代器时,会得到一个value_type对象。

5、访问元素

在一个关联容器中查找元素的操作

lower_bound和upper_bound不适用于无序容器

下标和at操作只适用于非const的map和unordered_map(下标操作无对应关键字,则会新增元素)

c.find(k) 返回一个迭代器,指向第一个关键字为k的元素
c.count(k) 返回关键字为k的元素的数量
c.lower_bound(k) 返回一个迭代器,指向第一个关键字不小于k的元素
c.upper_bound(k) 返回一个迭代器,指向第一个关键字大于k的元素
c.equal_range(k) 返回一个pair,表示关键字等于k的元素的范围

对map使用find代替下标操作

有时,我们只想知道一个给定关键字是否在map中,而不想改变map。此时应该使用find:

if(word_count.find("foobar")==word_count.end())
    cout<<"foobar is not in the map"<<endl;

在multimap或multiset中查找元素

在容器中可能有很多元素具有给定的关键字。如果一个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;
}


//第二种方法
for(auto beg=authors.lower_bound(search_item),
         end=authors.upper_bound(search_item);
    beg!=end;++beg)
    cout<<beg->second<<endl;

//第三种方法
for(auto pos=authors.equal_range(search_item);
    pos.first!=pos.second;++pos.first)
    cout<<pos.first->second<<endl;

6、一个单词转换的map

#include<iostream>
#include<fstream>
#include<sstream>
#include<map>
#include<algorithm>
#include<string>

using namespace std;

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

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);
		else
			throw runtime_error("no rule for "+ key);
	return trans_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 << " ";
			cout << transform(word,trans_map);
		}
		cout << endl;
	}
}
int main()
{
	ifstream mapfile("map_file.txt");
	ifstream input("input.txt");
	word_transform(mapfile,input);

	return 0;

}

猜你喜欢

转载自blog.csdn.net/oil_you/article/details/82964998