10.3. The map Type10.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 map10.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_count 的 map 对象,由 string 类型的键索引,关联的值则 int 型。 Table 10.3. Constructors for map表 10.3. map 的构造函数
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“小于”k2,k2“小于”k3,则 k1 必然“小于”k3。对于两个键,如果它们相互之间都不存在“小于”关系,则容器将之视为相同的键。用做 map 对象的键时,可使用任意一个键值来访问相应的元素。
例如,在书店问题中,可增加一个名为 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. 该语句定义了一个名为 bookstore 的 map 对象,以 ISBN 类型的对象为索引,其所有元素都存储了一个关联的 Sales_item 类类型实例。
10.3.2. Types Defined by map10.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 对象的元素是键-值对,也即每个元素包含两个部分:键以及由键关联的值。map 的 value_type 就反映了这个事实。该类型比前面介绍的容器所使用的元素类型要复杂得多:value_type 是存储元素的键以及值的 pair 类型,而且键为 const。例如,word_count 数组的 value_type 为 pair<const string, int> 类型。 Table 10.4. Types Defined by the map Class表 10.4. map 类定义的类型
Dereferencing a map Iterator Yields a pairmap 迭代器进行解引用将产生 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_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 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 Typedefsmap 容器额外定义的类型别名(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_type 和 mapped_type,以获得键或值的类型。对于 word_count,其 key_type 是 string 类型,而 mapped_type 则是 int 型。如同顺序容器(第 9.3.1 节)一样,可使用作用域操作符(scope operator)来获取类型成员,如 map<string, int>::key_type。
10.3.3. Adding Elements to a map10.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 map10.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: 将发生以下事情:
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
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_type 和 mapped_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 开始。
10.3.5. Using map::insert10.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 操作
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_type 是 pair<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 下面对这个表达式一步步地展开解释:
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。
10.3.6. Finding and Retrieving a map Element10.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 容器提供了两个操作:count 和 find,用于检查某个键是否存在而不会插入该键。 Table 10.6. Interrogating a map Without Changing It表 10.6. 不修改 map 对象的查询操作
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。
10.3.7. Erasing Elements from a map10.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 对象中删除元素
10.3.8. Iterating across a map10.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 同样提供 begin 和 end 运算,以生成用于遍历整个容器的迭代器。例如,可如下将 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 循环的条件判断以及循环体中迭代器的自增都与输出 vector 或 string 容器内容的程序非常相像。首先,初始化 map_it 迭代器,使之指向 word_count 的第一元素。只要该迭代器不等于 end 的值,就输出当前元素并给迭代器加 1。这段程序的循环体要比前面类似的程序更加复杂,原因在于对于 map 的每个元素都必须分别输出它的键和值。
10.3.9. A Word Transformation Map10.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 对象。本程序的输入是两个文件。第一个文件包括了若干单词对,每对的第一个单词将出现在输入的字符串中,而第二个单词则是用于输出。本质上,这个文件提供的是单词转换的集合——在遇到第一个单词时,应该将之替换为第二个单词。第二个文件则提供了需要转换的文本。如果单词转换文件的内容是:
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 对象中取出对应的值替代此单词。最后,无论是否做了转换,都输出该单词。同时,程序使用 bool 值 firstword 判断是否需要输出空格。如果当前处理的是这一行的第一个单词,则无须输出空格。 /* * 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; }
|