C++Primer 第五版 ——《第十一章 》关联容器

目录

Preface

使用关联容器

定义关联容器 以及关联容器支持的操作(376P)

初始化 multimap 或 multiset ( 377P)

有序容器的关键字类型的限制 ( 378P)

pair 标准库类型 ( 379P )

pair 类型提供的操作(380P)

使用 pair 类型作为函数的返回值类型  以及 使用 make_pair 来创建 pair对象(380P)

关联容器支持的操作(381P)

关联容器的迭代器 ( 382P )

set 的迭代器是 const的 ( 382P)

使用 begin 和  end 操作来遍历关联容器 ( 382P )

关联容器通常不应该使用泛型算法( 383P)

使用关联容器的 insert 和 emplace 成员  添加元素 ( 384P)

检测 insert 的返回值 ( 385P)

向 multiset 和  multimap 添加元素 ( 386P)

使用 erase 成员 删除元素 ( 386P)

unordered_map 和 map 提供下标操作 、at函数 ( 387P )

使用 unordered_map 和 map 下标操作的返回值(388P)

使用 find、count、lower_bound、upper_bound、equal_range 访问元素 (388P)

使用 map 和 unordered_map 的find 操作 代替 下标操作(389P)

在 multimap 或 multiset 中查找元素

使用 lower_bound 和 upper_bound 操作( 389P、390P )

equal_range 函数( 391P)

无序容器 (394P)

无序容器支持的操作 395P )


Preface


关联容器和顺序容器的主要区别是:

  •  关联容器中的元素是按关键字来保存和访问的。
  • 顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。

关联容器的特点有:

  •   关联容器有一个特点是通过关键字来高效地访问和查找元素。
  • map 中的元素是一些关键字,然后这些关键字映射到一个值, 关键字起到索引的作用, 值则表示索引引用的元素数据。
  • set 中每个元素都是一个关键字; set 支持高效的查询关键字操作, 比如说查找一个给定的关键字是否在 set 中。
  • 类型 map 和 multimap 定义在头文件 map 中;
  • set 和 multiset 定义在头文件 set 中;
  • 无序容器则定义在头文件 unordered_map 和unordered_set 中。

使用关联容器


map 是关键字 - 值对的集合,所以 被通常被称为关联数组,与正常数组的不同在于:

  • 下标不必是整数 我们是通过一个关键字 而不是 整数下标来查找值。例如:将一个人的名字作为关键字,将其电话号码作为值。
  •   那么 set 就是 关键字的简单集合,set 中没有值。  当只是想知道一个值是否存在时, set 是最有用的。

使用 map:

int main()
{
	std::map 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 << " 出现 " << w.second << ((w.second > 1) ? " times" : " time") << endl;
	}
	
	system("pause");
	return 0;
}

输出结果为:

huang
cheng
huang
cjemh
cheng
^Z
cheng 出现 2 times
cjemh 出现 1 time
huang 出现 2 times
  • 注意:  定义一个map , 我们必须指定关键字和值的类型。上述程序中map 保存的,每个元素, 关键字是string类型,值是 size_t 类型。
  • 但我们从一个 map 中获取一个元素时,会得到一个pair 类型的对象( 说明获取到的这个元素是 pair 类型的)。pair 是一个模板类型,其中包含了两个名为 first 和 second 的公有成员数据。
  • map 所使用的 pair 用 first 成员保存关键字, 用 second 成员保存其关键字对应的值。

使用 set:

int main()
{
	//统计输入中每个单词出现的次数
	std::map word_count; // 从 string 到 size_t 的空map
	std::set exclude = { "The", "But", "And", "Or", "An", "A",
	                             "the", "but", "and", "or", "an", "a" };
	string word;
	while (cin >> word)
	{
		
		if (exclude.find(word) == exclude.end())
		{
			++word_count[word]; 
		}
	}
	
	system("pause");
	return 0;
}

输出结果为:

huang
chengt
the
or
and
tao
^Z
chengt 出现 1 time
huang 出现 1 time
tao 出现 1 time
  • 注意: 定义一个set , 必须指定其元素类型, 本例中元素类型是string. 
  • 我们可以对所有的关联容器中的元素,进行列表初始化。

练习题11.1:描述 map 和 vector的不同:

  • map 属于关联容器;vector 属于 顺序容器
  • map 保存的是键值对的集合,它的下标不需要是整数; 而 vector 是某种类型的集合,它的下标需要是整数
  • map中的元素是按 关键字来保存和访问的;vector 是以它们在容器中的位置来顺序保存和访问的。

定义关联容器 以及关联容器支持的操作(376P)


关联容器,不管是有序的还是无须的,都支持下图中普通的容器操作。

C++Primer 第五版 ——《第十一章 》关联容器_第1张图片

关联容器不支持的操作有:

  •  关联容器不支持顺序容器位置相关的操作,例如 push_front 或push_back。因为关联容器中的元素是根据关键字存储的。
  • 此外,关联容器不支持构造函数或插入操作,这些操作接受元素值和数量值的操作。
  • 关联容器除了跟顺序容器支持的一样的操作(上述的图片操作)外,还支持顺序容器不支持的操作 和 类型别名。
  • 关联容器的迭代器都是双向的
  •  每个关联容器都定义了一个默认的构造函数,它创建了一个指定类型的空容器。

下面的程序使用了  列表初始化和 一个值范围内的元素 来初始化 set 和 map  容器:

int main()
{

	// list initialization
	std::set exclude = { "the", "but", "and", "or", "an", "a",
							   "The", "But", "And", "Or", "An", "A" };
	// 三个元素; authors将姓映射为名
	std::map authors = { {"Joyce", "James"},
										 {"Austen", "Jane"},
										 {"Dickens", "Charles"} };

	std::map word_count = authors; //用一个map 初始化另一个map,只要这些值可以转换为容器中元素的类型。 map 的元素类型是 string
	cout << "输出map 容器 word_count 中所有的键值-对:\n";
	for (const auto &w : word_count)
	{
		cout << w.first << " 对应" << w.second << endl;
	}
	cout << endl;
	std::set exclude1(exclude.cbegin(), exclude.cend()); // 用一个值范围初始化化关联容器。 set 的元素类型就是关键字类型
	cout << "输出set 容器 exclude1 中所有的关键字元素:\n";
	for (auto tt : exclude1)
	{
		cout << tt << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

输出结果为:

输出map 容器 word_count 中所有的键值-对:
Austen 对应Jane
Dickens 对应Charles
Joyce 对应James

输出set 容器 exclude1 中所有的关键字元素:
A An And But Or The a an and but or the
  • 不管是 map  还是  set 中, 其关键字必须是唯一的,不可重复。

初始化 multimap 或 multiset ( 377P)


  •  对于set 和 map  容器 ,  关键字只能一一对应一个元素值。 但是multimap 和 multiset 一个关键字可以对应不同的元素值, 但是注意的是一个元素值不可以对应多个关键字。

下面的程序演示了具有唯一关键字的 set 容器与 允许重复关键字的multiset 容器之间的区别:

int main()
{
	vector ivec;
	for (vector::size_type i = 0; i != 10; ++i)
	{
		ivec.push_back(i);
		ivec.push_back(i);
	}
	std::set iset(ivec.cbegin(), ivec.cend());
	std::multiset miset(ivec.cbegin(), ivec.cend());

	cout << ivec.size() << endl; // prints 20
	cout << iset.size() << endl; // prints 10
	cout << miset.size() << endl; // prints 20

	cout << "输出 set 容器中的所有元素:" << endl;
	for_each(iset.cbegin(), iset.cend(), [](int tt) {cout << tt << " "; });
	cout << endl;
	cout << "输出 multiset 容器中的所有元素:" << endl;
	for_each(miset.cbegin(), miset.cend(), [](int tt) {cout << tt << " "; });

	system("pause");
	return 0;
}
输出结果为:
20
10
20

输出 set 容器中的所有元素:
0 1 2 3 4 5 6 7 8 9

输出 multiset 容器中的所有元素:
0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 

练习题11.5:

  • map 是 键值对的集合;set 是简单的关键字集合
  • 在定义 map 的时候,必须提供 关键字类型和值类型; 在定义set 的时候只需要提供关键字类型, set的元素值类型就是关键字类型
  • 当需要查找给定值所对应的数据时, 应使用map, 其中保存的是 < 关键字, 值> 对, 可以按关键访问值。
  • 如果只需判定给定值是否存在时, 应使用set, 它是简单的值的集合。

练习题11.6:

  • 两者都可以保存元素集合。
  • 如果只需要顺序访问这些元素,或是按位置访问元素, 那么应使用list.
  • 如果需要快速判定是否有元素等于给定值, 则应使用set.

有序容器的关键字类型的限制 ( 378P)


 map, multimap, set, and multiset 这些容器的关键字类型必须定义了 比较元素的方法来比较元素。default,标准库使用关键字类型的 “ < ” 运算符来比较两个关键字。

我们也可以提供自定义的操作来代替关键字类型上 < 运算符。但是提供的该操作必须在关键字类型上定义一个严格弱序。可以将严格弱序看作是 “小于等于 ”。

传递给排序泛型算法的可调用对象,必须跟该关联容器中关键字的类型一样。


pair 标准库类型 ( 379P )


  • 该标准库类型定义在头文件 utility 中。一个pair 保存两个 public 数据成员( first 和 second )类似于容器,pair也是一个模板,我们可以创建该类型的实例。
  • 当我们定义一个 pair 时, 必须提供两个类型名( 两个类型可以相同或不同),然后 pair 的first 和 second 数据成员将具有对应的类型。

 实例化 pair 类型:

int main()
{
	std::pair anon; // holds two strings,
	std::pair word_count; // holds a string and an size_t
	std::pair> line; // holds string and vector
	// std::pair author(" huang", "cheng", "tao"); 错误, pair 的构造函数不接受2个以上的参数

	std::pair author{ " huang", "cheng" }; // first 数据成员初始化为 huang , second 初始化为 cheng
	cout << "输出 author 的first 的值:" << author.first << " , 输出author 的 second 的值:" << author.second << endl;

	std::pair vec(" tao", "taotao");
	cout << "输出 vec 的first 的值:" << vec.first << " , 输出 vec 的 second 的值:" << vec.second << endl;

	std::pair ve = { " taohuang", "taotttao" };
	cout << "输出 ve 的first 的值:" << ve.first << " , 输出 ve 的 second 的值:" << ve.second << endl;

	auto tt = make_pair(10, 20); // tt 的类型是 从 10 和 20 的类型推断出来的
	cout << "输出 tt 的first 的值:" << tt.first << " , 输出 tt 的 second 的值:" << tt.second << endl;
	system("pause");
	return 0;
}

输出结果为:

输出 author 的first 的值: huang , 输出author 的 second 的值:cheng
输出 vec 的first 的值: tao , 输出 vec 的 second 的值:taotao

输出 ve 的first 的值: taohuang , 输出 ve 的 second 的值:taotttao
输出 tt 的first 的值:10 , 输出 tt 的 second 的值:20

上述的程序,pair 的默认构造函数会 first 和 second 数据成员进行值初始化。例如: word_count 的string 初始化为空,size_t 初始化为 0.


pair 类型提供的操作(380P)



使用 pair 类型作为函数的返回值类型  以及 使用 make_pair 来创建 pair对象(380P)



std::pairprocess(vector &v)
{
	// process v
	if (!v.empty())
		return { v.back(), v.back().size() }; // list initialize
	else
		return std::pair(); // 隐式构造返回值
}
int main()
{
	vector vec = { "huang","cheng","tao","taoshui" };
	auto tt = process(vec);

	cout << "first 的值为:" << tt.first << " ,second的值为:" << tt.second << endl;
	
	system("pause");
}

输出结果为:
first 的值为:taoshui ,second的值为:7

使用 make_pair 来创建 pair对象:

std::pairprocess(vector &v)
{
	// process v
	if (!v.empty())// 当使用 make_pair 创建 pair 对象时,pair 的两个类型来自于 make_pair 的参数
		return  make_pair(v.back(), v.back().size()); // 
	else
		return std::pair(); // 隐式构造返回值
}
int main()
{
	vector vec = { "huang","cheng","tao","taoshui" };
	auto tt = process(vec);

	cout << "first 的值为:" << tt.first << " ,second的值为:" << tt.second << endl;
	
	system("pause");
	return 0;
}

输出结果为:

first 的值为:taoshui ,second的值为:7

练习题11.12:

int main()
{

	vector> vec;
	string s;
	int v;
	while (cin>>s && cin >>v)
	{
		vec.push_back(pair(s, v));
	}
	for (const auto &t:vec)
	{
		cout << "first 值:" << t.first << " , second 值:" << t.second << endl;
	}
	system("pause");
	return 0;
}

string
55
dsa
44
sa
444
^Z
first 值:string , second 值:55
first 值:dsa , second 值:44
first 值:sa , second 值:444

 


关联容器支持的操作(381P)


  下图这些类型别名表示容器关键字 和 值 的 类型

C++Primer 第五版 ——《第十一章 》关联容器_第2张图片

C++Primer 第五版 ——《第十一章 》关联容器_第3张图片

  • key_type 和 value_type 对于 set 来说都是一样的,即set 保存的值 ( 即关键字 )的类型。
  • 对于一个 map 来说, 其中的元素关键字-对,即 map 中的每一个元素都是一个 pair 对象,包含了一个关键字,和对应关联的值。但是需要注意的是: 我们不能改变 map 中一个元素的关键字,因此 pair 对象( 即map 中的每一个元素)的关键字部分都是 const的:
set::value_type v1; // v1 is a string
set::key_type v2; // v2 is a string

map::value_type v3; // v3 is a pair

map::key_type v4; // v4 is a string
map::mapped_type v5; // v5 is an int
  • 注意: 只有map 类型  (unordered_map, unordered_multimap, multimap, and map) c才定义了 mapped_type. set  有关的容器类型没有。

关联容器的迭代器 ( 382P )


  • 当我们解引用一个关联容器的迭代器时,会得到该迭代器引用的值,该值是一个 value_type 类型的值。
  • 对于 map 而言,value_type 是一个 pair 类型, 其 first 成员保存才 const 的关键字,  second 成员保存关键字对应的值
int main()
{
	std::map word_count = { {0, 100},
	                                   { 1,200, },
	                                   { 2,300 }};
	auto map_it = word_count.begin(); // 获得指向 word_count 中一个元素的迭代器

	// *map_it 是指向一个 pair 对象的引用
	cout << "打印此元素的关键字为:" << map_it->first << endl;
	cout << " 打印此元素的值为:" << map_it->second << endl;

	// map_it->first = "new key"; // 错误, 关键字是const的
	++map_it->second; // 正确, 我们可以通过迭代器改变元素
	cout << "\n输出移动后的值:" << map_it->second << endl; // 打印此元素的值

	system("pause");
	return 0;
}

打印此元素的关键字为:0
 打印此元素的值为:100
输出移动后的值:101
  • 必须记住, 一个map 的 value_type 是一个pair 类型, 我们可以改变pair 类型的元素值, 但不能改变其关键字的值

set 的迭代器是 const的 ( 382P)


  •   set 容器中定义了 iterator 和 const_iterator 类型, 但是它们都只允许只读访问set 中的元素。 set 中的关键字也是const的。所以说用一个set 的迭代器来读取元素的值,但不能修改其值。
int main()
{
	std::set iset = { 0,1,2,3,4,5,6,7,8,9 };
	//std::set::iterator set_it = iset.begin(); // 等价的操作
	auto set_ii = iset.begin();
	if (set_ii != iset.end())
	{
		//*set_it = 42; //错误,set 中关键字是只读的
		cout << *set_ii << endl; // 正确,可以读关键字
	}
	system("pause");
	return 0;
}

输出结果为: 0

使用 begin 和  end 操作来遍历关联容器 ( 382P )


int main()
{
      std::map word_count = { {0, 100},{ 1,200}, { 2,300 },{4,400} };
	auto map_it = word_count.cbegin();
	
	while (map_it != word_count.cend())
	{
		
		cout << map_it->first << " occurs "<< map_it->second << " times!" << endl;
		++map_it; 
	}
	system("pause");
	return 0;
}

输出结果为:

0 occurs 100 times!
1 occurs 200 times!
2 occurs 300 times!
4 occurs 400 times!
  • 注意: 当使用一个迭代器遍历一个 map、multimap, set 或 multiset 时, 迭代器按关键字升序遍历元素。

关联容器通常不应该使用泛型算法( 383P)


  • 注意: 我们通常不对关联容器使用泛型算法。关联容器的关键字是 const 这一特性意味着我们不能将关联容器传递给 修改 或重排容器元素的算法,  因为这类算法需要向元素写入值, 而 set 类型中的元素是const的,  map中的元素是pair,   其第一个成员是const的。
  • 关联容器可以与读取元素的算法一起使用。然而,这些算法中有许多要搜索序列。由于关联容器中的元素可以通过关键字 (快速)找到,所以使用泛型搜索算法几乎总是一个坏主意。
  • 关联容器中定义了一个自己的find 成员,它通过 给定义的关键字直接获取其对应的元素值。
  • 在实际编程中, 如果我们真要对一个关联容器使用泛型算法, 要么是将它当作一个源序列, 要么当作一个目的位置。

练习题11.15:

map> vec;
  • 其 mapped_type 是 vector < int>
  • 其 key_type 是 int 
  • 其 value_ type 是 pair < const int , vector>

练习题 11.16:

int main()
{
	std::map word_count = { {0, 100},{ 1,200}, { 2,300 },{4,400} };
	auto map_it = word_count.begin();
	map_it->second = 500;

	while (map_it != word_count.end())
	{
		cout << map_it->first << " " << map_it->second << " ";
		++map_it;
	}
	system("pause");
	return 0;
}

练习题 11.18:

  •  map_it 的类型是 pair < const string, size_t > :: iterator

使用关联容器的 insert 和 emplace 成员  添加元素 ( 384P)


  • 由于 map 和 set、unordered_map、unordered_set  这些容器都包含不重复的关键字,  因此插入一个已存在的元素没有任何影响, 但在它们容器中没有一个给定的关键字时,只有第一个带此关键字的元素才被插入到容器中。

下面的程序演示这一点:

int main()
{
	std::vector ivec = { 2,4,6,8,2,4,6,8 }; // ivec has eight elements
	std::set set2; // empty set

	set2.insert(ivec.cbegin(), ivec.cend()); // set2 has four elements
	for_each(set2.cbegin(), set2.cend(), [](int tt) {cout << tt << " "; });
	cout << endl;


	set2.insert({ 1,3,5,7,1,3,5,7,6,2,8 }); // set2 now has eight elements
	for_each(set2.cbegin(), set2.cend(), [](int tt) {cout << tt << " "; });
	cout << endl;

	system("pause");
	return 0;
}

输出结果为:

2 4 6 8
1 2 3 4 5 6 7 8
  •  第一次输出可以看出 set2 只保存 单个不重复的元素,而且是有序的。
  • 当我们第二次在 set2 中才插入元素时,只保存单个不重复的元素,而且是有序的。

 

下面是在 map 中 添加元素。通常,如果我们没有想要插入的pair 对象。 此时我们可以在 insert的 参数列表中创建一个pair :

int main()
{
	std::map word_count = { {0, 100},{ 1,200}, { 2,300 },{4,400} };
	// 用 4种 方法 向 word_count 中插入元素
	word_count.insert({ 5, 500 });
	word_count.insert(std::make_pair(6, 700));
	word_count.insert(std::pair(7, 800));
	word_count.insert(std::map::value_type(8, 400));

	cout << "输出 word_count 中的所有元素:" << endl;
	for (const auto &w : word_count)
	{
		cout << w.first << " occurs " << w.second << ((w.second > 1) ? "times" : "time") << endl;
	}
	cout << endl;
	system("pause");
	return 0;
}

输出结果为:

输出 word_count 中的所有元素:

0 occurs 100times
1 occurs 200times
2 occurs 300times
4 occurs 400times
5 occurs 500times
6 occurs 700times
7 occurs 800times
8 occurs 400times
  • 注意: 对一个 map 进行 insert 操作时, 必须记住元素类型是 pair 类型的。 

检测 insert 的返回值 ( 385P)


 insert( 或 emplace)返回的值 取决于容器类型和参数:

  • 对于不包含重复关键字的容器 ( map 和 set、unordered_map、unordered_set),  当 使用 insert 和 emplace 的添加单一元素的版本时,会返回一个 pair 对象;pair 的 first 是一个迭代器,该迭代器指向被插入的那个元素;pair 的 second 是一个bool 值, 如果返回 true,表示该值插入成功; 如果 返回false, 表示该值已存在该容器中 ( 返回 false 的 insert 和 emplace 成员不作任何事)。 
int main()
{
	std::map word_count = { {0, 100},{ 1,200}, {4,400} };
	int num;
	while (cin >> num)
	{
		// 若num 已经在 word_count中, insert 什么也不做
		auto ret = word_count.insert({ num, 600 });
		if (!ret.second) // 若 num 已在  word_count中
		{
			++ret.first->second; // 递增计数器
		}
	}
	cout << "输出 word_count 中的所有元素:" << endl;
	for (const auto &w : word_count)
	{
		cout << w.first << " occurs " << w.second << ((w.second > 1) ? "times" : "time") << endl;
	}
	/*for_each(word_count.cbegin(), word_count.cend(), [](const auto &w)
	{cout << w.first << " occurs " << w.second << ((w.second > 1) ? "times" : "time") << endl; }); 等价语句 */
	system("pause");
	return 0;
}

输出结果为:

4
^Z
输出 word_count 中的所有元素:
0 occurs 100times
1 occurs 200times
4 occurs 401times

向 multiset 和  multimap 添加元素 ( 386P)


  •  multiset 和  multimap 中 一个关键字 可以对应到许多值。
  • 如果我们希望 关联容器中的 关键字可以对应到多个元素值时,应该使用 multimap, 而不是 map。
  • 由于 一个 multi 容器中的元素值不必唯一,所以 向 multimap 、multiset 、unordered_multimap、unordered_multiset 这些容器上 调用 insert 总会插入一个元素。

下面是在 multimap 中 添加元素:

int main()
{
	std::multimap authors;
	
	authors.insert({ 0, 100 });
	authors.insert({0,100 });
	for (const auto &w : authors)
	{
		cout << w.first << " occurs " << w.second << ((w.second > 1) ? "times" : "time") << endl;
	}
	authors.insert({ 1, 100 });
	cout << "添加一个不同的元素,再次输出:" << endl;
	for_each(authors.cbegin(), authors.cend(), [](const auto &w)
	{cout << w.first << " occurs " << w.second << ((w.second > 1) ? "times" : "time") << endl; }); 
	system("pause");
	return 0;
}

输出结果为:

0 occurs 100times
0 occurs 100times
添加一个不同的元素,再次输出:
0 occurs 100times
0 occurs 100times
1 occurs 100times
int main()
{
	multimap authors = { { "Barth, John", "Sot-Weed Factor" } ,
										  {"huang","C++"},
										  {"cheng","C#"} };
	
	authors.insert({ "Barth, John", "Sot-Weed Factor" });
	authors.insert({ "Barth, John", "Lost in the Funhouse" });

	for_each(authors.cbegin(), authors.cend(), [](const auto &w)
	{cout << w.first << " occurs " << w.second  << endl; });
	system("pause");
	return 0;
}

输出结果为:

Barth, John occurs Sot-Weed Factor
Barth, John occurs Sot-Weed Factor
Barth, John occurs Lost in the Funhouse
cheng occurs C#
huang occurs C++
  • 从输出结果可以看出 “ Barth, John ” 关键字对应着三个元素,而且因为用的是 multimap 系统还为它们提供了排序。

下面是在 multiset 中 添加元素:

int main()
{
	std::multiset  authors;

	authors.insert({ 0, 100 ,5,6,7,0,100,2});
	for (const auto &w : authors)
	{
		cout << w << " ";
	}
	cout << endl;
	authors.insert({ 5, 6 });
	authors.insert({ 2, 200 });
	cout << "再次添加二个元素,再次输出:" << endl;
	for_each(authors.cbegin(), authors.cend(), [](const auto &w) { cout << w << " "; });
	system("pause");
	return 0;
}

输出结果为:

0 0 2 5 6 7 100 100
再次添加二个元素,再次输出:
0 0 2 2 5 5 6 6 7 100 100 200 
  • 从输出结果可以看出,  添加的元素都被插入到 multiset 中, 而且系统按按从小到大的顺序排序。

使用 erase 成员 删除元素 ( 386P)


  •  我们可以 传递给 erase  一个迭代器来删除一个 元素, 或者 传递一对迭代器来删除一个元素范围。 该 erase 版本的函数返回void。
  • 关联容器还提供了了一个额外的 erase操作版本, 它接受一个 key_type 参数。此版本删除所有匹配给定关键字的元素( 如果存在的话 ), 返回实际删除的元素的数量

删除 map 中一个关键字为4的, 然后返回该关键字对应的被删除元素的数量:

int main()
{
	std::map word_count = { {0, 100},{ 1,200}, {4,400} };
	int removal_word = 4; // 删除关键字,返回删除的元素数量
	if (word_count.erase(removal_word))
	{
		cout << "ok: " << removal_word << " removed\n";
	}
	else 
		cout << "oops: " << removal_word << " not found!\n";
	system("pause");
	return 0;
}
  • 注意: 对于保存不重复关键字的容器 ( set 、map), erase 的返回值总是0或1。若返回值为0, 则表明想要删除的元素并不在容器中。

下面是在 multimap 中 删除给定关键字的程序:

int main()
{
	multimap authors = { { "Barth, John", "Sot-Weed Factor" } ,
										  {"huang","C++"},
										  {"cheng","C#"} };

	authors.insert({ "Barth, John", "Sot-Weed Factor" });
	authors.insert({ "Barth, John", "Lost in the Funhouse" });

	for_each(authors.cbegin(), authors.cend(), [](const auto &w)
	{cout << w.first << " occurs " << w.second << endl; });

	cout << endl;
	if (auto  tt = authors.erase("Barth, John"))
	{
		cout << "删除 关键字 Barth, John 的元素数量有:" << tt << endl;
	}
	else
		cout << "该容器中没有该关键字!" << endl;

	for_each(authors.cbegin(), authors.cend(), [](const auto &w)
	{cout << w.first << " occurs " << w.second << endl; });
	cout << endl;
	system("pause");
	return 0;
}

输出结果为:

Barth, John occurs Sot-Weed Factor
Barth, John occurs Sot-Weed Factor
Barth, John occurs Lost in the Funhouse
cheng occurs C#
huang occurs C++

删除 关键字 Barth, John 的元素数量有:3
cheng occurs C#
huang occurs C++

unordered_map 和 map 提供下标操作 、at函数 ( 387P )


  •   只有 map 和 unordered_map 容器 提供了 下标运算符和一个对应的 at 函数
  • 那么set 容器是不支持下标操作的, 因为set 中没有与关键字相关联的值, 因为元素本身就是关键字,因此 “ 获取与关键字关联的值 ” 的操作毫无意义。
  • 我们还不能对一个  multimap  或 一个 unordered_multimap  进行下标操作, 因为这些容器中可能有多个值与一个关键字相关联。
  • 只有 map 和 unordered_map 容器 提供下标运算符,并接受一个索引(即,一个关键字 ),获取与此关键字相关联的值。 但是, 与其它下标运算符不同的是,如果该关键字( 索引)并不在 map 中,  会把该关键字创建为一个新元素并插入到 map 中, 该关键字关联的值将进行值初始化 ( 值初始化什么类型的值取决于容器的值类型)。
  • 只有当 map 、unordered_map  是一个非const, 我们才能使用下标运算符来操作它们。因为下标运算符可能会插入一个新元素,所以我们不能对一个 const map  使用。

使用 下标运算符 获取 map 中的关键字:

int main()
{
	std::map  word_count; // empty map
  // 插入一个关键字为Anna的元素,关联值进行值初始化;然后将1赋子它
	word_count["Anna"] = 1;
	for_each(word_count.cbegin(), word_count.cend(), [](const auto &w) {cout << w.first << "对于元素值" << w.second; });
	cout << endl;
	system("pause");
	return 0;
}

输出结果为:

Anna对于元素值1

使用 at 函数获取 map 中的关键字:

int main()
{
	std::map  word_count = { {"huang",0} }; // empty map
	word_count.at("huang");
	word_count.at("da"); //错误, map 中没有 da 该关键字,会抛出一个 aut_of_range 的异常
	for_each(word_count.cbegin(), word_count.cend(), [](const auto &w) {cout << w.first << "对于元素值" << w.second; });
	cout << endl;
	system("pause");
	return 0;
}

使用 unordered_map 和 map 下标操作的返回值(388P)


map 的下标运算符 与我们使用的其他下标操作符的另一个不同之处是它的返回类型通常情况下,迭代器所返回的类型与下标运算符返回的类型是一样的。但对map 则不然:

  • 当我们对一个 map 类型进行下标操作时,会获得一个 mapped_type ( 每个关键字关联的值类型,只适用于map )对象;  但当解引用 map迭代器时,会得到一个value_type对象.
  • 下标和 at 操作只适用于非const 的 map 和 unordered_map.
  • 与其他下标运算符相同的是,  map的下标运算符也返回一个左值. 由于返回的是一个左值,  所以我们既可以读也可以写元素
  • 与 vector 与 string 不同的是, map 的下标运算符返回的类型与解引用 map 迭代器返回的类型是不同的。
int main()
{
	std::map  word_count; // empty map
  // 插入一个关键字为Anna的元素,关联值进行值初始化;然后将1赋子它
	word_count["Anna"] = 1; // 这样会打印出1
	++word_count["Anna"]; // 提取元素,将其增1,会打印2
	for_each(word_count.cbegin(), word_count.cend(), [](const auto &w) {cout << w.first << "对于元素值" << w.second; });
	cout << endl;
	system("pause");
	return 0;
}

输出结果为:

Anna对于元素值2
  • 有时我们只想知道一个元素是否存在 于 map 中,如果不存在,也不想添加该元素。在这种情况下,我们不能使用下标运算符。 应该使用 find  操作 来代替 下标操作。  详情看本书的 389P。

练习题11.24:

  • 首先在m 中查找关键字 0,未找到。
  • 然后将一个新的关键字-值对插入到m 中,关键字是是一个 const int,保存0. 值进行值初始化,在本题中初始化为 0
  • 然后获取新插入的元素,并将值 1 赋予关键字 0. 所以关键字 0 最后关联的值是 1 

练习题11.25:

  • 在本书的 94Page 中作者提到过 vector 、string 对象的下标运算符只可以用于访问已存在的元素, 而不能用于添加元素。
  • 那么本题的代码是错误的, v 是一个空 vector , 根本不包含任何元素。 当然也就不可以通过下标去访问任何元素。正确的办法应该是使用 push_back 类似这样操作。
  • 对于m, " 0 " 表示的是 “关键字0", 而对于v,  " 0 " 表示的是“ 位置0"

练习题11.26:

  • 对 map 进行下标操作, 应该使用其 key_type, 即关键字的类型,来对一个map 进行 下标操作。
  • 而下标操作返回的类型是mapped_type, 即关键字关联的值的类型。

示例如下  :

  • map 用来进行下标操作的类型是 string ,下标操作返回的类型: int

使用 find、count、lower_bound、upper_bound、equal_range 访问元素 (388P)


  • 如果我们想在关联容器中查找某个元素是否在容器中, 使用 find 是最好不过的了。 find 函数的功能是查找一个元素是否在容器中,如果找到,返回一个迭代器,指向第一个关键字的元素,如果没找到,返回 尾后迭代器。
  • 对于不允许重复关键字的容器( 如 set、map),是使用 find 还是count 可能没什么区别
  • 但是允许重复关键字的容器( 如 multiset、multimap),count 的这个函数还需要做额外的工作:需要计数如果该元素在容器中 ,它还会统计有多少个元素具有相同的关键字。  如果不需要计数 一个关键字关联多少个元素的话,最好使用 find 。
  • count 函数的功能是: 返回该关键字在容器中的数量,如果该关键字存在于容器中,具体的数量取决于该关键字关联的值的数量,如果不存在,返回 0。  不允许重复关键字的容器( 如 set、map), 如果容器中有该关键字,返回1. 否则 0.

set 容器 使用 find 和  count 算法的代码示例:

int main()
{
	std::set iset = { 800,300,0,22,23 };
	if (iset.find(1) != iset.cend())
	{
		cout << "找到该元素了!" << endl;
	}
	else
		cout << "没有该元素!" << endl;
	if (iset.find(800) != iset.cend())
	{
		cout << "找到该元素了!" << endl;
	}
	else
		cout << "没有该元素!" << endl;

	if (iset.count(1))
	{
		cout << "该关键字的数量为:" << iset.count(1) << endl;
	}
	else
		cout << "改关键字的数量为0" << endl;

	if (iset.count(22))
	{
		cout << "该关键字的数量为:" << iset.count(22) << endl;
	}
	else
		cout << "改关键字的数量为0" << endl;

	system("pause");
	return 0;
}


输出结果为:

没有该元素!
找到该元素了!
改关键字的数量为0
该关键字的数量为:1

multiset 容器 使用 find 和  count 算法的代码示例:

int main()
{
	std::multiset iset = { 800,300,0,22,23,600,300,22,0,800,23 };
	if (iset.find(1) != iset.cend())
	{
		cout << "找到该元素了!" << endl;
	}
	else
		cout << "没有该元素!" << endl;
	if (iset.find(800) != iset.cend())
	{
		cout << "找到该元素了!" << endl;
	}
	else
		cout << "没有该元素!" << endl;

	if (iset.count(800))
	{
		cout << "该关键字的数量为:" << iset.count(800) << endl;
	}
	else
		cout << "改关键字的数量为0" << endl;

	if (iset.count(22))
	{
		cout << "该关键字的数量为:" << iset.count(22) << endl;
	}
	else
		cout << "改关键字的数量为0" << endl;

	system("pause");
	return 0;
}


输出结果为:

没有该元素!
找到该元素了!
该关键字的数量为:2
该关键字的数量为:2

使用 map 和 unordered_map 的find 操作 代替 下标操作(389P)


通过 388P 中的学习的内容我们知道 对 map 和 unordered_map 使用下标操作可能会有副作用:

  • 即,如果该关键字 未在 map 中, 下标操作会插入一个具有给定关键字的元素到map 中,并且该关键字关联的值进行值初始化。
  • 有时我们只想知道一个元素是否存在 于 map 中,如果不存在,也不想添加该元素。在这种情况下,就不能使用下标运算符来检查一个元素是否存在于 map 中, 因为如果关键字不存在的话,下标运算符会插入一个新元素。在这种情况下, 应该使用find:

int main()
{
	multimap word_count = { { "Barth, John", "Sot-Weed Factor" } , {"huang","C++"},{"cheng","C#"} };

	if (word_count.find("foobar") == word_count.end())  // 如果该关键字不存在于 word_count 中,就进入循环,输出。
		cout << "foobar is not in the map" << endl;
	system("pause");
	return 0;
}

在 multimap 或 multiset 中查找元素

  • 在一个允许重复关键字的关联容器中, 可能有很多元素具有给定的关键字。 
  • 如果一个 multimap 或 multiset 中 有多个元素具有给定关键字, 则这些元素在容器中会相邻连续存储。
int main()
{
	std::multimap authors = { {"Alain de Botton","C++ Primer"},{"huangchengtao","从入门到放弃"},{"Alain de Botton","21天学会C++"} };
	string search_item("Alain de Botton");

	auto entries = authors.count(search_item); //该关键字对应的元素数量
	cout << "元素数量为:" << entries << endl;

	auto iter = authors.find(search_item); // 此作者的第一本书
	cout << "此作者的第一本书为:" << iter->second << endl;

	// 用一个循环查找该作者的所有著作
	cout << "打印该作者的所有书籍名称:";
	while (entries)
	{
		cout << iter->second << " ";// print each title
		++iter; // advance to the next title
		--entries; // keep track of how many we've printed
	}
	cout << endl;
	system("pause");
	return 0;
}

输出结果为:

元素数量为:2
此作者的第一本书为:C++ Primer
打印该作者的所有书籍名称:C++ Primer 21天学会C++
  • 当我们在 multimap或multiset 遍历时,可以确保按顺序返回具有给定关键字的的元素。 为什么会按顺序返回呢?
  • 因为如果一个 multimap 或 multiset 中 有多个元素具有给定关键字, 则这些元素在容器中会相邻连续存储。 上述的程序的输出结果可以说明这一点。

使用 lower_bound 和 upper_bound 操作( 389P、390P )


  • 这两个操作都接受一个关键字, 返回一个迭代器。
  • 如果该关键字在容器中, lower_bound  返回的迭代器将指向第一个匹配给定关键字的元素, 而 upper_bound  返回的迭代器则指向最后一个匹配给定关键字的元素之后的位置。
  • 如果该关键字不在 容器 中, 则 lower_bound 和 upper_bound  会返回相等的迭代器, 该迭代器会指向一个不影响排序的关键字插入位置。
  • 因此, 用相同的关键字调用 lower_bound 和 upper_bound  会得到一个迭代器范围, 该范围表示该关键字的所有元素。
  • 注意: lower_bound 和 upper_bound  不适用于无序容器。

这两个操作返回的迭代器可能是容器的尾后迭代器。那在什么情况是会发生呢?

  • 如果我们查找的元素与该容器中最大的关键字关联, 则此关键字的 upper_bound 返回尾后迭代器。
  • 如果关键字不存在, 并且还大于容器中任何关键字,则lower_bound返回的也是尾后迭代器。
int main()
{
	std::multimap authors = { {"Alain de Botton","C++ Primer"},{"huang","算法导论"} ,{"Alain de Botton","21天学会C++"} };
	string search_item("Alain de Botton");

	auto entries = authors.count(search_item); //元素数量
	cout << "Alain de Botton 作者的著作的元素数量为:" << entries << endl;

	auto iter = authors.find(search_item); // 此作者的第一本书
	cout << "此作者的第一本书为:" << iter->second << endl;

	cout << "打印出该作者的所有书籍:";
	for (auto beg = authors.lower_bound(search_item), end = authors.upper_bound(search_item); beg != end; ++beg)
	{
		cout << beg->second << " "; //打印每个标题
	}
	cout << endl;
	system("pause");
	return 0;
}

输出结果为:

Alain de Botton 作者的著作的元素数量为:2
此作者的第一本书为:C++ Primer
打印出该作者的所有书籍:C++ Primer 21天学会C++

注意:如果 lower_bound 和 upper_bound 返回相同的迭代器, 则给定关键字不在容器中。


equal_range 函数( 391P)


  • 此函数接受一个关键字, 返回 一个 迭代器 pair
  • 若关键字存在, 则第一个迭代器指向第一个与关键字匹配的元素, 第二个迭代器指向最后一个匹配元素之后的位置。
  • 若未找到匹配元素,则两个迭代器都指向关键字可以插入的位置。
int main()
{
	std::multimap authors = { {"Alain de Botton","C++ Primer"},{"Alain de Botton","21天学会C++"},
	{"huang","算法导论"} };
	string search_item("Alain de Botton");

	auto entries = authors.count(search_item); //元素数量
	cout << "Alain de Botton 作者的著作的元素数量为:" << entries << endl;

	auto iter = authors.find(search_item); // 此作者的第一本书
	cout << "此作者的第一本书为:" << iter->second << endl;

	cout << "打印出该作者的所有书籍:";
	for (auto pos = authors.equal_range(search_item); pos.first != pos.second; ++pos.first)
	{
		cout << pos.first->second << endl; // print each title
	}
	cout << endl;
	system("pause");
	return 0;
}

输出结果为:

Alain de Botton 作者的著作的元素数量为:2
此作者的第一本书为:C++ Primer
打印出该作者的所有书籍:C++ Primer
21天学会C++

 


无序容器 (394P)


  • 这些容器不是使用比较运算符来组织元素的, 而是使用 哈希函数 和 关键字 类型 的 “  == ” 运算符来比较元素的。

那我们什么时候使用无序容器呢?

  • 当容器中 关键字对应的元素之间没有明显的顺序关系时,无序容器是最有用的。
  • 对于维护元素顺序的成本非常高的应用程序,这些容器也非常有用。

曾用于 set 和 map 的操作(如 find、insert ) 都能用于在 unordered_map 和 unordered_set 上。

我们通常可以使用无序容器来代替相应的有序容器,反之亦然。但是,因为无序容器的元素不是按顺序存储的,所以使用无序容器的程序的输出(通常)与使用有序容器的相同程序的输出不同。

int main()
{
	std::unordered_map word_count;
	string word;
	while (cin >> word)
	{
		++word_count[word]; // fetch and increment the counter for word
	}
	for (const auto &w : word_count) // for each element in the map
	{
		// print the results
		cout << w.first << " occurs " << w.second << ((w.second > 1) ? " times" : " time") << endl;
	}
	system("pause");
	return 0;
}

输出结果为:

containers
use
can
examples
^Z
containers occurs 1 time
use occurs 1 time
can occurs 1 time
examples occurs 1 time

无序容器支持的操作 395P


 

你可能感兴趣的:(Primer,2,C++,Primer,中文版(第五版),C++,关联容器)