Team LiB
Previous Section Next Section

10.3. The map Type

10.3. map 类型

A map is a collection of keyvalue pairs. The map type is often referred to as an associative array: It is like the built-in array type, in that the key can be used as an index to fetch a value. It is associative in that values are associated with a particular key rather than being fetched by position in the array.

map 是键-值对的集合。map 类型通常可理解为关联数组(associative array):可使用键作为下标来获取一个值,正如内置数组类型一样。而关联的本质在于元素的值与某个特定的键相关联,而并非通过元素在数组中的位置来获取。

10.3.1. Defining a map

10.3.1. map 对象的定义

To use a map, we must include the map header. When we define a map object, we must indicate both the key and value type:

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

     // count number of times each word occurs in the input
     map<string, int> word_count; // empty map from string to int

defines a map object word_count that is indexed by a string and that holds an associated int value.

这个语句定义了一个名为 word_countmap 对象,由 string 类型的键索引,关联的值则 int 型。

Table 10.3. Constructors for map
表 10.3. map 的构造函数

map<k, v> m;

Create an empty map named m with key and value types k and v.

创建一个名为 m 的空 map 对象,其键和值的类型分别为 kv

map<k, v> m(m2);

Create m as a copy of m2; m and m2 must have the same key and value types.

创建 m2 的副本 mmm2 必须有相同的键类型和值类型

map<k, v> m(b, e);

Create m as a copy of the elements from the range denoted by iterators b and e. Elements must have a type that can be converted to pair<const k, v>.

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


Constraints on the Key Type
键类型的约束

Whenever we use an associative container, its keys have not only a type, but also an associated comparison function. By default, the library uses the < operator for the key type to compare the keys. Section 15.8.3 (p. 605) will show how we can override the default and provide our own function.

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

Whichever comparison function we use must define a strict weak ordering over the key type. We can think of a strict weak ordering as "less than," although we might choose to define a more complicated comparison function. However we define it, such a comparison function must always yield false when we compare a key with itself. Moreover, if we compare two keys, they cannot both be "less than" each other, and if k1 is "less than" k2, which in turn is "less than" k3, then k1 must be "less than" k3. If we have two keys, neither of which is "less than" the other, the container will treat them as equal. When used as a key to a map, either value could be used to access the corresponding element.

所用的比较函数必须在键类型上定义严格弱排序(strict weak ordering)。所谓的严格弱排序可理解为键类型数据上的“小于”关系,虽然实际上可以选择将比较函数设计得更复杂。但无论这样的比较函数如何定义,当用于一个键与自身的比较时,肯定会导致 false 结果。此外,在比较两个键时,不能出现相互“小于”的情况,而且,如果 k1“小于”k2k2“小于”k3,则 k1 必然“小于”k3。对于两个键,如果它们相互之间都不存在“小于”关系,则容器将之视为相同的键。用做 map 对象的键时,可使用任意一个键值来访问相应的元素。

In practice, what's important is that the key type must define the < operator and that that operator should "do the right thing."

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



例如,在书店问题中,可增加一个名为 ISBN 的类型,封装与国际标准图书编号(ISBN)相关的规则。在我们的实现中,国际标准图书编号是 string 类型,可做比较运算以确定编号之间的大小关系。因此,ISBN 类型可以支持 < 运算。假设我们已经定义了这样的类型,则可定义一个 map 容器对象,以便高效地查找书店中存放的某本书。

     map<ISBN, Sales_item> bookstore;

defines a map object named bookstore that is indexed by an ISBN. Each element in the map holds an associated instance of our Sales_item class.

该语句定义了一个名为 bookstoremap 对象,以 ISBN 类型的对象为索引,其所有元素都存储了一个关联的 Sales_item 类类型实例。

The key type needs to support only the < operator. There is no requirement that it support the other relational or equality operators.

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



Exercises Section 10.3.1

Exercise 10.5:

Define a map that associates words with a list of line numbers on which the word might occur.

定义一个 map 对象,将单词与一个 list 对象关联起来,该 list 对象存储对应的单词可能的行号。

Exercise 10.6:

Could we define a map from vector<int>::iterator to int? What about from list<int>::iterator to int? What about from pair<int, string> to int? In each case, if not, explain why not.

可否定义一个 map 对象以 vector<int>::iterator 为键关联 int 型对象?如果以 list<int>::iterator 关联 int?对于每种情况,如果不允许,请解释其原因。


10.3.2. Types Defined by map

10.3.2. map 定义的类型

The elements of a map are keyvalue pairs That is, each element has two parts: its key and the value associated with that key. The value_type for a map reflects this fact. This type is more complicated than those we've seen for other containers: value_type is a pair that holds the key and value of a given element. Moreover, the key is const. For example, the value_type of the word_count array is pair<const string, int>.

map 对象的元素是键-值对,也即每个元素包含两个部分:键以及由键关联的值。mapvalue_type 就反映了这个事实。该类型比前面介绍的容器所使用的元素类型要复杂得多:value_type 是存储元素的键以及值的 pair 类型,而且键为 const。例如,word_count 数组的 value_typepair<const string, int> 类型。

Table 10.4. Types Defined by the map Class
表 10.4. map 类定义的类型

map<K, V>::key_type

The type of the keys used to index the map.

map 容器中,用做索引的键的类型

map<K, V>::mapped_type

The type of the values associated with the keys in the map.

map 容器中,键所关联的值的类型

map<K, V>::value_type

A pair whose first element has type const map<K, V>::key_type and second has type map<K, V>::mapped_type.

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


When learning the map interface, it is essential to remember that the value_type is a pair and that we can change the value but not the key member of that pair.

在学习 map 的接口时,需谨记 value_typepair 类型,它的值成员可以修改,但键成员不能修改。



Dereferencing a map Iterator Yields a pair
map 迭代器进行解引用将产生 pair 类型的对象

When we dereference an iterator, we get a reference to a value of the container's value_type. In the case of map, the value_type is a pair:

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

     // 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

Dereferencing the iterator yields a pair object in which first member holds the const key and second member holds the value.

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

Additional map Typedefs
map 容器额外定义的类型别名(typedef)

The map class defines two additional types, key_type and mapped_type, that let us access the type of either the key or the value. For word_count, the key_type is string and mapped_type is int. As with the sequential containers (Section 9.3.1, p. 317), we use the scope operator to fetch a type memberfor example, map<string, int>::key_type.

map 类额外定义了两种类型:key_typemapped_type,以获得键或值的类型。对于 word_count,其 key_typestring 类型,而 mapped_type 则是 int 型。如同顺序容器(第 9.3.1 节)一样,可使用作用域操作符(scope operator)来获取类型成员,如 map<string, int>::key_type

Exercises Section 10.3.2

Exercise 10.7:

What are the mapped_type, key_type, and value_type of a map from int to vector<int>?

对于以 int 型对象为索引关联 vector<int> 型对象的 map 容器,它的 mapped_typekey_typevalue_type 分别是什么?

Exercise 10.8:

Write an expression using a map iterator to assign a value to an element.

编写一个表达式,使用 map 的迭代器给其元素赋值。


10.3.3. Adding Elements to a map

10.3.3. 给 map 添加元素

Once the map is defined, the next step is to populate it with the keyvalue element pairs. We can do so either by using the insert member or by fetching an element using the subscript operator and then assigning a value to the element returned. In both cases, the fact that there can be only a single element for a given key affects the behavior of these operations.

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

10.3.4. Subscripting a map

10.3.4. 使用下标访问 map 对象

When we write

如下编写程序时:

     map <string, int> word_count; // empty map

     // insert default initialzed element with key Anna; then assign 1 to its value
     word_count["Anna"] = 1;

the following steps take place:

将发生以下事情:

1.
word_count is searched for the element whose key is Anna. The element is not found.
word_count 中查找键为 Anna 的元素,没有找到。
2.
A new keyvalue pair is inserted into word_count. The key is a const string holding Anna. The value is value initialized, meaning in this case that the value is 0.
将一个新的键-值对插入到 word_count 中。它的键是 const string 类型的对象,保存 Anna。而它的值则采用值初始化,这就意味着在本例中值为 0。
3.
The new keyvalue pair is inserted into word_count.
将这个新的键-值对插入到 word_count 中。
4.
The newly inserted element is fetched and is given the value 1.
读取新插入的元素,并将它的值赋为 1。

Subscripting a map behaves quite differently from subscripting an array or vector: Using an index that is not already present adds an element with that index to the map.

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



As with other subscript operators, the map subscript takes an index (that is, a key) and fetches the value associated with that key. When we look for a key that is already in the map, then the behavior is the same for a map subscript or a vector subscript: The value associated with the key is returned. For maps only, if the key is not already present, a new element is created and inserted into the map for that key. The associated value is value-initialized: An element of class type is initialized using the default constructor for the element type; a built-in type is initialized to 0.

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

Using the Value Returned from a Subscript Operation
下标操作符返回值的使用

As usual, the subscript operator returns an lvalue. The lvalue it returns is the value associated with the key. We can read or write the element:

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

     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

Unlike vector or string, the type returned by map subscript operator differs from the type obtained by dereferencing a map iterator.

有别于 vectorstring 类型,map 下标操作符返回的类型与对 map 迭代器进行解引用获得的类型不相同。



As we've seen, a map iterator returns a value_type, which is a pair that contains a const key_type and mapped_type; the subscript operator returns a value of type mapped_type.

显然,map 迭代器返回 value_type 类型的值——包含 const key_typemapped_type 类型成员的 pair 对象;下标操作符则返回一个 mapped_type 类型的值。

Programming Implications of the Subscript Behavior
下标行为的编程意义

The fact that subscript adds an element if it is not already in the map allows us to write surprisingly succinct programs:

对于 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];

This program creates a map that keeps track of how many times each word occurs. The while loop reads the standard input one word at a time. Each time it reads a new word, it uses that word to index word_count. If word is already in the map, then its value is incremented.

这段程序创建一个 map 对象,用来记录每个单词出现的次数。while 循环每次从标准输入读取一个单词。如果这是一个新的单词,则在 word_count 中添加以该单词为索引的新元素。如果读入的单词已在 map 对象中,则将它所对应的值加 1。

The interesting part is what happens when a word is encountered for the first time: A new element indexed by word, with an initial value of zero, is created and inserted into word_count. The value of that element is immediately incremented so that each time we insert a new word into the map it starts off with an occurrence count of one.

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

Exercises Section 10.3.4

Exercise 10.9:

Write a program to count and print the number of times each word occurs in the input.

编写程序统计并输出所读入的单词出现的次数。

Exercise 10.10:

What does the following program do?

解释下面的程序的功能:

     map<int, int> m;
     m[0] = 1;

Contrast the behavior of the previous program with this one:

比较上一程序和下面程序的行为

     vector<int> v;
     v[0] = 1;

Exercise 10.11:

What type can be used to subscript a map? What type does the sub-script operator return? Give a concrete examplethat is, define a map and then write the types that could be used to subscript the map and the type that would be returned from the subscript operator.

哪些类型可用做 map 容器对象的下标?下标操作符返回的又是什么类型?给出一个具体例子说明,即定义一个 map 对象,指出哪些类型可用作其下标,以及下标操作符返回的类型。


10.3.5. Using map::insert

10.3.5. map::insert 的使用

The insert members operate similarly to the operations on sequential containers (Section 9.3.3, p. 318), with one important caveat: We must account for the effect of the key. The key impacts the argument types: The versions that insert a single element take a value that is a keyvalue pair. Similarly, for the version that takes an iterator pair, the iterators must refer to elements that are keyvalue pairs. The other difference is the return type from the version of insert that takes a single value, which we will cover in the remainder of this section.

map 容器的 insert 成员与顺序容器的类似,但有一点要注意:必须考虑键的作用。键影响了实参的类型:插入单个元素的 insert 版本使用键-值 pair 类型的参数。类似地,对于参数为一对迭代器的版本,迭代器必须指向键-值 pair 类型的元素。另一个差别则是:map 容器的接受单个值的 insert 版本的返回类型。本节的后续部分将详细阐述这一特性。

Table 10.5. insert Operations on maps
表 10.5. map 容器提供的 insert 操作

m.insert(e)

e is a value of the value_type for m. If the key(e.first) is not in m, inserts a new element with value e.second. If the key is in m, then m is unchanged. Returns a pair containing a map iterator referring to the element with key e.first and a bool indicating whether the element was inserted or not.

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

m.insert(beg, end)

beg and end are iterators that denote a range of values that are keyvalue pairs with the same type as m's value_type. For each element in the range, if the given key is not already in m, it inserts the key and its associated value into m. Returns void.

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

m.insert(iter, e)

e is a value of the value_type for m. If the key(e.first) is not in m, inserts the new element using the iterator iter as a hint for where to begin the search for where the new element should be stored. Returns an iterator that refers to the element in m with given key.

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


Using insert Instead of Subscripting
insert 代替下标运算

When we use a subscript to add an element to a map, the value part of the element is value-initialized. Often we immediately assign to that value, which means that we've initialized and assigned the same object. Alternatively, we could insert the element directly by using the syntactically more intimidating insert member:

使用下标给 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));

The argument to this version of insert

这个 insert 函数版本的实参:

     map<string, int>::value_type(anna, 1)

is a newly created pair that is directly inserted into the map. Remember that value_type is a synonym for the type pair<const K, V>, where K is the key type and V is the type of the associated value. The argument to insert constructs a new object of the appropriate pair type to insert into the map. By using insert, we can avoid the extraneous initialization of the value that happens when we insert a new map element as a side-effect of using a subscript.

是一个新创建的 pair 对象,将直接插入到 map 容器中。谨记 value_typepair<const K, V> 类型的同义词,K 为键类型,而 V 是键所关联的值的类型。insert 的实参创建了一个适当的 pair 类型新对象,该对象将插入到 map 容器。在添加新 map 元素时,使用 insert 成员可避免使用下标操作符所带来的副作用:不必要的初始化。

The argument to insert is fairly unwieldy. There are two ways to simplify it. We might use make_pair:

传递给 insert 的实参相当笨拙。可用两种方法简化:使用 make_pair:

     word_count.insert(make_pair("Anna", 1));

Or use a typedef:

或使用 typedef

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

Either approach improves readability by making the call less complicated.

这两种方法都使用调用变得简单,提高了程序的可读性。

Testing the Return from insert
检测 insert 的返回值

There can be only one element with a given key in a map. If we attempt to insert an element with a key that is already in the map, then insert does nothing. The versions of insert that take an iterator or iterator pair do not indicate whether or how many elements were inserted.

map 对象中一个给定键只对应一个元素。如果试图插入的元素所对应的键已在容器中,则 insert 将不做任何操作。含有一个或一对迭代器形参的 insert 函数版本并不说明是否有或有多少个元素插入到容器中。

However, the version of insert that takes a single keyvalue pair does return a value. That value is a pair that contains an iterator that refers to the element in the map with the corresponding key, and a bool that indicates whether the element was inserted. If the key is already in the map, then the value is unchanged, and the bool portion of the return is false. If the key isn't present, then the element is inserted and the bool is TRue. In either case, the iterator refers to the element with the given key. We could rewrite our word count program to use insert:

但是,带有一个键-值 pair 形参的 insert 版本将返回一个值:包含一个迭代器和一个 bool 值的 pair 对象,其中迭代器指向 map 中具有相应键的元素,而 bool 值则表示是否插入了该元素。如果该键已在容器中,则其关联的值保持不变,返回的 bool 值为 true。在这两种情况下,迭代器都将指向具有给定键的元素。下面是使用 insert 重写的单词统计程序:

     // 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) {
         // inserts element with key equal to word and value 1;
         // if word already in word_count, insert does nothing
         pair<map<string, int>::iterator, bool> ret =
                   word_count.insert(make_pair(word, 1));
         if (!ret.second)          // word already in word_count
             ++ret.first->second;  // increment counter
     }

For each word, we attempt to insert it with a value 1. The if test examines the bool in the return from the insert. If it is false, then the insertion didn't happen and an element indexed by word was already in word_count. In this case we increment the value associated with that element.

对于每个单词,都尝试 insert 它,并将它的值赋 1。if 语句检测 insert 函数返回值中的 bool 值。如果该值为 false,则表示没有做插入操作,按 word 索引的元素已在 word_count 中存在。此时,将该元素所关联的值加 1。

Unwinding the Syntax
语法展开

The definition of ret and the increment may be hard to decipher:

ret 的定义和自增运算可能比较难解释:

     pair<map<string, int>::iterator, bool> ret =
             word_count.insert(make_pair(word, 1));

It should be easy to see that we're defining a pair and that the second type of the pair is bool. The first type of that pair is a bit harder to understand. It is the iterator type defined by the map<string, int> type.

首先,应该很容易看出我们定义的是一个 pair 对象,它的 second 成员为 bool 类型。而它的 first 成员则比较难理解,这是 map<string, int> 容器所定义的迭代器类型。

We can understand the increment by first parenthesizing it to reflect the precedence (Section 5.10.1, p. 168) of the operators:

根据操作符的优先级次序(第 5.10.1 节),可如下从添加圆括号开始理解自增操作:

     ++((ret.first)->second); // equivalent expression

Explaining this expression step by step, we have

下面对这个表达式一步步地展开解释:

  • ret holds return value from insert, which is a pair. The first member of that pair is a map iterator referring to the key that was inserted.

    ret 存储 insert 函数返回的 pair 对象。该 pairfirst 成员是一个 map 迭代器,指向插入的键。

  • ret.first fetches the map iterator from the pair returned by insert.

    ret.firstinsert 返回的 pair 对象中获取 map 迭代器。

  • ret.first->second dereferences that iterator obtaining a value_type object. That object is also a pair, in which the second member is the value part of the element we added.

    ret.first->second 对该迭代器进行解引用,获得一个 value_type 类型的对象。这个对象同样是 pair 类型的,它的 second 成员即为我们所添加的元素的值部分。

  • ++ret.first->second increments that value.

    ++ret.first->second 实现该值的自增运算。

Putting it back together, the increment statement fetches the iterator for the element indexed by word and increments the value part of that element.

归结起来,这个自增语句获取指向按 word 索引的元素的迭代器,并将该元素的值加 1。

Exercises Section 10.3.5

Exercise 10.12:

Rewrite the word-count program that you wrote in the exercises for Section 10.3.4 (p. 364) to use insert instead of subscripting. Explain which program you think is easier to write and read. Explain your reasoning.

重写第 10.3.4 节习题的单词统计程序,要求使用 insert 函数代替下标运算。你认为哪个程序更容易编写和阅读?请解释原因。

Exercise 10.13:

Given a map<string, vector<int> >, write the types used as an argument and as the return value for the version of insert that inserts one element.

假设有 map<string, vector<int> > 类型,指出在该容器中插入一个元素的 insert 函数应具有的参数类型和返回值类型。


10.3.6. Finding and Retrieving a map Element

10.3.6. 查找并读取 map 中的元素

The subscript operator provides the simplest method of retrieving a value:

下标操作符给出了读取一个值的最简单方法:

     map<string,int> word_count;
     int occurs = word_count["foobar"];

As we've seen, using a subscript has an important side effect: If that key is not already in the map, then subscript inserts an element with that key.

但是,使用下标存在一个很危险的副作用:如果该键不在 map 容器中,那么下标操作会插入一个具有该键的新元素。

Whether this behavior is correct depends on our expectations. In this example, if "foobar" weren't already present, it would be added to the map with an associated value of 0. In this case, occurs gets a value of 0.

这样的行为是否正确取决于程序员的意愿。在这个例子中,如果“foobar”不存在,则在 map 中插入具有该键的新元素,其关联的值为 0。在这种情况下,occurs 获得 0 值。

Our word-counting programs relied on the fact that subscripting a nonexistent element inserts that element and initializes the value to 0. There are times, though, when we want to know if an element is present but do not want the element inserted if it is not present. In such cases, we cannot use the subscript operator to determine whether the element is present.

我们的单词统计程序的确是要通过下标引用一个不存在的元素来实现新元素的插入,并将其关联的值初始化为 0。然而,大多数情况下,我们只想知道某元素是否存在,而当该元素不存在时,并不想做做插入运算。对于这种应用,则不能使用下标操作符来判断元素是否存在。

There are two operations, count and find, that we can use to determine if a key is present without causing it to be inserted.

map 容器提供了两个操作:countfind,用于检查某个键是否存在而不会插入该键。

Table 10.6. Interrogating a map Without Changing It
表 10.6. 不修改 map 对象的查询操作

m.count(k)

Returns the number of occurrences of k within m.

返回 mk 的出现次数

m.find(k)

Returns an iterator to the element indexed by k, if there is one, or returns an off-the-end iterator (Section 3.4, p. 97) if the key is not present.

如果 m 容器中存在按 k 索引的元素,则返回指向该元素的迭代器。如果不存在,则返回超出末端迭代器(第 3.4 节


Using count to Determine Whether a Key is in the map
使用 count 检查 map 对象中某键是否存在

The count member for a map always returns either 0 or 1. A map may have only one instance of any given key, so count effectively indicates whether the key is present. The return from count is more useful for multimaps, which we cover in Section 10.5 (p. 375). If the return value is nonzero, we can use the subscript operator to fetch the value associated with the key without worrying that doing so will insert the element into the map:

对于 map 对象,count 成员的返回值只能是 0 或 1。map 容器只允许一个键对应一个实例,所以 count 可有效地表明一个键是否存在。而对于 multimaps 容器,count 的返回值将有更多的用途,相关内容将会在第 10.5 节中介绍。如果返回值非 0,则可以使用下标操作符来获取该键所关联的值,而不必担心这样做会在 map 中插入新元素:

     int occurs = 0;
     if (word_count.count("foobar"))
         occurs = word_count["foobar"];

Of course, executing count followed by the subscript effectively looks for the element twice. If we want to use the element if it is present, we should use find.

当然,在执行 count 后再使用下标操作符,实际上是对元素作了两次查找。如果希望当元素存在时就使用它,则应该用 find 操作。

Retrieving an Element Without Adding It
读取元素而不插入该元素

The find operation returns an iterator to the element or the end iterator if the element is not present:

find 操作返回指向元素的迭代器,如果元素不存在,则返回 end 迭代器:

     int occurs = 0;
     map<string,int>::iterator it = word_count.find("foobar");
     if (it != word_count.end())
         occurs = it->second;

We should use find when we want to obtain a reference to the element with the specified key if it exists, and do not want to create the element if it does not exist.

如果希望当具有指定键的元素存在时,就获取该元素的引用,否则就不在容器中创建新元素,那么应该使用 find

Exercises Section 10.3.6

Exercise 10.14:

What is the difference between the map operations count and find?

map 容器的 countfind 运算有何区别?

Exercise 10.15:

What kinds of problems would you use count to solve? When might you use find instead?

你认为 count 适合用于解决哪一类问题?而 find 呢?

Exercise 10.16:

Define and initialize a variable to hold the result of a call to find on a map from string to vector of int.

定义并初始化一个变量,用来存储调用键为 string、值为 vector<int>map 对象的 find 函数的返回结果。


10.3.7. Erasing Elements from a map

10.3.7. 从 map 对象中删除元素

There are three variants of the erase operation to remove elements from a map. As with the sequential containers, we can erase a single element or a range of elements by passing erase an iterator or an iterator pair. These versions of erase are similar to the corresponding operations on sequential containers with one exception: The map operations return void, whereas those on the sequential containers return an iterator to the element following the one that was removed.

map 容器中删除元素的 erase 操作有三种变化形式(表 10.7)。与顺序容器一样,可向 erase 传递一个或一对迭代器,来删除单个元素或一段范围内的元素。其删除功能类似于顺序容器,但有一点不同:map 容器的 erase 操作返回 void,而顺序容器的 erase 操作则返回一个迭代器,指向被删除元素后面的元素。

The map type supplies an additional erase operation that takes a value of the key_type and removes the element with the given key if the element exists. We could use this version to remove a specific word from word_count before printing the results:

除此之外,map 类型还提供了一种额外的 erase 操作,其参数是 key_type 类型的值,如果拥有该键的元素存在,则删除该元素。对于单词统计程序,可使用这个版本的 erase 函数来删除 word_count 中指定的单词,然后输出被删除的单词:

     // erase of a key returns number of elements removed
     if (word_count.erase(removal_word))
          cout << "ok: " << removal_word << " removed\n";
     else cout << "oops: " << removal_word << " not found!\n";

The erase function returns a count of how many elements were removed. In the case of a map, that number is either zero or one. If the return value is zero, then the element we wanted to erase was not in the map.

erase 函数返回被删除元素的个数。对于 map 容器,该值必然是 0 或 1。如果返回 0,则表示欲删除的元素在 map 不存在。

Table 10.7. Removing Elements from a map
表 10.7. 从 map 对象中删除元素

m.erase(k)

Removes the element with key k from m. Returns size_type indicating the number of elements removed.

删除 m 中键为 k 的元素。返回 size_type 类型的值,表示删除的元素个数

m.erase(p)

Removes element referred to by the iterator p from m. p must refer to an actual element in m; it must not be equal to m.end(). Returns void.

从 m 中删除迭代器 p 所指向的元素。p 必须指向 m 中确实存在的元素,而且不能等于 m.end()。返回 void

m.erase(b, e)

Removes the elements in the range denoted by the iterator pair b, e. b and e must be a valid range of elements in m: b and e must refer to elements in m or one past the last element in m. b and e must either be equalin which case the range is emptyor the element to which b refers must occur before the element referred to by e. Returns void.

从 m 中删除一段范围内的元素,该范围由迭代器对 be 标记。be 必须标记 m 中的一段有效范围:即 be 都必须指向 m 中的元素或最后一个元素的下一个位置。而且,be 要么相等(此时删除的范围为空),要么 b 所指向的元素必须出现在 e 所指向的元素之前。返回 void 类型


10.3.8. Iterating across a map

10.3.8. map 对象的迭代遍历

Like any other container, map provides begin and end operations that yield iterators that we can use to traverse the map. For example, we could print the map named word_count that we built on page 363 as follows:

与其他容器一样,map 同样提供 beginend 运算,以生成用于遍历整个容器的迭代器。例如,可如下将 map 容器 word_count 的内容输出:

     // get iterator positioned on the first element
     map<string, int>::const_iterator
                             map_it = word_count.begin();
     // for each element in the map
     while (map_it != word_count.end()) {
         // print the element key, value pairs
         cout << map_it->first << " occurs "
              << map_it->second << " times" << endl;
         ++map_it; // increment iterator to denote the next element
     }

The while condition and increment for the iterator in this loop look a lot like the programs we wrote that printed the contents of a vector or a string. We initialize an iterator, map_it, to refer to the first element in word_count. As long as the iterator is not equal to the end value, we print the current element and then increment the iterator. The body of the loop is more complicated than those earlier programs because we must print both the key and value for each element.

while 循环的条件判断以及循环体中迭代器的自增都与输出 vectorstring 容器内容的程序非常相像。首先,初始化 map_it 迭代器,使之指向 word_count 的第一元素。只要该迭代器不等于 end 的值,就输出当前元素并给迭代器加 1。这段程序的循环体要比前面类似的程序更加复杂,原因在于对于 map 的每个元素都必须分别输出它的键和值。

The output of our word-count program prints the words in alphabetical order. When we use an iterator to traverse a map, the iterators yield elements in ascending key order.

这个单词统计程序依据字典顺序输出单词。在使用迭代器遍历 map 容器时,迭代器指向的元素按键的升序排列。



10.3.9. A Word Transformation Map

10.3.9. “单词转换” map 对象

We'll close this section with a program to illustrate creating, searching, and iterating across a map. Our problem is to write a program that, given one string, transforms it into another. The input to our program is two files. The first file contains several word pairs. The first word in the pair is one that might be in the input string. The second is the word to use in the output. Essentially, this file provides a set of word transformationswhen we find the first word, we should replace it by the second. The second file contains the text to transform. If the contents of the word transformation file is

下面的程序说明如何创建、查找和迭代遍历一个 map 对象,我们将以此结束本节内容。这个程序求解的问题是:给出一个 string 对象,把它转换为另一个 string 对象。本程序的输入是两个文件。第一个文件包括了若干单词对,每对的第一个单词将出现在输入的字符串中,而第二个单词则是用于输出。本质上,这个文件提供的是单词转换的集合——在遇到第一个单词时,应该将之替换为第二个单词。第二个文件则提供了需要转换的文本。如果单词转换文件的内容是:

'em

them

cuz

because

gratz

grateful

i

I

nah

no

pos

supposed

sez

said

tanx

thanks

wuz

was


and the text we are given to transform is

而要转换的文本是:

     nah i sez tanx cuz i wuz pos to
     not cuz i wuz gratz

then the program should generate the following output:

则程序将产生如下输出结果:

     no I said thanks because I was supposed to
     not because I was grateful

The Word Transformation Program
单词转换程序

Our solution, which appears on the next page, stores the word transformation file in a map, using the word to be replaced as the key and the word to use as the replacement as its corresponding value. We then read the input, looking up each word to see if it has a transformation. If so, we do the transformation and then print the transformed word. If not, we print the original word.

下面给出的解决方案是将单词转换文件的内容存储在一个 map 容器中,将被替换的单词作为键,而用作替换的单词则作为其相应的值。接着读取输入,查找输入的每个单词是否对应有转换。若有,则实现转换,然后输出其转换后的单词,否则,直接输出原词。

Our main program takes two arguments (Section 7.2.6, p. 243): the name of the word transformation file and the name of the file to transform. We start by checking the number of arguments. The first argument, argv[0], is always the name of the command. The file names will be in argv[1] and argv[2].

该程序的 main 函数需要两个实参(第 7.2.6 节):单词转换文件的名字以及需要转换的文件名。程序执行时,首先检查实参的个数。第一个实参 argv[0] 是命令名,而执行该程序所需要的两个文件名参数则分别存储在 argv[1]argv[2] 中。

Once we know that argv[1] is valid, we call open_file (Section 8.4.3, p. 299) to open the word transformation file. Assuming the open succeeded, we read the transformation pairs. We call insert using the first word as the key and the second as the value. When the while concludes, trans_map contains the data we need to transform the input. If there's a problem with the arguments, we throw (Section 6.13, p. 215) an exception and exit the program.

如果 argv[1] 的值合法,则调用 open_file第 8.4.3 节)打开单词转换文件。假设 open 操作成功,则读入“单词转换对”。以“转换对”中的第一个单词为键,第二个为值,调用 insert 函数在容器中插入新元素。while 循环结束后,trans_map 容器对象包含了转换输入文本所需的数据。而如果该实参有问题,则抛出异常(第 6.13 节)并结束程序的运行。

Next, we call open_file to open the file we want to transform. The second while uses getline to read that file a line at a time. We read by line so that our output will have line breaks at the same position as our input file. To get the words from each line we use a nested while loop that uses an istringstream. This part of the program is similar to the sketch we wrote on page 300.

接下来,调用 open_file 打开要转换的文件。第二个 while 循环使用 getline 函数逐行读入文件。因为程序每次读入一行,从而可在输出文件的相同位置进行换行。然后在内嵌的 while 循环中使用 istringstream 将每一行中的单词提取出来。这部分程序与第 8.5 节的程序框架类似。

The inner while checks each word to see if it is in the transformation map. If it is, then we replace the word by its corresponding value from the map. Finally, we print the word, transformed or not. We use the bool firstword to determine whether to print a space. If it is the first word in the line, we don't print a space.

内层的 while 循环检查每个单词,判断它是否在转换的 map 中出现。如果在,则从该 map 对象中取出对应的值替代此单词。最后,无论是否做了转换,都输出该单词。同时,程序使用 boolfirstword 判断是否需要输出空格。如果当前处理的是这一行的第一个单词,则无须输出空格。

    /*
     * A program to transform words.
     * Takes two arguments: The first is name of the word transformation file
     *                      The second is name of the input to transform
     */
    int main(int argc, char **argv)
    {
        // map to hold the word transformation pairs:
        // key is the word to look for in the input; value is word to use in the output
        map<string, string> trans_map;
        string key, value;
        if (argc != 3)
            throw runtime_error("wrong number of arguments");
        // open transformation file and check that open succeeded
        ifstream map_file;
        if (!open_file(map_file, argv[1]))
            throw runtime_error("no transformation file");
        // read the transformation map and build the map
         while (map_file >> key >> value)
             trans_map.insert(make_pair(key, value));
        // ok, now we're ready to do the transformations
        // open the input file and check that the open succeeded
        ifstream input;
        if (!open_file(input, argv[2]))
            throw runtime_error("no input file");
        string line;    // hold each line from the input
        // read the text to transform it a line at a time
        while (getline(input, line)) {
            istringstream stream(line);   // read the line a word at a time
            string word;
            bool firstword = true;   // controls whether a space is printed
            while (stream >> word) {
               // ok: the actual mapwork, this part is the heart of the program
               map<string, string>::const_iterator map_it =
                                  trans_map.find(word);
               // if this word is in the transformation map
               if (map_it != trans_map.end())
                   // replace it by the transformation value in the map
                   word = map_it->second;
               if (firstword)
                   firstword = false;
               else
                   cout << " ";  // print space between words
               cout << word;
            }
            cout << endl;        // done with this line of input
    }
    return 0;
 }

Exercises Section 10.3.9

Exercise 10.17:

Our transformation program uses find to look for each word:

上述转换程序使用了 find 函数来查找单词:

     map<string, string>::const_iterator map_it =
              trans_map.find(word);

Why do you suppose the program uses find? What would happen if it used the subscript operator instead?

你认为这个程序为什么要使用 find 函数?如果使用下标操作符又会怎么样?

Exercise 10.18:

Define a map for which the key is the family surname and the value is a vector of the children's names. Populate the map with at least six entries. Test it by supporting user queries based on a surname, which should list the names of children in that family.

定义一个 map 对象,其元素的键是家庭姓氏,而值则是存储该家庭孩子名字的 vector 对象。为这个 map 容器输入至少六个条目。通过基于家庭姓氏的查询检测你的程序,查询应输出该家庭所有孩子的名字。

Exercise 10.19:

Extend the map from the previous exercise by having the vector store a pair that holds a child's name and birthday. Revise the program accordingly. Test your modified test program to verify its correctness.

把上一题的 map 对象再扩展一下,使其 vector 对象存储 pair 类型的对象,记录每个孩子的名字和生日。相应地修改程序,测试修改后测试程序以检查所编写的 map 是否正确。

Exercise 10.20:

List at least three possible applications in which the map type might be of use. Write the definition of each map and indicate how the elements are likely to be inserted and retrieved.

列出至少三种可以使用 map 类型的应用。为每种应用定义 map 对象,并指出如何插入和读取元素。


Team LiB
Previous Section Next Section