EXPLORATION 12

image

Conditions and Logic

You first met the bool type in Exploration 2. This type has two possible values: true and false, which are reserved keywords (unlike in C). Although most Explorations have not needed to use the bool type, many have used logical expressions in loop and if-statement conditions. This Exploration examines the many aspects of the bool type and logical operators.

I/O and bool

C++ I/O streams permit reading and writing bool values. By default, streams treat them as numeric values: true is 1 and false is 0. The manipulator std::boolalpha (declared in <ios>, so you get it for free from <iostream>) tells a stream to interpret bool values as words. By default, the words are true and false. (In Exploration 18, you’ll discover how to use a language other than English.) You use the std::boolalpha manipulator the same way you do any other manipulator (as you saw in Exploration 8). For an input stream, use an input operator with the manipulator.

Write a program that demonstrates how C++ formats and prints  bool values, numerically and textually.

Compare your program with Listing 12-1.

Listing 12-1.  Printing bool Values

#include <iostream>
 
int main()
{
  std::cout << "true=" << true << ' ';
  std::cout << "false=" << false << ' ';
  std::cout << std::boolalpha;
  std::cout << "true=" << true << ' ';
  std::cout << "false=" << false << ' ';
}

How do you think C++ handles bool values for input?

_____________________________________________________________

Write a program to test your assumptions. Were you correct? ________________ Explain how an input stream handles bool input:

_____________________________________________________________

_____________________________________________________________

By default, when an input stream has to read a bool value, it actually reads an integer, and if the integer’s value is 1, the stream interprets that as true. The value 0 is false, and any other value results in an error.

With the std::boolalpha manipulator, the input stream requires the exact text true or false. Integers are not allowed, nor are any case differences. The input stream accepts only those exact words.

Use the std::noboolalpha manipulator to revert to the default numeric Boolean values. Thus, you can mix alphabetic and numeric representations of bool in a single stream, as follows:

bool a{true}, b{false};
std::cin >> std::boolalpha >> a >> std::noboolalpha >> b;
std::cout << std::boolalpha << a << ' ' << std::noboolalpha << b;

Reading and writing bool values does not actually occur all that often in most programs.

Boolean Type

C++ automatically converts many different types to bool, so you can use integers, I/O stream objects, and other values whenever you need a bool, such as in a loop or if-statement condition. You can see this for yourself in Listing 12-2.

Listing 12-2.  Automatic Type Conversion to bool

 1 #include <iostream>
 2
 3 int main()
 4 {
 5   if (true)        std::cout << "true ";
 6   if (false)       std::cout << "false ";
 7   if (42)          std::cout << "42 ";
 8   if (0)           std::cout << "0 ";
 9   if (42.4242)     std::cout << "42.4242 ";
10   if (0.0)         std::cout << "0.0 ";
11   if (-0.0)        std::cout << "-0.0 ";
12   if (-1)          std::cout << "-1 ";
13   if ('')        std::cout << "'\0' ";
14   if ('1')        std::cout << "'\1' ";
15   if ("1")         std::cout << ""1" ";
16   if ("false")     std::cout << ""false" ";
17   if (std::cout)   std::cout << "std::cout ";
18   if (std::cin)    std::cout << "std::cin ";
19 }

Predict the output from Listing 12-2:

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

Check your answer. Were you right? ________________

You may have been fooled by lines 15 and 16. C++ does not interpret the contents of string literals to decide whether to convert the string to true or false. All character strings are true, even empty ones. (The C++ language designers did not do this to be perverse. There’s a good reason that strings are true, but you will have to learn quite a bit more C++ in order to understand why.)

On the other hand, character literals (lines 13 and 14) are completely different from string literals. The compiler converts the escape character '', which has numeric value zero, to false. All other characters are true.

Recall from many previous examples (especially in Exploration 3) that loop conditions often depend on an input operation. If the input succeeds, the loop condition is true. What is actually happening is that C++ knows how to convert a stream object (such as std::cin) to bool. Every I/O stream keeps track of its internal state, and if any operation fails, the stream remembers that fact. When you convert a stream to bool, if the stream is in a failure state, the result is false. Not all complex types can be converted to bool, however.

What do you expect to happen when you compile and run Listing 12-3?

_____________________________________________________________

Listing 12-3.  Converting a std::string to bool

#include <iostream>
#include <string>
 
int main()
{
  std::string empty{};
 
  if (empty)
    std::cout << "empty is true ";
  else
    std::cout << "empty is false ";
 
 
 
}

The compiler reports an error, because it does not know how to convert std::string to bool.

image Note  Although an istream knows how to convert an input string to bool, the std::string type lacks the information it needs to interpret the string. Without knowledge of the string’s context, it is unrealistic to ask a string to interpret text, such as “true,” “vrai,” or “richtig.”

What about std::vector? Do you think C++ defines a conversion from std::vector to bool? ________________. Write a program to test your hypothesis. What is your conclusion?

_____________________________________________________________

This is another case in which no general solution is feasible. Should an empty vector be false, whereas all others are true? Maybe a std::vector<bool> that contains only false elements should be false. Only the application programmer can make these decisions, so the C++ library designers wisely chose not to make them for you; therefore, you cannot convert std::vector to bool. However, there are ways of obtaining the desired result by calling member functions.

Logic Operators

Real-world conditions are often more complicated than merely converting a single value to bool. To accommodate this, C++ offers the usual logical operators: and, or, and not (which are reserved keywords). They have their usual meaning from mathematical logic, namely that and is false, unless both operands are true, or is true, unless both operands are false, and not inverts the value of its operand.

More important, however, is that the built-in and and or operators do not evaluate their right-hand operand, unless they have to. The and operator must evaluate its right-hand operand, only if the left-hand operand is true. (If the left-hand operand is false, the entire expression is false, and there is no need to evaluate the right-hand operand.) Similarly, the or operator evaluates its right-hand operand only if the left-hand operand is true. Stopping the evaluation early like this is known as short-circuiting.

For example, suppose you are writing a simple loop to examine all the elements of a vector to determine whether they are all equal to zero. The loop ends when you reach the end of the vector or when you find an element not equal to zero.

Write a program that reads numbers into a vector, searches the vector for a nonzero element, and prints a message about whether the vector is all zero.

You can solve this problem without using a logical operator, but try to use one, just for practice. Take a look at Listing 12-4, to see one way to solve this problem.

Listing 12-4.  Using Short-Circuiting to Test for Nonzero Vector Elements

 1 #include <iostream>
 2 #include <iterator>
 3 #include <vector>
 4
 5 int main()
 6 {
 7   std::vector<int> data{};
 8   data.insert(data.begin(),
 9               std::istream_iterator<int>(std::cin),
10               std::istream_iterator<int>());
11
12   std::vector<int>::iterator iter{}, end{data.end()};
13   for (iter = data.begin(); iter != end and *iter == 0; ++iter)
14     /*empty*/;
15   if (iter == data.end())
16     std::cout << "data contains all zeroes ";
17   else
18     std::cout << "data does not contain all zeroes ";
19 }

Line 13 is the key. The iterator advances over the vector and tests for zero-valued elements.

What happens when the iterator reaches the end of the vector?

_____________________________________________________________

The condition iter != data.end() becomes false at the end of the vector. Because of short-circuiting, C++ never evaluates the *iter == 0 part of the expression, which is good.

Why is this good? What would happen if short-circuiting did not take place?

_____________________________________________________________

_____________________________________________________________

Imagine that iter != end is false; in other words, the value of iter is end. That means *iter is just like *end, which is bad—really bad. You are not allowed to dereference the one-past-the-end iterator. If you are lucky, it would crash your program. If you are unlucky, your program would continue to run, but with completely unpredictable and erroneous data, and, therefore, unpredictable and erroneous results.

Short-circuiting guarantees that C++ will not evaluate *iter when iter equals end, which means iter will always be valid when the program dereferences it, which is good. Some languages (such as Ada) use different operators for short-circuiting and non–short-circuiting operations. C++ does not. The built-in logical operators always perform short-circuiting, so you never accidentally use non–short-circuiting when you intended to use the short-circuiting operator.

Old-Fashioned Syntax

The logical operators have symbolic versions: && for and, || for or, and ! for not. The keywords are clearer, easier to read, easier to understand, and less error-prone. That’s right, less error-prone. You see, && means and, but & is also an operator. Similarly, | is a valid operator. Thus, if you accidentally write & instead of &&, your program will compile and even run. It might seem to run correctly for a while, but it will eventually fail, because & and && mean different things. (You’ll learn about & and | later in this book.) New C++ programmers aren’t the only ones to make this mistake. I’ve seen highly experienced C++ programmers write & when they mean &&, or | instead of ||. Avoid this error by using only the keyword logical operators.

I was hesitant about even mentioning the symbolic operators, but I can’t ignore them. Many C++ programs use the symbolic operators instead of the keyword equivalents. These C++ programmers, having grown up with the symbols, prefer to continue to use the symbols rather than the keywords. This is your chance to become a trendsetter. Eschew the old-fashioned, harder-to-read, harder-to-understand, error-prone symbols and embrace the keywords.

Comparison Operators

The built-in comparison operators always yield bool results, regardless of their operands. You have already seen == and != for equality and inequality. You also saw < for less than, and you can guess that > means greater than. Similarly, you probably already know that <= means less than or equal and >= means greater than or equal.

These operators produce the expected results when you use them with numeric operands. You can even use them with vectors of numeric types.

Write a program that demonstrates how < works with a vector of int. (If you’re having trouble writing the program, take a look at Listing 12-5.) What are the rules that govern < for a vector?

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

Listing 12-5.  Comparing Vectors

#include <iostream>
#include <vector>
 
int main()
{
   std::vector<int> a{ 10, 20, 30 },  b{ 10, 20, 30 };
 
   if (a != b) std::cout << "wrong: a != b ";
   if (a < b)  std::cout << "wrong: a < b ";
   if (a > b)  std::cout << "wrong: a > b ";
   if (a == b) std::cout << "okay: a == b ";
   if (a >= b) std::cout << "okay: a >= b ";
   if (a <= b) std::cout << "okay: a <= b ";
 
   a.push_back(40);
   if (a != b) std::cout << "okay: a != b ";
   if (a < b)  std::cout << "wrong: a < b ";
   if (a > b)  std::cout << "okay: a > b ";
   if (a == b) std::cout << "wrong: a == b ";
   if (a >= b) std::cout << "okay: a >= b ";
   if (a <= b) std::cout << "wrong: a <= b ";
 
   b.push_back(42);
   if (a != b) std::cout << "okay: a != b ";
   if (a < b)  std::cout << "okay: a < b ";
   if (a > b)  std::cout << "wrong: a > b ";
   if (a == b) std::cout << "wrong: a == b ";
   if (a >= b) std::cout << "wrong: a >= b ";
   if (a <= b) std::cout << "okay: a <= b ";
}

C++ compares vectors at the element level. That is, the first elements of two vectors are compared. If one element is smaller than the other, its vector is considered less than the other. If one vector is a prefix of the other (that is, the vectors are identical up to the length of the shorter vector), the shorter vector is less than the longer one.

C++ uses the same rules when comparing std::string types, but not when comparing two character string literals.

Write a program that demonstrates how C++ compares two std::string objects by comparing their contents.

Compare your solution with mine in Listing 12-6.

Listing 12-6.  Demonstrating How C++ Compares Strings

#include <iostream>
#include <string>
 
int main()
{
   std::string a{"abc"}, b{"abc"};
   if (a != b) std::cout << "wrong: abc != abc ";
   if (a < b)  std::cout << "wrong: abc < abc ";
   if (a > b)  std::cout << "wrong: abc > abc ";
   if (a == b) std::cout << "okay: abc == abc ";
   if (a >= b) std::cout << "okay: abc >= abc ";
   if (a <= b) std::cout << "okay: abc <= abc ";
 
   a.push_back('d'),
   if (a != b) std::cout << "okay: abcd != abc ";
   if (a < b)  std::cout << "wrong: abcd < abc ";
   if (a > b)  std::cout << "okay: abcd > abc ";
   if (a == b) std::cout << "wrong: abcd == abc ";
   if (a >= b) std::cout << "okay: abcd >= abc ";
   if (a <= b) std::cout << "wrong: abcd <= abc ";
 
   b.push_back('e'),
   if (a != b) std::cout << "okay: abcd != abce ";
   if (a < b)  std::cout << "okay: abcd < abce ";
   if (a > b)  std::cout << "wrong: abcd > abce ";
   if (a == b) std::cout << "wrong: abcd == abce ";
   if (a >= b) std::cout << "wrong: abcd >= abce ";
   if (a <= b) std::cout << "okay: abcd <= abce ";
}

Testing how C++ compares quoted string literals is more difficult. Instead of using the contents of the string, the compiler uses the location of the strings in memory, which is a detail of the compiler’s internal workings and has no bearing on anything practical. Thus, unless you know how the compiler works, you cannot predict how it will compare two quoted strings. In other words, don’t do that. Make sure you create std::string objects before you compare strings. It’s okay if only one operand is std::string. The other can be a quoted string literal, and the compiler knows how to compare the std::string with the literal, as demonstrated in the following example:

if ("help" > "hello") std::cout << "Bad. Bad. Bad. Don’t do this!
";
if (std::string("help") > "hello") std::cout << "this works ";
if ("help" > std::string("hello")) std::cout << "this also works ";
if (std::string("help") > std::string("hello")) std::cout << "and this works ";

The next Exploration does not relate directly to Boolean logic and conditions. Instead, it shows how to write compound statements, which you need in order to write any kind of useful conditional statement.

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

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