C++ Primer 第11章 关联容器 第一次学习笔记

1.      关联容器常识

a)        关联容器中的元素是按关键字来保存和访问的。与之相对,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。虽然关联容器的很多行为与顺序容器相同,但其不同之处反映了关键字的作用

b)        关联容器支持高效的关键字查找和访问

c)        两个主要的关联器类型是map和set。

          i.             map中的元素是一些关键字-值对:关键字起到索引的作用,值则表示与索引相关联的数据

          ii.             set中每个元素只包含一个关键字:set支持高效的关键字查询操作——检查一个给定关键字是否在set中

 

2.      关联容器

a)        标准库提供8个关联容器

b)        这八个容器间的不同体现在:

           i.             或者是一个set,或者是一个map

           ii.             或者要求不重复的关键字,或者允许重复关键字

           iii.             按顺序保存元素,或无序保存

c)        允许重复关键字的容器的名字中都包含单词multi;不保持关键字按顺序存储的容器的名字都以单词unordered开头。如,unordered_multi_set是一个允许重复关键字,元素无序保存的集合;而一个set是一个要求不重复关键字,有序存储的集合

d)        类型map和multimap定义在头文件map中;set和multiset定义在头文件set中;无序容器则定义在头文件unordered_map和unordered_set中

按关键字有序保存元素
map关联数组;保存关键字-值对
set关键字即值,即只保存关键字的容器
multimap关键字可重复出现的map
multiset关键字可重复出现的set




无序集合
unordered_map用哈希函数组织的map
unordered_set用哈希函数组织的set
unordered_multimap哈希组织的map;关键字可以重复出现
unordered_multiset哈希组织的set;关键字可以重复出现









3.      如何使用这类容器的栗子(帮助理解)

a)        map是关键字-值对的集合。可以将一个人的名字作为关键字,将其电话号码作为值。这样的数据结构成为“将名字映射到电话号码”。map类型通常被称为关联数组。关联数组与正常数组类似,不同之处在于其下标不必是整数。我们可以通过一个关键字而不是位置来查找值。例,给定一个名字到电话号码的map,我们可以使用一个人的名字作为下标来获取此人的的电话号码

b)        与之相对,set就是关键字的简单集合。当只是想知道一个值是否存在时,set是最有用的。例如,一个企业可以定义一个名为bad_checks的set来保存那些曾经开过空头支票的人的名字。在接受一张支票之前,可以查询bad_checks来检查顾客的名字是否在其中




4.      使用map

#include <iostream>
#include <map>
using namespace std;
int main(){
	//统计每个单词在输入中出现的次数 
	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;
	return 0;
}

a)        类似顺序容器,关联容器也是模板。为了定义一个map,我们必须指定关键字和值的类型。在这个栗子中,map保存的每个元素中,关键字是string类型,值是size_t类型。

b)        当对word_count进行下标操作时,我们使用一个string作为下标获得与此string相关联的size_t类型的计数器

c)        while循环每次从标准输入读取一个单词。它使用每个单词对word_count进行下标操作。如果word还未在map中,下标运算符会创建一个新元素,其关键字为word,值为0

d)        读取完所有输入后,范围for语句会遍历map,打印每个单词和对应的计数器



5.      使用set

#include <iostream>
#include <map>
#include <set>
using namespace std;
int main(){
	//统计每个单词在输入中出现的次数 
	map<string,size_t> word_count;		//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的计数器并递增 
	}
	for(const auto &w:word_count)
			//对map中的每个元素 
		cout<<w.first<<" occurs "<<w.second
			<<((w.second>1)?" times":" time")<<endl;
	return 0;
}

a)        与其他容器类似,set也是模板。为了定义一个set,必须指定其元素类型,这个栗子中元素类型是string

b)        与顺序容器类似,可以对一个关联容器的元素进行列表初始化,使集合exclude中保存了12个我们想忽略的单词

c)        与前一个程序相比很重要的不同是,在统计每个单词出现次数之前,我们用if语句检查单词是否在忽略集合中,只统计不在exclude中的单词

d)        find调用返回一个迭代器。如果给定关键字在set中,迭代器指向该关键字。如果给定关键字不在set中,find则返回尾后迭代器。在此栗子中,仅当word不在exclude中时我们才更新word的计数器




6.      关联容器概述

a)        关联容器都支持普通容器操作(表9.2,第295页)

b)        关联容器不支持顺序容器的位置相关的操作,例如push_front或push_back,因为关联容器中元素是根据关键字存储的

c)        除了与顺序容器相同的操作外,关联容器还支持一些顺序容器不支持的操作类型别名

d)        关联容器的迭代器都是双向




7.      定义关联容器

a)        当定义一个map时,必须既指明关键字类型又指明值类型;而定义一个set时,只需指明关键字类型,因为set中没有值

b)        每个关联容器都定义了一个默认构造函数,它创建一个指定类型的空容器。我们也可以将关联容器初始化为另一个同类型容器的拷贝,或是从一个值范围来初始化关联容器,只要这些值可以转化为容器所需类型就可以

c)        我们也可以对关联容器进行值初始化,初始化器必须能转换为容器中元素的类型

map<string,size_t> word_count;		//空容器
//列表初始化
set<string> exclude={"and","the","a","an"};
//三个元素;authors将姓映射为名 
map<string,string> authors={{"Joyce","James"},
			    {"Austen","Jane"},
			    {"Dickens","Charles"}};

d)        对于set元素类型就是关键字类型。当初始化一个map时,必须提供关键字类型和值类型。我们将每个关键字-值对包围在花括号中:{key,value}来指出它们一起构成了map中的一个元素。在每个花括号中,关键字第一个元素第二个元素。因此,authors将姓映射到名,初始化后它包含三个元素




8.      关键字类型的要求

a)        关联容器对其关键字类型有一些限制

b)        对于有序容器——map、multimap、set、multiset,关键字类型必须定义元素比较的方法。默认情况下,标准库使用关键字类型的<运算符来比较两个关键字

c)        在集合类型中,关键字类型就是元素类型;在映射类型中,关键字类型是元素的第一部分的类型。因此,上述两个栗子中,exclude的关键字类型是string,word_count的关键字类型也是string

d)        如果两个关键字是等价的(即,任何一个都不“小于等于”另一个),那么容器将它们视作相等来处理


 


9.      pair类型

a)        pair标准库类型定义在utility头文件中

b)        一个pair保存两个数据成员。类似容器,pair是一个用来生成特定类型的模板。当创建一个pair时,我们必须提供两个类型名,pair的成员将具有对应的类型。两个类型不要求一样:

pair<string,string> anon;		//保存两个string
pair<string,size_t> word_count;		//保存一个string和一个size_t
pair<string,vector<int>> line;		//保存string和vector<int> 
pair的默认构造函数对数据成员进行值初始化

c)        两个成员分别命名为first和second,用普通的成员访问符号访问它们




10.      关联容器迭代器

a)        当解引用一个关联容器迭代器时,我们会得到一个类型为容器的value_type的值的引用。对map而言,value_type是一个pair类型,其first成员保存const的关键字,second成员保存值

b)        一个map的value_type是一个pair,我们可以改变pair的值,但不能改变关键字成员的值

//获得指向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;		//正确:我们可以通过迭代器改变元素 

c\)        虽然set类型同时定义了iterator和const_iterator类型,但两种类型都只允许只读访问set中的元素。与不能改变一个map元素的关键字一样,一个set中的关键字也是const。可以用一个set迭代器来读取元素的值,但不能修改

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;	//正确:可以读关键字 
}

d)        map和set类型都支持begin和end操作。我们可以用这两个函数获取迭代器,然后用迭代器遍历容器

#include <iostream>
#include <map>
using namespace std;
int main(){
	map<string,size_t> word_count={{"111",1},{"222",2},{"333",3}};
	//获得一个指向首元素的迭代器 
	auto map_it=word_count.begin();
	//比较当前迭代器和尾后迭代器 
	while(map_it!=word_count.end()){
		//解引用迭代器,打印关键字-值对 
		++map_it->second;
		++map_it;		//递增迭代器,移动到下一个元素 
	}
	for(auto &w:word_count)
		cout<<w.first<<" "<<w.second<<"  ";	
	return 0;
}
输出结果为111 2  222 3  333 4




11.      添加元素

a)        关联容器的insert成员向容器中添加一个元素或一个元素范围

b)        由于mapset包含不重复的关键字,因此插入一个已存在的元素对容器没有任何影响

c)        insert有两个版本,分别接受一对迭代器,或是一个初始化器列表

	vector<int> ivec={2,4,6,8,2,4,6,8};		//vector有8个元素 
	set<int> set2;		//空集合 
	set2.insert(ivec.begin(),ivec.end());	//set2有4个元素 
	set2.insert({1,3,5,7,1,3,5,7});		//set2现在有8个元素

d)        对一个map进行insert操作时,元素类型是pair

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












12.      删除元素

a)        关联容器定义了三个坂本的erase。与顺序容器一样,我们可以通过传递给erase一个迭代器或一个迭代器对来删除一个元素或者一个元素范围。指定的元素被删除,函数返回void

b)        关联容器提供一个额外的erase操作,它接受一个key_type参数。此坂本删除所有匹配给定关键字的元素,返回实际删除的元素的数量。下面的栗子删除了word_count的一个特定的单词

	//删除一个关键字,返回删除的元素数量 
	if(word_count.erase(removal_word))
		cout<<"ok: "<<removal_word<<" removed\n";
	else cout<<"oops: "<<removal_word<<" not found!\n";

c)        对于保存不重复关键字的容器,erase的返回值总是0或1。若返回值为0,则表明想要删除的元素并不在容器中

从关联容器删除元素
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




13.      map的下标操作

a)        map容器提供了下标运算符和一个对应的at函数

b)        set类型不支持下标,因为set中没有与关键字相关联的“值”,元素本身就是关键字

c)        map下标运算符接受一个索引(即,一个关键字),获取与此关键字相关联的值。如果关键字并不在map中,会为它创建一个元素并插入到map中,关联值将进行

始化。例如:

	map<string,size_t> word_count;		//empty map
	//插入一个关键字为Anna的元素,关联值进行值初始化;然后将1赋予它
	word_count["Anna"]=1;
map的下标操作
c[k]返回关键字为k的元素;如果k不在c中,添加一个关键字为k的元素,对其进行值初始化
c.at(k)访问关键字为k的元素,带参数检查;若k不在c中,抛出一个out_of_range异常




            i.     在word_count中搜索关键字为Anna的元素,未找到;

            ii.    讲一个新的关键字-值对插入到word_count中。关键字是一个const string,保存Anna。值进行值初始化,在本栗中意味着值为0

            iii.    提取出新插入的元素,并将值1赋予它

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




14.      访问元素

a)        关联容器提供多种查找一个指定元素的方法。如果我们所关心的只不过是一个特定元素是否已在容器中,可能find是最佳选择。如果不需要计数,最好使用find:

	set<int> iset={0,1,2,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 
在一个关联容器中查找元素的操作
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()






b)        对map类型,下标运算符提供了最简单的提取元素的方法。但是有一个严重的副作用:如果关键字还未在map中,下标操作会插入一个具有给定关键字的元素。但有时,我们只是想知道一个给定关键字是否在map中,而不想改变map,这样就不能使用下标运算符来检查一个元素是否存在。因为如果关键字不存在的话,下标运算符会插入一个新元素。在这种情况下,应该使用find:

	map<string,size_t> word_count={{"foobar",7},{"foob",3},{"foobar",5}};
	if(word_count.find("foobar")==word_count.end())		//若foobar不在map中,返回尾后迭代器
		cout<<"not in the map"<<endl;
	else	cout<<word_count.count("foobar")<<endl;		//输出1,因为map包含不重复的元素,两个foobar对它没有影响








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值