(Some examples taken from Eric

Roberts’ The Art and Science of C)

Section 10/27/1999

Today’s Goals:

0. Logical operators

1.  Cascading If Else statement

2.  Switch statement

In addition to relation operators (<, >, >=, <=, ==, !=), there are three logical operators that take Boolean operands to form new Boolean values.

!operand Logical not

operand1 & operand2 Logical and

operand1 || operand2 Logical or

!operand is TRUE only if operand is FALSE

operand1 & operand2 is TRUE only if both operands are TRUE

operand1 || operand2 is TRUE if at least one operand is TRUE

Here are the corresponding truth tables for these logical operators:

Let p and q represent Boolean values.

p / q / p & q / p || q
FALSE / FALSE / FALSE / FALSE
FALSE / TRUE / FALSE / TRUE
TRUE / FALSE / FALSE / TRUE
TRUE / TRUE / TRUE / TRUE
p / !p
TRUE / FALSE
FALSE / TRUE

So how would you express “x is not equal to either 2 or 3”

if (x != 2 || x != 3)... IS WRONG

The expression (x != 2 || x != 3) is TRUE if either x != 2 is TRUE or x != 3 is TRUE, or if x != 2 and x != 3 are TRUE.

Let’s rephrase our expression to be equivalently “x is not equal to 2 and x is not equal to 3.”

This can be expressed as:

if (x != 2 & x != 3)

Practice with Booleans:

By De Morgan’s laws,

!(p || q) is the same as !p & !q

!(p & q) is the same as !p || !q

We can prove each law by creating the truth tables for each expression:

p / q / p || q / !(p || q)
FALSE / FALSE / FALSE / TRUE
FALSE / TRUE / TRUE / FALSE
TRUE / FALSE / TRUE / FALSE
TRUE / TRUE / TRUE / FALSE
p / q / !p / !q / !p & !q
FALSE / FALSE / TRUE / TRUE / TRUE
FALSE / TRUE / TRUE / FALSE / FALSE
TRUE / FALSE / FALSE / TRUE / FALSE
TRUE / TRUE / FALSE / FALSE / FALSE

Since the truth values of !(p || q) are equivalent to the truth values of !p & !q, the two expressions are equivalent.

p / q / p & q / !(p & q)
FALSE / FALSE / FALSE / TRUE
FALSE / TRUE / FALSE / TRUE
TRUE / FALSE / FALSE / TRUE
TRUE / TRUE / TRUE / FALSE
p / q / !p / !q / !p || !q
FALSE / FALSE / TRUE / TRUE / TRUE
FALSE / TRUE / TRUE / FALSE / TRUE
TRUE / FALSE / FALSE / TRUE / TRUE
TRUE / TRUE / FALSE / FALSE / FALSE

Since the truth values of !(p & q) are equivalent to the truth values of !p || !q, the two expressions are equivalent.

So how would we express 0 < x < 10?

0<x & x<10

Short-circuit evaluation

Given an expression of the form,

exp1 & exp2

or

exp1 || exp2

the subexpressions are evaluated from left to right. Furthermore, evaluation of the expression ends as soon as the answer can be found. For example, in

exp1 & exp2

if exp1 is FALSE, then we know the entire expression (exp1 & exp2) is FALSE without even evaluating exp2.

In

exp1 || exp2

if exp1 is TRUE, then we know the entire expression (exp1 || exp2) is TRUE without even evaluating exp2.

But why is this feature even useful? Short circuit evaluation is useful because it allows the first expression to control execution of the second one. Sometimes, the validity of the expression is determined by the first.

For example, suppose we had the following expression:

(x != 0) & ((y/x) == 0)

If x is equal to 0, then x != 0 is FALSE and ((y/x) == 0) is never evaluated. This is good since we cannot divide x by 0.

However, if x is not equal to 0, then dividing y by x is no problem, and evaluating the second expression is possible.

Avoiding redundancy

Let the variable temp hold a Boolean value.

The statement

if (temp == 1)... is redundant since it is equivalent to

if (temp)...

In both cases, we will execute the body of the if statement only if temp is true.

Syntax for cascading if, else statements

if (condition1) {

statement1

} else if (condition2) {

statement2

} else if (condition3) {

statement3 /* Note that we can continue with any number of “else if (condition)”’s*/

} else {

statementfinal

}

For example,

if (x==1) {

printf (“x=1\n”);

} else if (x==2) {

printf (“x=2\n”);

} else if (x==3) {

printf (“x=3\n”);

} else {

printf (“x doesn’t equal 1, 2, or 3\n”);

}

Switch statement

Sometimes a decision can be broken down into several cases. In this case, we should use a switch statement, which has the following syntax:

switch (e) {

case c1:

statements1

break;

case c2:

statements2

break;

...

default:

default statements

break;

}

where e is a control expression that determines which statements are executed. Each ci is a constant.

If e is equal to a ci , then the corresponding statementsi will be executed. The default statements are executed only if none of the ci’s match e.

So

if (x==1) {

printf (“x=1\n”);

} else if (x==2) {

printf (“x=2\n”);

} else if (x==3) {

printf (“x=3\n”);

} else {

printf (“x doesn’t equal 1, 2, or 3\n”);

}

can be rewritten equivalently as:

switch (x) {

case 1:

printf (“x=1\n”);

break;

case 2:

printf (“x=2\n”);

break;

case 3:

printf (“x=3\n”);

break;

default:

printf (“x doesn’t equal 1, 2, or 3\n”);

break;

}

What would happen if we omitted the break statements? Like so,

switch (x) { /* THIS EXAMPLE IS WRONG */

case 1:

printf (“x=1\n”);

case 2:

printf (“x=2\n”);

case 3:

printf (“x=3\n”);

default:

printf (“x doesn’t equal 1, 2, or 3\n”);

}

If x was equal to 1, the statements in case 1 would be evaluated. In addition, the remaining cases would also be evaluated since there are no break statements. The output would be

x=1

x=2

x=3

x doesn’t equal 1, 2, or 3

which is obviously not our intention.

If x was equal to 2, then case 1 would be skipped since x doesn’t equal 1, but then case 2 would be evaluated. Since there are no break statements, case 3 and the default statements would also be executed, giving the following as output:

x=2

x=3

x doesn’t equal 1, 2, or 3