Boolean Values and Conditional Expressions
Comparison and Logical Operators
Conditional if statements operate on Boolean values. An
expression with a comparison operator, such as
1 == 2
evaluates to true or
and false, depending on the operands. (The above
expression evaluates to false. This
is the standard behavior of all the comparison and logical operators
in C: ==, <=, >=, <, >, !=, &&
and, ||.
It is easy to misuse the comparison operators. In mathematics, the expression:
-2 < -1 < 0
makes sense and would evaluate to true. However, in C,
the semantics of these expressions is different, and the above
expression in C actually evaluates to false.
Specifically, comparison operators associate left to right and
evaluate to Boolean values, so
-2 < -1 < 0 is evaluated as if
parentheses were added to give
(-2 < -1) < 0. The first comparison
expression evalutes to true, which
is actually the integer value 1, because -2 is indeed less than
-1. Next, 1 is compared to 0. 1 is not less than 0 so the entire
expression evaluates to
false.
To do multiple tests at once, use logical AND
(&&), and logical OR (||) operators.
For example, the previous example can be correctly expressed in C
as:
(-1 > -2) && (-1 < 0)
This expression in C works fine, but note that some simplification is
possible, because the && operation has lower
precedence than either > or <.
Taking advantage of the assumed precedence of these options, the
parentheses can be removed from the above expression, and C will
still understand that it should perform the comparisons
with > and < operations before
applying
&&. Thus, C programmers could simplify the
above expression to
-1 > -2 && -1 < 0
However, this version is harder to read by humans, who must then "manually" apply the precedence rules.
Conditionals
In C, anything that evaluates to a 0 will
mean false and oppositely anything that
is non-zero will evaluate to be true. We can use this
property of Booleans in C to simplify conditionals. Here is how:
if( rGetIRTtx ("left", 3) == 1)
rTurnLeft (1,1);
else
rForward (1,1);
In interpreting this code, let's start
with rGetIRTxt ("left", 3).
The rGetIRTxt function examines the left IR sensor.
Since sensors are subject to possible experimental error, it is
common to take multiple readings and average. The
call rGetIRTxt ("left", 3) examines
the left sensor 3 times and returns the average.
Returning to the above code, the code segment says if the call for the left sensor value is 1, then turn left, if not just go forward. But since 1 is a non-zero value, it will be true, so we don't have to write it. We could just say:
if( rGetIRTxt ("left", 3) )
rTurnLeft (1,1);
else
rForward (1,1);
Similarly, if we wanted the robot to beep whenever there was nothing
in front of it, and just go forward if there is nothing to beep for,
we could use the same property. The only change we would need to do
would be to logically negate so that it would say "if rGetIRTxt returns a not
true value, beep". Here is the code:
if( ! rGetIRTxt ("left", 3) )
rForward (1,1);
else
rBeep (1,500);
Redundant Comparisons
Conditional statements (ifs) are designed to work with
Boolean values. If an expression is true, the
specified code will execute. In addition to all the logical and
comparison operators in C, many functions also return Boolean values
to indicate different outcomes.
It is a good idea to avoid testing against Boolean values in your
conditionals if a value is already considered to be Boolean. If a
function or expression is supposed to return true on
success and false on failure, do not further test that
result against true or false. Consider the
following:
if ( true )
and:
if ( true == true )
Initially, it may seem the second case is more accurate than the first. But, add a third case:
if ( true == true == true)
The third case is no more descriptive or accurate than the second in the same way the second is no more accurate than the first. It is good practice to avoid this kind of redundancy in your testing.
Returning Boolean Values
Since comparison and logical operators in C evaluate to Boolean values, good coding practice takes advantage of this evaluation when returning Boolean results from functions. For example, consider the following code segment:
if ( x == y )
return 1;
else
return 0;
This code may be correct, but it also is inefficient and wordy.
Think about how operators which return Boolean values (such as
the == operator) work. If x
and y have common values, then the result of the
comparison x == y is true—which in C is
represented by the value 1. If the comparison is false, the result
is false, represented by the value 0. Thus, the return values are
exactly what C computes with the
statement x == y, and the complex statement
above can be simplified to
return ( x == y );
Switch Statements
Instead of using many if statements, we can use switch statements to simplify our code. When there are cases in which a variable is compared to other integer values, and a certain code is executed if the compared values are equal, we can use switch cases.
Here is how to use switch statements:
switch (variable)
{
case value0:
//Execute this code if variable == value0
break;
case value1:
//Execute this code if variable == value1
break;
case value2:
//Execute this code if variable == value2
break;
// And so on with other cases,
default:
// Execute this code if variable did not equal any of the cases above
break;
}
The program
second-counter.c
provides an example of how switch statements work:
#include
#include
#include
int
main (void)
{
/* declare the seconds as an integer */
int seconds;
/* generate a random integer and call it seconds */
srand (time (0));
seconds = rand () % 10 ;
/*tell me how many up to how many seconds we will count */
printf ("I will count to %d seconds....\n", seconds);
/* make different cases to tell the program what to print in any case */
switch (seconds)
{
case 1:
printf ("One Mississippi!\n");
break;
case 2:
printf ("One Mississippi! Two Mississippi!\n");
break;
case 3:
printf ("One Mississippi! Two Mississippi! Three Mississippi!\n");
break;
case 4:
printf ("One Mississippi! Two Mississippi! Three Mississippi! Four Mississippi!\n");
break;
case 5:
printf ("One Mississippi! Two Mississippi! Three Mississippi! Four Mississippi! Five Mississippi!\n");
break;
default:
printf ("Oh Susanna, oh don't you cry for me. I come from Alabama with a banjo on my knee!\n");
break;
}
return 0;
} // main
Testing Two Things at Once
Suppose we want the robot to appropriate take action if it senses an obstacle in front of one of its sensors. A simple, but effective, algorithm to avoid an obstacle might be:
if ( left sensor )
turn right;
else if ( right sensor )
turn left;
Consider the case where there is an obstacle in front of both sensors. Using the previous code, first it would check if there is an obstacle in front of the left sensor. Because there is one, it would turn right. Then it would go on to whatever comes next, which might be going forward. However, the left sensor could still have the same obstacle in front of it if the robot didn't turn enough.
An alternative might be to enumerate the possible cases, as follows.
if ( left sensor && !right sensor)
turn right;
else if ( !left sensor && right sensor)
turn left;
else if ( left sensor && right sensor )
turn 180 degrees;
else
go forward;
This checks for any situation that might occur and tells the robot what to do in each one. How might we make that code work better?
The problem becomes even more prevalent if the data from the sensors was gathered beforehand (as they should be), and then the tests were executed:
int leftIR = rGetIRTxt ("left", 3);
int rightIR = rGetIRTxt ("right", 3);
if ( leftIR )
turn right;
else if ( rightIR )
turn left;
In this case, if both were true, the robot would just turn right, then turn left, and end back where it started.
It is very important that you consider when exactly you are asking for sensor data from the robot. The above example illustrates the generally correct method of gathering data before examining it. To illustrate the importance, consider the following code:
if ( rGetIRTxt ("left", 3) && other_condition1 )
result1 ();
else if ( rGetIRTxt ("left", 3) && other_condition2 )
result2 ();
else if ( rGetIRTxt ("left", 3) && other_condition3 )
result3 ();
else if ( rGetIRTxt ("left", 3) && other_condition4 )
result4 ();
By placing the call to the function rGetIRTxt in the if
statement, you are actually gathering the data four
different times. Calling the function many times is
less efficient than calling it once, giving the value to a variable,
and just using that variable. Suppose the robot is moving
beforehand. When the program gets to the first
test, rGetIRTxt fails and so execution moves on to the
first else. But, by now the robot has moved in range of an obstacle.
The value for rGetIRTxt would then be different when the
robot is queried again—causing the first else if
to pass based on the altered value of the IR sensor. If you examine
the logic in the case that all other_conditions are
true, the program never should have gotten to the first else
if if the value of rGetIRTxt was
initially false .
To avoid the situation altogether, store the value from one
call to rGetIRTxt first. Then perform all the tests on
that variable.
