Making Decisions - Programming in C (Fourth Edition) (2015)

Programming in C (Fourth Edition) (2015)

5. Making Decisions

In Chapter 4, “Program Looping,” you learned that one of the fundamental properties of a computer is its capability to repetitively execute a sequence of instructions. But another fundamental property lies in its capability to make decisions. You saw how these decision-making powers were used in the execution of the various looping statements to determine when to terminate the program loop. Without such capabilities, you would never be able to “get out” of a program loop and would end up executing the same sequence of statements over and over again, theoretically forever (which is why such a program loop is called an infinite loop).

The C programming language also provides several other decision-making constructs, which are covered in this chapter:

Image The if statement

Image The switch statement

Image The conditional operator

The if Statement

The C programming language provides a general decision-making capability in the form of a language construct known as the if statement. The general format of this statement is as follows:

if ( expression )
program statement

Imagine that you could translate a statement such as “If it is not raining, then I will go swimming” into the C language. Using the preceding format for the if statement, this might be “written” in C as follows:

if ( it is not raining )
I will go swimming

The if statement is used to stipulate execution of a program statement (or statements if enclosed in braces) based upon specified conditions. I will go swimming if it is not raining. Similarly, in the program statement

if ( count > COUNT_LIMIT )
printf ("Count limit exceeded\n");

the printf() statement is executed only if the value of count is greater than the value of COUNT_LIMIT; otherwise, it is ignored.

An actual program example helps drive this point home. Suppose you want to write a program that accepts an integer typed in from the terminal and then displays the absolute value of that integer. A straightforward way to calculate the absolute value of an integer is to simply negate the number if it is less than zero. The use of the phrase “if it is less than zero” in the preceding sentence signals that a decision must be made by the program. This decision can be affected by the use of an if statement, as shown in Program 5.1.

Program 5.1 Calculating the Absolute Value of an Integer


// Program to calculate the absolute value of an integer

#include <stdio.h>

int main (void)
{
int number;

printf ("Type in your number: ");
scanf ("%i", &number);

if ( number < 0 )
number = -number;

printf ("The absolute value is %i\n", number);

return 0;
}


Program 5.1 Output


Type in your number: -100
The absolute value is 100


Program 5.1 Output (Rerun)


Type in your number: 2000
The absolute value is 2000


The program was run twice to verify that it is functioning properly. Of course, it might be desirable to run the program several more times to get a higher level of confidence so that you know it is indeed working correctly, but at least you know that you have checked both possible outcomes of the decision made by the program.

After a message is displayed to the user and the integer value that is entered is stored in number, the program tests the value of number to see if it is less than zero. If it is, the following program statement, which negates the value of number, is executed. If the value of number is not less than zero, this program statement is automatically skipped. (If it is already positive, you don’t want to negate it.) The absolute value of number is then displayed by the program, and program execution ends.

Look at Program 5.2, which uses the if statement. Imagine that you have a list of grades for which you want to compute the average. But in addition to computing the average, suppose that you also need a count of the number of failing grades in the list. For the purposes of this problem, assume that a grade less than 65 is considered a failing grade.

The notion of keeping count of the number of failing grades indicates that you must make a decision as to whether a grade qualifies as a failing grade. Once again, the if statement comes to the rescue.

Program 5.2 Calculating the Average of a Set of Grades and Counting the Number of Failing Test Grades


/* Program to calculate the average of a set of grades and count
the number of failing test grades */

#include <stdio.h>

int main (void)
{
int numberOfGrades, i, grade;
int gradeTotal = 0;
int failureCount = 0;
float average;

printf ("How many grades will you be entering? ");
scanf ("%i", &numberOfGrades);

for ( i = 1; i <= numberOfGrades; ++i ) {
printf ("Enter grade #%i: ", i);
scanf ("%i", &grade);

gradeTotal = gradeTotal + grade;

if ( grade < 65 )
++failureCount;
}

average = (float) gradeTotal / numberOfGrades;

printf ("\nGrade average = %.2f\n", average);
printf ("Number of failures = %i\n", failureCount);

return 0;
}


Program 5.2 Output


How many grades will you be entering? 7
Enter grade #1: 93
Enter grade #2: 63
Enter grade #3: 87
Enter grade #4: 65
Enter grade #5: 62
Enter grade #6: 88
Enter grade #7: 76

Grade average = 76.29
Number of failures = 2


The variable gradeTotal, which is used to keep a cumulative total of the grades as they are typed in, is initially set to 0. The number of failing test grades is stored in the variable failureCount, whose value also is initially set to 0. The variable average is declared to be of typefloat because the average of a set of integers is not necessarily an integer itself.

The program then asks the user to enter the number of grades that will be keyed in and stores the value that is entered in the variable numberOfGrades. A loop is then set up that will be executed for each grade. The first part of the loop prompts the user to enter in the grade. The value that is entered is stored in the variable called, appropriately enough, grade.

The value of grade is then added into gradeTotal, after which a test is made to see if it is a failing test grade. If it is, the value of failureCount is incremented by 1. The entire loop is then repeated for the next grade in the list.

When all of the grades have been entered and totaled, the program then calculates the grade average. On impulse, it seems that a statement such as

average = gradeTotal / numberOfGrades;

would do the trick. However, recall that if the preceding statement were used, the decimal portion of the result of the division would be lost. This is because an integer division would be performed because both the numerator and the denominator of the division operation are integers.

Two different solutions are possible for this problem. One is to declare either numberOfGrades or gradeTotal to be of type float. This then guarantees that the division is carried out without the loss of the decimal places. The only problem with this approach is that the variablesnumberOfGrades and gradeTotal are used by the program to store only integer values. Declaring either of them to be of type float only obscures their use in the program and is generally not a very clean way of doing things.

The other solution, as used by the program, is to actually convert the value of one of the variables to a floating-point value for the purposes of the calculation. The type cast operator (float) is used to convert the value of the variable gradeTotal to type float for purposes of evaluation of the expression. Because the value of gradeTotal is cast into a floating-point value before the division takes place, the division is treated as the division of a floating value by an integer. Because one of the operands is now a floating-point value, the division operation is carried out as a floating-point operation. This means, of course, that you obtain those decimal places that you want in the average.

After the average has been calculated, it is displayed at the terminal to two decimal places of accuracy. If a decimal point followed by a number (known collectively as a precision modifier) is placed directly before the format character f (or e) in a printf() format string, the corresponding value is displayed to the specified number of decimal places, rounded. So in Program 5.2, the precision modifier .2 is used to cause the value of average to be displayed to two decimal places.

After the program has displayed the number of failing grades, execution of the program is complete.

Note that if the user of the program enters 0 as the number of test grades to be recorded, the program will generate some odd results, like NaN (Not a Number) or something else; it will vary by system however, depending on how your computer deals with division by 0. You may wonder why someone would bother running a program to record test scores if there are no test scores to enter, but this is the type of possible error-checking you can add to your program.

The if-else Construct

If someone asks you whether a particular number is even or odd, you most likely make the determination by examining the last digit of the number. If this digit is either 0, 2, 4, 6, or 8, you readily state that the number is even. Otherwise, you claim that the number is odd.

An easier way for a computer to determine whether a particular number is even or odd is affected not by examining the last digit of the number to see if it is 0, 2, 4, 6, or 8, but by simply determining whether the number is evenly divisible by 2. If it is, the number is even; else it is odd.

You have already seen how the modulus operator % is used to compute the remainder of one integer divided by another. This makes it the perfect operator to use in determining whether an integer is evenly divisible by 2. If the remainder after division by 2 is zero, it is even; else it is odd.

Look at Program 5.3—a program that determines whether an integer value typed in by the user is even or odd and that displays an appropriate message at the terminal.

Program 5.3 Determining if a Number Is Even or Odd


// Program to determine if a number is even or odd

#include <stdio.h>

int main (void)
{
int number_to_test, remainder;

printf ("Enter your number to be tested.: ");
scanf ("%i", &number_to_test);

remainder = number_to_test % 2;

if ( remainder == 0 )
printf ("The number is even.\n");

if ( remainder != 0 )
printf ("The number is odd.\n");

return 0;
}


Program 5.3 Output


Enter your number to be tested: 2455
The number is odd.


Program 5.3 Output (Rerun)


Enter your number to be tested: 1210
The number is even.


After the number is typed in, the remainder after division by 2 is calculated. The first if statement tests the value of this remainder to see if it is equal to zero. If it is, the message “The number is even” is displayed.

The second if statement tests the remainder to see if it’s not equal to zero and, if that’s the case, displays a message stating that the number is odd.

The fact is that whenever the first if statement succeeds, the second one must fail, and vice versa. Recall from the discussions of even/odd numbers at the beginning of this section that if the number is evenly divisible by 2, it is even; else it is odd.

When writing programs, this “else” concept is so frequently required that almost all modern programming languages provide a special construct to handle this situation. In C, this is known as the if-else construct and the general format is as follows:

if ( expression )
program statement 1
else
program statement 2

The if-else is actually just an extension of the general format of the if statement. If the result of the evaluation of expression is TRUE, program statement 1, which immediately follows, is executed; otherwise, program statement 2 is executed. In either case, either program statement 1 or program statement 2 is executed, but not both.

You can incorporate the if-else statement into Program 5.3, replacing the two if statements with a single if-else statement. The use of this new program construct actually helps to reduce the program’s complexity and also improves its readability, as shown in Program 5.4.

Program 5.4 Revising the Program to Determine if a Number Is Even or Odd


// Program to determine if a number is even or odd (Ver. 2)

#include <stdio.h>

int main ()
{
int number_to_test, remainder;

printf ("Enter your number to be tested: ");
scanf ("%i", &number_to_test);

remainder = number_to_test % 2;

if ( remainder == 0 )
printf ("The number is even.\n");
else
printf ("The number is odd.\n");

return 0;
}


Program 5.4 Output


Enter your number to be tested: 1234
The number is even.


Program 5.4 Output (Rerun)


Enter your number to be tested: 6551
The number is odd.


Remember that the double equal sign == is the equality test and the single equal sign is the assignment operator. It can lead to lots of headaches if you forget this and inadvertently use the assignment operator inside the if statement.

Compound Relational Tests

The if statements that you’ve used so far in this chapter set up simple relational tests between two numbers. In Program 5.1, you compared the value of number against 0, whereas in Program 5.2, you compared the value of grade against 65. Sometimes, it becomes desirable, if not necessary, to set up more sophisticated tests. Suppose, for example, that in Program 5.2 you want to count not the number of failing grades, but instead the number of grades that are between 70 and 79, inclusive. In such a case, you do not merely want to compare the value of grade against one limit, but against the two limits 70 and 79 to make certain that it falls within the specified range.

The C language provides the mechanisms necessary to perform these types of compound relational tests. A compound relational test is simply one or more simple relational tests joined by either the logical AND or the logical OR operator. These operators are represented by the character pairs&& and || (two vertical bar characters), respectively. As an example, the C statement

if ( grade >= 70 && grade <= 79 )
++grades_70_to_79;

increments the value of grades_70_to_79 only if the value of grade is greater than or equal to 70 and less than or equal to 79. In a like manner, the statement

if ( index < 0 || index > 99 )
printf ("Error - index out of range\n");

causes execution of the printf() statement if index is less than 0 or greater than 99.

The compound operators can be used to form extremely complex expressions in C. The C language grants the programmer ultimate flexibility in forming expressions. This flexibility is a capability that is often abused. Simpler expressions are almost always easier to read and debug.

When forming compound relational expressions, liberally use parentheses to aid readability of the expression and to avoid getting into trouble because of a mistaken assumption about the precedence of the operators in the expression. You can also use blank spaces to aid in the expression’s readability. An extra blank space around the && and || operators visually sets these operators apart from the expressions that are being joined by these operators.

To illustrate the use of a compound relational test in an actual program example, write a program that tests to see whether a year is a leap year. A year is a leap year if it is evenly divisible by 4. What you might not realize, however, is that a year that is divisible by 100 is not a leap year unless it also is divisible by 400.

Try to think how you would go about setting up a test for such a condition. First, you could compute the remainders of the year after division by 4, 100, and 400, and assign these values to appropriately named variables, such as rem_4, rem_100, and rem_400, respectively. Then, you could proceed to test these remainders to determine if the desired criteria for a leap year are met.

If you rephrase the previous definition of a leap year, you can say that a year is a leap year if it is evenly divisible by 4 and not by 100 or if it is evenly divisible by 400. Stop for a moment to reflect on this last sentence and to verify to yourself that it is equivalent to our previously stated definition. Now that you have reformulated our definition in these terms, it becomes a relatively straightforward task to translate it into a program statement as follows:

if ( (rem_4 == 0 && rem_100 != 0) || rem_400 == 0 )
printf ("It's a leap year.\n");

The parentheses around the subexpression

rem_4 == 0 && rem_100 != 0

are not required because that is how the expression will be evaluated anyway.

If you add a few statements in front of this test to declare your variables and to enable the user to key in the year from the terminal, you end up with a program that determines if a year is a leap year, as shown in Program 5.5.

Program 5.5 Determining if a Year Is a Leap Year


// Program to determine if a year is a leap year

#include <stdio.h>

int main (void)
{
int year, rem_4, rem_100, rem_400;

printf ("Enter the year to be tested: ");
scanf ("%i", &year);

rem_4 = year % 4;
rem_100 = year % 100;
rem_400 = year % 400;


if ( (rem_4 == 0 && rem_100 != 0) || rem_400 == 0 )
printf ("It's a leap year.\n");
else
printf ("Nope, it's not a leap year.\n");

return 0;
}


Program 5.5 Output


Enter the year to be tested: 1955
Nope, it's not a leap year.


Program 5.5 Output (Rerun)


Enter the year to be tested: 2000
It's a leap year.


Program 5.5 Output (Second Rerun)


Enter the year to be tested: 1800
Nope, it's not a leap year.


The previous examples show a year that was not a leap year because it wasn’t evenly divisible by 4 (1955), a year that was a leap year because it was evenly divisible by 400 (2000), and a year that wasn’t a leap year because it was evenly divisible by 100 but not by 400 (1800). To complete the run of test cases, you should also try a year that is evenly divisible by 4 but not by 100. This is left as an exercise for you.

As mentioned previously, C gives you a tremendous amount of flexibility in forming expressions. For instance, in the preceding program, you did not have to calculate the intermediate results rem_4, rem_100, and rem_400—you could have performed the calculation directly inside the ifstatement as follows:

if ( ( year % 4 == 0 && year % 100 != 0 ) || year % 400 == 0 )

The use of blank spaces to set off the various operators still makes the preceding expression readable. If you decide to ignore adding blanks and remove the unnecessary set of parentheses, you end up with an expression that looks like this:

if(year%4==0&&year%100!=0)||year%400==0)

This expression is perfectly valid and (believe it or not) executes identically to the expression shown immediately prior. Obviously, those extra blanks go a long way toward aiding understanding of complex expressions.

Nested if Statements

In the general format of the if statement, remember that if the result of evaluating the expression inside the parentheses is TRUE, the statement that immediately follows is executed. It is perfectly valid that this program statement be another if statement, as in the following statement:

if ( gameIsOver == 0 )
if ( playerToMove == YOU )
printf ("Your Move\n");

If the value of gameIsOver is 0, the following statement is executed, which is another if statement. This if statement compares the value of playerToMove against YOU. If the two values are equal, the message “Your Move” is displayed at the terminal. Therefore, the printfstatement is executed only if gameIsOver equals 0 and playerToMove equals YOU. In fact, this statement could have been equivalently formulated using compound relationals as follows:

if ( gameIsOver == 0 && playerToMove == YOU )
printf ("Your Move\n");

A more practical example of “nested” if statements is if you added an else clause to the previous example, as follows:

if ( gameIsOver == 0 )
if ( playerToMove == YOU )
printf ("Your Move\n");
else
printf ("My Move\n");

Execution of this statement proceeds as described previously. However, if gameIsOver equals 0 and the value of playerToMove is not equal to YOU, then the else clause is executed. This displays the message “My Move” at the terminal. If gameIsOver does not equal 0, the entireif statement that follows, including its associated else clause, is skipped.

Notice how the else clause is associated with the if statement that tests the value of playerToMove, and not with the if statement that tests the value of gameIsOver. The general rule is that an else clause is always associated with the last if statement that does not contain anelse.

You can go one step further and add an else clause to the outermost if statement in the preceding example. This else clause is executed if the value of gameIsOver is not 0.

if ( gameIsOver == 0 )
if ( playerToMove == YOU )
printf ("Your Move\n");
else
printf ("My Move\n");
else
printf ("The game is over\n");

The proper use of indentation goes a long way toward aiding your understanding of the logic of complex statements.

Of course, even if you use indentation to indicate the way you think a statement will be interpreted in the C language, it might not always coincide with the way that the compiler actually interprets the statement. For instance, removing the first else clause from the previous example

if ( gameIsOver == 0 )
if ( playerToMove == YOU )
printf ("Your Move\n");
else
printf ("The game is over\n");

does not result in the statement being interpreted as indicated by its format. Instead, this statement is interpreted as

if ( gameIsOver == 0 )
if ( playerToMove == YOU )
printf ("Your Move\n");
else
printf ("The game is over\n");

because the else clause is associated with the last un-elsed if. You can use braces to force a different association in those cases in which an innermost if does not contain an else, but an outer if does. The braces have the effect of “closing off” the if statement. Thus,

if ( gameIsOver == 0 ) {
if ( playerToMove == YOU )
printf ("Your Move\n");
}
else
printf ("The game is over\n");

achieves the desired effect, with the message “The game is over” being displayed if the value of gameIsOver is not 0.

The else if Construct

You’ve seen how the else statement comes into play when you have a test against two possible conditions—either the number is even, else it is odd; either the year is a leap year, else it is not. However, programming decisions that you have to make are not always so black-and-white. Consider the task of writing a program that displays −1 if a number typed in by a user is less than zero, 0 if the number typed in is equal to zero, and 1 if the number is greater than zero. (This is actually an implementation of what is commonly called the sign function.) Obviously, you must make three tests in this case—to determine if the number that is keyed in is negative, zero, or positive. Our simple if-else construct does not work. Of course, in this case, you could always resort to three separate if statements, but this solution does not always work in general—especially if the tests that are made are not mutually exclusive.

You can handle the situation just described by adding an if statement to your else clause. Because the statement that followed an else can be any valid C program statement, it seems logical that it can be another if. Thus, in the general case, you could write

if ( expression 1 )
program statement 1
else
if ( expression 2 )
program statement 2
else
program statement 3

which effectively extends the if statement from a two-valued logic decision to a three-valued logic decision. You can continue to add if statements to the else clauses, in the manner just shown, to effectively extend the decision to an n-valued logic decision.

The preceding construct is so frequently used that it is generally referred to as an else if construct and is usually formatted differently from that shown previously as

if ( expression 1 )
program statement 1
else if ( expression 2 )
program statement 2
else
program statement 3

This latter method of formatting improves the readability of the statement and makes it clearer that a three-way decision is being made.

Program 5.6 illustrates the use of the else if construct by implementing the sign function discussed earlier.

Program 5.6 Implementing the Sign Function


// Program to implement the sign function

#include <stdio.h>

int main (void)
{
int number, sign;

printf ("Please type in a number: ");
scanf ("%i", &number);

if ( number < 0 )
sign = -1;
else if ( number == 0 )
sign = 0;
else // Must be positive
sign = 1;

printf ("Sign = %i\n", sign);

return 0;
}


Program 5.6 Output


Please type in a number: 1121
Sign = 1


Program 5.6 Output (Rerun)


Please type in a number: -158
Sign = -1


Program 5.6 Output (Second Rerun)


Please type in a number: 0
Sign = 0


If the number that is entered is less than zero, sign is assigned the value −1; if the number is equal to zero, sign is assigned the value 0; otherwise, the number must be greater than zero, so sign is assigned the value 1.

Program 5.7 analyzes a character that is typed in from the terminal and classifies it as either an alphabetic character (a–z or A–Z), a digit (0–9), or a special character (anything else). To read a single character from the terminal, the format characters %c are used in the scanf() call.

Program 5.7 Categorizing a Single Character Entered at the Terminal


// Program to categorize a single character that is entered at the terminal

#include <stdio.h>

int main (void)
{
char c;

printf ("Enter a single character:\n");
scanf ("%c", &c);

if ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') )
printf ("It's an alphabetic character.\n");
else if ( c >= '0' && c <= '9' )
printf ("It's a digit.\n");
else
printf ("It's a special character.\n");

return 0;
}


Program 5.7 Output


Enter a single character:
&
It's a special character.


Program 5.7 Output (Rerun)


Enter a single character:
8
It's a digit.


Program 5.7 Output (Second Rerun)


Enter a single character:
B
It's an alphabetic character.


The first test that is made after the character is read in determines whether the char variable c is an alphabetic character. This is done by testing if the character is either a lowercase letter or an uppercase letter. The former test is made by the expression

( c >= 'a' && c <= 'z' )

which is TRUE if c is within the range of characters 'a' through 'z'; that is, if c is a lowercase letter. The latter test is made by the expression

( c >= 'A' && c <= 'Z' )

which is TRUE if c is within the range of characters 'A' through 'Z'; that is, if c is an uppercase letter. These tests work on all computer systems that store characters inside the machine in a format known as ASCII format.1

1. It’s better to use routines in the standard library called islower() and isupper() and avoid the internal representation issue entirely. To do that, you need to include the line #include <ctype.h> in your program. However, we’ve put this here for illustrative purposes only.

If the variable c is an alphabetic character, the first if test succeeds and the message It's an alphabetic character. is displayed. If the test fails, the else if clause is executed. This clause determines if the character is a digit. Note that this test compares the character cagainst the characters '0' and '9' and not the integers 0 and 9. This is because a character was read in from the terminal, and the characters '0' to '9' are not the same as the numbers 0–9. In fact, on a computer system that uses the ASCII format mentioned previously, the character '0'is actually represented internally as the number 48, the character '1' as the number 49, and so on.

If c is a digit character, the phrase It's a digit. is displayed. Otherwise, if c is not alphabetic and is not a digit, the final else clause is executed and displays the phrase “It’s a special character.” Execution of the program is then complete.

You should note that even though scanf() is used here to read just a single character, the Enter (or Return) key must still be pressed after the character is typed to send the input to the program. In general, whenever you’re reading data from the terminal, the program doesn’t see any of the data typed on the line until the Enter key is pressed.

For your next example, suppose you want to write a program that allows the user to type in simple expressions of the form

number operator number

The program evaluates the expression and displays the results at the terminal, to two decimal places of accuracy. The operators that you want to have recognized are the normal operators for addition, subtraction, multiplication, and division. Program 5.8 makes use of a large if statement with many else if clauses to determine which operation is to be performed.

Program 5.8 Evaluating Simple Expressions


/* Program to evaluate simple expressions of the form
number operator number */

#include <stdio.h>

int main (void)
{
float value1, value2;
char operator;

printf ("Type in your expression.\n");
scanf ("%f %c %f", &value1, &operator, &value2);

if ( operator == '+' )
printf ("%.2f\n", value1 + value2);
else if ( operator == '-' )
printf ("%.2f\n", value1 - value2);
else if ( operator == '*' )
printf ("%.2f\n", value1 * value2);
else if ( operator == '/' )
printf ("%.2f\n", value1 / value2);

return 0;
}


Program 5.8 Output


Type in your expression.
123.5 + 59.3
182.80


Program 5.8 Output (Rerun)


Type in your expression.
198.7 / 26
7.64


Program 5.8 Output (Second Rerun)


Type in your expression.
89.3 * 2.5
223.25


The scanf() call specifies that three values are to be read into the variables value1, operator, and value2. A floating value can be read in with the %f format characters, the same characters used for the output of floating values. This is the format used to read in the value of the variable value1, which is the first operand of your expression.

Next, you want to read in the operator. Because the operator is a character ('+', '-', '*', or '/') and not a number, you read it into the character variable operator. The %c format characters tell the system to read in the next character from the terminal. The blank spaces inside the format string indicate that an arbitrary number of blank spaces are to be permitted on the input. This enables you to separate the operands from the operator with blank spaces when you type in these values. If you had specified the format string "%f%c%f" instead, no spaces would have been permitted after typing in the first number and before typing in the operator. This is because when the scanf() function is reading a character with the %c format characters, the next character on the input, even if it is a blank space, is the character that is read. However, it should be noted that, in general, the scanf() function always ignores leading spaces when it is reading in either a decimal or floating-point number. Therefore, the format string "%f %c%f" would have worked just as well in the preceding program.

After the second operand has been keyed in and stored in the variable value2, the program proceeds to test the value of operator against the four permissible operators. When a correct match is made, the corresponding printf() statement is executed to display the results of the calculation. Execution of the program is then complete.

A few words about program thoroughness are in order at this point. While the preceding program does accomplish the task that it was set to perform, the program is not really complete because it does not account for mistakes made by the user. For example, what happens if the user types in a? for the operator by mistake? The program simply “falls through” the if statement and no messages ever appear at the terminal to alert the user that he incorrectly typed in his expression.

Another case that is overlooked is when the user types in a division operation with zero as the divisor. You know by now that you should never attempt to divide a number by zero in C. The program should check for this case.

Trying to predict the ways that a program can fail or produce unwanted results and then taking preventive measures to account for such situations is a necessary part of producing good, reliable programs. Running a sufficient number of test cases against a program often points the finger to portions of the program that do not account for certain cases. But it goes further than that. It must become a matter of self-discipline while coding a program to always say “What would happen if ...” and to insert the necessary program statements to handle the situation properly.

Program 5.8A, a modified version of Program 5.8, accounts for division by zero and the keying in of an unknown operator.

Program 5.8A Revising the Program to Evaluate Simple Expressions


/* Program to evaluate simple expressions of the form
value operator value */

#include <stdio.h>

int main (void)
{
float value1, value2;
char operator;

printf ("Type in your expression.\n");
scanf ("%f %c %f", &value1, &operator, &value2);

if ( operator == '+' )
printf ("%.2f\n", value1 + value2);
else if ( operator == '-' )
printf ("%.2f\n", value1 - value2);
else if ( operator == '*' )
printf ("%.2f\n", value1 * value2);
else if ( operator == '/' )
if ( value2 == 0 )
printf ("Division by zero.\n");
else
printf ("%.2f\n", value1 / value2);
else
printf ("Unknown operator.\n");

return 0;
}


Program 5.8A Output


Type in your expression.
123.5 + 59.3
182.80


Program 5.8A Output (Rerun)


Type in your expression.
198.7 / 0
Division by zero.


Program 5.8A Output (Second Rerun)


Type in your expression.
125 $ 28
Unknown operator.


When the operator that is typed in is the slash, for division, another test is made to determine if value2 is 0. If it is, an appropriate message is displayed at the terminal. Otherwise, the division operation is carried out and the results are displayed. Pay careful attention to the nesting of the ifstatements and the associated else clauses in this case.

The else clause at the end of the program catches any “fall throughs.” Therefore, any value of operator that does not match any of the four characters tested causes this else clause to be executed, resulting in the display of “Unknown operator.”

The switch Statement

The type of if-else statement chain that you encountered in the last program example—in which the value of a variable is successively compared against different values—is so commonly used when developing programs that a special program statement exists in the C language for performing precisely this function. The name of the statement is the switch statement, and its general format is

switch ( expression )
{
case value1:
program statement
program statement
...
break;
case value2:
program statement
program statement
...
break;
...
case valuen:
program statement
program statement
...
break;
default:
program statement
program statement
...
break;
}

The expression enclosed within parentheses is successively compared against the values value1, value2, ..., valuen, which must be simple constants or constant expressions. If a case is found whose value is equal to the value of expression, the program statements that follow the case are executed. Note that when more than one such program statement is included, they do not have to be enclosed within braces.

The break statement signals the end of a particular case and causes execution of the switch statement to be terminated. Remember to include the break statement at the end of every case. Forgetting to do so for a particular case causes program execution to continue into the next case whenever that case gets executed.

The special optional case called default is executed if the value of expression does not match any of the case values. This is conceptually equivalent to the “fall through” else that you used in the previous example. In fact, the general form of the switch statement can be equivalently expressed as an if statement as follows:

if ( expression == value1 )
{
program statement
program statement
...
}
else if ( expression == value2 )
{
program statement
program statement
...
}
...
else if ( expression == valuen )
{
program statement
program statement
...
}
else
{
program statement
program statement
...
}

Bearing this mind, you can translate the big if statement from Program 5.8A into an equivalent switch statement, as shown in Program 5.9.

Program 5.9 Revising the Program to Evaluate Simple Expressions, Version 2


/* Program to evaluate simple expressions of the form
value operator value */

#include <stdio.h>

int main (void)
{
float value1, value2;
char operator;

printf ("Type in your expression.\n");
scanf ("%f %c %f", &value1, &operator, &value2);

switch (operator)
{
case '+':
printf ("%.2f\n", value1 + value2);
break;
case '-':
printf ("%.2f\n", value1 - value2);
break;
case '*':
printf ("%.2f\n", value1 * value2);
break;
case '/':
if ( value2 == 0 )
printf ("Division by zero.\n");
else
printf ("%.2f\n", value1 / value2);
break;
default:
printf ("Unknown operator.\n");
break;
}

return 0;
}


Program 5.9 Output


Type in your expression.
178.99 - 326.8
-147.81


After the expression has been read in, the value of operator is successively compared against the values as specified by each case. When a match is found, the statements contained inside the case are executed. The break statement then sends execution out of the switch statement, where execution of the program is complete. If none of the cases match the value of operator, the default case, which displays Unknown operator. is executed.

The break statement in the default case is actually unnecessary in the preceding program because no statements follow this case inside the switch. Nevertheless, it is a good programming habit to remember to include the break at the end of every case.

When writing a switch statement, bear in mind that no two case values can be the same. However, you can associate more than one case value with a particular set of program statements. This is done simply by listing the multiple case values (with the keyword case before the value and the colon after the value in each case) before the common statements that are to be executed. As an example, in the following switch statement, the printf statement, which multiples value1 by value2, is executed if operator is equal to an asterisk or to the lowercase letter x.

switch (operator)
{
...
case '*':
case 'x':
printf ("%.2f\n", value1 * value2);
break;
...
}

Boolean Variables

Many new programmers soon find themselves with the task of having to write a program to generate a table of prime numbers. To refresh your memory, a positive integer p is a prime number if it is not evenly divisible by any other integers, other than 1 and itself. The first prime integer is defined to be 2. The next prime is 3, because it is not evenly divisible by any integers other than 1 and 3, and 4 is not prime because it is evenly divisible by 2.

There are several approaches that you can take to generate a table of prime numbers. If you have the task of generating all prime numbers up to 50, for example, the most straightforward (and simplest) algorithm to generate such a table is simply to test each integer p for divisibility by all integers from 2 through p−1. If any such integer is evenly divided by p, then p is not prime; otherwise, it is a prime number. Program 5.10 illustrates the program to generate a table of prime numbers.

Program 5.10 Generating a Table of Prime Numbers


// Program to generate a table of prime numbers

#include <stdio.h>

int main (void)
{
int p, d;
_Bool isPrime;

for ( p = 2; p <= 50; ++p ) {
isPrime = 1;

for ( d = 2; d < p; ++d )
if ( p % d == 0 )
isPrime = 0;

if ( isPrime != 0 )
printf ("%i ", p);
}


printf ("\n");
return 0;
}


Program 5.10 Output


2 3 5 7 11 13 17 19 23 29 31 37 41 43 47


Several points are worth noting about the program in Program 5.10. The outermost for statement sets up a loop to cycle through the integers 2 through 50. The loop variable p represents the value you are currently testing to see if it is prime. The first statement in the loop assigns the value 1to the variable isPrime. The use of this variable will become apparent shortly.

A second loop is set up to divide p by the integers from 2 through p−1. Inside the loop, a test is made to see if the remainder of p divided by d is 0. If it is, you know that p cannot be prime because an integer other than 1 and itself can evenly divide it. To signal that p is no longer a candidate as a prime number, the value of the variable isPrime is set equal to 0.

When the innermost loop finishes execution, the value of isPrime is tested. If its value is not equal to zero, no integer was found that evenly divides p; therefore, p must be a prime number, and its value is displayed.

You might have noticed that the variable isPrime takes on either the value 0 or 1, and no other values. That’s why you declared it to be a _Bool variable. Its value is 1 as long as p still qualifies as a prime number. But as soon as a single even divisor is found, its value is set to 0 to indicate that p no longer satisfies the criteria for being prime. Often, variables that are used in such a manner are referred to as flags. A flag typically assumes only one of two different values. Furthermore, the value of a flag is usually tested at least once in the program to see if it is “on” (TRUE) or “off” (FALSE), and some particular action is taken based upon the results of the test.

In C, the notion of a flag being TRUE or FALSE is most naturally translated into the values 1 and 0, respectively. So in the Program 5.10, when you set the value of isPrime to 1 inside the loop, you are effectively setting it as TRUE to indicate that p “is prime.” If during the course of execution of the inner for loop an even divisor is found, the value of isPrime is set to FALSE to indicate that p no longer “is prime.”

It is no coincidence that the value 1 is typically used to represent the TRUE or “on” state and 0 to represent the FALSE or “off” state. This representation corresponds to the notion of a single bit inside a computer. When the bit is “on,” its value is 1; when it is “off,” its value is 0. But in C, there is an even more convincing argument in favor of these logic values. It has to do with the way the C language treats the concept of TRUE and FALSE.

Recall from the beginning of this chapter that if the conditions specified inside the if statement are “satisfied,” the program statement that immediately follows executes. But what exactly does “satisfied” mean? In the C language, satisfied means nonzero, and nothing more. So the statement

if ( 100 )
printf ("This will always be printed.\n");

results in execution of the printf() statement because the condition in the if statement (in this case, simply the value 100) is nonzero and, therefore, is satisfied.

In each of the programs in this chapter, the notions of “nonzero means satisfied” and “zero means not satisfied” are used. This is because whenever a relational expression is evaluated in C, it is given the value 1 if the expression is satisfied and 0 if the expression is not satisfied. So evaluation of the statement

if ( number < 0 )
number = -number;

actually proceeds as follows:

1. The relational expression number < 0 is evaluated. If the condition is satisfied, that is, if number is less than zero, the value of the expression is 1; otherwise, its value is 0.

2. The if statement tests the result of the expression evaluation. If the result is nonzero, the statement that immediately follows is executed; otherwise, the statement is skipped.

The preceding discussion also applies to evaluation of conditions inside the for, while, and do statements. Evaluation of compound relational expressions such as in the statement

while ( char != 'e' && count != 80 )

also proceeds as outlined previously. If both specified conditions are valid, the result is 1; but if either condition is not valid, the result of the evaluation is 0. The results of the evaluation are then checked. If the result is 0, the while loop terminates; otherwise it continues.

Returning to Program 5.10 and the notion of flags, it is perfectly valid in C to test if the value of a flag is TRUE by an expression such as

if ( isPrime )

rather than with the equivalent expression

if ( isPrime != 0 )

To easily test if the value of a flag is FALSE, you can use the logical negation operator, !. In the expression

if ( ! isPrime )

the logical negation operator is used to test if the value of isPrime is FALSE (read this statement as “if not isPrime”). In general, an expression such as

! expression

negates the logical value of expression. So if expression is zero, the logical negation operator produces a 1. And if the result of the evaluation of expression is nonzero, the negation operator yields a 0.

The logical negation operator can be used to easily “flip” the value of a flag, such as in the expression

myMove = ! myMove;

As you might expect, this operator has the same precedence as the unary minus operator, which means that it has higher precedence than all binary arithmetic operators and all relational operators. So to test if the value of a variable x is not less than the value of a variable y, such as in

! ( x < y )

the parentheses are required to ensure proper evaluation of the expression. Of course, you could have equivalently expressed the previous expression as

x >= y

In Chapter 3, “Variables, Data Types, and Arithmetic Expressions,” you learned about some special values that are defined in the language which you can use when working with Boolean values. These are the type bool, and the values true and false. To use these, you need to include the header file <stdbool.h> inside your program. Program 5.10A is a rewrite of Program 5.10, which takes advantage of this data type and values.

Program 5.10A Revising the Program to Generate a Table of Prime Numbers


// Program to generate a table of prime numbers

#include <stdio.h>
#include <stdbool.h>

int main (void)
{
int p, d;
bool isPrime;

for ( p = 2; p <= 50; ++p ) {
isPrime = true;

for ( d = 2; d < p; ++d )
if ( p % d == 0 )
isPrime = false;

if ( isPrime != false )
printf ("%i ", p);
}


printf ("\n");
return 0;
}


Program 5.10A Output


2 3 5 7 11 13 17 19 23 29 31 37 41 43 47


As you can see, by including <stdbool.h> in your program, you can declare variables to be of type bool instead of _Bool. This is strictly for cosmetic purposes because the former is easier to read and type than the latter, and it fits in more with the style of the other basic C data types, such as int, float, and char.

The Conditional Operator

Perhaps the most unusual operator in the C language is one called the conditional operator. Unlike all other operators in C—which are either unary or binary operators—the conditional operator is a ternary operator; that is, it takes three operands. The two symbols that are used to denote this operator are the question mark (?) and the colon (:). The first operand is placed before the ?, the second between the ? and the :, and the third after the :.

The general format of the conditional operator is

condition ? expression1 : expression2

where condition is an expression, usually a relational expression, that is evaluated first whenever the conditional operator is encountered. If the result of the evaluation of condition is TRUE (that is, nonzero), then expression1 is evaluated and the result of the evaluation becomes the result of the operation. If condition evaluates FALSE (that is, zero), then expression2 is evaluated and its result becomes the result of the operation.

The conditional operator is most often used to assign one of two values to a variable depending upon some condition. For example, suppose you have an integer variable x and another integer variable s. If you want to assign −1 to s if x were less than zero, and the value of x2 to sotherwise, the following statement could be written:

s = ( x < 0 ) ? -1 : x * x;

The condition x < 0 is first tested when the preceding statement is executed. Parentheses are generally placed around the condition expression to aid in the statement’s readability. This is usually not required because the precedence of the conditional operator is very low—lower, in fact, than all other operators besides the assignment operators and the comma operator.

If the value of x is less than zero, the expression immediately following the ? is evaluated. This expression is simply the constant integer value −1, which is assigned to the variable s if x is less than zero.

If the value of x is not less than zero, the expression immediately following the : is evaluated and assigned to s. So if x is greater than or equal to zero, the value of x * x, or x2, is assigned to s.

As another example of the use of the conditional operator, the following statement assigns to the variable maxValue the maximum of a and b:

maxValue = ( a > b ) ? a : b;

If the expression that is used after the : (the “else” part) consists of another conditional operator, you can achieve the effects of an “else if” clause. For example, the sign function that was implemented in Program 5.6 can be written in one program line using two conditional operators as follows:

sign = ( number < 0 ) ? -1 : (( number == 0 ) ? 0 : 1);

If number is less than zero, sign is assigned the value −1; else if number is equal to zero, sign is assigned the value 0; else it is assigned the value 1. The parentheses around the “else” part of the preceding expression are actually unnecessary. This is because the conditional operator associates from right to left, meaning that multiple uses of this operator in a single expression, such as in

e1 ? e2 : e3 ? e4 : e5

group from right to left and, therefore, are evaluated as

e1 ? e2 : ( e3 ? e4 : e5 )

It is not necessary that the conditional operator be used on the right-hand side of an assignment—it can be used in any situation in which an expression could be used. This means that you could display the sign of the variable number, without first assigning it to a variable, using a printfstatement as shown:

printf ("Sign = %i\n", ( number < 0 ) ? –1 : ( number == 0 ) ? 0 : 1);

The conditional operator is very handy when writing preprocessor macros in C. This is seen in detail in Chapter 12, “The Preprocessor.”

This concludes the discussions on making decisions. In Chapter 6, “Working with Arrays,” you get your first look at more sophisticated data types. The array is a powerful concept that will find its way into many programs that you will develop in C. Before moving on, test your understanding of the material covered in this chapter by completing the following exercises.

Exercises

1. Type in and run the 12 programs presented in this chapter. Compare the output produced by each program with the output presented after each program in the text. Try experimenting with each program by keying in values other than those shown.

2. Write a program that asks the user to type in two integer values at the terminal. Test these two numbers to determine if the first is evenly divisible by the second, and then display an appropriate message at the terminal.

3. Write a program that accepts two integer values typed in by the user. Display the result of dividing the first integer by the second, to three-decimal-place accuracy. Remember to have the program check for division by zero.

4. Write a program that acts as a simple “printing” calculator. The program should allow the user to type in expressions of the form

number operator

The following operators should be recognized by the program:

+ - * / S E

The S operator tells the program to set the “accumulator” to the typed-in number. The E operator tells the program that execution is to end. The arithmetic operations are performed on the contents of the accumulator with the number that was keyed in acting as the second operand. The following is a “sample run” showing how the program should operate:

Begin Calculations
10 S Set Accumulator to 10
= 10.000000 Contents of Accumulator
2 / Divide by 2
= 5.000000 Contents of Accumulator
55 - Subtract 55
-50.000000
100.25 S Set Accumulator to 100.25
= 100.250000
4 * Multiply by 4
= 401.000000
0 E End of program
= 401.000000
End of Calculations.

Make certain that the program detects division by zero and also checks for unknown operators.

5. You developed Program 4.9 to reverse the digits of an integer typed in from the terminal. However, this program does not function well if you type in a negative number. Find out what happens in such a case and then modify the program so that negative numbers are correctly handled. For example, if the number −8645 is typed in, the output of the program should be 5468−.

6. Write a program that takes an integer keyed in from the terminal and extracts and displays each digit of the integer in English. So, if the user types in 932, the program should display

nine three two

Remember to display “zero” if the user types in just a 0. (Note: This exercise is a hard one!)

7. Program 5.10 has several inefficiencies. One inefficiency results from checking even numbers. Because it is obvious that any even number greater than 2 cannot be prime, the program could simply skip all even numbers as possible primes and as possible divisors. The inner for loop is also inefficient because the value of p is always divided by all values of d from 2 through p−1. This inefficiency could be avoided by adding a test for the value of isPrime in the conditions of the for loop. In this manner, the for loop could be set up to continue as long as no divisor was found and the value of d was less than p. Modify Program 5.10 to incorporate these two changes. Then run the program to verify its operation. (Note: In Chapter 6, you discover even more efficient ways of generating prime numbers.)