(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 || qFALSE / 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