【C++】关联容器(三):关联容器操作

11.3 关联容器操作

关联容器还定义了如下表所列出的类型,这些类型表示容器关键字和值的类型:

  • key_type:此容器类型的关键字类型;
  • mapped_type:每个关键字关联的类型,只适用于 map;
  • value_type:对于 set,与 key_type 相同;对于 map,为 pair<const key_type, mapped_type>;

11.3.1 关联容器迭代器

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

cpp 复制代码
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;			// 正确, 可以通过迭代器修改值的元素

需要记住的是,一个 map 的 value_type 是一个 pair,我们可以改变 pair 的值,但是不可以改变它的关键字成员。

set 的迭代器是 const 的

尽管 set 类型同时定义了 iterator 和 const_iterator 类型,但两种类型都只允许访问 set 当中的元素。

遍历关联容器

map 和 set 类型都支持 begin 和 end 操作。可以用这些函数获取迭代器,然后使用迭代器来遍历容器。

例如:

cpp 复制代码
auto map_it = word_count.cbegin();
while(map_it != word_count.cend()) {
	cout << map_it -> first << " occurs " << map_it -> second << " times" << endl;
	++ map_it;
}

关联容器和算法

我们通常不对关联容器使用泛型算法。关键字(map 的 key 以及 set 当中存储的元素)为 const 这一特性意味着不能将关联容器传递给修改或重拍容器元素的算法,因为这类算法需要向元素写入值,而 set 类型中的元素是 const 的,map 中的元素是 pair,其第一个成员是 const 的

关联容器可以用于只读取元素的算法。但很多这类算法都需要搜索序列。由于关联容器中的元素不能通过它们的关键字进行(快速)查找,因此对其使用泛型搜索算法几乎总是不好的。

在实际编程中,如果想要对一个关联容器使用算法,要么将它当作一个源序列,要么当作一个目的位置。例如可以用泛型 copy 算法将元素从关联容器拷贝到一个序列当中,也可以调用 inserter 将一个插入器绑定到一个关联容器。

11.3.2 添加元素

关联容器的 insert 成员向容器中添加一个元素一个元素范围。由于 map 和 set 包含不重复的关键字,因此插入一个已存在的元素对容器没有任何影响。

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

向 map 添加元素

对一个 map 进行 insert 时,元素类型是 pair。

在 C++ 11 标准下,最简单的创建 pair 的方式是花括号初始化,也可以调用 make_pair 或 显式构造 pair:

cpp 复制代码
word_count.insert({word, 1});
word_count.insert(make_pair(word, 1));
word_count.insert(map<string, size_t>::value_type(word, 1));
word_count.insert(pair<string, size_t>(word, 1));

检测 insert 的返回值

insert 返回的值依赖于容器类型和参数。对于不包含重复关键字的容器,添加单一元素的 insert 和 emplace 版本返回一个 pair,告诉我们插入操作是否成功。pair 的 first 是一个迭代器,指向给定关键字的元素;second 是一个 bool,指出元素是插入成功还是已经存在于容器当中。如果关键字已经存在于关联容器当中,那么 insert 什么事情也不做,second 的 bool 值为 false。如果关键字不存在,元素才会被插入到容器当中,且 bool 为 true。

以下是一个使用 insert 重写单词记数程序的例子:

cpp 复制代码
map<string, size_t> word_count;
string word;
while(cin >> word) {
	auto ret = word_count.insert({word, 1});	// 使用花括号初始化 pair
	if(!ret.second) {							// 如果 word 已经存在于 word_count 中
		++ ret.first -> second;					// 递增计数器
	}
}

展开递增语句

上述的递增计数器语句较难理解,可以添加一些括号来反映优先级:

cpp 复制代码
++ ((ret.first) -> second);	// 等价的表达式

向 multiset 和 multimap 添加元素

有时我们希望能够添加具有相同关键字的多个元素。此时我们可以使用 multimap 而不是 map。

由于一个 multi 关联容器中的关键字不必是唯一的,在这些类型上调用 insert 总会插入一个元素:

cpp 复制代码
multimap<string, string> authors;
authors.insert({"Barth, John", "Sot-Weed Factor"});
authors.insert({"Barth, John", "Lost in the Funhouse"});

对允许重复添加关键字的容器,接受单个元素的 insert 操作返回一个指向新元素的迭代器。这里允许返回一个 bool 值,因为 insert 总是向这类容器加入一个新元素。

11.3.3 删除元素

关联容器定义了三个版本的 erase。可以传递给 erase 一个迭代器或一个迭代器范围来删除一个元素或一个元素范围。这两个版本的 erase 较为相似,指定的元素会被删除,函数返回 void。

关联容器提供了一个额外的 erase 操作,它接受一个 key_type 参数。此版本删除所有匹配给定关键字的元素(如果存在的话),返回实际删除的元素的数量。可以用这一版本的 erase 在打印结果之前删除 word_count 中特定的单词:

cpp 复制代码
if(word_count.erase(removal_word)) {
	cout << "ok: " <, removal_word << " removed\n";
} else {
	cout << "oops: " << removal_word << " not found!\n";
}

对于保存不重复关键字的容器,这一版本的 erase 总是返回 0 或 1,因为要么容器中不包含这一关键字,如果包含的话,也只包含一个,所以成功删除的数量是一个。而对于允许重复关键字的容器,删除元素的数量可能大于 1。

11.3.4 map 的下标操作

map 和 unordered_map 容器提供了下标运算符和一个对应的 at 函数。set 类型不支持下标,因为 set 中没有与关键字相关联的"值"。元素本身就是关键字,因此"获取与一个关键字相关联的值"的操作没有意义。

同样地,我们也不能对一个 multimap 或 unordered_multimap 进行下标操作,因为这些容器有多个值与一个关键字关联。

类似之前的下标运算符,map 下标运算符接受一个索引(即关键字),获取与此关键字相关联的值。但是与其它下标运算符不同的是,如果关键字不在 map 当中,那么 map 会创建一个元素并插入到 map 当中,关联值将进行值初始化

如果使用 at 进行下标操作,则与直接使用下标运算符略有不同。当关键字不在 map 时,使用 at 操作会抛出一个 out_of_range 异常。

使用下标操作的返回值

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

11.3.5 访问元素

关联容器提供了多种查找一个指定元素的方法:

  • 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()。

对 map 使用 find 代替下标操作

对于 map 和 unordered_map,下标操作的一个严重副作用是:如果关键字不在 map 中,则会执行插入。

有时我们只想知道一个关键字是否在 map 中,而不想改变 map,此时使用 find 更合适:

cpp 复制代码
if(word_count.find("foobar") == word_count.end())
	cout << "foobar is not in the map" << endl;

在 multimap 或 multiset 中查找元素

如果一个 multimap 或 multiset 中有多个元素具有给定关键字,这些元素在容器中会相邻存储。

一个使用 find 和 count 进行查找的例子如下:

cpp 复制代码
string search_item("Alain de Botton");	// 要查找的作者
auto entries = authors.count(search_item);	// 查找元素的数量
auto iter = authors.find(search_item);		// 此作者的第一本书
while(entries) {
	cout << ite	r -> second << endl; 		// 打印每个题目
	++ iter;								// 前进到下一本书
	-- entries;								// 记录已经打印了多少本书
}

一种不同的,面向迭代器的解决方式

还可以用 lower_bound 和 upper_bound 来解决此问题。这两个操作都接受一个关键字,返回一个迭代器。如果关键字在容器中,lower_bound 返回的迭代器将指向第一个具有给定关键字的元素,而 upper_bound 返回的迭代器指向最后一个匹配给定关键字的元素之后的位置。

equal_range 函数

equal_range 比使用 lower_bound 和 upper_bound 更为直接。此函数接受一个关键字,返回一个迭代器 pair:

cpp 复制代码
for(auto pos = authors.equal_range(search_item); pos.first != pos.second; ++ pos.first) {
	cout << pos.first -> second << endl;
}
相关推荐
澄澈天空30 分钟前
c++ mfc调用UpdateData(TRUE)时,发生异常
c++·mfc
XZHOUMIN31 分钟前
MFC中如何在工具条动态增加菜单
c++·mfc
R6bandito_44 分钟前
Qt几何数据类型:QLine类型详解(基础向)
c语言·开发语言·c++·经验分享·qt
禊月初三1 小时前
C++面试突破---C/C++基础
c语言·c++·面试
程序猿阿伟1 小时前
《平衡之策:C++应对人工智能不平衡训练数据的数据增强方法》
前端·javascript·c++
CodeGrindstone2 小时前
Muduo网络库剖析 --- 架构设计
网络·c++·网络协议·tcp/ip
9毫米的幻想2 小时前
【C++】—— set 与 multiset
开发语言·c++·rpc
想成为高手4992 小时前
深入理解AVL树:结构、旋转及C++实现
开发语言·数据结构·c++
£suPerpanda2 小时前
P3916 图的遍历(Tarjan缩点和反向建边)
数据结构·c++·算法·深度优先·图论
黑果果的思考3 小时前
C++看懂并使用-----回调函数
开发语言·c++