目录
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 )
关联容器和顺序容器的主要区别是:
- 关联容器中的元素是按关键字来保存和访问的。
- 顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。
关联容器的特点有:
- 关联容器有一个特点是通过关键字来高效地访问和查找元素。
- 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 是以它们在容器中的位置来顺序保存和访问的。
关联容器,不管是有序的还是无须的,都支持下图中普通的容器操作。
关联容器不支持的操作有:
- 关联容器不支持顺序容器位置相关的操作,例如 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 中, 其关键字必须是唯一的,不可重复。
- 对于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.
map, multimap, set, and multiset 这些容器的关键字类型必须定义了 比较元素的方法来比较元素。default,标准库使用关键字类型的 “ < ” 运算符来比较两个关键字。
我们也可以提供自定义的操作来代替关键字类型上 < 运算符。但是提供的该操作必须在关键字类型上定义一个严格弱序。可以将严格弱序看作是 “小于等于 ”。
传递给排序泛型算法的可调用对象,必须跟该关联容器中关键字的类型一样。
- 该标准库类型定义在头文件 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.
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
下图这些类型别名表示容器关键字 和 值 的 类型 。
- 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 有关的容器类型没有。
- 当我们解引用一个关联容器的迭代器时,会得到该迭代器引用的值,该值是一个 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 容器中定义了 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
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 时, 迭代器按关键字升序遍历元素。
- 注意: 我们通常不对关联容器使用泛型算法。关联容器的关键字是 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
- 由于 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( 或 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 中 一个关键字 可以对应到许多值。
- 如果我们希望 关联容器中的 关键字可以对应到多个元素值时,应该使用 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 一个迭代器来删除一个 元素, 或者 传递一对迭代器来删除一个元素范围。 该 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++
- 只有 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;
}
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 是最好不过的了。 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
通过 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 中 有多个元素具有给定关键字, 则这些元素在容器中会相邻连续存储。
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 返回的迭代器则指向最后一个匹配给定关键字的元素之后的位置。
- 如果该关键字不在 容器 中, 则 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 返回相同的迭代器, 则给定关键字不在容器中。
- 此函数接受一个关键字, 返回 一个 迭代器 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++
- 这些容器不是使用比较运算符来组织元素的, 而是使用 哈希函数 和 关键字 类型 的 “ == ” 运算符来比较元素的。
那我们什么时候使用无序容器呢?
- 当容器中 关键字对应的元素之间没有明显的顺序关系时,无序容器是最有用的。
- 对于维护元素顺序的成本非常高的应用程序,这些容器也非常有用。
曾用于 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