3.3. More logical expressions
This chapter has already shown how C makes no distinction between
‘logical’ and other values. The relational operators all give a
result of 0
or 1
for false and true,
respectively. Whenever the control of flow statements demand it, an
expression is evaluated to determine what to do next. A 0
means ‘don't do it’; anything else means ‘do’. It means that
the fragments below are all quite reasonable.
while (a<b)... while (a).... if ( (c=getchar()) != EOF )...
No experienced C programmer would be surprised by any of them. The second
of them, while (a)
, is a common abbreviation for
while (a != 0)
, as you should be able to work
out.
What we need now is a way of writing more complicated expressions
involving these logical true and false values. So far, it has to be done
like this, when we wanted to say
if(a<b AND c<d)
if (a < b){ if (c < d)... }
It will not be a source of great amazement to find that there is a way of expressing such a statement.
There are three operators involved in this sort of operation: the logical
AND &&
, the logical OR ||
and
the NOT !
. The last is unary, the other two are binary.
All of them take expressions as their operands and give as results
either 1
or 0
.
The &&
gives 1
only when both
of its operands are non-zero. The ||
gives 0
only when both operands are zero.
The !
gives 0
if its operand is
non-zero and vice versa. Easy really. The results are of type
int
for all three.
Do not confuse &
and |
(the bitwise
operators) with their logical counterparts. They are not the same.
One special feature of the logical operators, found in very few of the
other operators, is their effect on the sequence of evaluation of an
expression. They evaluate left to right (after precedence is taken into
account) and every logical expression ceases evaluation as soon as the
overall result can be determined. For example, a sequence of
||
s can stop as soon as one operand is found to be non-zero.
This next fragment guarantees never to divide by zero.
if (a!=0 && b/a > 5)... /* alternative */ if (a && b/a > 5)
In either version b/a
will only be evaluated if a is
non-zero. If a were zero, the overall result would already have been
decided, so the evaluation must stop to conform with C's rules for the
logical operators.
The unary NOT is simple. It isn't all that common to see it in use largely because most expresssions can be rearranged to do without it. The examples show how.
if (!a)... /* alternative */ if (a==0)... if(!(a>b)) /* alternative */ if(a <= b) if (!(a>b && c<d))... /* alternative */ if (a<=b || c>=d)...
Each of the examples and the alternatives serve to show ways of avoiding
(or at least doing without) the !
operator. In fact, it's most
useful as an aid to readability. If the problem that you are solving has a
natural logical relationship inherent in it—say the (b*b-4*a*c)
> 0
found in quadratic equation solving—then it probably
reads better if you write if( !((b*b-4*a*c) > 0))
than
if( (b*b-4*a*c) <= 0)
—but it's up to you. Pick the one
that feels right.
Most expressions using these logical operators work out just about right in terms of the precedence rules, but you can get a few nasty surprises. If you look back to the precedence tables, you will find that there are some operators with lower precedence than the logical ones. In particular, this is a very common mistake:
if(a&b == c){...
What happens is that b
is compared for equality with
c
, then the 1
or 0
result is anded
with a!
Some distinctly unexpected behaviour has been caused
by that sort of error.