EXPLORATION 11

image

Increment and Decrement

The previous Exploration introduced the increment (++) operator to advance an iterator. This operator works on numeric types as well. Not surprisingly, it has a decrement counterpart: --. This Exploration takes a  closer look at these operators, which appear so often, they are part of the language name.

image Note  I know that you C, Java, etc. programmers have been waiting for this Exploration ever since I wrote i = i + 1 in Exploration 7. As you saw in the previous Exploration, the ++ operator means more in C++ than what you’re used to. That’s why I waited until now to introduce it.

Increment

The ++ operator is familiar to C, Java, Perl, and many other programmers. C was the first widespread language to introduce this operator to mean “increment” or “add 1.” C++ expanded the usage it inherited from C; the standard library uses the ++ operator in several new ways, such as advancing an iterator (as you saw in the previous Exploration).

The increment operator comes in two flavors: prefix and postfix. The best way to understand the difference between these two flavors is with a demonstration, as shown in Listing 11-1.

Listing 11-1.  Demonstrating the Difference Between Prefix and Postfix Increment

#include <iostream>
 
int main()
{
  int x{42};
 
  std::cout << "x   = " << x   << " ";
  std::cout << "++x = " << ++x << " ";
  std::cout << "x   = " << x   << " ";
  std::cout << "x++ = " << x++ << " ";
  std::cout << "x   = " << x   << " ";
}

Predict the output of the program.

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

What is the actual output?

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

Explain the difference between prefix (++x) and postfix (x++) increment.

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

Described briefly, the prefix operator increments the variable first: the value of the expression is the value after incrementing. The postfix operator saves the old value, increments the variable, and uses the old value as the value of the expression.

As a general rule, use prefix instead of postfix, unless you need the postfix functionality. Rarely is the difference significant, but the postfix operator must save a copy of the old value, which might impose a small performance cost. If you don’t have to use postfix, why pay that price?

Decrement

The increment operator has a decrement counterpart: --. The decrement operator subtracts one instead of adding one. Decrement also has a prefix and postfix flavor. The prefix operator pre-decrements, and the postfix operator post-decrements.

You can increment and decrement any variable with a numeric type; however, only some iterators permit decrement.

For example, write iterators move forward only. You can use the increment operator (prefix or postfix), but not decrement. Test this for yourself. Write a program that uses std::ostream_iterator and try to use the decrement operator on the iterator. (If you need a hint, look at Listing 10-4. Save the ostream_iterator object in a variable. Then use the decrement operator. It doesn’t matter that the program makes no sense; it won’t get past the compiler anyway.)

What error message do you get?

_____________________________________________________________

_____________________________________________________________

Different compilers issue different messages, but the essence of the message should be that the -- operator is not defined. If you need help with the program, see Listing 11-2.

Listing 11-2.  Erroneous Program That Applies Decrement to an Output Iterator

#include <iostream>
#include <iterator>
#include <vector>
 
int main()
{
  std::vector<int> data{ 10, 42 };
  std::ostream_iterator<int> out{ std::ostream_iterator<int>(std::cout, "") };
  std::copy(data.begin(), data.end(), out);
  --out;
 
}

A vector’s iterators allow increment and decrement. Using increment and decrement operators on iterators, write a program that reads integers from the standard input into a vector, reverses the order of the vector, and writes the result. (No fair peeking in a language reference and using the std::reverse algorithm. Use two iterators: one pointing to the start of the vector and the other pointing to the end. Stop the loop when the iterators meet. Make sure they don’t pass each other, and make sure your program does not try to dereference the one-past-the-end iterator.)

Test your program on input with both an even and an odd number of integers. Compare your program with the one in Listing 11-3.

Listing 11-3.  Reversing the Input Order

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
 
int main()
{
  std::vector<int> data{};
  int x{};
  while (std::cin >> x)
    data.push_back(x);
 
  for (std::vector<int>::iterator start{data.begin()}, end{data.end()};
       start != end;
       /*empty*/)
  {
    --end;
    if (start != end)
    {
      std::iter_swap(start, end);
      ++start;
    }
  }
 
  std::copy(data.begin(), data.end(), std::ostream_iterator<int>(std::cout, " "));
}

The start iterator points to the beginning of the data vector, and end initially points to one past the end. If the vector is empty, the for loop terminates without executing the loop body. Then the loop body decrements end so that it points to an actual element of the vector. If the vector contains an even number of elements, the if condition is true, so std::iter_swap is called, and the program advances start one position.

As the name implies, std::iter_swap swaps the values stored at two iterators, in this case, the iterators start and end. You can think of it as performing the following:

int tmp = *start;
*start = *end;
*end = tmp;

Notice that the program is careful to compare start != end after each increment or decrement operation. If the program had only one comparison, it would be possible for start and end to pass each other. The loop condition would never be true, and the program would exhibit undefined behavior, so the sky would fall, the earth would swallow me, or worse.

Also note how the for loop has an empty postiteration part. The iteration logic appears in different places in the loop body, which is not the preferred way to write a loop but is necessary in this case.

You can rewrite the loop, so the postiteration logic appears only in the loop header. Some programmers argue that distributing the increment and decrement in the loop body makes the loop harder to understand and, in particular, harder to prove the loop terminates correctly. On the other hand, cramming everything in the loop header makes the loop condition especially tricky to understand, as you can see in Listing 11-4.

Listing 11-4.  Rewriting the for Loop

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
 
int main()
{
  std::vector<int> data{};
  int x{};
  while (std::cin >> x)
    data.push_back(x);
 
  for (std::vector<int>::iterator start{data.begin()}, end{data.end()};
       start != end and start != --end;
       ++start)
  {
      std::iter_swap(start, end);
  }
 
  std::copy(data.begin(), data.end(), std::ostream_iterator<int>(std::cout, " "));
}

To keep all the logic in the loop header, it was necessary to use a new operator: and. You will learn more about this operator in the next Exploration; meanwhile, just believe that it implements a logical and operation and keep reading.

Most experienced C++ programmers will probably prefer Listing 11-4, whereas most beginners will probably prefer Listing 11-3. Hiding a decrement in the middle of a condition makes the code harder to read and understand. It’s too easy to overlook the decrement. As you gain experience with C++, however, you will become more comfortable with increments and decrements, and Listing 11-4 will start to grow on you.

image Note  I prefer Listing 11-3 over Listing 11-4. I really don’t like to bury increment and decrement operators in the middle of a complicated condition.

So what else would experienced C++ programmers do? Because they have broader knowledge of the C++ standard library, they would make better use of it. In particular, they would use the std::reverse algorithm, which reverses the elements in a range.

std::reverse(data.begin(), data.end());

Another idea is to use istream_iterator, which you learned about in Exploration 9. This time, you will use it a little differently. Instead of using back_inserter (introduced in the previous Exploration) and the copy algorithm, Listing 11-5 calls the insert member function, which copies values from any iterator range into the vector. The values are inserted before the position given by the first argument (data.end(), in this case).

Listing 11-5.  Taking Advantage of the Standard Library

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
 
int main()
{
  std::vector<int> data{};
 
  // Read integers from the standard input, and append them to the end of data.
  data.insert(data.end(),
              std::istream_iterator<int>(std::cin), std::istream_iterator<int>());
 
  // Reverse the order of the contents of data.
  std::reverse(data.begin(), data.end());
  
 // Print data, one number per line.
  std::copy(data.begin(), data.end(), std::ostream_iterator<int>(std::cout, " "));
}

As you learn more C++, you will find other aspects of this program that lend themselves to improvement. I encourage you to revisit old programs and see how your new techniques can often simplify the programming task. I’ll do the same as I revisit examples throughout this book.

Listing 11-4 introduced the and operator. The next Exploration takes a closer look at this operator, as well as other logical operators and their use in conditions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset