“Contrariwise,” continued Tweedledee. “If it was so, it might be; and if it were so, it would be; but as it isn’t, it ain’t. That’s logic.”
LEWIS CARROLL, Through the Looking-Glass
A Boolean expression is an expression that can be thought of as being true or false (that is, true if satisfied or false if not satisfied). Thus far you have used Boolean expressions as the test condition in if-else
statements and as the controlling expression in loops, such as a while
loop. However, a Boolean expression has an independent identity apart from any if-else
statement or loop statement you might use it in. The C++ type bool
provides you the ability to declare variables that can carry the values true
and false
.
A Boolean expression can be evaluated in the same way that an arithmetic expression is evaluated. The only difference is that an arithmetic expression uses operations such as +, *, and / and produces a number as the final result, whereas a Boolean expression uses relational operations such as == and < and Boolean operations such as &&, ||, and ! to produce one of the two values true
and false
as the final result. Note that ==, !=, <, <=, and so forth operate on pairs of any built-in type to produce a Boolean value true
or false
.
If you understand the way Boolean expressions are evaluated, you will be able to write and understand complex Boolean expressions and be able to use Boolean expressions for the value returned by a function.
First let’s review evaluating an arithmetic expression; the same technique will work to evaluate Boolean expressions. Consider the following arithmetic expression:
(x + 1) * (x + 3)
Assume that the variable x
has the value 2
. To evaluate this arithmetic expression, you evaluate the two sums to obtain the numbers 3
and 5
, then you combine these two numbers 3
and 5
using the *
operator to obtain 15
as the final value. Notice that in performing this evaluation, you do not multiply the expressions (x + 1)
and (x + 3)
. Instead, you multiply the values of these expressions. You use 3
; you do not use (x + 1)
. You use 5
; you do not use (x + 3)
.
The computer evaluates Boolean expressions the same way. Subexpressions are evaluated to obtain values, each of which is either true
or false
. These individual values of true
or false
are then combined according to the rules in the tables shown in Display 3.1. For example, consider the Boolean expression
!( ( y < 3) || (y > 7) )
which might be the controlling expression for an if-else
statement or a while
statement. Suppose the value of y
is 8
. In this case, (y < 3)
evaluates to false
and (y > 7)
evaluates to true
, so the Boolean expression above is equivalent to
!(false || true)
Consulting the tables for ||
(which is labeled OR in Display 3.1), the computer sees that the expression inside the parentheses evaluates to true
. Thus, the computer sees that the entire expression is equivalent to
!(true)
Consulting the tables again, the computer sees that !(true) evaluates to false
, and so it concludes that false
is the value of the original Boolean expression.
Almost all the examples we have constructed thus far have been fully parenthesized to show exactly how each &&
, ||
, and !
is used to construct an expression. Parentheses are not always required. If you omit parentheses, the default precedence is as follows: perform !
first, then evaluate relational operators such as <
, then evaluate &&
, and then evaluate ||
. However, it is a good practice to include most parentheses in order to make the expression easier to understand. One place where parentheses can safely be omitted is a simple string of &&
’s or ||
’s (but not a mixture of the two). The following expression is acceptable in terms of both the C++ compiler and readability:
(temperature > 90) && (humidity > 0.90) && (pool_gate == OPEN)
Since the relational operations >
and ==
are evaluated before the &&
operation, you could omit the parentheses in the expression above and it would have the same meaning, but including some parentheses makes the expression easier to read.
When parentheses are omitted from an expression, the computer groups items according to rules known as precedence rules. Some of the precedence rules for C++ are given in Display 3.2. If one operation is evaluated before another, the operation that is evaluated first is said to have higher precedence. Binary operations of equal precedence are evaluated in left-to-right order. Unary operations of equal precedence are evaluated in right-to-left order. A complete set of precedence rules is given in Appendix 2.
Notice that the precedence rules include both arithmetic operators such as +
and *
as well as Boolean operators such as &&
and ||
. This is because many expressions combine arithmetic and Boolean operations, as in the following simple example:
(x + 1) > 2 || (x + 1) < −3
If you check the precedence rules given in Display 3.2, you will see that this expression is equivalent to
((x + 1) > 2) || ((x + 1) < −3)
because >
and <
have higher precedence than ||
. In fact, you could omit all the parentheses in the expression above and it would have the same meaning, although it would be harder to read. Although we do not advocate omitting all the parentheses, it might be instructive to see how such an expression is interpreted using the precedence rules. Here is the expression without any parentheses:
x + 1 > 2 || x + 1 < −3
The precedence rules say first apply the unary −
, then apply the +
signs, then do the >
and the <
, and finally do the ||
, which is exactly what the fully parenthesized version says to do.
The preceding description of how a Boolean expression is evaluated is basically correct, but in C++, the computer actually takes an occasional shortcut when evaluating a Boolean expression. Notice that in many cases you need to evaluate only the first of two subexpressions in a Boolean expression. For example, consider the following:
(x >= 0) && (y > 1)
If x
is negative, then (x >= 0)
is false
, and as you can see in the tables in Display 3.1, when one subexpression in an &&
expression is false
, then the whole expression is false
, no matter whether the other expression is true
or false
. Thus, if we know that the first expression is false
, there is no need to evaluate the second expression. A similar thing happens with ||
expressions. If the first of two expressions joined with the ||
operator is true
, then you know the entire expression is true
, no matter whether the second expression is true
or false
. The C++ language uses this fact to sometimes save itself the trouble of evaluating the second subexpression in a logical expression connected with an &&
or an ||
. C++ first evaluates the leftmost of the two expressions joined by an &&
or an ||
. If that gives it enough information to determine the final value of the expression (independent of the value of the second expression), then C++ does not bother to evaluate the second expression. This method of evaluation is called short-circuit evaluation.
Some languages, other than C++, use complete evaluation. In complete evaluation, when two expressions are joined by an &&
or an ||
, both subexpressions are always evaluated and then the truth tables are used to obtain the value of the final expression.
Both short-circuit evaluation and complete evaluation give the same answer, so why should you care that C++ uses short-circuit evaluation? Most of the time you need not care. As long as both subexpressions joined by the &&
or the ||
have a value, the two methods yield the same result. However, if the second subexpression is undefined, you might be happy to know that C++ uses short-circuit evaluation.
Let’s look at an example that illustrates this point. Consider the following statement:
if ( (kids != 0) && ((pieces/kids) >= 2) )
cout << "Each child may have two pieces!";
If the value of kids
is not zero, this statement involves no subtleties. However, suppose the value of kids
is zero and consider how short-circuit evaluation handles this case. The expression (kids != 0)
evaluates to false
, so there would be no need to evaluate the second expression. Using short-circuit evaluation, C++ says that the entire expression is false
, without bothering to evaluate the second expression. This prevents a run-time error, since evaluating the second expression would involve dividing by zero.
C++ sometimes uses integers as if they were Boolean values. In particular, C++ converts the integer 1
to true
and converts the integer 0
to false
. The situation is even a bit more complicated than simply using 1
for true
and 0
for false
. The compiler will treat any nonzero number as if it were the value true
and will treat 0
as if it were the value false
. As long as you make no mistakes in writing Boolean expressions, this conversion causes no problems and you usually need not even be aware of it. However, when you are debugging, it might help to know that the compiler is happy to combine integers using the Boolean operators &&
, ||
, and !
.
Determine the value, true
or false
, of each of the following Boolean expressions, assuming that the value of the variable count
is 0
and the value of the variable limit
is 10
. Give your answer as one of the values true
or false
.
(count == 0) && (limit < 20)
count == 0 && limit < 20
(limit > 20) || (count < 5)
!(count == 12)
(count == 1) && (x < y)
(count < 10) || (x < y)
!( ((count < 10) || (x < y)) && (count >= 0) )
((limit/count) > 7) || (limit < 20)
(limit < 20) || ((limit/count) > 7)
((limit/count) > 7) && (limit < 0)
(limit < 0) && ((limit/count) > 7)
(5 && 7) + (!6)
Name two kinds of statements in C++ that alter the order in which actions are performed. Give some examples.
In college algebra we see numeric intervals given as
2 < x < 3
In C++ this interval does not have the meaning you may expect. Explain and give the correct C++ Boolean expression that specifies that x
lies between 2
and 3
.
Does the following sequence produce division by zero?
j = −1;
if((j > 0) && (1/(j + 1) > 10)) cout << i << endl;
An enumeration type is a type whose values are defined by a list of constants of type int
. An enumeration type is very much like a list of declared constants.
When defining an enumeration type, you can use any int
values and can have any number of constants defined in an enumeration type. For example, the following enumeration type defines a constant for the length of each month:
enum MonthLength { JAN_LENGTH = 31, FEB_LENGTH = 28,
MAR_LENGTH = 31, APR_LENGTH = 30, MAY_LENGTH = 31,
JUN_LENGTH = 30, JUL_LENGTH = 31, AUG_LENGTH = 31,
SEP_LENGTH = 30, OCT_LENGTH = 31, NOV_LENGTH = 30,
DEC_LENGTH = 31 };
As this example shows, two or more named constants in an enumeration type can receive the same int
value.
If you do not specify any numeric values, the identifiers in an enumeration-type definition are assigned consecutive values beginning with 0
. For example, the type definition
enum Direction { NORTH = 0, SOUTH = 1, EAST = 2, WEST = 3 };
is equivalent to
enum Direction { NORTH, SOUTH, EAST, WEST };
The form that does not explicitly list the int
values is normally used when you just want a list of names and do not care about what values they have.
If you initialize only some enumeration constant to some values, say
enum MyEnum { ONE = 17, TWO, THREE, FOUR = −3, FIVE };
then ONE
takes the value 17
, TWO
takes the next int
value 18
, THREE
takes the next value 19
, FOUR
takes −3
, and FIVE
takes the next value, −2
.
In short, the default for the first enumeration constant is 0
. The rest increase by 1 unless you set one or more of the enumeration constants.
C++11 introduced a new version of enumerations called strong enums or enum classes that avoids some problems of conventional enums. For example, you may not want an enum to act as an integer. Additionally, enums are global in scope so you can’t have the same enum value twice. To define a strong enum, add the word class
after enum. You can qualify an enum value by providing the enum name followed by two colons followed by the value. For example:
enum class Days { Sun, Mon, Tue, Wed };
enum class Weather { Rain, Sun };
Days d = Days::Tue;
Weather w = Weather::Sun;
The variables d
and w
are not integers so we can’t treat them as such. For example, it would be illegal to check if (d == 0)
whereas this is legal in a traditional enum. It is legal to check if (d == Days::Sun)
.