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.