The relational operators take operands of arithmetic or pointer type; the logical operators take operands of any type that can be converted to bool
. These operators all return values of type bool
. Arithmetic and pointer operand(s) with a value of zero are false
; all other values are true
. The operands to these operators are rvalues and the result is an rvalue.
Table 4.2. Logical and Relational Operators
The overall result of the logical AND operator is true
if and only if both its operands evaluate to true
. The logical OR (||
) operator evaluates as true
if either of its operands evaluates as true
.
The logical AND and OR operators always evaluate their left operand before the right. Moreover, the right operand is evaluated if and only if the left operand does not determine the result. This strategy is known as short-circuit evaluation:
• The right side of an
&&
is evaluated if and only if the left side istrue
.
• The right side of an
||
is evaluated if and only if the left side isfalse
.
Several of the programs in Chapter 3 used the logical AND operator. Those programs used the left-hand operand to test whether it was safe to evaluate the right-hand operand. For example, the for
condition on page 94:
index != s.size() && !isspace(s[index])
first checks that index
has not reached the end of its associated string
. We’re guaranteed that the right operand won’t be evaluated unless index
is in range.
As an example that uses the logical OR, imagine we have some text in a vector
of string
s. We want to print the string
s, adding a newline after each empty string
or after a string
that ends with a period. We’ll use a range-based for
loop (§ 3.2.3, p. 91) to process each element:
// note s as a reference to const; the elements aren't copied and can't be changed
for (const auto &s : text) { // for each element in text
cout << s; // print the current element
// blank lines and those that end with a period get a newline
if (s.empty() || s[s.size() - 1] == '.')
cout << endl;
else
cout << " "; // otherwise just separate with a space
}
After we print the current element, we check to see if we need to print a newline. The condition in the if
first checks whether s
is an empty string
. If so, we need to print a newline regardless of the value of the right-hand operand. Only if the string
is not empty do we evaluate the second expression, which checks whether the string
ends with a period. In this expression, we rely on short-circuit evaluation of ||
to ensure that we subscript s
only if s
is not empty.
It is worth noting that we declared s
as a reference to const
(§ 2.5.2, p. 69). The elements in text
are string
s, and might be large. By making s
a reference, we avoid copying the elements. Because we don’t need to write to the elements, we made s
a reference to const
.
The logical NOT operator (!
) returns the inverse of the truth value of its operand. We first used this operator in § 3.2.2 (p. 87). As another example, assuming vec
is a vector
of int
s, we might use the logical NOT operator to see whether vec
has elements by negating the value returned by empty
:
// print the first element in vec if there is one
if (!vec.empty())
cout << vec[0];
The subexpression
!vec.empty()
evaluates as true
if the call to empty
returns false
.
The relational operators (<
, <=
, >
, <=
) have their ordinary meanings and return bool
values. These operators are left associative.
Because the relational operators return bool
s, the result of chaining these operators together is likely to be surprising:
// oops! this condition compares k to the bool result of i < j
if (i < j < k) // true if k is greater than 1!
This condition groups i
and j
to the first <
operator. The bool
result of that expression is the left-hand operand of the second less-than operator. That is, k
is compared to the true
/false
result of the first comparison! To accomplish the test we intended, we can rewrite the expression as follows:
// ok: condition is true if i is smaller than j and j is smaller than k
if (i < j && j < k) { /* ... */ }
bool
LiteralsIf we want to test the truth value of an arithmetic or pointer object, the most direct way is to use the value as a condition:
if (val) { /* ... */ } // true if val is any nonzero value
if (!val) { /* ... */ } // true if val is zero
In both conditions, the compiler converts val
to bool
. The first condition succeeds so long as val
is nonzero; the second succeeds if val
is zero.
We might think we could rewrite a test of this kind as
if (val == true) { /* ... */ } // true only if val is equal to 1!
There are two problems with this approach. First, it is longer and less direct than the previous code (although admittedly when first learning C++ this kind of abbreviation can be perplexing). Much more importantly, when val
is not a bool
, this comparison does not work as expected.
If val
is not a bool
, then true
is converted to the type of val
before the ==
operator is applied. That is, when val
is not a bool
, it is as if we had written
if (val == 1) { /* ... */ }
As we’ve seen, when a bool
is converted to another arithmetic type, false
converts to 0
and true
converts to 1
(§ 2.1.2, p. 35). If we really cared whether val
was the specific value 1
, we should write the condition to test that case directly.
It is usually a bad idea to use the boolean literals
true
andfalse
as operands in a comparison. These literals should be used only to compare to an object of typebool
.
Exercises Section 4.3
Exercise 4.8: Explain when operands are evaluated in the logical AND, logical OR, and equality operators.
Exercise 4.9: Explain the behavior of the condition in the following
if
:const char *cp = "Hello World";
if (cp && *cp)Exercise 4.10: Write the condition for a
while
loop that would readint
s from the standard input and stop when the value read is equal to42
.Exercise 4.11: Write an expression that tests four values,
a
,b
,c
, andd
, and ensures thata
is greater thanb
, which is greater thanc
, which is greater thand
.Exercise 4.12: Assuming
i
,j
, andk
are allint
s, explain whati != j < k
means.