Logic and more on operators¶
The bool
type¶
Apart from numbers and text, there is one more fundamental type in C++: The
truth value. They have the type name bool
(which is short for Boolean
value, named after the mathematician George Boole) and can have one of two
values: true
or false
. Booleans are crucial to every program, as they
allow to make decisions, as we will see in later sections of this chapter.
The syntax for declaring, assigning, and so on is the same as for other types.
The only difference is that the literals are the keywords true
and false
instead of numbers or quoted strings. cout
and cin
, however, will
print/accept “0” and “1” (mainly for historical reasons).
#include <iostream>
int main()
{
std::cout << "true : " << true << "\n";
std::cout << "false: " << false << "\n";
std::cout << "\n";
auto is_valid = true; // is_valid gets the type bool.
std::cout << "Valid? " << is_valid << "\n";
is_valid = false;
std::cout << "Still valid? " << is_valid << "\n";
std::cout << "\n";
bool some_bool;
std::cout << "Enter a boolean value (0 for false, 1 for true): ";
std::cin >> some_bool;
std::cout << "You entered " << some_bool << ".\n";
}
Example output (with input “0”):
true : 1
false: 0
Valid? 1
Still valid? 0
Enter a boolean value (0 for false, 1 for true): 0
You entered 0.
“Calculating” with Booleans¶
There exist three useful operations which you can perform on Booleans. The first
one is logical negation aka not with the unary !
operator. It
makes true
values false
and vice versa (it is true if x is not true):
x | !x |
---|---|
false | true |
true | false |
The second one is logical conjunction aka and. The binary &&
operator results in true if and only if [1] both of its operands are
true
(if x and y are true):
x | y | x && y |
---|---|---|
false | false | false |
false | true | false |
true | false | false |
true | true | true |
The third one is logical disjunction aka (inclusive) or. The binary
||
operator is true if at least one operand is true
. Note that this is
also true if both operands are true
; that is, it’s not an “either x or y”,
(exclusive or) it’s “x or y or both” (inclusive or).
x | y | x || y |
---|---|---|
false | false | false |
false | true | true |
true | false | true |
true | true | true |
In composite expressions like x || y && !z
, !
is evaluated first (in
general, unary operators are evaluated before binary ones in C++) and &&
is
evaluated before ||
. That is, the aforementioned expression has the same
meaning as x || (y && (!z))
.
The following program demonstrates some uses of these operators:
#include <iostream>
int main()
{
auto has_armor = false;
auto has_sword = true;
std::cout << "In need of equipment: " << !(has_armor || has_sword) << "\n";
bool quest_complete;
std::cout << "Have you found the potion yet? ";
std::cin >> quest_complete;
bool is_member;
std::cout << "Are you a member of the guild? ";
std::cin >> is_member;
auto paid_bribe = false;
std::cout << "Ready for boss fight: "
<< (has_armor && has_sword && quest_complete) << "\n";
std::cout << "Gets offered guild items: "
<< (is_member && (quest_complete || paid_bribe)) << "\n";
}
Note that we had to put the expressions in parentheses for output. That’s
because the compiler would otherwise try to generate instructions that evaluate
<<
first, reading the code like e.g.
(std::cout << "Ready for boss fight: " << has_armor)
&& has_sword && (quest_complete << "\n");
which is, of course, bogus and leads to a compile error.
Comparison operators¶
We already saw the arithmetic operators +
, -
, *
and /
on
numbers. These took two numbers, converted it to a common type if necessary,
and returned a number of that common type. There is, however, another category
of operators which are used to compare numbers. Like the arithmetic operators,
they take two numbers, converting to a common type if necessary. However, they
do not return a number, but a truth value: a bool
. Here they are:
- The equality operators:
==
(that’s a double equals sign) returnstrue
if its operands are the same. E.g.1 == 1
yieldstrue
, while1 == 2
yields false.!=
means not-equal (≠) and is the opposite of==
: it returnstrue
if its operands are not the same. E.g.1 != 2
returnstrue
, while1 != 1
is false.
- The relational operators:
- Less-than
<
returnstrue
if its left-hand side operand is smaller than its right-hand side operand. E.g.1 < 2
is true, while2 < 1
and1 < 1
are both false. - Less-than-or-equal
<=
tests if the right-hand side is at least as big as the left-hand side:1 <= 2
and1 <= 1
aretrue
, while2 <= 1
is not. - Greater-than
>
tests if the left-hand side is greater than the right-hand side. E.g.2 > 1
is true, while1 > 2
and1 > 1
aren’t. It always returns the opposite of<=
(and not of<
, as one might think; this is not a quirk, it’s perfectly, mathematically logical). - Greater-than-or-equal
>=
tests if the left-hand side is at least as big as the right-hand side:2 >= 1
and1 >= 1
aretrue
, while1 >= 2
is not. It always returns the opposite of<
.
- Less-than
Warning
Don’t confuse the equality test operator ==
with the assignment
operator =
which is written with just a single equals sign. Often, both
will compile because all basic types can be converted to booleans (true
when not equal to zero) and =
returns the assigned-to variable. The
following will always print 1
and set a
to 1
(the integer
value of true
):
std::cout << (a = 42 || some_condition);
Warning
Be careful when comparing floating point values for equality!
Due to their inexactness, this can have unexpected results. The usual way to
deal with this (if you cannot avoid equality comparison) is to check if the
difference between two numbers is below a certain threshold (e.g. 0.00001
)
instead.
Warning
Comparison operators can’t be chained. While mathematically e.g.
the expression \(0 < x < m\) is true iff \(x\) is between \(0\)
and \(m\) exclusively, in C++ 0 < x < m
would compare the boolean
result of the expression 0 < x
to m
. Sadly, due to bool
being
convertible to (other) numeric types, this and similar expressions compile
(with a warning you would hopefully have enabled and notice!) but certainly
dont’t do what you want.
This example program let’s you enter two numbers and outputs the results of all comparison operators on them as a table:
#include <iostream>
int main()
{
// Get numbers from user:
int x;
std::cout << "Enter x: ";
std::cin >> x;
int y;
std::cout << "Enter y: ";
std::cin >> y;
std::cout << "\n";
// Test equality:
std::cout << "x == y? " << (x == y) << "\n";
std::cout << "x != y? " << (x != y) << "\n";
std::cout << "\n";
// Print relation table:
std::cout << "Relation: < <= > >=\n";
std::cout << "------------------------\n";
std::cout << "x OP y? | "
<< (x < y) << " " << (x <= y) << " "
<< (x > y) << " " << (x >= y) << "\n";
std::cout << "y OP x? | "
<< (y < x) << " " << (y <= x) << " "
<< (y > x) << " " << (y >= x) << "\n";
}
Example output for inputs x = 1 and y = 2:
Enter x: 1
Enter y: 2
x == y? 0
x != y? 1
Relation: < <= > >=
------------------------
x OP y? | 1 1 0 0
y OP x? | 0 0 1 1
Example output for x and y both 1:
Enter x: 1
Enter y: 1
x == y? 1
x != y? 0
Relation: < <= > >=
------------------------
x OP y? | 0 1 0 1
y OP x? | 0 1 0 1
Mixed expressions and more on operators¶
Before we go on with using Booleans to influence which parts of our programs execute, you should know some more things about operators and expressions.
It is entirely possible and often useful do arithmetic calculations in expressions with comparison operators and use logical connectives on their Boolean results:
#include <iostream>
int main()
{
const auto monster_level = 12;
int player_level;
std::cout << "Enter your level: ";
std::cin >> player_level;
int sword_strength;
std::cout << "Enter your sword's strength: ";
std::cin >> sword_strength;
bool is_player_in_god_mode;
std::cout << "Are you a cheater? ";
std::cin >> is_player_in_god_mode;
auto winning = is_player_in_god_mode
|| player_level + sword_strength > monster_level;
std::cout << "You beat the monster: " << winning << "\n";
}
Example output with player level 5 and sword strength 6 (no cheating):
Enter your level: 5
Enter your sword's strength: 6
Are you a cheater? 0
You beat the monster: 0
Note that operators are evaluated in this order:
- Unary operators (unary
-
and!
) - Arithmetic operators (first
*
,/
and%
then+
and-
) - “Input and output” operators (
>>
and<<
) - Comparison operators (first
<
,<=
,>
and>=
then==
and!=
) - Logical connectives (first
&&
, then||
) - Ternary conditional operator
? :
(see later section) - Assignment operator (
=
) and compound assignment operators (+=
,-=
,*=
,/=
and%=
)
That is, the initialization expression of winning
has the same meaning as
is_player_in_god_mode
|| ((player_level + sword_strength) > monster_level)
C++’s order of operator evaluation is intended to allow omitting parentheses in most cases; however, I recommend using better too many than too few parentheses: not everyone can always correctly remember the order of operator evaluation and parentheses make the order of evaluation unambiguous to the reader.
How early an operator is evaluated in relation to other operators is a property that has its own name: the precedence (level) of an operator. The higher the level, the earlier an operator is evaluated.
Operator associativity and “multi-assignment”¶
As the complement to its precedence, there is also the associativity of
a binary operator: It tells you in which order operators of the same
precedence level are evaluated (thus all operators of the same precedence
level must have the same associativity). Most operators we know yet are
left-associative; that is they are evaluated from left to right. For example
the expression x - y - z
is evaluated as (x - y) - z
. If -
was
right-associative, this expression would be evaluated as x - (y - z)
.
The only right-associative operators we have seen until now are the assignment
operators. Thus the expression x = y = z
is evaluated as x = (y = z)
.
What does it mean?
The assignment operator does two things (in this order): first, it (obviously)
assigns the right-hand side operand to the variable on the left-hand side. And
second, it returns the assigned-to variable. Thus x = y = z;
is the same as
y = z; x = y;
: x
and y
both get the value of z
. If =
was
left-associative, x = y = z
would be evaluated as (x = y) = z
, which is
the same as x = y; x = z;
, leaving y
unchanged and assigning to x
twice in a row. Fortunately, this is not the case. You can thus use statements
of the form
a = b /* = … */ = z = some_expression;
to assign the value some_expression
to all of the variables a
through
z
.
The same that has been said about =
also applies to the compound assignment
operators +=
, -=
, *=
, /=
and %=
. However, while a = b =
c
has an intuitive and easy-to-read meaning, a += b += c
does not. I thus
recommend using either one or multiple “simple” assignment operators =
or
only one compound assignment operator.
Note
If you should ever come across a situation where the multiple compound assignments would make the code a lot shorter, this would be a good opportunity for a comment, like:
// Calculate a1 as the sum of a1..a9;
// ai will contain the intermediate value a9 + a 8 + ... + ai
a1 += a2 += a3 += a4 += a5 += a6 += a7 += a8 += a9;
However, the above would most certainly be better written using two concepts we will cover later, namely loops and arrays.
Background: The input/output operators (>>
/<<
)¶
The statement
std::cout << "x = " << x << "\n";
consists solely of an expression: The <<
s are the operators and
std::cout
, "x = "
, x
and "\n"
are the operands. Because <<
is
left-associative, this expression means the same as
((std::cout << "x = ") << x) << "\n";
The trick here is that std::cout
is an object of a special type (a
std::ostream
, i.e. output stream [2]) for which the <<
is defined
as “Output the ritht-hand side operand (on the stream on the left-hand side) and
then return the left-hand side”. Thus, the expression std::cout << "x = "
returns std::cout
after printing "x = "
, and what is left of the
original expression is equivalent to
(std::cout << x) << "\n";
this process continues with std::cout << x
returning std::cout
(after
printing x
‘ value) leaving just
std::cout << "\n";
This prints the linebreak and then returns std::cout
again, but this time
the expression’s value is not used, which is legal in C++: you can even write
statements like 1 + 2;
as-is in your source code and it will compile and do
nothing (this is one more thing where (at least some) compilers can warn you;
more often than not, such pointless statements result from typing mistakes or
copy & paste errors).
Analogously the same applies to std::cin
(which is of type
std::istreaam
, i.e. input stream) and the >>
operator; that is you can
read more than one variable in one expression (and thus statement), like this:
int x, y;
std::cout << "Enter the x and y coordinates: ";
std::cin >> x >> y;
The ternary conditional operator ? :
¶
Let’s start with an example program that uses the ternary operator:
#include <iostream>
int main()
{
auto const correct_pin = 1234;
unsigned pin;
std::cout << "Enter PIN: ";
std::cin >> pin;
std::cout << (pin == correct_pin ? "OK" : "Invalid!") << '\n';
}
Example outputs:
Enter PIN: 1234
OK
Enter PIN: 2334
Invalid!
That is, the ternary operator has the following syntax:
boolean_expression ? true_expression : false_expression
In fact ternary means just that the operator has three operands (the succession is thus unary: 1, binary: 2, ternary: 3 operands) ‒ this is an unique property in C++. The operator works as follows:
First, evaluate boolean_expression
. Then, if it was true, evaluate and yield
true_expression
. Otherwise evaluate and yield false_expression
.
true_expression
and false_expression
must thus be convertible to a
common type. For numbers the same rules as for the arithmetic operators apply,
e.g. condition ? 3 : 0.4
will have the type double
; it does not matter
which value gets actually selected by condition
since, as you already know,
types are determined at compile time.
The ? :
is right associative and it’s precedence is just above the
assignment operators [3]. It is also right associative, so that
expressions containing multiple ?:
s have a useful meaning. E.g. the
following:
auto num_str = x == 1 ? "one" :
x == 2 ? "two" :
x == 3 ? "three" :
"many";
means the same as:
auto num_str = x == 1 ? "one" :
(x == 2 ? "two" :
(x == 3 ? "three" :
"many"));
I.e. if x == 1
set num_str
to "one"
, otherwise if x == 2
to
"two"
, otherwise if x == 3
to "three"
, otherwise to "many"
.
Summary¶
- The
bool
(ean) type can store the truth valuestrue
andfalse
. - For
std::cin
andstd::cout
,true
is1
andfalse
is0
. - The unary logical negation operator
!
makestrue
false
and vice versa. - The and-operator
x && y
yieldstrue
iffx
andy
are bothtrue
. - The or-operator
x || y
yieldstrue
ifx
ory
or both aretrue
. - Comparison operators take numeric values and yield boolean ones.
- The equality comparison operators are
==
for “equals” and!=
for “not equals” (≠). Don’t confuse equality comparison==
and assignment=
! - The relational comparison operators are
<
(less-than),>
(greater-than),<=
(less-than-or-equal) and>=
(greater-than-or-equal). - Operators are evaluated in order of their precedence. Operators of the same precedence are evaluated according to their associativity (left or right).
=
can be used multiple times in the same expression to assign multiple variables the same value (e.g.x = y = 42
).- The ternary conditional operator
c ? t : f
evaluates tot
ifc
istrue
and tof
otherwise. It is right associative and can thus be composed likec1 ? t1 : c2 ? t2 : f
, meaningt1
ifc1
, otherwiset2
ifc2
, otherwisef
.
Footnotes
[1] | The phrase “if and only if” is so common in mathematics and logic that it is shortened to the single word iff. |
[2] | You can imagine a stream as a pipeline where you put data in at one
end (e.g. your program or the keyboard) and it comes out at the other end
(e.g. the screen or in the variable given to std::cin ). |
[3] | However, in c ? t : f the t is “parenthesized” between the
? and the : . |