C++ primer 第11章 关联容器


关联容器和顺序容器有着根本的不同:关联容器中的元素是按关键字来保存和访问的,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。
两个主要的管理容器类型是map和set。map中的元素是key-value对,set中每个元素只包含一个关键字,set支持高效的关键字查询操作——检查一个给定关键字是否在set中。
在这里插入图片描述

使用关联容器

map示例

	map<string, size_t>word_count;
	string word;
	while (cin>>word) {
		++word_count[word];
	}
	for (const auto &wc:word_count) {
		cout << wc.first << " 出现的次数:" << wc.second << endl;
	}

输入:

hello
hi
haha
hello
haha
ha
^Z

输出:

ha 出现的次数:1
haha 出现的次数:2
hello 出现的次数:2
hi 出现的次数:1

关联容器概述

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

定义关联容器

关联容器值初始化

set<string>st={"set","the","hello"};
map<string,string>mp={{"zhang","san"},{"wang","wu"},{"li","si"}};

multimap和multiset

multimap和multiset允许关键字重复。

关键字类型的要求

关联容器对其关键字类型有一些限制。对于有序容器——map、multimap、set以及multiset,关键字类型必须定义元素比较的方法。
例如,我们不能直接定义Sales_data的multiset,因为Sales_data没有<运算符,但是可以用compareIsbn函数来定义一个multiset,次函数在Sales_data对象的ISBN成员上定义了一个严格弱序。

bool compareIsbn(const Sales_data &lhs,const Sales_data &rhs){
	return lhs.isbn()<rhs.isbn();
}
multiset<Sales_data,decltype(compareIsbn)*>bookstore(compareIsbn);

此处我们使用decltype来指出自定义操作的类型。当用decltype来获得一个函数指针类型时,必须加上一个*来指出我们要使用一个给定函数类型的指针。用compareIsbn来初始化bookstore对象,表示当我们向bookstore添加元素时,通过调用compareIsbn来为这些元素排序。可以用compareIsbn代替&compareIsbn作为构造函数的参数,因为当我们使用一个函数的名字时,在需要的情况下它会自动转化为一个指针,当然,使用&compareIsbn的效果也是一样的。

pair类型

标准库类型pair定义在头文件utility中。
一个pair保存两个数据成员,类似容器,pair是一个用来生成特定类型的模板。
例如: pair<string,int>p;
与其他标准库类型不同,pair的数据成员是public的,两个成员名分别为first和second。

pair上的操作

在这里插入图片描述

关联容器操作

关联容器额外的类型别名

在这里插入图片描述

set<string>::value_type v1;       v1是一个string
set<string>::key_type v2;         v2是一个string
map<string,int>::key_type v3;     v3是一个string
map<string,int>::mapped_type v4;  v4是一个int
map<string,int>::value_type v5;   v5是一个pair<const string ,int>

v5是一个pair<const string ,int>,关键字是const,不能改变

关联容器迭代器

map迭代器

当解引用一个关联容器迭代器时,我们会得到一个类型为容器的value_type的值的引用。下述代码不能执行语句
it->first=“newKey”,因为关键字是const!!
但可执行语句 it->second = 10;

	map<string, int>mp{ { "a",1 },{ "b",2 },{ "c",3 } };
	auto it = mp.begin();
	//(*it).first与it->first等价
	//迭代器it即为一个pair类型
	cout << (*it).first<< " "<<(*it).second << endl;
	cout << it->first << " " << it->second << endl;
	//可以通过迭代器更改元素
	it->second = 10;
	cout << it->first << " " << it->second << endl;
	it++;
	cout << it->first << " " << it->second << endl;

输出结果:

a 1
a 1
a 10
b 2

set迭代器

与不能改变一个map元素的关键字一样,set中的关键字也是const的,可以用一个set迭代器来读取元素的值,但不能修改:

	set<int>st{1,3,5,7,9,2,3,4,6,5};
	auto it = st.begin();
	while (it!=st.end()) {
		//*it = 100;  此句错误,关键字是const的,只能读不能修改
		cout << *it << " ";
		it++;
	}

输出结果:

1 2 3 4 5 6 7 9

关联容器和算法

我们通常不对关联容器使用泛型算法。关键字是const这一特性意味着不能将关联容器传递给修改或重排容器元素的算法,因为这类算法需要向元素写入值,而set类型中的元素是const的,map中的元素是pair,且其第一个成员是const的。
关联容器可用于只读取元素的算法,但是很多这类算法都要搜索序列。关联容器定义了一个名为find的成员,它通过一个给定的关键字直接获取元素,我们可以用泛型find算法来查找一个元素,但此算法会进行顺序搜索。使用关联容器定义的专用的find成员会比调用泛型find快得多。
在实际编程中,如果我们真要对一个关联容器使用算法, 要么是将它当做一个源序列,要么当做一个目的位置。例如可以用泛型copy算法将元素从一个关联容器拷贝到另一个序列。类似的,可以调用inserter将一个插入器绑定到一个关联容器。通过使用inserter,我们可以将关联容器当做一个目的位置来调用另一个算法。

添加元素

向map添加元素

向map用insert添加元素的四种方法:

map<string,int>wc;
string words="hello";
wc.insert({words,1});
wc.insert(make_pair(words,1));
wc.insert(pair<string,int>(words,1));
wc.insert(map<string,int>::value_type(words,1));

在这里插入图片描述

检测insert的返回值

添加单一元素的insert和emplace版本返回一个pair,pair的first成员是一个迭代器,指向具有给定关键字的元素;second成员是一个bool值,指出元素是插入成功还是已经存在于容器中,如果插入成功,则返回true,否则返回false。

	map<string,int>mp{ { "b",2 },{ "a",1 },{ "c",3 } };
	auto it1 = mp.insert({ "b",10 });//it1是一个pair
	cout << it1.second<<endl;
	auto mp1 = it1.first;  //mp1是一个迭代器
	cout << mp1->first << " "<<mp1->second << endl;

	auto it2 = mp.insert({ "a2",10 });
	cout << it2.second << endl;
	auto mp2 = it2.first;
	cout << mp2->first << " " << mp2->second << endl;

输出结果:

0
b 2
1
a2 10

第一次插入失败,insert返回的迭代器指向map已存在的元素。
第二次插入成功,insert返回的迭代器指向插入的元素。

而对于允许重复的multimap,insert操作返回一个指向新元素的迭代器,无需返回bool值,因为insert总是向这类容器中加入一个新元素。

使用insert代替下标操作写单词计数程序

	map<string, int>mp;
	string str;
	while (cin>>str) {
		auto ret = mp.insert({ str,1 });
		if (!ret.second) {
			auto tmp = ret.first;
			(tmp->second)++;
		}
	}
	for (auto mp3 : mp) {
		cout << mp3.first << " " << mp3.second << endl;
	}

输入:

hello
hi
haha
hahaha
haha
hi
hello
hello
helll
hehehe
^Z

输出:

haha 2
hahaha 1
hehehe 1
helll 1
hello 3
hi 2

删除元素

关联容器定义了三个版本的erase,如图所示:
在这里插入图片描述
与顺序容器一样,我们可以通过传递给erase一个迭代器或一个迭代器对来删除一个元素或者一个元素范围。

	map<string, int>mp{ { "b",2 },{ "a",1 },{ "c",3 } };
	auto it =mp.erase("a");//将{ "a",1 }删除,it是删除的元素的数量,此处是1
	map<string, int>mp{ { "b",2 },{ "a",1 },{ "c",3 } };
	auto it =mp.erase(mp.begin());//将mp.begin()指向的元素删除,it是一个迭代器指向删除元素之后元素的迭代器
	//此处mp.begin()指向{ "a",1 },it指向{ "b",2 }

map的下标操作

map和unodered_map容器提供了下标运算符和一个对应的at函数,set类型不支持下标,因为set中没有与关键字相关联的“值”。我们不能对一个multimap或一个unodered_multimap进行下标操作,因为这些容器中可能有多个值与一个关键字相关联。
在这里插入图片描述

map下标运算符接受一个索引(即,一个关键字),获取与此关键字相关联的值。但是,与其他下标运算符不同的是,如果关键字并不在map中,会为它创建一个元素并插入到map中,关联值将进行值初始化。例如下述代码,mp["d"];使得mp中增加了元素{ “d”,0 }。

	map<string, int>mp{ { "b",2 },{ "a",1 },{ "c",3 } };
	for (auto mp3 : mp) {
		cout << mp3.first << " " << mp3.second << endl;
	}
	cout << endl;

	mp["d"];
	for (auto mp3 : mp) {
		cout << mp3.first << " " << mp3.second << endl;
	}

输出结果:

a 1
b 2
c 3

a 1
b 2
c 3
d 0

由于下标运算符可能插入一个新元素,我们只可以对非const的map使用下标操作。
与vector和string不同,map的下标运算符返回的类型(mapped_type)与解引用map迭代器得到的类型(value_type)不同。

访问元素

在这里插入图片描述
在这里插入图片描述

multimap查找元素代码示例一:count和find

给定一个作者到著作的映射,打印一个特定的作者的所有著作

string item("luxun");
auto entries = authors.count(item);
auto iter = authors.find(item);
while(entries){
	cout<<iter->second<<endl;
	++iter;
	--entries;
}

multimap查找元素代码示例二:lower_bound和upper_bound

可以用lower_bound和upper_bound解决此问题。lower_bound返回的迭代器将指向第一个具有给定关键字的元素,而upper_bound返回的迭代器则指向最后一个匹配给定关键字的元素之后的位置。因此用相同的关键字调用lower_bound和upper_bound会得到一个迭代器范围,表示所有具有该关键字的元素。如果关键字不存在,则lower_bound和upper_bound指向相同的位置,迭代器范围即为空。
重写代码示例一的程序如下:

for(auto beg = authors.lower_bound(item),end=authors.upper_bound(item);
beg!=end;++beg){
	cout<<beg->second<<endl;
}

multimap查找元素代码示例三:equal_range函数

equal_range:接受一个关键字,返回一个迭代器pair,第一个迭代器指向第一个与关键字匹配的元素,第二个迭代器指向最后一个匹配元素之后的位置,若未找到匹配元素,则两个迭代器都指向关键字可以插入的位置,即upper_bound返回的迭代器的位置。

for(auto pos = authors.equal_range(item);
pos.first!=pos.second;++pos.first){
	cout<<pos.first->second<<endl;
}

无序容器

unordered_map
unordered_set
unordered_multimap
unordered_multiset
无序容器在存储上组织为一组桶,每个桶保存零个或多个元素。无序容器使用一个哈希函数将元素映射到桶。为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶。容器将具有一个特定哈希值的所有元素都保存在相同的桶中。如果容器允许重复关键字,所有具有相同关键字的元素也都会在同一个桶中。因此,无序容器的性能依赖于哈希函数的质量和桶的数量和大小。

无序容器管理操作

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值