Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

// Iterate over all tuples

for (std::map<char,int>::iterator it = mymap.begin(); it != mymap.end(); ++it) std::cout << it->first << " => " << it->second << '\n';

Output:

a => 200 b => 100 c => 300

Section 9.4: Reverse Iterators

If we want to iterate backwards through a list or vector we can use a reverse_iterator. A reverse iterator is made from a bidirectional, or random access iterator which it keeps as a member which can be accessed through base().

To iterate backwards use rbegin() and rend() as the iterators for the end of the collection, and the start of the collection respectively.

For instance, to iterate backwards use:

std::vector<int> v{1, 2, 3, 4, 5};

for (std::vector<int>::reverse_iterator it = v.rbegin(); it != v.rend(); ++it)

{

cout << *it; } // prints 54321

A reverse iterator can be converted to a forward iterator via the base() member function. The relationship is that the reverse iterator references one element past the base() iterator:

std::vector<int>::reverse_iterator r = v.rbegin(); std::vector<int>::iterator i = r.base();

assert(&*r == &*(i-1)); // always true if r, (i-1) are dereferenceable // and are not proxy iterators

+---

+---

+---

+

---+---

+---

+

---+

| | 1 | 2 | 3 | 4 | 5 | |

+---

+---

+---

+---

+---

+---

+---

+

 

 

 

 

|

|

 

 

 

|

 

|

rend() |

 

 

rbegin()

 

end()

 

|

 

 

 

 

 

rbegin().base()

 

begin()

 

 

 

 

 

rend().base()

In the visualization where iterators mark positions between elements, the relationship is simpler:

+

---+

---+

---+

---+

---+

| 1 | 2 | 3 | 4 | 5 |

+---

+---

+---

+---

+---

+

|

|

|

end()

|

rbegin()

begin()

rbegin().base()

rend()

 

rend().base()

 

GoalKicker.com – C++ Notes for Professionals

39

Section 9.5: Stream Iterators

Stream iterators are useful when we need to read a sequence or print formatted data from a container:

// Data stream. Any number of various whitespace characters will be OK. std::istringstream istr("1\t 2 3 4");

std::vector<int> v;

// Constructing stream iterators and copying data from stream into vector. std::copy(

//Iterator which will read stream data as integers. std::istream_iterator<int>(istr),

//Default constructor produces end-of-stream iterator. std::istream_iterator<int>(),

std::back_inserter(v));

//Print vector contents.

std::copy(v.begin(), v.end(),

//Will print values to standard output as integers delimeted by " -- ". std::ostream_iterator<int>(std::cout, " -- "));

The example program will print 1 -- 2 -- 3 -- 4 -- to standard output.

Section 9.6: C Iterators (Pointers)

// This creates an array with 5 values. const int array[] = { 1, 2, 3, 4, 5 };

#ifdef BEFORE_CPP11

//You can use `sizeof` to determine how many elements are in an array. const int* first = array;

const int* afterLast = first + sizeof(array) / sizeof(array[0]);

//Then you can iterate over the array by incrementing a pointer until

//it reaches past the end of our array.

for (const int* i = first; i < afterLast; ++i) { std::cout << *i << std::endl;

}

#else

// With C++11, you can let the STL compute the start and end iterators: for (auto i = std::begin(array); i != std::end(array); ++i) {

std::cout << *i << std::endl;

}

#endif

This code would output the numbers 1 through 5, one on each line like this:

1

2

3

4

GoalKicker.com – C++ Notes for Professionals

40

5

Breaking It Down

const int array[] = { 1, 2, 3, 4, 5 };

This line creates a new integer array with 5 values. C arrays are just pointers to memory where each value is stored together in a contiguous block.

const int* first = array;

const int* afterLast = first + sizeof(array) / sizeof(array[0]);

These lines create two pointers. The first pointer is given the value of the array pointer, which is the address of the first element in the array. The sizeof operator when used on a C array returns the size of the array in bytes. Divided by the size of an element this gives the number of elements in the array. We can use this to find the address of the block after the array.

for (const int* i = first; i < afterLast; ++i) {

Here we create a pointer which we will use as an iterator. It is initialized with the address of the first element we want to iterate over, and we'll continue to iterate as long as i is less than afterLast, which means as long as i is pointing to an address within array.

std::cout << *i << std::endl;

Finally, within the loop we can access the value our iterator i is pointing to by dereferencing it. Here the dereference operator * returns the value at the address in i.

Section 9.7: Write your own generator-backed iterator

A common pattern in other languages is having a function that produces a "stream" of objects, and being able to use loop-code to loop over it.

We can model this in C++ as

template<class T>

struct generator_iterator {

using difference_type=std::ptrdiff_t; using value_type=T;

using pointer=T*; using reference=T;

using iterator_category=std::input_iterator_tag; std::optional<T> state;

std::function< std::optional<T>() > operation;

//we store the current element in "state" if we have one: T operator*() const {

return *state;

}

//to advance, we invoke our operation. If it returns a nullopt

//we have reached the end:

generator_iterator& operator++() { state = operation();

return *this;

}

generator_iterator operator++(int) { auto r = *this;

GoalKicker.com – C++ Notes for Professionals

41

++(*this); return r;

}

// generator iterators are only equal if they are both in the "end" state:

friend bool operator==( generator_iterator const& lhs, generator_iterator const& rhs ) { if (!lhs.state && !rhs.state) return true;

return false;

}

friend bool operator!=( generator_iterator const& lhs, generator_iterator const& rhs ) { return !(lhs==rhs);

}

// We implicitly construct from a std::function with the right signature: generator_iterator( std::function< std::optional<T>() > f ):operation(std::move(f))

{

if (operation)

state = operation();

}

// default all special member functions: generator_iterator( generator_iterator && ) =default; generator_iterator( generator_iterator const& ) =default;

generator_iterator& operator=( generator_iterator && ) =default; generator_iterator& operator=( generator_iterator const& ) =default; generator_iterator() =default;

};

live example.

We store the generated element early so we can more easily detect if we are already at the end.

As the function of an end generator iterator is never used, we can create a range of generator iterators by only copying the std::function once. A default constructed generator iterator compares equal to itself, and to all other end-generator-iterators.

GoalKicker.com – C++ Notes for Professionals

42