I l @ ve RuBoard |
The simple form of an if statement gives you the choice of executing a statement (possibly compound) or skipping it. C also enables you to choose between two statements by using the if else form. Let's use the if else form to fix an awkward segment from Listing 7.1.
if (all_days != 0) printf("%d days total: %.1f%% were below freezing.\n", all_days, 100.0 * (float) cold_days / all_days); if (all_days == 0) printf("No data entered!\n");
If the program finds that days is not equal to , it should know that days must be without retesting, and it does. With if else you can take advantage of that knowledge by rewriting the fragment this way:
if (all_days!= 0) printf("%d days total: %.1f%% were below freezing.\n", all_days, 100.0 * (float) cold_days / all_days); else printf("No data entered!\n");
Only one test is made. If the if test expression is true, the temperature data is printed. If it's false, the warning message is printed.
Note the general form of the if else statement:
if ( expression ) statement1 else statement2
If expression is true (nonzero), statement1 is executed. If expression is false or zero, the single statement following the else is executed. The statements can be simple or compound. The indentation is not required by C, but it is the standard style. It shows at a glance the statements that depend on a test for execution.
If you want more than one statement between the if and the else , you must use braces to create a single block. The following construction violates C syntax, for the compiler expects just one statement (single or compound) between the if and the else :
if (x > 0) printf("Incrementing x:\n"); x++; else printf("x <= 0 \n");
Instead, use this:
if (x > 0) { printf("Incrementing x:\n"); x++; } else printf ("x <= 0 \n");
The if statement enables you to choose whether to do one action. The if else statement enables you to choose between two actions. Figure 7.1 compares the two statements.
Most of the examples have used numeric input. To give you practice with other types, let's look at a character-oriented example. You already know how use scanf() and printf() with the %c specifier to read and write characters ; but now you'll meet a pair of C functions specifically designed for character-oriented I/O: getchar() and putchar() .
The getchar() function takes no arguments, and it returns the next character from input. For instance, the following statement reads the next input character and assigns its value to the variable ch :
ch getchar();
This statement has the same effect as the following statement:
scanf("%;c", &ch);
The putchar() function prints its argument. For example, the next statement prints as a character the value previously assigned to ch :
putchar(ch);
This statement has the same effect as the following:
printf("%c", ch);
Because these functions deal only with characters, they are faster and more compact than the more general scanf() and printf() functions. Also, note that they don't need format specifiers; that's because they work with characters only. Both functions are typically defined in the stdio.h file. (Also, typically, they are preprocessor macros rather than true functions; we'll talk about function-like macros in Chapter 16, "The C Preprocessor and the C Library.")
Let's see how these functions work by writing a program that repeats an input line but replaces each nonspace character with the character that follows it in the ASCII code sequence. Spaces will be reproduced as spaces. You can state the desired response this way: If the character is a space, print it; otherwise , print the next character in the ASCII sequence. The C code looks much like this statement, as you can see in Listing 7.2.
/* cypher1.c -- alters input, preserving spaces */ #include <stdio.h> #define SPACE ` ' /* that's quote-space-quote */ int main(void) { char ch; ch = getchar(); /* read a character */ while (ch != `\n') /* while not end of line */ { if (ch == SPACE) /* leave the space */ putchar(ch); /* character unchanged */ else putchar(ch + 1); /* change other characters */ ch = getchar(); /* get next character */ } putchar(ch); /* print the newline */ return 0; }
Here's a sample run:
CALL ME HAL. DBMM NF IBM/
Compare this loop to the one from Listing 7.1, which uses the status returned by scanf() instead of the value of the input item to determine when to terminate the loop. Listing 7.2, however, uses the value of the input item itself to decide when to terminate the loop. This difference results in a slightly different loop structure, with one read statement before the loop and one read statement at the end of each loop. C's flexible syntax, however, enables you to emulate Listing 7.1 by combining reading and testing into a single expression. That is, you can replace a loop of the form
getchar(); /* read a character */ `\n') /* while not end of line */ { ... /* process character */ ch = getchar(); /* get next character */ }
with one that looks like this:
while ((ch = getchar()) != `\n') { ... /* process character */ }
The critical line is this:
while ((ch = getchar()) != `\n')
It demonstrates a characteristic C programming style: combining two actions in one expression. The actions are assigning a value to ch and comparing this value to the newline character. The parentheses around ch = getchar() make it the left operand of the != operator. To evaluate this expression, the computer must first call the getchar() function and then assign its return value to ch . Because the value of an assignment expression is the value of the left member, the value of ch = getchar() is just the new value of ch . Therefore, after ch is read, the test condition boils down to ch != `\n' , that is, to ch not being the newline character.
This particular idiom is very common in C programming. All the parentheses are necessary. Suppose you mistakenly used this:
while getchar() != `\n')
The != operator has higher precedence than = , so the first expression to be evaluated is getchar() != `\n' . Because this is a relational expression, its value is 1 or (true or false). Then this value is assigned to ch . Omitting the parentheses means that ch is assigned or 1 rather than the return value of getchar() ; this is not desirable.
Notice that the output for Listing 7.2 shows a period being converted to a slash; that's because the ASCII code for the slash character is one greater than the code for the period character. But if the point of the program is to convert only letters, it would be nice to leave all non- letters , not just spaces, unaltered. The logical operators, discussed later in this chapter, provide a way to test whether a character is not a space and not a comma, and so on, but it would be rather cumbersome to list all the possibilities. Fortunately, ANSI C has a standard set of functions for analyzing characters; the ctype.h header file contains the prototypes . These functions take a character as an argument and return nonzero (true) if the character belongs to a particular category and false otherwise. For example, the isalpha () function returns true if its argument is a letter. Listing 7.3 generalizes Listing 7.2 by using this function; it also incorporates the shortened loop structure we just discussed.
/* cypher2.c -- alters input, preserving non-letters */ #include <stdio.h> #include <ctype.h> /* for isalpha() */ int main(void) { char ch; while ((ch = getchar()) != `\n') { if (isalpha(ch)) /* if a letter, */ putchar(ch + 1); /* change it */ else putchar(ch); /* otherwise print as is */ } putchar(ch); /* print the newline */ return 0; }
Here is a sample run; note how both lowercase and uppercase letters are encyphered, but spaces and punctuation aren't:
See, it's a programmer! Tff, ju't b qsphsbnnfs!
Tables 7.1 and 7.2 list several functions provided when you include the ctype.h header file. Note that the mapping functions don't modify the original argument; instead, they return the modified value. That is,
tolower(ch); // no effect on ch
doesn't change ch . To change ch do this:
tolower(ch); // convert ch to lowercase
Name | True If Argument Is |
---|---|
isalnum () | Alphanumeric (alphabetic or numeric) |
isalpha() | Alphabetic |
iscntrl () | A control character, such as Ctrl+B |
isdigit () | A digit |
isgraph () | Any printing character other than a space |
islower () | A lowercase character |
isprint () | A printing character |
ispunct () | A punctuation character (any printing character other than a space or an alphanumeric character) |
isspace () | A whitespace character: space, newline, formfeed, carriage return, vertical tab, horizontal tab, or, possibly, other implementation-defined characters |
isupper () | An uppercase character |
isxdigit () | A hexadecimal-digit character |
Name | Action |
---|---|
tolower() | If the argument is an uppercase character, returns the lowercase version; otherwise, just returns the original argument |
toupper() | If the argument is a lowercase character, returns the uppercase version; otherwise, just returns the original argument |
Life often offers us more than two choices. You can extend the if else structure with else if to accommodate this fact. Let's look at a particular example. Utility companies often have charges that depend on the amount of energy the customer uses. Here are the rates one company charges for electricity, based on kilowatt-hours (kWh):
first 240 kWh: | $0.11439 per kWh |
next 300 kWh: | $0.13290 per kWh |
over 540 kWh: | $0.14022 per kWh |
If you worry about your energy management, you might want to prepare a program to calculate your energy costs. The program in Listing 7.4 is a first step in that direction.
/* electric.c -- calculates electric bill */ #include <stdio.h> #define RATE1 0.11439 /* rate for first 240 kwh */ #define RATE2 0.12032 /* rate for next 300 kwh */ #define RATE3 0.14022 /* rate for over 540 kwh */ #define BREAK1 240.0 /* first breakpoint for rates */ #define BREAK2 540.0 /* second breakpoint for rates */ #define BASE1 (RATE1 * BREAK1) /* cost for 240 kwh */ #define BASE2 (BASE1 + (RATE2 * (BREAK2 - BREAK1))) /* cost for 540 kwh */ int main(void) { double kwh; /* kilowatt-hours used */ double bill; /* charges */ printf("Please enter the kwh used.\n"); scanf("%lf", &kwh); /* %lf for type double */ if (kwh <= BREAK1) bill = RATE1 * kwh; else if (kwh <= BREAK2) /* kwh between 240 and 540 */ bill = BASE1 + (RATE2 * (kwh - BREAK1)); else /* kwh above 540 */ bill = BASE2 + (RATE3 * (kwh - BREAK2)); printf("The charge for %.1f kwh is $%1.2f.\n", kwh, bill); return 0; }
Here's sample output:
Please enter the kwh used. 438 The charge for 438.0 kwh is .28.
Listing 7.4 uses symbolic constants for the rates so that the constants are conveniently gathered in one place. If the power company changes its rates (it's possible), having the rates in one place makes it easy to update them. The listing also expresses the rate breakpoints symbolically. They, too, are subject to change. BASE1 and BASE2 are expressed in terms of the rates and breakpoints. Then, if the rates or breakpoints change, the bases are updated automatically. You may recall that the preprocessor does not do calculations. Where BASE1 appears in the program, it will be replaced by 0.11439 * 240.0 . Don't worry; the compiler does evaluate this expression to its numerical value ( 27.4536 ) so that the final program code uses 27.4536 rather than a calculation.
The flow of the program is straightforward. The program selects one of three formulas, depending on the value of kwh . Figure 7.2 illustrates this flow. We should point out that the only way the program can reach the first else is if kwh is equal to or greater than 240 . Therefore, the else if (kwh <= BREAK2) line really is equivalent to demanding that kwh be between 240 and 540 , as we noted in the program comment. Similarly, the final else can be reached only if kwh exceeds 540 . Finally, note that BASE1 and BASE2 represent the total charges for the first 240 and 540 kilowatt-hours, respectively. Therefore, you need add on only the additional charges for electricity in excess of those amounts.
Actually, the else if is a variation on what you already knew. For example, the core of the program is just another way of writing
if (kwh <=BREAK1) bill = RATE1 * kwh; else if (kwh <=BREAK2) bill = BASE1 + RATE2 * (kwh - BREAK1); else bill = BASE2 + RATE3 * (kwh - BREAK2);
That is, the program consists of an if else statement for which the statement part of the else is another if else statement. The second if else statement is said to be nested inside the first. Recall that the entire if else structure counts as a single statement, which is why we didn't have to enclose the nested if else in braces. However, using braces would clarify the intent of this particular format.
These two forms are perfectly equivalent. The only differences are in where you put spaces and newlines, and these differences are ignored by the compiler. Nonetheless, the first form is better because it shows more clearly that you are making a three-way choice. This form makes it easier to skim the program and see what the choices are. Save the nested forms of indentation for when they are needed, for instance, when you must test two separate quantities . An example of such a situation is having a 10% surcharge for kilowatt-hours in excess of 540 during the summer only.
You can string together as many else if statements as you need (within compiler limits, of course), as illustrated by this fragment:
if (score < 1000) bonus = 0; else if (score < 1500) bonus = 1; else if (score < 2000) bonus = 2; else if (score < 2500) bonus = 4; else bonus = 6;
(This might be part of a game program, in which bonus represents how many additional photon bombs or food pellets you get for the next round.)
Speaking of compiler limits, the ANSI C standard requires that a compiler support a minimum of 15 levels of nesting, and the C9X draft extends the minimum requirement to 127 levels.
When you have a lot of ifs and elses, how does the computer decide which if goes with which else ? For example, consider the following program fragment:
if (number > 6) if (number < 12) printf("You're close!\n"); else printf("Sorry, you lose a turn!\n");
When is Sorry, you lose a turn! printed? When number is less than or equal to 6 , or when number is greater than 12 ? In other words, does the else go with the first if or the second? The answer is, the else goes with the second if . That is, you would get these responses:
Number | Response |
---|---|
5 | none |
10 | You're close! |
15 | Sorry, you lose a turn! |
The rule is that an else goes with the most recent if unless braces indicate otherwise (see Figure 7.3).
We indented the example to make it look as though the else goes with the first if , but remember that the compiler ignores indentation. If you really want the else to go with the first if , you could write the fragment this way:
if (number > 6) { if (number < 12) printf("You're close!\n"); } else printf("Sorry, you lose a turn!\n");
Now you would get these responses:
Number | Response |
---|---|
5 | Sorry, you lose a turn! |
10 | You're close! |
15 | none |
You've already seen that the if else if else sequence is a form of nested if , one that selects from a series of alternatives. Another kind of nested if is used when choosing a particular selection leads to an additional choice. For example, a program could use an if else to select between males and females. Each branch within the if else then could contain another if else to distinguish between different income groups.
Let's apply this form of nested if to the following problem. Given an integer, print all the integers that divide into it evenly; if there are no divisors, report that the number is prime.
This problem requires some forethought before you whip out the code. First, you need an overall design for the program. For convenience, the program should use a loop to enable you to input numbers to be tested . That way, you don't have to run the program again each time you want to examine a new number. We've already developed a model for this kind of loop:
prompt user while the scanf() return value is 1 analyze the number and report results prompt user
Recall that by using scanf() in the loop test condition, the program attempts both to read a number and to check to see whether the loop should be terminated .
Next, you need a plan for finding divisors. Perhaps the most obvious approach is something like this:
for (div = 2; div < num; div++) if (num % div == 0) printf("%d is divisible by %d\n", num, div);
The loop checks all the numbers between 2 and num to see whether they divide evenly into num . Unfortunately, this approach wastes computer time. You can do much better. Consider, for example, finding the divisors of 144. You find that 144 % 2 is 0, meaning 2 goes into 144 evenly. If you then actually divide 2 into 144, you get 72, which also is a divisor, so you can get two divisors instead of one divisor out of a successful num % div test. The real payoff, however, comes in changing the limits of the loop test. To see how this works, look at the pairs of divisors you get as the loop continues: 2,72; 3,48; 4,36; 6,24; 8,18; 9,16; 12,12; 16,9; 18,8;. . . . Ah! After you get past the 12,12 pair, you start getting the same divisors (in reverse order) you already found. Instead of running the loop to 143, you can stop after reaching 12. That saves a lot of cycles!
Generalizing this discovery, you see that you have to test only up to the square root of num instead of to num . For numbers like 9, this is not a big savings, but the difference is enormous for a number like 10,000. Instead of messing with square roots, however, you can express the test condition as follows:
for (div = 2; (div * div) <= num; div++) if (num % div == 0) rintf("%d is divisible by %d and %d.\n", num, div, num / div);
If num is 144, the loop runs through div = 12 . If num is 145, the loop runs through div = 13 .
There are two reasons for using this test rather than a square root test. First, integer multiplication is much faster than extracting a square root. Second, the square root function hasn't been formally introduced yet.
We need to address just two more problems, and then you'll be ready to program. First, what if the test number is a perfect square? Reporting that 144 is divisible by 12 and 12 is a little clumsy, but you can use a nested if statement to test whether div equals num / div . If so, the program will print just one divisor instead of two.
for (div = 2; (div * div) <= num; div++) { if (num % div == 0) { if (div * div != num) printf("%d is divisible by %d and %d.\n", num, div, num / div); else printf("%d is divisible by %d.\n", num, div); } }
Note
Technically, the if else statement counts as a single statement, so the braces around it are not needed. The outer if is a single statement also, so the braces around it are not needed. However, when statements get long, the braces make it easier to see what is happening, and they offer protection if later you add another statement to an if or to the loop.
Second, how do you know if a number is prime? If num is prime, then program flow never gets inside the if statement. To solve this problem, you can set a variable to some value, say 1 , outside the loop and reset the variable to inside the if statement. Then, after the loop is completed, you can check to see whether the variable is still 1 . If it is, the if statement was never entered, and the number is prime. Such a variable is often called a flag.
Listing 7.5 incorporates all the ideas we've discussed. To extend the range, we've switched to type long .
/* divisors.c -- nested ifs display divisors of a number */ #include <stdio.h> #define NO 0 #define YES 1 int main(void) { long num; /* number to be checked */ long div; /* potential divisors */ int prime; printf("Please enter an integer for analysis; "); printf("Enter q to quit.\n"); while (scanf("%ld", #) == 1) { for (div = 2, prime = YES; (div * div) <= num; div++) { if (num % div == 0) { if ((div * div) != num) printf("%ld is divisible by %ld and %ld.\n", num, div, num / div); else printf("%ld is divisible by %ld.\n", num, div); prime = NO; /* number is not prime */ } } if (prime == YES) printf("%ld is prime.\n", num); printf("Please enter another integer for analysis; "); printf("Enter q to quit.\n"); } return 0; }
Note that the program uses the comma operator in the for loop control expression to enable you to initialize prime to YES for each new input number.
Here's a sample run:
Please enter an integer for analysis; Enter q to quit. 36 36 is divisible by 2 and 18. 36 is divisible by 3 and 12. 36 is divisible by 4 and 9. 36 is divisible by 6. Please enter another integer for analysis; Enter q to quit. 149 149 is prime. Please enter another integer for analysis; Enter q to quit. 30077 30077 is divisible by 19 and 1583. Please enter another integer for analysis; Enter q to quit. q
Summary: Using if Statements for Making ChoicesKeywords: if, else General Comments: In each of the following forms, the statement can be either a simple statement or a compound statement. A "true" expression means one with a nonzero value. Form 1: if ( expression ) statement The statement is executed if the expression is true. Form 2: if ( expression ) statement1 else statement2 If the expression is true, statement1 is executed. Otherwise, statement2 is executed. Form 3: if ( expression1 ) statement1 else if ( expression2 ) statement2 else statement3 If expression1 is true, then statement1 is executed. If expression1 is false but expression2 is true, statement2 is executed. Otherwise, if both expressions are false, statement3 is executed. Example: if (legs == 4) printf("It might be a horse.\n"); else if (legs > 4) printf("It is not a horse.\n"); else /* case of legs < 4 */ { legs++; printf("Now it has one more leg.\n") } |
I l @ ve RuBoard |