map 是键-值对的集合。map 类型通常可理解为关联数组(associative array):

可使用键作为下标来获取一个值,正如内置数组类型一样。
而关联的本质在于元素的值与某个特定的键相关联,而并非通过元素在数组中的位置来获取。

[1. map 对象的定义]

要使用 map 对象,则必须包含 map 头文件。在定义 map 对象时,必须分别指明键和值的类型:

// count number of times each word occurs in the input
map<string, int> word_count; // empty map from string to int
// 表 10.3. map 的构造函数
map<k, v> m;      创建一个名为 m 的空 map 对象,其键和值的类型分别为 k 和 v

map<k, v> m(m2);   创建 m2 的副本 m,m 与 m2 必须有相同的键类型和值类型

map<k, v> m(b, e);  创建 map 类型的对象 m,存储迭代器 b 和 e 标记的范围内所有元素的副本。
             元素的类型必须能转换为 pair<const k, v>

键类型的约束

在使用关联容器时,它的键不但有一个类型,而且还有一个相关的比较函数。
默认情况下,标准库使用键类型定义的 < 操作符来实现键(key type)的比较。
后续篇章将会介绍如何重写默认的操作符,并提供自定义的操作符函数。

所用的比较函数必须在键类型上定义严格弱排序strict weak ordering)。
所谓的严格弱排序可理解为键类型数据上的“小于”关系,虽然实际上可以选择将比较函数设计得更复杂。
但无论这样的比较函数如何定义,当用于一个键与自身的比较时,肯定会导致 false 结果。

此外,在比较两个键时,不能出现相互“小于”的情况,
而且,如果 k1“小于”k2,k2“小于”k3,则 k1 必然“小于”k3。
对于两个键,如果它们相互之间都不存在“小于”关系,则容器将之视为相同的键。
用做 map 对象的键时,可使用任意一个键值来访问相应的元素。

在实际应用中,键类型必须定义 < 操作符,而且该操作符应能“正确地工作”,这一点很重要

对于键类型,唯一的约束就是必须支持 < 操作符,至于是否支持其他的关系或相等运算,则不作要求。

[2. map 定义的类型]

map 对象的元素是“键值对”,也即每个元素包含两个部分:键以及由键关联的值。

map 的 value_type 是存储元素的键以及值的 pair 类型,而且键为 const。
例如,word_count 数组的 value_type 为 pair<const string, int> 类型。

//表 10.4. map 类定义的类型
map<K, V>::key_type
在 map 容器中,用做索引的键的类型

map<K, V>::mapped_type
在 map 容器中,键所关联的值的类型

map<K, V>::value_type
一个 pair 类型,它的 first 元素具有 const map<K, V>::key_type 类型,
而 second 元素则为 map<K, V>::mapped_type 类型

需谨记: value_type 是 pair 类型,它的值成员可以修改,但键成员不能修改。

    map 迭代器进行解引用将产生 pair 类型的对象。

对迭代器进行解引用时,将获得一个引用,指向容器中一个 value_type 类型的值。
对于 map 容器,其 value_type 是 pair 类型:

// get an iterator to an element in word_count
map<string, int>::iterator map_it = word_count.begin();

// *map_it is a reference to a pair<const string, int> object
cout << map_it->first; // prints the key for this element
cout << " " << map_it->second; // prints the value of the element
map_it->first = "new key"; // error: key is const
++map_it->second; // ok: we can change value through an iterator

对迭代器进行解引用将获得一个 pair 对象,它的 first 成员存放键,为 const,而 second 成员则存放值。

map 容器额外定义的类型别名(typedef)
map 类额外定义了两种类型:key_type 和 mapped_type,以获得键或值的类型。
如同顺序容器一样,可使用作用域操作符来获取类型成员,如 map<string, int>::key_type。

[3. 给 map 添加元素]

定义了 map 容器后,下一步工作就是在容器中添加键-值元素对。
该项工作可使用 insert 成员实现;或者,先用下标操作符获取元素,然后给获取的元素赋值。
在这两种情况下,一个给定的键只能对应于一个元素这一事实影响了这些操作的行为。

[4. 使用下标访问 map 对象]

如下编写程序时:

map <string, int> word_count; // empty map
// insert default initialzed element with key Anna; then assign 1 to its value
word_count["Anna"] = 1;

将发生以下事情:

1. 在 word_count 中查找键为 Anna 的元素,没有找到。
2. 将一个新的键-值对插入到 word_count 中。
  它的键是 const string 类型的对象,保存 Anna。
  而它的值则采用值初始化,本例中值为 0。
3. 将这个新的键-值对插入到 word_count 中。
4. 读取新插入的元素,并将它的值赋为 1。

使用下标访问 map 与使用下标访问数组或 vector 的行为截然不同:
用下标访问不存在的元素将导致在 map 容器中添加一个新元素,它的键即为该下标值。

如同其他下标操作符一样,map 的下标也使用索引(其实就是键)来获取该键所关联的值。
如果该键已在容器中,则 map 的下标运算与 vector 的下标运算行为相同:返回该键所关联的值。
只有在所查找的键不存在时,map 容器才为该键创建一个新的元素,并将它插入到此 map 对象中。
此时,所关联的值采用值初始化:类类型的元素用默认构造函数初始化,而内置类型的元素初始化为 0。

下标操作符返回值的使用
通常来说,下标操作符返回左值。它返回的左值是特定键所关联的值。可如下读或写元素:

cout << word_count["Anna"]; // fetch element indexed by Anna; prints 1
++word_count["Anna"]; // fetch the element and add one to it
cout << word_count["Anna"]; // fetch the element and print it; prints 2

map 下标操作符返回的类型与对 map 迭代器进行解引用获得的类型不相同。
显然,map 迭代器返回 value_type 类型的值,
包含 const key_type 和 mapped_type 类型成员的 pair 对象;
下标操作符则返回一个 mapped_type 类型的值。

下标行为的编程意义
对于 map 容器,如果下标所表示的键在容器中不存在,则添加新元素,这一特性可简化程序:

// count number of times each word occurs in the input
map<string, int> word_count; // empty map from string to int
string word;
while (cin >> word)
  ++word_count[word];

上述代码中最有趣的是,在输入的单词是第一次出现时,
会在 word_count 中创建并插入一个以该单词为索引的新元素,同时将它的值初始化为 0。
然后其值立即加 1,所以每次在 map 中添加新元素时,所统计的出现次数正好从 1 开始。

[5. map::insert 的使用]

map 容器的 insert 成员与顺序容器的类似,但有一点要注意:必须考虑键的作用。
键影响了实参的类型:插入单个元素的 insert 版本使用 “键值对” 类型的参数。
类似地,对于参数为一对迭代器的版本,迭代器必须指向 “键值对” 类型的元素。
另一个差别则是:map 容器的接受单个值的 insert 版本的返回类型。

// 表 10.5. map 容器提供的 insert 操作
m.insert(e)
e 是一个用在 m 上的 value_type 类型的值。
如果键(e.first)不在 m 中,则插入一个值为 e.second 的新元素;
如果该键在 m 中已存在,则保持 m 不变。
该函数返回一个 pair 类型对象,
包含指向键为 e.first 的元素的 map 迭代器,以及一个 bool 类型的对象,表示是否插入了该元素

m.insert(beg, end)
beg 和 end 是标记元素范围的迭代器,其中的元素必须为 m.value_type 类型的键-值对。
对于该范围内的所有元素,如果它的键在 m 中不存在,则将该键及其关联的值插入到 m。
返回 void 类型

m.insert(iter, e)
e 是一个用在 m 上的 value_type 类型的值。
如果键(e.first)不在 m 中,则创建新元素,并以迭代器 iter 为起点搜索新元素存储的位置。
返回一个迭代器,指向 m 中具有给定键的元素

5.1 以 insert 代替下标运算
使用下标给 map 容器添加新元素时,元素的值部分将采用值初始化。
通常,我们会立即为其赋值,其实就是对同一个对象进行初始化并赋值。

然而,添加元素还有另一个方法 —— 直接使用 insert 成员,其语法更紧凑。

// if Anna not already in word_count, inserts new element with value 1
word_count.insert(map<string, int>::value_type("Anna", 1));

这个 insert 函数版本的实参是一个新创建的 pair 对象,将直接插入到 map 容器中。

谨记 value_type 是 pair<const K, V> 类型的同义词,K 为键类型,而 V 是键所关联的值的类型。

insert 的实参创建了一个适当的 pair 类型新对象,该对象将插入到 map 容器。
在添加新 map 元素时,使用 insert 成员可避免使用下标操作符所带来的副作用:不必要的初始化。
传递给 insert 的实参相当笨拙。可用 2 种方法简化:

// 1. 使用 make_pair 
word_count.insert(make_pair("Anna", 1));

// 2. 使用 typedef
typedef map<string,int>::value_type valType;
word_count.insert(valType("Anna", 1));

5.2 检测 insert 的返回值
map 对象中一个给定键只对应一个元素。
如果试图插入的元素所对应的键已在容器中,则 insert 将不做任何操作。
含有一个或一对迭代器形参的 insert 函数版本并不说明是否有或有多少个元素插入到容器中。
但是,带有一个 “键值对” 形参的 insert 版本将返回一个值:
包含一个迭代器和一个 bool 值的 pair 对象,
(其中迭代器指向 map 中具有相应键的元素,而 bool 值则表示是否插入了该元素。)
如果该键已在容器中,则其关联的值保持不变,返回的 bool 值为 true。
在这两种情况下,迭代器都将指向具有给定键的元素。
下面是使用 insert 重写的单词统计程序

 1 // count number of times each word occurs in the input
 2 map<string, int> word_count; // empty map from string to int
 3 string word;
 4 while (cin >> word) {
 5   // inserts element with key equal to word and value 1;
 6   // if word already in word_count, insert does nothing
 7   pair<map<string, int>::iterator, bool> ret =
 8   word_count.insert(make_pair(word, 1));
 9   if (!ret.second) // word already in word_count
10     ++ret.first->second; // increment counter
11 }
View Code

相关文章:

  • 2022-02-05
  • 2022-12-23
  • 2021-10-18
  • 2022-12-23
  • 2022-12-23
  • 2021-09-11
  • 2021-09-03
  • 2021-05-28
猜你喜欢
  • 2021-09-13
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-08-07
  • 2021-10-25
相关资源
相似解决方案