Team LiB
Previous Section Next Section

11.3. Revisiting Iterators

11.3. 再谈迭代器

In Section 11.2.2 (p. 398) we saw that the library defines iterators that are independent of a particular container. In fact, there are three additional kinds of iterators:

第 11.2.2 节已强调标准库所定义的迭代器不依赖于特定的容器。事实上,C++ 语言还提供了另外三种迭代器:

  • insert iterators: These iterators are bound to a container and can be used to insert elements to the container.

    插入迭代器:这类迭代器与容器绑定在一起,实现在容器中插入元素的功能。

  • iostream iterators: These iterators can be bound to input or output streams and used to iterate through the associated IO stream.

    iostream 迭代器:这类迭代器可与输入或输出流绑定在一起,用于迭代遍历所关联的 IO 流。

  • reverse iterators: These iterators move backward, rather than forward. Each container type defines its own reverse_iterator types, which are retuned by the rbegin and rend functions.

    反向迭代器:这类迭代器实现向后遍历,而不是向前遍历。所有容器类型都定义了自己的 reverse_iterator 类型,由 rbeginrend 成员函数返回。

These iterator types are defined in the iterator header.

上述迭代器类型都在 iterator 头文件中定义。

This section will look at each of these kinds of iterators and show how they can be used with the generic algorithms. We'll also take a look at how and when to use the container const_iterators.

本节将详细分析上述每种迭代器,并介绍在泛型算法中如何使用这些迭代器,还会了解什么时候应该使用和如何使用 const_iterator 容器

11.3.1. Insert Iterators

11.3.1. 插入迭代器

In Section 11.2.2 (p. 398) we saw that we can use back_inserter to create an iterator that adds elements to a container. The back_inserter function is an example of an inserter. An inserter is an iterator adaptor (Section 9.7, p. 348) that takes a container and yields an iterator that inserts elements into the specified container. When we assign through an insert iterator, the iterator inserts a new element. There are three kinds of inserters, which differ as to where elements are inserted:

第 11.2.2 节使用 back_insert 创建一个迭代器,用来给容器添加元素。back_inserter 函数是一种插入器。插入器是一种迭代器适配器(第 9.7 节),带有一个容器参数,并生成一个迭代器,用于在指定容器中插入元素。通过插入迭代器赋值时,迭代器将会插入一个新的元素。C++ 语言提供了三种插入器,其差别在于插入元素的位置不同。

  • back_inserter, which creates an iterator that uses push_back.

    back_inserter,创建使用 push_back 实现插入的迭代器。

  • front_inserter, which uses push_front.

    front_inserter,使用 push_front 实现插入。

  • inserter, which uses insert. In addition to a container, inserter takes a second argument: an iterator indicating the position ahead of which insertion should begin.

    inserter,使用 insert 实现插入操作。除了所关联的容器外,inserter 还带有第二实参:指向插入起始位置的迭代器。

front_inserter Requires push_front
front_inserter 需要使用 push_front

front_inserter operates similarly to back_inserter: It creates an iterator that treats assignment as a call to push_front on its underlying container.

front_inserter 的操作类似于 back_inserter:该函数将创建一个迭代器,调用它所关联的基础容器的 push_front 成员函数代替赋值操作。

We can use front_inserter only if the container has a push_front operation. Using front_inserter on a vector, or other container that does not have push_front, is an error.

只有当容器提供 push_front 操作时,才能使用 front_inserter。在 vector 或其他没有 push_front 运算的容器上使用 front_inserter,将产生错误。

inserter Yields an Iterator that Inserts at a Given Place
inserter 将产生在指定位置实现插入的迭代器

The inserter adaptor provides a more general form. This adaptor takes both a container and an iterator denoting a position at which to do the insertion:

inserter 适配器提供更普通的插入形式。这种适配器带有两个实参:所关联的容器和指示起始插入位置的迭代器。

     // position an iterator into ilst
     list<int>::iterator it =
                      find (ilst.begin(), ilst.end(), 42);
     // insert replaced copies of ivec at that point in ilst
     replace_copy (ivec.begin(), ivec.end(),
                   inserter (ilst, it), 100, 0);

We start by using find to locate an element in ilst. The call to replace_copy uses an inserter that will insert elements into ilst just before of the element denoted by the iterator returned from find. The effect of the call to replace_copy is to copy the elements from ivec, replacing each value of 100 by 0. The elements are inserted just ahead of the element denoted by it.

首先用 find 定位 ilst 中的某个元素。使用 inserter 作为实参调用 replace_copyinserter 将会在 ilst 中由 find 返回的迭代器所指向的元素前面插入新元素。而调用 replace_copy 的效果是从 ivec 中复制元素,并将其中值为 100 的元素替换为 0 值。ilst 的新元素在 it 所标明的元素前面插入。

When we create an inserter, we say where to insert new elements. Elements are always inserted in front of the position denoted by the iterator argument to inserter.

在创建 inserter 时,应指明新元素在何处插入。inserter 函数总是在它的迭代器实参所标明的位置前面插入新元素。

We might think that we could simulate the effect of front_inserter by using inserter and the begin iterator for the container. However, an inserter behaves quite differently from front_inserter. When we use front_inserter, the elements are always inserted ahead of the then first element in the container. When we use inserter, elements are inserted ahead of a specific position. Even if that position initially is the first element, as soon as we insert an element in front of that element, it is no longer the one at the beginning of the container:

也许我们会认为可使用 inserter 和容器的 begin 迭代器来模拟 front_inserter 的效果。然而,inserter 的行为与 front_inserter 的有很大差别。在使用 front_inserter 时,元素始终在容器的第一个元素前面插入。而使用 inserter 时,元素则在指定位置前面插入。即使此指定位置初始化为容器中的第一个元素,但是,一旦在该位置前插入一个新元素后,插入位置就不再是容器的首元素了:

     list<int> ilst, ilst2, ilst3;     // empty lists
     // after this loop ilst contains: 3 2 1 0
     for (list<int>::size_type i = 0; i != 4; ++i)
          ilst.push_front(i);
     // after copy ilst2 contains: 0 1 2 3
     copy (ilst.begin(), ilst.end(), front_inserter(ilst2));
     // after copy, ilst3 contains: 3 2 1 0
     copy (ilst.begin(), ilst.end(),
                  inserter (ilst3, ilst3.begin()));

When we copy into ilst2, elements are always inserted ahead of any other element in the list. When we copy into ilst3, elements are inserted at a fixed point. That point started out as the head of the list, but as soon as even one element is added, it is no longer the first element.

在复制并创建 ilst2 的过程中,元素总是在这个 list 对象的所有元素之前插入。而在复制创建 ilst3 的过程中,元素则在 ilst3 中的固定位置插入。刚开始时,这个插入位置是此 list 对象的头部,但插入一个元素后,就不再是首元素了。

Recalling the discussion in Section 9.3.3 (p. 318), it is important to understand that using front_inserter results in the elements appearing in the destination in reverse order.

回顾第 9.3.3 节的讨论,应该清楚理解 front_inserter 的使用将导致元素以相反的次序出现在目标对象中,这点非常重要。

Exercises Section 11.3.1

Exercise 11.13:

Explain the differences among the three insert iterators.

解释三种插入迭代器的区别。

Exercise 11.14:

Write a program that uses replace_copy to copy a sequence from one container to another, replacing elements with a given value in the first sequence by the specified new value. Write the program to use an inserter, a back_inserter and a front_inserter. Discuss how the output sequence varies in each case.

编写程序使用 replace_copy 将一个容器中的序列复制给另一个容器,并将前一个序列中给定的值替换为指定的新值。分别使用 inserterback_inserterfront_inserter 实现这个程序。讨论在不同情况下输出序列如何变化。

Exercise 11.15:

The algorithms library defines a function named unique_copy that operates like unique, except that it takes a third iterator denoting a sequence into which to copy the unique elements. Write a program that uses unique_copy to copy the unique elements from a list into an initially empty vector.

算法标准库定义了一个名为 unique_copy 的函数,其操作与 unique 类似,唯一的区别在于:前者接受第三个迭代器实参,用于指定复制不重复元素的目标序列。编写程序使用 unique_copy 将一个 list 对象中不重复的元素复制到一个空的 vector 对象中。

11.3.2. iostream Iterators

11.3.2. iostream 迭代器

Even though the iostream types are not containers, there are iterators that can be used with iostream objects: An istream_iterator reads an input stream, and an ostream_iterator writes an output stream. These iterators treat their corresponding stream as a sequence of elements of a specified type. Using a stream iterator, we can use the generic algorithms to read (or write) data to (or from) stream objects.

虽然 iostream 类型不是容器,但标准库同样提供了在 iostream 对象上使用的迭代器:istream_iterator 用于读取输入流,而 ostream_iterator 则用于写输出流(表 11.1)。这些迭代器将它们所对应的流视为特定类型的元素序列。使用流迭代器时,可以用泛型算法从流对象中读数据(或将数据写到流对象中)。

Table 11.1. iostream Iterator Constructors
表 11.1 iostream 迭代器的构造函数

istream_iterator<T> in(strm);

Create istream_iterator that reads objects of type T from input stream strm.

创建从输入流 strm 中读取 T 类型对象的 istream_iterator 对象

istream_iterator<T> in;

Off-the-end iterator for istream_iterator.

istream_iterator 对象的超出末端迭代器

ostream_iterator<T> in(strm);

Create ostream_iterator that writes objects of type T to the output stream strm.

创建将 T 类型的对象写到输出流 strmostream_iterator 对象

ostream_iterator<T> in(strm, delim);

 

Create ostream_iterator that writes objects of type T to the output stream strm using delim as a separator between elements. delim is a null-terminated character array.

创建将 T 类型的对象写到输出流 strmostream_iterator 对象,在写入过程中使用 delim 作为元素的分隔符。delim 是以空字符结束的字符数组

The stream iterators define only the most basic of the iterator operations: increment, dereference, and assignment. In addition, we can compare two istream iterators for equality (or inequality). There is no comparison for ostream iterators.

流迭代器只定义了最基本的迭代器操作:自增、解引用和赋值。此外,可比较两个 istream 迭代器是否相等(或不等)。而 ostream 迭代器则不提供比较运算(表 11.2)。

Table 11.2. istream_iterator Operations
表 11.2. istream_iterator 的操作

it1 == it2 it1 != it2

Equality (inequality) between two istream_iterators. The iterators must read the same type. Two iterators are equal if they are both the end value. Two non-end-of-stream iterators are equal if they are constructed using the same input stream.

比较两上 istream_iterator 对象是否相等(不等)。迭代器读取的必须是相同的类型。如果两个迭代器都是 end 值,则它们相等。对于两个都不指向流结束位置的迭代器,如果它们使用同一个输入流构造,则它们也相等

*it

Returns the value read from the stream.

返回从流中读取的值

it->mem

Synonym for (*it).mem. Returns member, mem, of the object read from the stream.

(*it).mem 的同义诩。返回从流中读取的对象的 mem 成员

++it it++

Advances the iterator by reading the next value from the input stream using the >> operator for the element type. As usual, the prefix version advances the stream and returns a reference to the incremented iterator. The postfix version advances the stream but returns the old value.

通过使用元素类型提供的 >> 操作从输入流中读取下一个元素值,使迭代器向前移动。通常,前缀版本使用迭代器在流中向前移动,并返回对加 1 后的迭代器的引用。而后缀版本使迭代器在流中向前移动后,返回原值

Defining Stream Iterators
流迭代器的定义

The stream iterators are class templates: An istream_iterator can be defined for any type for which the input operator (the >> operator) is defined. Similarly, an ostream_iterator can be defined for any type that has an output operator (the << operator).

流迭代器都是类模板:任何已定义输入操作符(>> 操作符)的类型都可以定义 istream_iterator。类似地,任何已定义输出操作符(<< 操作符)的类型也可定义 ostream_iterator

When we create a stream iterator, we must specify the type of objects that the iterator will read or write:

在创建流迭代器时,必须指定迭代器所读写的对象类型:

     istream_iterator<int> cin_it(cin);    // reads ints1 from cin
     istream_iterator<int> end_of_stream;  // end iterator value
     // writes Sales_items from the ofstream named outfile
     // each element is followed by a space
     ofstream outfile;
     ostream_iterator<Sales_item> output(outfile, " ");

We must bind an ostream_iterator to a specific stream. When we create an istream_iterator, we can bind it to a stream. Alternatively, we can supply no argument, which creates an iterator that we can use as the off-the-end value. There is no off-the-end iterator for ostream_iterator.

ostream_iterator 对象必须与特定的流绑定在一起。在创建 istream_iterator 时,可直接将它绑定到一个流上。另一种方法是在创建时不提供实参,则该迭代器指向超出末端位置。ostream_iterator 不提供超出末端迭代器。

When we create an ostream_iterator, we may (optionally) provide a second argument that specifies a delimiter to use when writing elements to the output stream. The delimiter must be a C-style character string. Because it is a C-style string, it must be null-terminated; otherwise, the behavior is undefined.

在创建 ostream_iterator 对象时,可提供第二个(可选的)实参,指定将元素写入输出流时使用的分隔符。分隔符必须是 C 风格字符串。因为它是 C 风格字符串,所以必须以空字符结束;否则,其行为将是未定义的。

Operations on istream_iterators
istream_iterator 对象上的操作

Constructing an istream_iterator bound to a stream positions the iterator so that the first dereference reads the first value from the stream.

构造与流绑定在一起的 istream_iterator 对象时将对迭代器定位,以便第一次对该迭代器进行解引用时即可从流中读取第一个值。

As an example, we could use an istream_iterator to read the standard input into a vector:

考虑下面例子,可使用 istream_iterator 对象将标准输入读到 vector 对象中。

     istream_iterator<int> in_iter(cin); // read ints from cin
     istream_iterator<int> eof; // istream "end" iterator
     // read until end of file, storing what was read in vec
     while (in_iter != eof)
             // increment advances the stream to the next value
             // dereference reads next value from the istream
             vec.push_back(*in_iter++);

This loop reads ints from cin, and stores what was read in vec. On each trip the loop checks whether in_iter is the same as eof. That iterator was defined as the empty istream_iterator, which is used as the end iterator. An iterator bound to a stream is equal to the end iterator once its associated stream hits end-of-file or encounters another error.

这个循环从 cin 中读取 int 型数据,并将读入的内容保存在 vec 中。每次循环都检查 in_iter 是否为 eof。其中 eof 迭代器定义为空的 istream_iterator 对象,用作结束迭代器。绑在流上的迭代器在遇到文件结束或某个错误时,将等于结束迭代器的值。

The hardest part of this program is the argument to push_back, which uses the dereference and postfix increment operators. Precedence rules (Section 5.5, p. 163) say that the result of the increment is the operand to the dereference. Incrementing an istream_iterator advances the stream. However, the expression uses the postfix increment, which yields the old value of the iterator. The effect of the increment is to read the next value from the stream but return an iterator that refers to the previous value read. We dereference that iterator to obtain that value.

本程序最难理解的部分是传递给 push_back 的实参,该实参使用解引用和后自增操作符。根据优先级规则(第 5.5 节),自增运算的结果将是解引用运算的操作数。对 istream_iterator 对象做自增运算使该迭代器在流中向前移动。然而,使用后自增运算的表达式,其结果是迭代器原来的值。自增的效果是使迭代器的流中移动到下一个值,但返回指向前一个值的迭代器。对该迭代器进行解引用获取该值。

What is more interesting is that we could rewrite this program as:

更有趣的是可以这样重写程序:

     istream_iterator<int> in_iter(cin); // read ints from cin
     istream_iterator<int> eof;      // istream "end" iterator
     vector<int> vec(in_iter, eof);  // construct vec from an iterator range

Here we construct vec from a pair of iterators that denote a range of elements. Those iterators are istream_iterators, which means that the range is obtained by reading the associated stream. The effect of this constructor is to read cin until it hits end-of-file or encounters an input that is not an int. The elements that are read are used to construct vec.

这里,用一对标记元素范围的迭代器构造 vec 对象。这些迭代器是 istream_iterator 对象,这就意味着这段范围的元素是通过读取所关联的流来获得的。这个构造函数的效果是读 cin,直到到达文件结束或输入的不是 int 型数值为止。读取的元素将用于构造 vec 对象。

Using ostream_iterators and ostream_iterators
ostream_iterator 对象和 ostream_iterator 对象的使用

We can use an ostream_iterator to write a sequence of values to a stream in much the same way that we might use an iterator to assign a sequence of values to the elements of a container:

可使用 ostream_iterator 对象将一个值序列写入流中,其操作的过程与使用迭代器将一组值逐个赋给容器中的元素相同:

     // write one string per line to the standard output
     ostream_iterator<string> out_iter(cout, "\n");
     // read strings from standard input and the end iterator
     istream_iterator<string> in_iter(cin), eof;
     // read until eof and write what was read to the standard output
     while (in_iter != eof)
         // write value of in_iter to standard output
         // and then increment the iterator to get the next value from cin
        *out_iter++ = *in_iter++;

This program reads cin, writing each word it reads on separate line on cout.

这个程序读 cin,并将每个读入的值依次写到 cout 中不同的行中。

We start by defining an ostream_iterator to write strings to cout, following each string by a newline. We define two istream_iterators that we'll use to read strings from cin. The while loop works similarly to our previous example. This time, instead of storing the values we read into a vector, we print them to cout by assigning the values we read to out_iter.

首先,定义一个 ostream_iterator 对象,用于将 string 类型的数据写到 cout 中,每个 string 对象后跟一个换行符。定义两个 istream_iterator 对象,用于从 cin 中读取 string 对象。while 循环类似前一个例子。但是这一次不是将读取的数据存储在 vector 对象中,而是将读取的数据赋给 out_iter,从而输出到 cout 上。

The assignment works similarly to the one in the program on page 205 that copied one array into another. We dereference both iterators, assigning the right-hand value into the left, incrementing each iterator. The effect is to write what was read to cout and then increment each iterator, reading the next value from cin.

这个赋值类似于第 6.7 节将一个数组复制给另一个数组的程序。对这两个迭代器进行解引用,将右边的值赋给左边的元素,然后两个迭代器都自增 1。其效果就是:将读取的数据输出到 cout 上,然后两个迭代器都加 1,再从 cin 中读取下一个值。

Using istream_iterators with Class Types
在类类型上使用 istream_iterator

We can create an istream_iterator for any type for which an input operator (>>) exists. For example, we might use an istream_iterator to read a sequence of Sales_item objects to sum:

提供了输入操作符(>>)的任何类型都可以创建 istream_iterator 对象。例如,可如下使用 istream_iterator 对象读取一系列的 Sales_iter 对象,并求和:

     istream_iterator<Sales_item> item_iter(cin), eof;
     Sales_item sum; // initially empty Sales_item
     sum = *item_iter++; // read first transaction into sum and get next record
     while (item_iter != eof) {
        if (item_iter->same_isbn(sum))
            sum = sum + *item_iter;
        else {
            cout << sum << endl;
            sum = *item_iter;
        }
        ++item_iter; // read next transaction
     }
     cout << sum << endl; // remember to print last set of records

This program binds item_iter to cin and says that the iterator will read objects of type Sales_item. The program next reads the first record into sum:

该程序将迭代器 item_itercin 绑在一起,意味着迭代器将读取 Sales_item 类型的对象。然后给迭代器加 1,使流从标准输入中读取下一记录。

     sum = *item_iter++; // read first transaction into sum and get next record

This statement uses the dereference operator to fetch the first record from the standard input and assigns that value to sum. It increments the iterator, causing the stream to read the next record from the standard input.

这个语句使用解引用操作符获取标准输入的第一个记录,并将这个值赋给 sum。然后给迭代器加 1,使流从标准输入中读取下一记录。

The while loop executes until we hit end-of-file on cin. Inside the while, we compare the isbn of the record we just read with sum's isbn. The first statement in the while uses the arrow operator to dereference the istream iterator and obtain the most recently read object. We then run the same_isbn member on that object and the object in sum.

while 循环反复执行直到到达 cin 的结束位置为止。在 while 循环中,将刚读入记录的 isbnsumisbn 比较。while 中的第一个语句使用了箭头操作符对 istream 迭代器进行解引用,获得最近读入的对象。然后在该对象和 sum 对象上调用 same_isbn 成员。

If the isbns are the same, we increment the totals in sum. Otherwise, we print the current value of sum and reset it as a copy of the most recently read transaction. The last step in the loop is to increment the iterator, which in this case causes the next transaction to be read from the standard input. The loop continues until an error or end-of-file is encountered. Before exiting we remember to print the values associated with the last ISBN in the input.

如果 isbn 值相同,则增加总和 sum。否则,输出 sum 的当前值,并将它重设为最近读取对象的副本。循环的最后一步是给迭代器加 1,在本例中,将导致从标准输入中读入下一个 Sales_item 对象。循环持续直到遇到错误或结束位置为止。在结束程序之前,记住输出从输入中读入的最后一个 ISBN 所关联的值。

Limitations on Stream Iterators
流迭代器的限制

The stream iterators have several important limitations:

流迭代器有下面几个重要的限制:

  • It is not possible to read from an ostream_iterator, and it is not possible to write to an istream_iterator.

    不可能从 ostream_iterator 对象读入,也不可能写到 istream_iterator 对象中。

  • Once we assign a value to an ostream_iterator, the write is committed. There is no way to subsequently change a value once it is assigned. Moreover, each distinct value of an ostream_iterator is expected to be used for output exactly once.

    一旦给 ostream_iterator 对象赋了一个值,写入就提交了。赋值后,没有办法再改变这个值。此外,ostream_iterator 对象中每个不同的值都只能正好输出一次。

  • There is no -> operator for ostream_iterator.

    ostream_iterator 没有 -> 操作符。

Using Stream Iterators with the Algorithms
与算法一起使用流迭代器

As we know, the algorithms operate in terms of iterator operations. And as we've seen, stream iterators define at least some of the iterator operations. Because the stream iterators support iterator operations, we can use them with at least some of the generic algorithms. As an example, we could read numbers from the standard input and write the unique numbers we read on the standard output:

正如大家所知,算法是基于迭代器操作实现的。如同前面所述,流迭代器至少定义了一些迭代器操作。由于流迭代器操作,因此,至少可在一些泛型算法上使用这类迭代器。考虑下面的例子,从标准输入读取一些数,再将读取的不重复的数写到标准输出:

     istream_iterator<int> cin_it(cin);    // reads ints from cin
     istream_iterator<int> end_of_stream;  // end iterator value
     // initialize vec from the standard input:
     vector<int> vec(cin_it, end_of_stream);
     sort(vec.begin(), vec.end());

     // writes ints to cout using " " as the delimiter
     ostream_iterator<int> output(cout, " ");

     // write only the unique elements in vec to the standard output
     unique_copy(vec.begin(), vec.end(), output);

If the input to this program is

如果程序的输入是:

     23 109 45 89 6 34 12 90 34 23 56 23 8 89 23

then the output would be

输出则是:

     6 8 12 23 34 45 56 89 90 109

The program creates vec from the iterator pair, input and end_of_stream. The effect of this initializer is to read cin until end-of-file or an error occurs. The values read are stored in vec.

程序用一对迭代器 inputend_of_stream 创建了 vec 对象。这个初始化的效果是读取 cin 直到文件结束或者出现错误为止。读取的值保存在 vec 里。

Once the input is read and vec initialized, we call sort to sort the input. Duplicated items from the input will be adjacent after the call to sort.

读取输入和初始化 vec 后,调用 sort 对输入的数排序。sort 调用完成后,重复输入的数就会相邻存储。

The program uses unique_copy, which is a copying version of unique. It copies the unique values in its input range to the destination iterator. This call uses our output iterator as the destination. The effect is to copy the unique values from vec to cout, following each value by a space.

程序再使用 unique_copy 算法,这是 unique 的“复制”版本。该算法将输入范围中不重复的值复制到目标迭代器。该调用将输出迭代器用作目标。其效果是将 vec 中不重复的值复制给 cout,每个复制的值后面输出一个空格。

Exercises Section 11.3.2

Exercise 11.16:

Rewrite the program on 410 to use the copy algorithm to write the contents of a file to the standard output.

重写(第 11.3.2 节第 3 小节)的程序,使用 copy 算法将一个文件的内容写到标准输出中。

Exercise 11.17:

Use a pair of istream_iterators to initialize a vector of ints.

使用一对 istream_iterator 对象初始化一个 int 型的 vector 对象。

Exercise 11.18:

Write a program to read a sequence of integer numbers from the standard input using an istream_iterator. Write the odd numbers into one file, using an ostream_iterator. Each value should be followed by a space. Write the even numbers into a second file, also using an ostream_iterator. Each of these values should be placed on a separate line.

编写程序使用 istream_iterator 对象从标准输入读入一系列整数。使用 ostream_iterator 对象将其中的奇数写到一个文件中,并在每个写入的值后面加一个空格。同样使用 ostream_iterator 对象将偶数写到第二个文件,每个写入的值都存放在单独的行中。

11.3.3. Reverse Iterators

11.3.3. 反向迭代器

A reverse iterator is an iterator that traverses a container backward. That is, it traverses from the last element toward the first. A reverse iterator inverts the meaning of increment (and decrement): ++ on a reverse iterator accesses the previous element; -- accesses the next element.

反向迭代器是一种反向遍历容器的迭代器。也就是,从最后一个元素到第一个元素遍历容器。反向迭代器将自增(和自减)的含义反过来了:对于反向迭代器,++ 运算将访问前一个元素,而 -- 运算则访问下一个元素。

Recall that each container defines begin and end members. These members return respectively an iterator to the first element of the container and an iterator one past the last element of the container. The containers also define rbegin and rend, which return reverse iterators to the last element in the container and one "past" (that is, one before) the beginning of the container. As with ordinary iterators, there are both const and nonconst reverse iterators. Figure 11.1 on the facing page illustrates the relationship between these four iterators on a hypothetical vector named vec.

回想一下,所有容器都定义了 beginend 成员,分别返回指向容器首元素和尾元素下一位置的迭代器。容器还定义了 rbeginrend 成员,分别返回指向容器尾元素和首元素前一位置的反向迭代器。与普通迭代器一样,反向迭代器也有常量(const)和非常量(nonconst)类型。图 11.1 使用一个假设名为 vecvector 类型对象阐明了这四种迭代器之间的关系。

Figure 11.1. Comparing begin/end and rbegin/rend Iterators
图 11.1 比较 begin/endrbegin/rend 迭代器

Given a vector that contains the numbers from 0 to 9 in ascending order

假设有一个 vector 容器对象,存储 0-9 这 10 个以升序排列的数字:

     vector<int> vec;
     for (vector<int>::size_type i = 0; i != 10; ++i)
         vec.push_back(i); // elements are 0,1,2,...9

the following for loop prints the elements in reverse order:

下面的 for 循环将以逆序输出这些元素:

     // reverse iterator of vector from back to front
     vector<int>::reverse_iterator r_iter;
     for (r_iter = vec.rbegin(); // binds r_iter to last element
          r_iter != vec.rend();  // rend refers 1 before 1st element
          ++r_iter)              // decrements iterator one element
         cout << *r_iter << endl;    // prints 9,8,7,...0

Although it may seem confusing to have the meaning of the increment and decrement operators reversed, doing so lets us use the algorithms transparently to process a container forward or backward. For example, we could sort our vector in descending order by passing sort a pair of reverse iterators:

虽然颠倒自增和自减这两个操作符的意义似乎容易使人迷惑,但是它让程序员可以透明地向前或向后处理容器。例如,为了以降序排列 vector,只需向 sort 传递一对反向迭代器:

     // sorts vec in "normal" order
     sort(vec.begin(), vec.end());
     // sorts in reverse: puts smallest element at the end of vec
     sort(vec.rbegin(), vec.rend());
Reverse Iterators Require Decrement Operators
反向迭代器需要使用自减操作符

Not surprisingly, we can define a reverse iterator only from an iterator that supports -- as well as ++. After all, the purpose of a reverse iterator is to move the iterator backward through the sequence. The iterators on the standard containers all support decrement as well as increment. However, the stream iterators do not, because it is not possible to move backward through a stream. Therefore, it is not possible to create a reverse iterator from a stream iterator.

从一个既支持 -- 也支持 ++ 的迭代器就可以定义反向迭代器,这不用感到吃惊。毕竟,反向迭代器的目的是移动迭代器反向遍历序列。标准容器上的迭代器既支持自增运算,也支持自减运算。但是,流迭代器却不然,由于不能反向遍历流,因此流迭代器不能创建反向迭代器。

Relationship between Reverse Iterators and Other Iterators
反向迭代器与其他迭代器之间的关系

Suppose we have a string named line that contains a comma-separated list of words, and we want to print the first word in line. Using find, this task is easy:

假设有一个名为 linestring 对象,存储以逗号分隔的单词列表。我们希望输出 line 中的第一个单词。使用 find 可很简单地实现这个任务:

     // find first element in a comma-separated list
     string::iterator comma = find(line.begin(), line.end(), ',');
     cout << string(line.begin(), comma) << endl;

If there is a comma in line, then comma refers to that comma; otherwise it is line.end(). When we print the string from line.begin() to comma we print characters up to the comma, or the entire string if there is no comma.

如果在 line 中有一个逗号,则 comma 指向这个逗号;否则,comma 的值为 line.end()。在输出 string 对象中从 line.begin()comma 的内容时,从头开始输出字符直到遇到逗号为止。如果该 string 对象中没有逗号,则输出整个 string 字符串。

If we wanted the last word in the list, we could use reverse iterators instead:

如果要输出列表中最后一个单词,可使用反向迭代器:

     // find last element in a comma-separated list
     string::reverse_iterator rcomma =
                            find(line.rbegin(), line.rend(), ',');

Because we pass rbegin() and rend(), this call starts with the last character in line and searches backward. When find completes, if there is a comma, then rcomma refers to the last comma in the linethat is it refers to the first comma found in the backward search. If there is no comma, then rcomma is line.rend().

因为此时传递的是 rbegin()rend(),这个函数调用从 line 的最后一个字符开始往回搜索。当 find 完成时,如果列表中有逗号,那么 rcomma 指向其最后一个逗号,即指向反向搜索找到的第一个逗号。如果没有逗号,则 rcomma 的值为 line.rend()

The interesting part comes when we try to print the word we found. The direct attempt

在尝试输出所找到的单词时,有趣的事情发生了。直接尝试:

     // wrong: will generate the word in reverse order
     cout << string(line.rbegin(), rcomma) << endl;

generates bogus output. For example, had our input been

会产生假的输出。例如,如果输入是:

     FIRST,MIDDLE,LAST

then this statement would print TSAL!

则将输出 TSAL

Figure 11.2 illustrates the problem: We are using reverse iterators, and such iterators process the string backward. To get the right output, we need to transform the reverse iterators line.rbegin() and rcomma into normal iterators that go forward. There is no need to transform line.rbegin() as we already know that the result of that transformation would be line.end(). We can transform rcomma by calling base, which is a member of each reverse iterator type:

图 11.2 阐明了这个问题:使用反向迭代器时,以逆序从后向前处理 string 对象。为了得到正确的输出,必须将反向迭代器 line.rbegin()rcomma 转换为从前向后移动的普通迭代器。其实没必要转换 line.rbegin(),因为我们知道转换的结果必定是 line.end()。只需调用所有反向迭代器类型都提供的成员函数 base 转换 rcomma 即可:

Figure 11.2. Relationship between Reverse and Ordinary Iterators
图 11.2. 反向迭代器与普通迭代器之间的区别

     // ok: get a forward iterator and read to end of line
     cout << string(rcomma.base(), line.end()) << endl;

Given the same preceeding input, this statement prints LAST as expected.

假设还是前面给出的输入,该语句将如愿输出 LAST。

The objects shown in Figure 11.2 visually illustrate the relationship between ordinary and reverse iterators. For example, rcomma and rcomma.base() refer to different elements, as do line.rbegin() and line.end(). These differences are needed to ensure that the range of elements whether processed forward or backward is the same. Technically speaking, the relationship between normal and reverse iterators is designed to accommodate the properties of a left-inclusive range (Section 9.2.1, p. 314), so that [line.rbegin(), rcomma) and [rcomma.base(), line.end()) refer to the same elements in line.

图 11.2 显示的对象直观地解释了普通迭代器与反向迭代器之间的关系。例如,正如 line_rbegin() 和 line.end() 一样,rcommarcomma.base() 也指向不同的元素。为了确保正向和反向处理元素的范围相同,这些区别必要的。从技术上来说,设计普通迭代器与反向迭代器之间的关系是为了适应左闭合范围(第 9.2.1 节)这个性质的,所以,[line.rbegin(), rcomma)[rcomma.base(), line.end()) 标记的是 line 中的相同元素。

The fact that reverse iterators are intended to represent ranges and that these ranges are asymmetric has an important consequence. When we initialize or assign a reverse iterator from a plain iterator, the resulting iterator does not refer to the same element as the original.

反向迭代器用于表示范围,而所表示的范围是不对称的,这个事实可推导出一个重要的结论:使用普通的迭代器对反向迭代器进行初始化或赋值时,所得到的迭代器并不是指向原迭代器所指向的元素。

Exercises Section 11.3.3

Exercise 11.19:

Write a program that uses reverse_iterators to print the contents of a vector in reverse order.

编写程序使用 reverse_iterator 对象以逆序输出 vector 容器对象的内容。

Exercise 11.20:

Now print the elements in reverse order using ordinary iterators.

现在,使用普通的迭代器逆序输出上题中对象的元素。

Exercise 11.21:

Use find to find the last element in a list of ints with value 0.

使用 find 在一个 int 型的 list 中寻找值为 0 的最后一个元素。

Exercise 11.22:

Given a vector that has 10 elements, copy the elements from position 3 through 7 in reverse order to a list.

假设有一个存储了 10 个元素的 vector 对象,将其中第 3 个至第 7 个位置上的元素以逆序复制给 list 对象。

11.3.4. const Iterators

11.3.4. const 迭代器

Careful readers will have noted that in the program on page 392 that used find, we defined result as a const_iterator. We did so because we did not intend to use the iterator to change a container element.

细心的读者可能已经注意到,在第 11.1 节使用 find 的程序中,我们将 result 定义为 const_iterator 类型。这样做是因为我们不希望使用这个迭代器来修改容器中的元素。

On the other hand, we used a plain, nonconst iterator to hold the return from find_first_of on page 397, even though we did not intend to change any container elements in that program either. The difference in treatment is subtle and deserves an explanation.

另一方面,虽然第 11.2.1 节的程序也不打算改变容器内的任何元素,但是它却使用了普通的非 const 迭代器来保存 find_first_of 的返回值。这两种处理存在细微的差别,值得解释一下。

The reason is that in the second case, we use the iterator as an argument to find_first_of:

原因是,在第二个例子中,程序将迭代器用作 find_first_of 的实参:

     find_first_of(it, roster1.end(),
                   roster2.begin(), roster2.end())

The input range for this call is specified by it and the iterator returned from a call to roster1.end(). Algorithms require the iterators that denote a range to have exactly the same type. The iterator returned by roster1.end() depends on the type of roster1. If that container is a const object, then the iterator is const_iterator; otherwise, it is the plain iterator type. In this program, roster1 was not const, and so end returns an iterator.

该函数调用的输入范围由 it 和调用 roster1.end() 返回的迭代器指定。算法要求用于指定范围的两个迭代器必须具有完全一样的类型。roster1.end() 返回的迭代器依赖于 roster1 的类型。如果该容器是 const 对象,则返回的迭代器是 const_iterator 类型;否则,就是普通的 iterator 类型。在这个程序中,roster1 不是 const 对象,因而 end 返回的只是一个普通的迭代器。

If we defined it as a const_iterator, the call to find_first_of would not compile. The types of the iterators used to denote the range would not have been identical. it would have been a const_iterator, and the iterator returned by roster1.end() would be iterator.

如果我们将 it 定义为 const_iterator,那么 find_first_of 的调用将无法编译。用来指定范围的两个迭代器的类型不相同。itconst_iterator 类型的对象,而 rotser1.end() 返回的则是一个 iterator 对象。

11.3.5. The Five Iterator Categories

11.3.5. 五种迭代器

Iterators define a common set of operations, but some iterators are more powerful than other iterators. For example, ostream_iterators support only increment, dereference, and assignment. Iterators on vectors support these operations and the decrement, relational, and arithmetic operators as well. As a result, we can classify iterators based on the set of operations they provide.

迭代器定义了常用的操作集,但有些迭代器具有比其他迭代器更强大的功能。例如 ostream_iterator 只支持自增、解引用和赋值运算,而 vector 容器提供的迭代器除了这些运算,还支持自减、关系和算术运算。因此,迭代器可根据所提供的操作集进行分类。

Similarly, we can categorize algorithms by the kinds of operations they require from their iterators. Some, such as find, require only the ability to read through the iterator and to increment it. Others, such as sort, require the ability to read, write, and randomly access elements. The iterator operations required by the algorithms are grouped into five categories. These five categories correspond to five categories of iterators, which are summarized in Table 11.3.

类似地,还可根据算法要求它的迭代器提供什么类型的操作,对算法分类。有一些算法,例如 find,只要求迭代器提供读取所指向内容和自增的功能。另一些算法,,比如 sort,则要求其迭代器有读、写和随机访问元素的能力。算法要求的迭代器操作分为五个类别,分别对应表 11.3 列出的五种迭代器。

Table 11.3. Iterator Categories
表 11.3. 迭代器种类

Input iterator(输入迭代器)

Read, but not write; increment only

读,不能写;只支持自增运算

Output iterator(输出迭代器)

Write, but not read; increment only

写,不能读;只支持自增运算

Forward iterator(前向迭代器)

Read and write; increment only

读和写;只支持自增运算

Bidirectional iterator(双向迭代器)

Read and write; increment and decrement

读和写;支持自增和自减运算

Random access iterator(随机访问迭代器)

Read and write; full iterator arithmetic

读和写;支持完整的迭代器算术运算

  1. Input iterators can read the elements of a container but are not guaranteed to be able to write into a container. An input iterator must provide the following minimum support:

    输入迭代器可用于读取容器中的元素,但是不保证能支持容器的写入操作。输入迭代器必须至少提供下列支持。

    • Equality and inequality operators (==, !=) to compare two iterators.

      相等和不等操作符(==!=),比较两个迭代器。

    • Prefix and postfix increment (++) to advance the iterator.

      前置和后置的自增运算(++),使迭代器向前递进指向下一个元素。

    • Dereference operator (*) to read an element; dereference may appear only on the right-hand side of an assignment.

      用于读取元素的解引用操作符(*),此操作符只能出现在赋值运算的右操作数上。

    • The arrow operator (->) as a synonym for (*it).member that is, dereference the iterator and fetch a member from the underlying object.

      箭头操作符(->),这是 (*it).member 的同义语,也就是说,对迭代器进行解引用来获取其所关联的对象的成员。

    Input iterators may be used only sequentially; there is no way to examine an element once the input iterator has been incremented. Generic algorithms requiring only this level of support include find and accumulate. The library istream_iterator type is an input iterator.

    输入迭代器只能顺序使用;一旦输入迭代器自增了,就无法再用它检查之前的元素。要求在这个层次上提供支持的泛型算法包括 findaccumulate。标准库 istream_iterator 类型输入迭代器。

  2. Output iterators can be thought of as having complementary functionality to input iterators; An output iterator can be used to write an element but it is not guaranteed to support reading. Output iterators require:

    输出迭代器 可视为与输入迭代器功能互补的迭代器;输出迭代器可用于向容器写入元素,但是不保证能支持读取容器内容。输出迭代器要求:

    • Prefix and postfix increment (++) to advance the iterator.

      前置和后置的自增运算(++),使迭代器向前递进指向下一个元素。

    • Dereference (*), which may appear only as the left-hand side of an assignment. Assigning to a dereferenced output iterator writes to the underlying element.

      解引用操作符(*),引操作符只能出现在赋值运算的左操作数上。给解引用的输出迭代器赋值,将对该迭代器所指向的元素做写入操作。

    Output iterators may require that each iterator value must be written exactly once. When using an output iterator, we should use * once and only once on a given iterator value. Output iterators are generally used as a third argument to an algorithm and mark the position where writing should begin. For example, the copy algorithm takes an output iterator as its third parameter and copies elements from its input range to the destination indicated by the output iterator. The ostream_iterator type is an output iterator.

    输出迭代器可以要求每个迭代器的值必须正好写入一次。使用输出迭代器时,对于指定的迭代器值应该使用一次 * 运算,而且只能用一次。输出迭代器一般用作算法的第三个实参,标记起始写入的位置。例如,copy 算法使用一个输出迭代器作为它的第三个实参,将输入范围内的元素复制到输出迭代器指定的目标位置。标准库 ostream_iterator 类型输出迭代器。

  3. Forward iterators read from and write to a given container. They move in only one direction through the sequence. Forward iterators support all the operations of both input iterators and output iterators. In addition, they can read or write the same element multiple times. We can copy a forward iterator to remember a place in the sequence so as to return to that place later. Generic algorithms that require a forward iterator include replace.

    前向迭代器 用于读写指定的容器。这类迭代器只会以一个方向遍历序列。前向迭代器支持输入迭代器和输出迭代器提供的所有操作,除此之外,还支持对同一个元素的多次读写。可复制前向迭代器来记录序列中的一个位置,以便将来返回此处。需要前向迭代器的泛型算法包括 replace

  4. Bidirectional iterators read from and write to a container in both directions. In addition to supporting all the operations of a forward iterator, a bidirectional iterator also supports the prefix and postfix decrement (--) operators. Generic algorithms requiring a bidirectional iterator include reverse. All the library containers supply iterators that at a minimum meet the requirements for a bidirectional iterator.

    双向迭代器 从两个方向读写容器。除了提供前向迭代器的全部操作之外,双向迭代器还提供前置和后置的自减运算(--)。需要使用双向迭代器的泛型算法包括 reverse。所有标准库容器提供的迭代器都至少达到双向迭代器的要求。

  5. Random-access iterators provide access to any position within the container in constant time. These iterators support all the functionality of bidirectional iterators. In addition, random-access iterators support:

    随机访问迭代器 提供在常量时间内访问容器任意位置的功能。这种迭代器除了支持双向迭代器的所有功能之外,还支持下面的操作:

    • The relational operators <, <=, >, and >= to compare the relative positions of two iterators.

      关系操作符 <<=>>=,比较两个迭代器的相对位置。

    • Addition and subtraction operators +, +=, -, and -= between an iterator and an integral value. The result is the iterator advanced (or retreated) the integral number of elements within the container.

      迭代器与整型数值 n 之间的加法和减法操作符 ++=--=,结果是迭代器在容器中向前(或退回)n 个元素。

    • The subtraction operator - when applied to two iterators, which yields the distance between two iterators.

      两个迭代器之间的减法操作符(--),得到两个迭代器间的距离。

    • The subscript operator iter[n] as a synonym for *(iter + n).

      下标操作符 iter[n],这是 *(iter + n) 的同义词。

    Generic algorithms requiring a random-access iterator include the sort algorithms. The vector, deque, and string iterators are random-access iterators, as are pointers when used to access elements of a built-in array.

    需要随机访问迭代器的泛型算法包括 sort 算法。vectordequestring 迭代器是随机访问迭代器,用作访问内置数组元素的指针也是随机访问迭代器。

With the exception of output iterators, the iterator categories form a sort of hierarchy: Any iterator of a higher category can be used where an iterator of lesser power is required. We can call an algorithm requiring an input iterator with an input iterator or a forward, bidirectional, or random-access iterator. Only a random-access iterator may be passed to an algorithm requiring a random-access iterator.

除了输出迭代器,其他类别的迭代器形成了一个层次结构:需要低级类别迭代器的地方,可使用任意一种更高级的迭代器。对于需要输入迭代器的算法,可传递前向、双向或随机访问迭代器调用该算法。调用需要随机访问迭代器的算法时,必须传递随机访问迭代器。

The map, set, and list types provide bidirectional iterators. Iterators on string, vector, and deque are random-access iterators, as are pointers bound to arrays. An istream_iterator is an input iterator, and an ostream_iterator is an output iterator.

mapsetlist 类型提供双向迭代器,而 stringvectordeque 容器上定义的迭代器都是随机访问迭代器都是随机访问迭代器,用作访问内置数组元素的指针也是随机访问迭代器。istream_iterator 是输入迭代器,而 ostream_iterator 则是输出迭代器。

Key Concept: Associative Containers and the Algorithms

关键概念:关联容器与算法

Although the map and set types provide bidirectional iterators, we can use only a subset of the algorithms on associative containers. The problem is that the key in an associative container is const. Hence, any algorithm that writes to elements in the sequence cannot be used on an associative container. We may use iterators bound to associative containers only to supply arguments that will be read.

尽管 mapset 类型提供双向迭代器,但关联容器只能使用算法的一个子集。问题在于:关联容器的键是 const 对象。因此,关联容器不能使用任何写序列元素的算法。只能使用与关联容器绑在一起的迭代器来提供用于读操作的实参。

When dealing with the algorithms, it is best to think of the iterators on associative containers as if they were input iterators that also support decrement, not as full bidirectional iterators.

在处理算法时,最好将关联容器上的迭代器视为支持自减运算的输入迭代器,而不是完整的双向迭代器。

The C++ standard specifies the minimum iterator category for each iterator parameter of the generic and numeric algorithms. For example, findwhich implements a one-pass, read-only traversal over a containerminimally requires an input iterator. The replace function requires a pair of iterators that are at least forward iterators. The first two iterators to replace_copy must be at least forward. The third, which represents a destination, must be at least an output iterator.

C++ 标准为所有泛型和算术算法的每一个迭代器形参指定了范围最小的迭代器种类。例如,find(以只读方式单步遍历容器)至少需要一个输入迭代器。replace 函数至少需要一对前向迭代器。replace_copy 函数的头两个迭代器必须至少是前向迭代器,第三个参数代表输出目标,必须至少是输出迭代器。

For each parameter, the iterator must be at least as powerful as the stipulated minimum. Passing an iterator of a lesser power results in an error; passing an stronger iterator type is okay.

对于每一个形参,迭代器必须保证最低功能。将支持更少功能的迭代器传递给函数是错误的;而传递更强功能的迭代器则没问题。

Errors in passing an invalid category of iterator to an algorithm are not guaranteed to be caught at compile-time.

向算法传递无效的迭代器类别所引起的错误,无法保证会在编译时被捕获到。

Exercises Section 11.3.5

Exercise 11.23:

List the five iterator categories and the operations that each supports.

列出五种迭代器类型及其各自支持的操作。

Exercise 11.24:

What kind of iterator does a list have? What about a vector?

list 容器拥有什么类型的迭代器?而 vector 呢?

Exercise 11.25:

What kinds of iterators do you think copy requires? What about reverse or unique?

你认为 copy 算法需要使用哪种迭代器?而 reverseunique 呢?

Exercise 11.26:

Explain why each of the following is incorrect. Identify which errors should be caught during compilation.

解释下列代码错误的原因,指出哪些错误可以在编译时捕获。

     (a) string sa[10];
         const vector<string> file_names(sa, sa+6);
         vector<string>::iterator it = file_names.begin()+2;

     (b) const vector<int> ivec;
         fill(ivec.begin(), ivec.end(), ival);

     (c) sort(ivec.begin(), ivec.rend());

     (d) sort(ivec1.begin(), ivec2.end());
Team LiB
Previous Section Next Section