CHAPTER 3

Making Decisions

In Chapter 2 you learned how to do calculations in your programs. In this chapter, you'll take great leaps forward in the range of programs you can write and the flexibility you can build into them. You'll add one of the most powerful programming tools to your inventory: the ability to compare the values of expressions and, based on the outcome, choose to execute one set of statements or another.

What this means is that you'll be able to control the sequence in which statements are executed in a program. Up until now, all the statements in your programs have been executed strictly in sequence. In this chapter you're going to change all that.

You are going to learn the following:

  • How to make decisions based on arithmetic comparisons
  • What logical operators are and how you can use them
  • More about reading data from the keyboard
  • How you can write a program that can be used as a calculator

The Decision-Making Process

You'll start with the essentials of in a program. Decision making in a program is concerned with choosing to execute one set of program statements rather than another. In everyday life you do this kind of thing all the time. Each time you wake up you have to decide whether it's a good idea to go to work. You may go through these questions:

Do I feel well?

If the answer is no, stay in bed. If the answer is yes, go to work.

You could rewrite this as follows:

If I feel well, I will go to work. Otherwise, I will stay in bed.

That was a straightforward decision. Later, as you're having breakfast, you notice it's raining, so you think:

If it is raining as hard as it did yesterday, I will take the bus. If it is raining harder than yesterday, I will drive to work. Otherwise, I will risk it and walk.

This is a more complex decision process. It's a decision based on several levels in the amount of rain falling, and it can have any of three different results.

As the day goes on, you're presented with more of these decisions. Without them you'd be stuck with only one course of action. Until now, in this book, you've had exactly the same problem with your programs. All the programs will run a straight course to a defined end, without making any decisions. This is a severe constraint on what your programs can do and one that you'll relieve now. First, you'll set up some basic building blocks of knowledge that will enable you to do this.

Arithmetic Comparisons

To make a decision, you need a mechanism for comparing things. This involves some new operators. Because you're dealing with numbers, comparing numerical values is basic to decision making. You have three fundamental relational operators that you use to compare values:

  • < is less than
  • == is equal to
  • > is greater than

Note The equal to operator has two successive equal signs (==). You'll almost certainly use one equal sign on occasions by mistake. This will cause considerable confusion until you spot the problem. Look at the difference. If you type my_weight = your_weight, it's an assignment that puts the value from the variable your_weight into the variable my_weight. If you type the expression my_weight == your_weight, you're comparing the two values: you're asking whether they're exactly the same—you're not making them the same. If you use = where you intended to use == the compiler cannot determine that it is an error because either is usually valid.


Expressions Involving Relational Operators

Have a look at these examples:

5 < 4      1 == 2      5 > 4

These expressions are called logical expressions or Boolean expressions because each of them can result in just one of two values: either true or false. As you saw in the previous chapter, the value true is represented by 1; false is represented by 0. The first expression is false because 5 is patently not less than 4. The second expression is also false because 1 is not equal to 2. The third expression is true because 5 is greater than 4.

Because a relational operator produces a Boolean result, you can store the result in a variable of type _Bool. For example

_Bool result = 5 < 4;        /* result will be false */

If you #include the <stdbool.h> header file in the source file, you can use bool instead of the keyword _Bool, so you could write the statement like this:

bool result = 5 < 4;         /* result will be false */

Keep in mind that any nonzero numerical value will result in true when it is converted to type _Bool. This implies that you can assign the result of an arithmetic expression to a _Bool variable and store true if it is nonzero and false otherwise.

The Basic if Statement

Now that you have the relational operators for making comparisons, you need a statement allowing you to make a decision. The simplest is the if statement. If you want to compare your weight with that of someone else and print a different sentence depending on the result, you could write the body of a program as follows:

if(your_weight > my_weight)
  printf("You are heavier than me. ");

if(your_weight < my_weight)
  printf("I am heavier than you. ");

if(your_weight == my_weight)
  printf("We are exactly the same weight. ");

Note how the statement following each if is indented. This is to show that it's dependent on the result of the if test. Let's go through this and see how it works. The first if tests whether the value in your_weight is greater than the value in my_weight. The expression for the comparison appears between the parentheses that immediately follow the keyword if. If the result of the comparison is true, the statement immediately after the if will be executed. This just outputs the following message:


You are heavier than me.

Execution will then continue with the next if.

What if the expression between the parentheses in the first if is false? In this case, the statement immediately following the if will be skipped, so the message won't be displayed. It will be displayed only if your_weight is greater than my_weight.

The second if works in essentially the same way. If the expression between parentheses after the keyword if is true, the following statement will be executed to output this message:


I am heavier than you.

This will be the case if your_weight is less than my_weight. If this isn't so, the statement will be skipped and the message won't be displayed. The third if is again the same. The effect of these statements is to print one message that will depend on whether your_weight is greater than, less than, or equal to my_weight. Only one message will be displayed because only one of these can be true.

The general form or syntax of the if statement is as follows:

if(expression)
  Statement1;

Next_statement;

Notice that the expression that forms the test (the if) is enclosed between parentheses and that there is no semicolon at the end of the first line. This is because both the line with the if keyword and the following line are tied together. The second line could be written directly following the first, like this:

if(expression) Statement1;

But for the sake of clarity, people usually put Statement1 on a new line.

The expression in parentheses can be any expression that results in a value of true or false. If the expression is true, Statement1 is executed, after which the program continues with Next_statement. If the expression is false, Statement1 is skipped and execution continues immediately with Next_statement. This is illustrated in Figure 3-1.

image

Figure 3-1. The operation of the if statement

You could have used the basic if statement to add some politically incorrect comments in the program that calculated the height of a tree at the end of the previous chapter. For example, you could have added the following code just after you'd calculated the height of the shortest person:

if(Shorty < 36)
  printf(" My, you really are on the short side, aren't you?");

Here, you have used the if statement to add a gratuitously offensive remark, should the individual be less than 36 inches tall.

Don't forget what I said earlier about what happens when a numerical value is converted to type _Bool. Because the control expression for an if statement is expected to produce a Boolean result, the compiler will arrange to convert the result of an if expression that produces a numerical result to type _Bool. You'll sometimes see this used in programs to test for a nonzero result of a calculation. Here's a statement that illustrates this:

if(count)
  printf("The value of count is not zero.");

This will only produce output if count is not 0, because a 0 value for count will mean the if expression is false.

Extending the if Statement: if-else

You can extend the if statement with a small addition that gives you a lot more flexibility. Imagine it rained a little yesterday. You could write the following:

If the rain today is worse than the rain yesterday,

I will take my umbrella.

Else

I will take my jacket.

Then I will go to work.

This is exactly the kind of decision-making the if-else statement provides. The syntax of the if-else statement is as follows:

if(expression)
  Statement1;
else
  Statement2;

Next_statement;

Here, you have an either-or situation. You'll always execute either Statement1 or Statement2 depending on whether expression results in the value true or false:

If expression evaluates to true, Statement1 is executed and the program continues with Next_statement.

If expression evaluates to false, Statement2 following the else keyword is executed, and the program continues with Next_statement.

The sequence of operations involved here is shown in Figure 3-2.

image

Figure 3-2. The operation of the if-else statement

Using Blocks of Code in if Statements

You can also replace either Statement1 or Statement2, or even both, by a block of statements enclosed between braces {}. This means that you can supply many instructions to the computer after testing the value of an expression using an if statement simply by placing these instructions together between braces. I can illustrate the mechanics of this by considering a real-life situation:

If the weather is sunny,

I will walk to the park, eat a picnic, and walk home.

Else

I will stay in, watch football, and drink beer.

The syntax for an if statement that involves statement blocks is as follows:

if(expression)
{
  StatementA1;
  StatementA2;
  ...
}
else
{
  StatementB1;
  StatementB2;
  ...
}
Next_statement;

All the statements that are in the block between the braces following the if condition will be executed if expression evaluates to true. If expression evaluates to false, all the statements between the braces following the else will be executed. In either case, execution continues with Next_statement. Have a look at the indentation. The braces aren't indented, but the statements between the braces are. This makes it clear that all the statements between an opening and a closing brace belong together.


Note Although I've been talking about using a block of statements in place of a single statement in an if statement, this is just one example of a general rule. Wherever you can have a single statement, you can equally well have a block of statements between braces. This also means that you can nest one block of statements inside another.


Nested if Statements

It's also possible to have ifs within ifs. These are called nested ifs. For example

If the weather is good,

I will go out in the yard.

And if it's cool enough,

I will sit in the sun.

Else

I will sit in the shade.

Else

I will stay indoors.

I will then drink some lemonade.

In programming terms, this corresponds to the following:

if(expression1)              /* Weather is good?            */
{
  StatementA;                /* Yes - Go out in the yard    */
  if(expression2)            /* Cool enough?                */
    StatementB;              /* Yes - Sit in the sun        */
  else
    StatementC;              /* No - Sit in the shade       */
}
else
  StatementD;                /* Weather not good - stay in  */
Statement E;                 /* Drink lemonade in any event */

Here, the second if condition, expression2, is only checked if the first if condition, expression1, is true. The braces enclosing StatementA and the second if are necessary to make both of these statements a part of what is executed when expression1 is true. Note how the else is aligned with the if it belongs to. The logic of this is illustrated in Figure 3-3.

image

Figure 3-3. Nested if statements

More Relational Operators

You can now add a few more relational operators that you can use to compare expressions in if statements. These three additional operators make up the complete set:

  • >= is greater than or equal to
  • <= is less than or equal to
  • != is not equal to

These are fairly self-explanatory, but let's consider some examples anyway, starting with a few arithmetic examples:

6 >= 5      5 <= 5      4 <= 5      4 != 5      10 != 10

These all result in the value true, except for the last one, which is false because 10 most definitely is equal to 10. These operators can be applied to values of type char and wchar_t as well as the other numerical types. If you remember, character types also have a numeric value associated with them. The ASCII table in Appendix B provides a full listing of all the standard ASCII characters and their numeric codes. Table 3-1 is an extract from Appendix B as a reminder for the next few examples.

Table 3-1. Characters and ASCII Codes

Character ASCII Code (Decimal)
A 65
B 66
P 80
Q 81
Z 90
b 98

A char value may be expressed either as an integer or as a keyboard character between quotes, such as 'A'. Don't forget, numeric values stored as type char may be signed or unsigned, depending on how your compiler implements the type. When type char is unsigned, values can be from 128 to +127. When char is an unsigned type, values can be from 0 to 255. Here are a few examples of comparing values of type char:

'Z' >= 'A'      'Q' <= 'P'      'B' <= 'b'      'B' != 66

With the ASCII values of the characters in mind, the first expression is true, because 'Z', which has the code value 90, comes after 'A', which has the code value 65. The second is false, as 'Q' doesn't come before 'P'. The third is true. This is because in ASCII code lowercase letters are 32 higher than their uppercase equivalents. The last is false. The value 66 is indeed the decimal ASCII representation for the character 'B'.

Logical Operators

Sometimes it just isn't enough to perform a single test for a decision. You may want to combine two or more checks on values and, if they're all true, perform a certain action. Or you may want to perform a calculation if one or more of a set of conditions are true.

For example, you may only want to go to work if you're feeling well and it's a weekday. Just because you feel great doesn't mean you want to go in on a Saturday or a Sunday. Alternatively, you could say that you'll stay at home if you feel ill or if it's a weekend day. These are exactly the sorts of circumstances for which the logical operators are intended.

The AND Operator &&

You can look first at the logical AND operator, &&. This is another binary operator because it operates on two items of data. The && operator combines two logical expressions—that is, two expressions that have a value true or false. Consider this expression:

Test1 && Test2

This expression evaluates to true if both expressions Test1 and Test2 evaluate to true. If either or both of the operands for the && operator are false, the result of the operation is false.

The obvious place to use the && operator is in an if expression. Let's look at an example:

if(age > 12 && age < 20)
  printf("You are officially a teenager.");

The printf() statement will be executed only if age has a value between 13 and 19 inclusive.

Of course, the operands of the && operator can be _Bool variables. You could replace the previous statement with the following:

_Bool test1 = age > 12;
_Bool test2 = age < 20;
if(test1 && test2)
  printf("You are officially a teenager.");

The values of the two logical expressions checking the value of age are stored in the variables test1 and test2. The if expression is now much simpler using the _Bool variables as operands.

Naturally, you can use more than one of these logical operators in an expression:

if(age > 12 && age < 20 && savings > 5000)
  printf("You are a rich teenager.");

All three conditions must be true for the printf() to be executed. That is, the printf() will be executed only if the value of age is between 13 and 19 inclusive, and the value of savings is greater than 5000.

The OR Operator ||

The logical OR operator, ||, covers the situation in which you want to check for any of two or more conditions being true. If either or both operands of the || operator is true, the result is true. The result is false only when both operands are false. Here's an example of using this operator:

if(a < 10 || b > c || c > 50)
  printf("At least one of the conditions is true.");

The printf() will be executed only if at least one of the three conditions, a<10, b>c, or c<50, is true. When a, b, and c all have the value 9, for instance, this will be the case. Of course, the printf() will also be executed when two of the conditions are true, as well as all three.

You can use the && and || logical operators in combination, as in the following code fragment:

if((age > 12 && age < 20) || savings > 5000)
  printf ("Either you're a teenager, or you're rich, or possibly both.");

The printf() statement will be executed if the value of age is between 12 and 20 or the value of savings is greater than 5000, or both. As you can see, when you start to use more operators, things can get confusing. The parentheses around the expression that is the left operand of the || operator are not strictly necessary but I put them in to make the condition easier to understand. Making use of Boolean variables can help. You could replace the previous statement with the following:

bool age_test1 = age > 12;
bool age_test2 = age < 20;
bool age_check = test1 && test2;
bool savings_check = savings > 5000;
if((age_check || savings_check)
 printf ("Either you're a teenager, or you're rich, or possibly both.");

Now you have declared four Boolean variables using bool, which assumes the <stdbool.h> header has been included into the source file. You should be able to see that the if statement works with essentially the same test as before. Of course, you could define the value of age_check in a single step, like this:

bool age_check = age > 12 && age < 20;
bool savings_check = savings > 5000;
if((age_check || savings_check)
 printf ("Either you're a teenager, or you're rich, or possibly both.");

This reduces the number of variables you use and still leaves the code reasonably clear.

The NOT Operator !

Last but not least is the logical NOT operator, represented by !. The ! operator is a unary operator, because it applies to just one operand. The logical NOT operator reverses the value of a logical expression: true becomes false, and false becomes true. Suppose you have two variables, a and b, with the values 5 and 2 respectively; then the expression a>b is true. If you use the logical NOT operator, the expression !(a>b) is false. I recommend that you avoid using this operator as far as possible; it tends to result in code that becomes difficult to follow. As an illustration of how not to use NOT, you can rewrite the previous example as follows:

if((!(age >= 12) && !(age >= 20)) || !(savings <= 5000))
{
  printf(" You're either not a teenager and rich ");
  printf("or not rich and a teenager, ");
  printf("or neither not a teenager nor not rich.");
}

As you can see, it becomes incredibly difficult to unravel the nots!

The Conditional Operator

There's another operator called the conditional operator that you can use to test data. It evaluates one of two expressions depending on whether a logical expression evaluates true or false.

Because three operands are involved—the logical expression plus two other expressions—this operator is also referred to as the ternary operator. The general representation of an expression using the conditional operator looks like this:

condition ? expression1 : expression2

Notice how the operator is arranged in relation to the operands. There is ? following the logical expression, condition, to separate it from the next operand, expression1. This is separated from the third operand, expression2, by a colon. The value that results from the operation will be produced by evaluating expression1 if condition evaluates to true, or by evaluating expression2 if condition evaluates to false. Note that only one of expression1 and expression2 will be evaluated. Normally this is of little significance, but sometimes this is important.

You can use the conditional operator in a statement such as this:

x = y > 7 ? 25 : 50;

Executing this statement will result in x being set to 25 if y is greater than 7, or to 50 otherwise. This is a nice shorthand way of producing the same effect as this:

if(y > 7)
  x = 25;
else
  x = 50;

The conditional operator enables you to express some things economically. An expression for the minimum of two variables can be written very simply using the conditional operator. For example, you could write an expression that compared two salaries and obtained the greater of the two, like this:

your_salary > my_salary ? your_salary : my_salary

Of course, you can use the conditional operator in a more complex expression. Earlier in Program 3.2 you calculated a quantity price for a product using an if-else statement. The price was $3.50 per item with a discount of 5 percent for quantities over ten. You can do this sort of calculation in a single step with the conditional operator:

total_price = unit_price*quantity*(quantity>10 ? 1.0 : 0.95);

In spite of its odd appearance, you'll see the conditional operator crop up quite frequently in C programs. A very handy application of this operator that you'll see in examples in this book and elsewhere is to vary the contents of a message or prompt depending on the value of an expression. For example, if you want to display a message indicating the number of pets that a person has, and you want the message to change between singular and plural automatically, you could write this:

printf("You have %d pet%s.", pets, pets == 1 ? "" : "s" );

You use the %s specifier when you want to output a string. If pets is equal to 1, an empty string will be output in place of the %s; otherwise, "s" will be output. Thus, if pets has the value 1, the statement will output this message:


You have 1 pet.

However, if the variable pets is 5, you will get this output:


You have 5 pets.

You can use this mechanism to vary an output message depending on the value of an expression in many different ways: she instead of he, wrong instead of right, and so on.

Operator Precedence: Who Goes First?

With all the parentheses you've used in the examples in this chapter, now is a good time to come back to operator precedence. Operator precedence determines the sequence in which operators in an expression are executed. You have the logical operators &&, ==, !=, and ||, plus the comparison operators and the arithmetic operators. When you have more than one operator in an expression, how do you know which ones are used first? This order of precedence can affect the result of an expression substantially.

For example, suppose you are to process job applications and you want to only accept applicants who are 25 or older and have graduated from Harvard or Yale. Here's the age condition you can represent by this conditional expression:

Age >= 25

Suppose that you represent graduation by the variables Yale and Harvard, which may be true or false. Now you can write the condition as follows:

Age >= 25 && Harvard || Yale

Unfortunately, this will result in howls of protest because you'll now accept Yale graduates who are under 25. In fact, this statement will accept Yale graduates of any age. But if you're from Harvard, you must be 25 or over to be accepted. Because of operator precedence, this expression is effectively the following:

(Age >= 25 && Harvard) || Yale

So you take anybody at all from Yale. I'm sure those wearing a Y-front sweatshirt will claim that this is as it should be, but what you really meant was this:

Age >= 25 && (Harvard || Yale)

Because of operator precedence, you must put the parentheses in to force the order of operations to be what you want.

In general, the precedence of the operators in an expression determines whether it is necessary for you to put parentheses in to get the result you want, but if you are unsure of the precedence of the operators you are using, it does no harm to put the parentheses in. Table 3-2 shows the order of precedence for all the operators in C, from highest at the top to lowest at the top.

There are quite a few operators in the table that we haven't addressed yet. You'll see the operators ˜, <<, >>, &, ^, and | later in this chapter in the "Bitwise Operators" section and you'll learn about the rest later in the book.

All the operators that appear in the same row in the table are of equal precedence. The sequence of execution for operators of equal precedence is determined by their associativity, which determines whether they're selected from left to right or from right to left. Naturally, parentheses around an expression come at the very top of the list of operators because they're used to override the natural priorities defined.

Table 3-2. Operator Order of Precedence

Operators Description Associativity
( )[]. -> Parenthesized expressionArray subscriptMember selection by objectMember selection by pointer Left-to-right
+ -++ —! ˜*&sizeof(type) Unary + and Prefix increment and prefix decrementLogical NOT and bitwise complementDereferenceAddress-ofSize of expression or typeExplicit cast to type such as (int) or (double) Type casts such as (int) or (double) Right-to-left
* / % Multiplication and division and modulus (remainder) Left-to-right
+ - Addition and subtraction Left-to-right
<< >> Bitwise shift left and bitwise shift right Left-to-right
< <=> >= Less than and less than or equal toGreater than and greater than or equal to Left-to-right
== != Equal to and not equal to Left-to-right
& Bitwise AND Left-to-right
^ Bitwise exclusive OR Left-to-right
| Bitwise OR Left-to-right
&& Logical AND Left-to-right
|| Logical OR Left-to-right
?: Conditional operator Right-to-left
=+= -=/= *=%=<<= >>=&= |=^= AssignmentAddition assignment and subtraction assignmentDivision assignment and multiplication assignmentModulus assignmentBitwise shift left assignment and bitwise shift right assignmentBitwise AND assignment and bitwise OR assignmentBitwise exclusive OR assignment Right-to-left
, Comma operator Left-to-right

As you can see from Table 3-2, all the comparison operators are below the binary arithmetic operators in precedence, and the binary logical operators are below the comparison operators. As a result, arithmetic is done first, then comparisons, and then logical combinations. Assignments come last in this list, so they're only performed once everything else has been completed. The conditional operator squeezes in just above the assignment operators.

Note that the ! operator is highest within the set of logical operators. Consequently the parentheses around logical expressions are essential when you want to negate the value of a logical expression.

Multiple-Choice Questions

Multiple-choice questions come up quite often in programming. One example is selecting a different course of action depending on whether a candidate is from one or other of six different universities. Another example is when you want to choose to execute a particular set of statements depending on which day of the week it is. You have two ways to handle multiple-choice situations in C. One is a form of the if statement described as the else-if that provides the most general way to deal with multiple choices. The other is the switch statement, which is restricted in the way a particular choice is selected; but where it does apply, it provides a very neat and easily understood solution. Let's look at the else-if statement first.

Using else-if Statements for Multiple Choices

The use of the else-if statement for selecting one of a set of choices looks like this:

if(choice1)
  /* Statement or block for choice 1 */
else if(choice2)
  /* Statement or block for choice 2 */
else if(choice3)
  /* Statement or block for choice 2 */

/* ... and so on ...   */
else
  /* Default statement or block  */

Each if expression can be anything as long as the result is true or false. If the first if expression, choice1, is false, the next if is executed. If choice2 is false, the next if is executed. This continues until an expression is found to be true, in which case the statement or block of statements for that if is executed. This ends the sequence, and the statement following the sequence of else-if statements is executed next.

If all of the if conditions are false, the statement or block following the final else will be executed. You can omit this final else, in which case the sequence will do nothing if all the if conditions are false. Here's a simple illustration of this:

if(salary<5000)
  printf("Your pay is very poor.");    /* pay < 5000            */
else if(salary<15000)
  printf("Your pay is not good.");     /* 5000 <= pay < 15000   */
else if(salary<50000)
  printf("Your pay is not bad.");      /* 15000 <= pay < 50000  */
else if(salary<100000)
  printf("Your pay is very good.");    /* 50000 <= pay < 100000 */
else
  printf("Your pay is exceptional.");  /* pay > 100000          */

Note that you don't need to test for lower limits in the if conditions after the first. This is because if you reach a particular if, the previous test must have been false.

Because any logical expressions can be used as the if conditions, this statement is very flexible and allows you to express a selection from virtually any set of choices. The switch statement isn't as flexible, but it's simpler to use in many cases. Let's take a look at the switch statement.

The switch Statement

The switch statement enables you to choose one course of action from a set of possible actions, based on the result of an integer expression. Let's start with a simple illustration of how it works.

Imagine that you're running a raffle or a sweepstakes. Suppose ticket number 35 wins first prize, number 122 wins second prize, and number 78 wins third prize. You could use the switch statement to check for a winning ticket number as follows:

switch(ticket_number)
{
  case 35:
    printf("Congratulations! You win first prize!");
    break;
  case 122:
    printf("You are in luck - second prize.");
    break;
  case 78:
    printf("You are in luck - third prize.");
    break;
  default:
    printf("Too bad, you lose.");
}

The value of the expression in parentheses following the keyword switch, which is ticket_number in this case, determines which of the statements between the braces will be executed. If the value of ticket_number matches the value specified after one of the case keywords, the following statements will be executed. If ticket_number has the value 122, for example, this message will be displayed:


You are in luck - second prize.

The effect of the break statement following the printf() is to skip over the other statements within that block and continue with whatever statement follows the closing brace. If you were to omit the break statement for a particular case, when the statements for that case are executed, execution would continue with the statements for the next case. If ticket_number has a value that doesn't correspond to any of the case values, the statements that follow the default keyword are executed, so you simply get the default message. Both default and break are keywords in C.

The general way of describing the switch statement is as follows:

switch(integer_expression)
{
  case constant_expression_1:
    statements_1;
    break;
    ....
  case constant_expression_n:
    statements_n;
    break;
  default:
    statements;
}

The test is based on the value of integer_expression. If that value corresponds to one of the case values defined by the associated constant_expression_n values, the statements following that case value are executed. If the value of integer_expression differs from every one of the case values, the statements following default are executed. Because you can't reasonably expect to select more than one case, all the case values must be different. If they aren't, you'll get an error message when you try to compile the program. The case values must all be constant expressions, which are expressions that can be evaluated at compile time. This means that a case value can't be dependent on a value that's determined when your program executes. Of course, the test expression integer_expression can be anything at all, as long as it evaluates to an integer.

You can leave out the default keyword and its associated statements. If none of the case values match, then nothing happens. Notice, however, that all of the case values for the associated constant_expression must be different. The break statement jumps to the statement after the closing brace.

Notice the punctuation and formatting. There's no semicolon at the end of the first switch expression. The body of the statement is enclosed within braces. The constant_expression value for a case is followed by a colon, and each subsequent statement ends with a semicolon, as usual.

Because an enumeration type is an integer type, you can use a variable of an enumeration type to control a switch. Here's an example:

enum Weekday {Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday};
enum Weekday today = Wednesday;
switch(today)
{
  case Sunday:
    printf("Today is Sunday.");
    break;
  case Monday:
    printf("Today is Monday.");
    break;
  case Tuesday:
    printf("Today is Tuesday.");
    break;
  case Wednesday:
    printf("Today is Wednesday.");
    break;
  case Thursday:
    printf("Today is Thursday.");
break;
  case Friday:
    printf("Today is Friday.");
    break;
  case Saturday:
    printf("Today is Saturday.");
    break;
  }

This switch selects the case that corresponds to the value of the variable today, so in this case the message will be that today is Wednesday. There's no default case in the switch but you could put one in to guard against an invalid value for today.

You can associate several case values with one group of statements. You can also use an expression that results in a value of type char as the control expression for a switch. Suppose you read a character from the keyboard into a variable, ch, of type char. You can test this character in a switch like this:

switch(tolower(ch))
{
  case 'a': case 'e': case 'i': case 'o': case 'u':
    printf("The character is a vowel.");
    break;
  case 'b': case 'c': case 'd': case 'f': case 'g': case 'h': case 'j': case 'k':
  case 'l': case 'm': case 'n': case 'p': case 'q': case 'r': case 's': case 't':
  case 'v': case 'w': case 'x': case 'y': case 'z':
    printf("The character is a consonant.");
    break;
  default:
    printf("The character is not a letter.");
    break;
}

Because you use the function tolower() that is declared in the <ctype.h> header file to convert the value of ch to lowercase, you only need to test for lowercase letters. In the case in which ch contains the character code for a vowel, you output a message to that effect because for the five case values corresponding to vowels you execute the same printf() statement. Similarly, you output a suitable message when ch contains a consonant. If ch contains a code that's neither a consonant nor a vowel, the default case is executed.

Note the break statement after the default case. This isn't necessary, but it does have a purpose. By always putting a break statement at the end of the last case, you ensure that the switch still works correctly if you later add a new case at the end.

You could simplify the switch by making use of another function that's declared in the <ctype.h> header. The isalpha() function will return a nonzero integer (thus true) if the character that's passed as the argument is an alphabetic character, and it will return 0 (false) if the character isn't an alphabetic character. You could therefore produce the same result as the previous switch with the following code:

if(!isalpha(ch))
    printf("The character is not a letter.");
else
  switch(tolower(ch))
  {
    case 'a': case 'e': case 'i': case 'o': case 'u':
      printf("The character is a vowel.");
    break;
  default:
printf("The character is a consonant.");
    break;
  }

The if statement tests for ch not being a letter, and if this is so, it outputs a message. If ch is a letter, the switch statement will sort out whether it is a vowel or a consonant. The five vowel case values produce one output, and the default case produces the other. Because you know that ch contains a letter when the switch statement executes, if ch isn't a vowel, it must be a consonant.

As well as the tolower(), toupper(), and isalpha() functions that I've mentioned, the <ctype.h> header also declares several other useful functions for testing a character, as shown in Table 3-3.

Table 3-3. Functions for Testing Characters

Function Tests For
islower() Lowercase letter
isupper() Uppercase letter
isalnum() Uppercase or lowercase letter
iscntrl() Control character
isprint() Any printing character including space
isgraph() Any printing character except space
isdigit() Decimal digit ('0' to '9')
isxdigit() Hexadecimal digit ('0' to '9', 'A' to 'F', 'a' to 'f')
isblank() Standard blank characters (space, ' ')
isspace() Whitespace character (space, ' ', ' ', 'v', ' ', 'f')
ispunct() Printing character for which isspace() and isalnum() return false

In each case, the function returns a nonzero integer value (which is interpreted as true) if it finds what it's testing for and 0 (false) otherwise.

Let's look at the switch statement in action with an example.

The goto Statement

The if statement provides you with the ability to choose one or the other of two blocks of statements, depending on a test. This is a powerful tool that enables you to alter the naturally sequential nature of a program. You no longer have to go from A to B to C to D. You can go to A and then decide whether to skip B and C and go straight to D.

The goto statement, on the other hand, is a blunt instrument. It directs the flow of statements to change unconditionally—do not pass Go, do not collect $200, go directly to jail. When your program hits a goto, it does just that. It goes to the place you send it, without checking any values or asking the user whether it is really what he or she wants.

I'm only going to mention the goto statement very briefly, because it isn't as great as it might at first seem. The problem with goto statements is that they seem too easy. This might sound perverse, but the important word is seems. It feels so simple that you can be tempted into using it all over the place, where it would be better to use a different statement. This can result in heavily tangled code.

When you use the goto statement, the position in the code to be moved to is defined by a statement label at that point. A statement label is defined in exactly the same way as a variable name, which is a sequence of letters and digits, the first of which must be a letter. The statement label is followed by a colon (:) to separate it from the statement it labels. If you think this sounds like a case label in a switch, you would be right. Case labels are statement labels.

Like other statements, the goto statement ends with a semicolon:

goto there;

The destination statement must have the same label as appears in the goto statement, which is there in this case. As I said, the label is written preceding the statement it applies to, with a colon separating the label from the rest of the statement, as in this example:

there: x=10;                      /* A labeled statement */

The goto statement can be used in conjunction with an if statement, as in the following example:

...
if(dice == 6)
  goto Waldorf;
else
  goto Jail;                     /* Go to the statement labeled Jail */

Waldorf:
  comfort = high;
  ...
  /* Code to prevent falling through to Jail */

Jail:                     /* The label itself. Program control is sent here */
  comfort = low;
  ...

You roll the dice. If you get 6, you go to the Waldorf; otherwise, you go to Jail. This might seem perfectly fine but, at the very least, it's confusing. To understand the sequence of execution, you need to hunt for the destination labels. Imagine your code was littered with gotos. It would be very difficult to follow and perhaps even more difficult to fix when things go wrong. So it's best to avoid the goto statement as far as possible. In theory it's always possible to avoid using the goto statement, but there are one or two instances in which it's a useful option. You'll look into loops in Chapter 4, but for now, know that exiting from the innermost loop of a deeply nested set of loops can be much simpler with a goto statement than with other mechanisms.

Bitwise Operators

Before you come to the big example for the chapter, you'll examine a group of operators that look something like the logical operators you saw earlier but in fact are quite different. These are called the bitwise operators, because they operate on the bits in integer values. There are six bitwise operators, as shown in Table 3-4.

Table 3-4. Bitwise Operators

Operator Description
& Bitwise AND operator
| Bitwise OR operator
^ Bitwise Exclusive OR (EOR) operator
˜ Bitwise NOT operator, also called the 1's complement operator
>> Bitwise shift right operator
<< Bitwise shift left operator

All of these only operate on integer types. The ˜ operator is a unary operator—it applies to one operand—and the others are binary operators.

The bitwise AND operator, &, combines the corresponding bits of its operands in such a way that if both bits are 1, the resulting bit is 1; otherwise, the resulting bit is 0. Suppose you declare the following variables:

int x = 13;
int y = 6;
int z = x&y;                 /* AND the bits of x and y */

After the third statement, z will have the value 4 (binary 100). This is because the corresponding bits in x and y are combined as follows:

x 0 0 0 0 1 1 0 1
y 0 0 0 0 0 1 1 0
x&y 0 0 0 0 0 1 0 0

Obviously the variables would have more bits than I have shown here, but the additional bits would all be 0. There is only one instance where corresponding bits in the variables x and y are both 1 and that is the third bit from the right; this is the only case where the result of AND ing the bits is 1.


Caution It's important not to get the bitwise operators and the logical operators muddled. The expression x & y will produce quite different results from x && y in general. Try it out and see.


The bitwise OR operator, |, results in 1 if either or both of the corresponding bits are 1; otherwise, the result is 0. Let's look at a specific example. If you combine the same values using the | operator in a statement such as this

int z = x|y;                 /* OR the bits of x and y */

the result would be as follows:

x 0 0 0 0 1 1 0 1
y 0 0 0 0 0 1 1 0
x|y 0 0 0 0 1 1 1 1

The value stored in z would therefore be 15 (binary 1111).

The bitwise EOR operator, ^, produces a 1 if both bits are different, and 0 if they're the same. Again, using the same initial values, the statement

int z = x^y;                 /*Exclusive OR the bits of x and y */

would result in z containing the value 11 (binary 1011), because the bits combine as follows:

x 0 0 0 0 1 1 0 1
y 0 0 0 0 0 1 1 0
x^y 0 0 0 0 1 0 1 1

The unary operator, ˜, flips the bits of its operand, so 1 becomes 0, and 0 becomes 1. If you apply this operator to x with the value 13 as before, and you write

int z = ˜x;                            /* Store 1's complement of x */

then z will have the value 14. The bits are set as follows:

x 0 0 0 0 1 1 0 1
˜x 1 1 1 1 0 0 1 0

The value 11110010 is 14 in 2's complement representation of negative integers. If you're not familiar with the 2's complement form, and you want to find out about it, it is described in Appendix A.

The shift operators shift the bits in the left operand by the number of positions specified by the right operand. You could specify a shift-left operation with the following statements:

int value = 12;
int shiftcount = 3;                    /* Number of positions to be shifted */
int result = value << shiftcount;      /* Shift left shiftcount positions   */

The variable result will contain the value 96. The binary number in value is 00001100. The bits are shifted to the left three positions, and 0s are introduced on the right, so the value of value << shiftcount, as a binary number, will be 01100000.

The right shift operator moves the bits to the right, but it's a little more complicated than left shift. For unsigned values, the bits that are introduced on the left (in the vacated positions as the bits are shifted right) are filled with zeros. Let's see how this works in practice. Suppose you declare a variable:

unsigned int value = 65372U;

As a binary value in a 2-byte variable, this is:

Suppose you now execute the following statement:

1111 1111 0101 1100

unsigned int result = value >> 2;      /* Shift right two bits */

The bits in value will be shifted two places to the right, introducing zeros at the left end, and the resultant value will be stored in result. In binary this will be 0, which is the decimal value 16343.

0011 1111 1101 0111

For signed values that are negative, where the leftmost bit will be 1, the result depends on your system. In most cases, the sign bit is propagated, so the bits introduced on the right are 1 bits, but on some systems zeros are introduced in this case too. Let's see how this affects the result.

Suppose you define a variable with this statement:

int new_value = −164;

This happens to be the same bit pattern as the unsigned value that you used earlier; remember that this is the 2's complement representation of the value:

1111 1111 0101 1100

Suppose you now execute this statement:

int new_result = new_value >> 2;       /* Shift right two bits */

This will shift the value in new_value two bit positions to the right and the result will be stored in new_result. If, as is usually the case, the sign bit is propagated, 1s will be inserted on the left as the bits are shifted to the right, so new_result will end up as

1111 1111 1101 0111

This is the decimal value −41, which is what you might expect because it amounts to −164/4. If the sign bit isn't propagated, however, as can occur on some computers, the value in new_result will be

0011 1111 1101 0111

So shifting right two bits in this case has changed the value −164 to +16343—perhaps a rather unexpected result.

The op= Use of Bitwise Operators

You can use all of the binary bitwise operators in the op= form of assignment. The exception is the operator ˜, which is a unary operator. As you saw in Chapter 2, a statement of the form

lhs op= rhs;

is equivalent to the statement

lhs = lhs op (rhs);

This means that if you write

value <<= 4;

the effect is to shift the contents of the integer variable, value, left four bit positions. It's exactly the same as the following:

value = value << 4;

You can do the same kind of thing with the other binary operators. For example, you could write the following statement:

value &= 0xFF;

where value is an integer variable. This is equivalent to the following:

value = value & 0xFF;

The effect of this is to keep the rightmost eight bits unchanged and to set all the others to zero.

Using Bitwise Operators

The bitwise operators look interesting in an academic kind of way, but what use are they? They don't come up in everyday programs, but in some areas they become very useful. One major use of the bitwise AND, &, and the bitwise OR, |, is in operations to test and set individual bits in an integer variable. With this capability you can use individual bits to store data that involves one of two choices. For example, you could use a single integer variable to store several characteristics of a person. You could store whether the person is male or female with one bit, and you could use three other bits to specify whether the person can speak French, German, or Italian. You might use another bit to record whether the person's salary is $50,000 or more. So in just four bits you have a substantial set of data recorded. Let's see how this would work out.

The fact that you only get a 1 bit when both of the bits being combined are 1 means that you can use the & operator to select a part of an integer variable or even just a single bit. You first define a value, usually called a mask, that you use to select the bit or bits that you want. It will contain a bit value of 1 for the bit positions you want to keep and a bit value of 0 for the bit positions you want to discard. You can then AND this mask with the value that you want to select from. Let's look at an example. You can define masks with the following statements:

unsigned int male       = 0x1;    /* Mask selecting first (rightmost) bit */
unsigned int french     = 0x2;    /* Mask selecting second bit            */
unsigned int german     = 0x4;    /* Mask selecting third bit             */
unsigned int italian    = 0x8;    /* Mask selecting fourth bit            */
unsigned int payBracket = 0x10;   /* Mask selecting fifth bit             */

In each case, a 1 bit will indicate that the particular condition is true. These masks in binary each pick out an individual bit, so you could have an unsigned int variable, personal_data, which would store five items of information about a person. If the first bit is 1, the person is male, and if the first bit is 0, the person is female. If the second bit is 1, the person speaks French, and if it is 0, the person doesn't speak French, and so on for all five bits at the right end of the data value.

You could therefore test the variable, personal_data, for a German speaker with the following statement:

if(personal_data & german)
  /* Do something because they speak German */

The expression personalData & german will be nonzero—that is, true—if the bit corresponding to the mask, german, is 1; otherwise, it will be 0.

Of course, there's nothing to prevent you from combining several expressions involving using masks to select individual bits with the logical operators. You could test whether someone is a female who speaks French or Italian with the following statement:

if(!(personal_data & male) && ((personal_data & french) ||
                                                     (personal_data & italian)))
  /* We have a French or Italian speaking female */

As you can see, it's easy enough to test individual bits or combinations of bits. The only other thing you need to understand is how to set individual bits. The OR operator swings into action here.

You can use the OR operator to set individual bits in a variable using the same mask as you use to test the bits. If you want to set the variable personal_data to record a person as speaking French, you can do it with this statement:

personal_data |= french;               /* Set second bit to 1 */

Just to remind you, the preceding statement is exactly the same as the following statement:

personal_data = personal_data|french;  /* Set second bit to 1 */

The second bit from the right in personal_data will be set to 1, and all the other bits will remain as they were. Because of the way the | operator works, you can set multiple bits in a single statement:

personal_data |= french|german|male;

This sets the bits to record a French- and German-speaking male. If the variable personalData previously recorded that the person spoke Italian, that bit would still be set, so the OR operator is additive. If a bit is already set, it will stay set.

What about resetting a bit? Suppose you want to change the male bit to female. This amounts to resetting a 1 bit to 0, and it requires the use of the ! operator with the bitwise AND:

personal_data &= !male;            /* Reset male to female */

This works because !male will have a 0 bit set for the bit that indicates male and all the other bits as 1. Thus, the bit corresponding to male will be set to 0: 0 ANDed with anything is 0, and all the other bits will be as they were. If another bit is 1, then 1&1 will still be 1. If another bit is 0, then 0&1 will still be 0.

I've used the example of using bits to record specific items of personal data. If you want to program a PC using the Windows application programming interface (API), you'll often use individual bits to record the status of various window parameters, so the bitwise operators can be very useful in this context.

Designing a Program

You've reached the end of Chapter 3 successfully, and now you'll apply what you've learned so far to build a useful program.

The Problem

The problem is to write a simple calculator that can add, subtract, multiply, divide, and find the remainder when one number is divided by another. The program must allow the calculation that is to be performed to be keyed in a natural way, such as 5.6 * 27 or 3 + 6.

The Analysis

All the math involved is simple, but the processing of the input adds a little complexity. You need to make checks on the input to make sure that the user hasn't asked the computer to do something impossible. You must allow the user to input a calculation in one go, for example

34.87 + 5

or

9 * 6.5

The steps involved in writing this program are as follows:

  1. Get the user's input for the calculation that the user wants the computer to perform.
  2. Check that input to make sure that it's understandable.
  3. Perform the calculation.
  4. Display the result.

The Solution

This section outlines the steps you'll take to solve the problem.

Step 1

Getting the user input is quite easy. You'll be using printf() and scanf(), so you need the <stdio.h> header file. The only new thing I'll introduce is in the way in which you'll get the input. As I said earlier, rather than asking the user for each number individually and then asking for the operation to be performed, you'll get the user to type it in more naturally. You can do this because of the way scanf() works, but I'll discuss the details of that after you've seen the first part of the program. Let's kick off the program with the code to read the input:

/*Program 3.11 A calculator*/
#include <stdio.h>

int main(void)
{
  double number1 = 0.0;          /* First operand value a decimal number  */
  double number2 = 0.0;          /* Second operand value a decimal number */
  char operation = 0;            /* Operation - must be +, -, *, /, or %  */

  printf(" Enter the calculation ");
  scanf("%lf %c %lf", &number1, &operation, &number2);

  /* Plus the rest of the code for the program */
  return 0;
}

The scanf() function is fairly clever when it comes to reading data. You don't actually need to enter each input data item on a separate line. All that's required is one or more whitespace characters between each item of input. (You create a whitespace character by pressing the spacebar, the Tab key, or the Enter key.)

Step 2

Next, you must check to make sure that the input is correct. The most obvious check to perform is that the operation to be performed is valid. You've already decided that the valid operations are +, -, /, *, and %, so you need to check that the operation is one of these.

You also need to check the second number to see if it's 0 if the operation is either / or %. If the right operand is 0, these operations are invalid. You could do all these checks using if statements, but a switch statement provides a far better way of doing this because it is easier to understand than a sequence of if statements:

/*Program 3.11 A calculator*/
#include <stdio.h>

int main(void)
{
  double number1 = 0.0;          /* First operand value a decimal number  */
  double number2 = 0.0;          /* Second operand value a decimal number */
  char operation = 0;            /* Operation - must be +, -, *, /, or %  */
printf(" Enter the calculation ");
  scanf("%lf %c %lf", &number1, &operation, &number2);

/* Code to check the input goes here */ switch(operation) {   case '+':                     /* No checks necessary for add      */     break;


  case '-':                     /* No checks necessary for subtract */
break;
case '*':                     /* No checks necessary for multiply */ break;
case '/': if(number2 == 0)           /* Check second operand for zero    */ printf(" aDivision by zero error! "); break;
case '%':                    /* Check second operand for zero    */ if((long)number2 == 0) printf(" aDivision by zero error! "); break;
default:                     /* Operation is invalid if we get to here */ printf(" aIllegal operation! "); break; }   /* Plus the rest of the code for the program */
  return 0;
}

Because you're casting the second operand to an integer when the operator is %, it isn't sufficient to just check the second operand against 0—you must check that number2 doesn't have a value that will result in 0 when it's cast to type long.

Steps 3 and 4

So now that you've checked the input, you can calculate the result. You have a choice here. You could calculate each result in the switch and store it to be output after the switch, or you could simply output the result for each case. Let's go for the latter approach. The code you need to add is as follows:

/*Program 3.11 A calculator*/
#include <stdio.h>

int main(void)
{
  double number1 = 0.0;          /* First operand value a decimal number  */
  double number2 = 0.0;          /* Second operand value a decimal number */
  char operation = 0;            /* Operation - must be +, -, *, /, or %  */
printf(" Enter the calculation ");
  scanf("%lf %c %lf", &number1, &operation, &number2);

  /* Code to check the input goes here */
  switch(operation)
  {
    case '+':                    /* No checks necessary for add       */
      printf("= %lf ", number1 + number2);
      break;

    case '-':                    /* No checks necessary for subtract */
      printf("= %lf ", number1 - number2);
      break;

    case '*':                    /* No checks necessary for multiply */
      printf("= %lf ", number1 * number2);
      break;

    case '/':
      if(number2 == 0)           /* Check second operand for zero    */
        printf(" aDivision by zero error! ");
      else
        printf("= %lf ", number1 / number2);
       break;

    case '%':                    /* Check second operand for zero    */
      if((long)number2 == 0)
        printf(" aDivision by zero error! ");
      else
        printf("= %ld ", (long)number1 % (long)number2);
      break;

    default:                     /* Operation is invalid if we get to here */
      printf(" aIllegal operation! ");
      break;
  }
  return 0;
}

Notice how you cast the two numbers from double to long when you calculate the modulus. This is because the % operator only works with integers in C.

All that's left is to try it out! Here's some typical output:


Enter the calculation
25*13
= 325.000000

Here's another example:


Enter the calculation
999/3.3
= 302.727273

And just one more


Enter the calculation
7%0


Division by zero error!

Summary

This chapter ends with quite a complicated example. In the first two chapters you really just looked at the groundwork for C programs. You could do some reasonably useful things, but you couldn't control the sequence of operations in the program once it had started. In this chapter you've started to feel the power of the language and how you can use data entered by the user or results calculated during execution to determine what happens next.

You've learned how to compare variables and then use if, if-else, else-if, and switch statements to affect the outcome. You also know how to use logical operators to combine comparisons between your variables. You should now understand a lot more about making decisions and taking different paths through your program code.

In the next chapter, you'll learn how to write even more powerful programs: programs that can repeat a set of statements until some condition is met. By the end of Chapter 4, you'll think your calculator is small-fry.

Exercises

The following exercises enable you to try out what you've learned in this chapter. If you get stuck, look back over the chapter for help. If you're still stuck, you can download the solutions from the Source Code/Download area of the Apress web site (http://www.apress.com), but that really should be a last resort.

Exercise 3-1. Write a program that will first allow a user to choose one of two options:

  1. Convert a temperature from degrees Celsius to degrees Fahrenheit.
  2. Convert a temperature from degrees Fahrenheit to degrees Celsius.

The program should then prompt for the temperature value to be entered and output the new value that results from the conversion. To convert from Celsius to Fahrenheit you can multiply the value by 1.8 and then add 32. To convert from Fahrenheit to Celsius, you can subtract 32 from the value, then multiply by 5, and divide the result by 9.

Exercise 3-2. Write a program that prompts the user to enter the date as three integer values for the month, the day in the month, and the year. The program should then output the date in the form 31st December 2003 when the user enters 12 31 2003, for example.

You will need to work out when th, nd, st, and rd need to be appended to the day value. Don't forget 1st, 2nd, 3rd, 4th; but 11th, 12th, 13th, 14th; and 21st, 22nd, 23rd, and 24th.

Exercise 3-3. Write a program that will calculate the price for a quantity entered from the keyboard, given that the unit price is $5 and there is a discount of 10 percent for quantities over 30 and a 15 percent discount for quantities over 50.

Exercise 3-4. Modify the last example in the chapter that implemented a calculator so that the user is given the option to enter y or Y to carry out another calculation, and n or N to end the program. (Note: You'll have to use a goto statement for this at this time, but you'll learn a better way of doing this in the next chapter).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.191.134