6. Making Decisions

A fundamental feature of any programming language lies in its capability to make decisions. Decisions were made when executing the looping statements to determine when to terminate a loop. The Objective-C programming language also provides several other decision-making constructs, which are covered in this chapter:

• The if statement

• The switch statement

• The conditional operator

The if Statement

The Objective-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

if ( expression )
   program statement

Imagine if you will that you could translate a statement such as “If it is not raining then I will go swimming” into the Objective-C language. Using the previous format for the if statement, this might be “written” in Objective-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 on specified conditions. I will go swimming if it is not raining. Similarly, in the program statement

if ( count > MAXIMUM_SONGS )
     [playlist maxExceeded];

the maxExceeded message is sent to playlist only if the value of count is greater than the value of MAXIMUM_SONGS; otherwise, it is ignored.

An actual program example will help drive the 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 previous sentence signals that a decision must be made by the program. This decision can be effected by the use of an if statement as shown in the program that follows.

Program 6.1.


// Calculate the absolute value of an integer

#import <stdio.h>

int main (int argc, char *argv[])
{
    int number;

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

    if ( number < 0 )
         number = -number;

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

    return 0;
}


Program 6.1. Output


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


Program 6.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 into number, the program tests the value of number to see whether 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.

Let's look at another program that uses the if statement. Let's add one more method to the Fraction class, called convertToNum. This method will provide the value of a fraction expressed as a real number. In other words, it will divide the numerator by the denominator and return the result as a double precision value. So, if you have the fraction 1/2, you want the method to return the value 0.5.

The declaration for such a method might look like this:

-(double) convertToNum;

And this is how you could write its definition:

-(double) convertToNum
{
   return numerator / denominator;
}

Well, not quite. There are actually two serious problems with this method as it's defined. Can you spot them? The first has to do with arithmetic conversions. You will recall that numerator and denominator are both integer instance variables. So, what happens when you divide two integers? Correct, it is done as an integer division! So, if you wanted to convert the fraction 1/2, the previous code would give you zero! This is easily corrected by using the type cast operator to convert one or both of the operands to a floating-point value before the division takes place:

(double) numerator / denominator

Recalling the relatively high precedence of this operator, numerator is first converted to double before the division occurs. Further, you don't need to convert the denominator because the rules of arithmetic conversion take care of that for you.

The second problem with this method is that you should check for division by zero (you should always check for that!). The invoker of this method could inadvertently have forgotten to set the denominator of the fraction or might have set the denominator of the fraction to zero, and you don't want your program to terminate abnormally.

The modified version of the convertToNum method is shown here:

-(double) convertToNum
{
  if (denominator != 0)
    return (double) numerator / denominator;
  else
    return 0.0;
}

We arbitrarily decided to return 0.0 if the denominator of the fraction is zero. Other options are available (such as printing an error message), but we won't go into them here.

Let's put this new method to use. Here's a test program to try it.

Program 6.2.


#import <stdio.h>
#import <objc/Object.h>

@interface Fraction: Object
{
  int   numerator;
  int   denominator;
}

-(void)   print;
-(void)   setNumerator: (int) n;
-(void)   setDenominator: (int) d;
-(int)    numerator;
-(int)    denominator;
-(double) convertToNum;
@end


@implementation Fraction;
-(void) print
{
  printf (" %i/%i ", numerator, denominator);
}

-(void) setNumerator: (int) n
{
  numerator = n;
}

-(void) setDenominator: (int) d
{
  denominator = d;
}

-(int) numerator
{
  return numerator;
}

-(int) denominator
{
  return denominator;
}


-(double) convertToNum
{
  if (denominator != 0)
    return (double) numerator / denominator;
  else
    return 0.0;
}
@end

int main (int argc, char *argv[])
{
Fraction *aFraction = [[Fraction alloc] init];
   Fraction *bFraction = [[Fraction alloc] init];

    [aFraction setNumerator: 1];  // 1st fraction is 1/4
   [aFraction setDenominator: 4];

    [aFraction print];
   printf (" = ");
   printf ("%g ", [aFraction convertToNum]);

[bFraction print];     // never assigned a value
printf (" = ");
   printf ("%g ", [bFraction convertToNum]);
   [aFraction free];
   [bFraction free];

return 0;
}


Program 6.2. Output


1/4 = 0.25
0/0 = 0


After setting aFraction to 1/4, the program uses the convertToNum method to convert the fraction to a decimal value. This value is then displayed as 0.25.

In the second case, bFraction's value is not explicitly set, so its numerator and denominator are initialized to zero, which is the default for instance variables. This explains the result from the print method. It also causes the if statement inside the convertToNum method to return the value 0, as verified from the output.

The if-else Construct

If someone asks you whether a particular number is even or odd, you will 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 will readily state that the number is even. Otherwise, you will claim that the number is odd.

An easier way for a computer to determine whether a particular number is even or odd is effected not by examining the last digit of the number to see whether 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; otherwise, 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 0, it is even; else, it is odd.

Now let's write a program that determines whether an integer value typed in by the user is even or odd and then displays an appropriate message at the terminal.

Program 6.3.


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

#import <stdio.h>

int main (int argc, char *argv[])

{
   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. ");

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

   return 0;
}


Program 6.3. Output


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


Program 6.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 whether 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. If you recall from our discussions of even/odd numbers at the beginning of this section, we said 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 Objective-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 the expression is TRUE, then 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 will be executed, but not both.

You can incorporate the if-else statement into the previous program, replacing the two if statements by a single if-else statement. You will see how the use of this new program construct actually helps somewhat reduce the program's complexity and also improve its readability.

Program 6.4.


// Determine if a number is even or odd (Ver. 2)

#import <stdio.h>

int main (int argc, char *argv[])
{
      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. ");
   else
        printf ("The number is odd. ");
   return 0;
}


Program 6.4. Output


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


Program 6.4. Output (Rerun)


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


Don't forget that the double equal sign (==)is the equality test and the single equal sign is the assignment operator. It can lead to a lot of headaches if you forget this and inadvertently use the assignment operator inside the if statement.

Compound Relational Tests

The if statements you have used so far in this chapter set up simple relational tests between two numbers. Program 6.1 compared the value of number against zero, whereas Program 6.2 compared the denominator of the fraction to zero. Sometimes it becomes desirable, if not necessary, to set up more sophisticated tests. Suppose, for example, you wanted to count the number of grades from an exam that were between 70 and 79, inclusive. In such a case, you would want to compare the value of a grade not merely against one limit, but against the two limits 70 and 79 to ensure that it fell within the specified range.

The Objective-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 Objective-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 ");

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 Objective-C. The Objective-C language grants the programmer ultimate flexibility in forming expressions, and 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 avoid getting into trouble because of a mistaken assumption about the precedence of the operators in the or expression. (The && operator has lower precedence than any arithmetic or relational operator but higher precedence than the || operator.) Blank spaces should also be used to aid in the expression's readability. An extra blank space around the && and || operators will visually set 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, let's write a program that tests to see whether a year is a leap year. We all know that 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 is also 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 whether the desired criteria for a leap year were met.

If we rephrase our previous definition of a leap year, we 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 the previously stated definition. Now that we 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. ");

The parentheses around the subexpression

rem_4 == 0 && rem_100 != 0

are not required because that is how the expression will be evaluated anyway, remembering that or && has higher precedence than ||.

In fact, in this particular example, the test

if ( rem_4 == 0 && ( rem_100 != 0 || rem_400 == 0 ) )

would work just as well.

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

Program 6.5.


// This program determines if a year is a leap year

#import <stdio.h>

int main (int argc, char *argv[])
{
  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. ");
  else
       printf ("Nope, it's not a leap year. ");
  return 0;
}


Program 6.5. Output


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


Program 6.5. Output (Rerun)


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


Program 6.5. Output (Rerun)


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


The previous examples use a year that is not a leap year because it isn't evenly divisible by 4 (1955), a year that is a leap year because it is evenly divisible by 400 (2000), and a year that isn't a leap year because it is 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 and not by 100. This is left as an exercise for you.

We mentioned that Objective-C gives the programmer a tremendous amount of flexibility in forming expressions. For instance, in the previous 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 if statement, 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 previous expression readable. If you decided to ignore adding blanks and removed the unnecessary set of parentheses, you could end up with an expression that looked like this:

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

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

Nested if Statements

In discussions of the general format of the if statement, we indicated that if the result of evaluating the expression inside the parentheses were TRUE, the statement that immediately followed would be executed. It is perfectly valid that this program statement be another if statement, as in the statement

if ( [chessGame isOver] == NO )
     if ( [chessGame whoseTurn] == YOU )
          [chessGame yourMove];

If the value returned by sending the isOver message to chessGame is NO, the following statement is executed, which is in turn another if statement. This if statement compares the value returned from the whoseTurn method against YOU. If the two values are equal, the yourMove message is sent to the chessGame object. Therefore, the yourMove message is sent only if both the game is not done and it's your turn. In fact, this statement could have been equivalently formulated using compound relationals, like so:

if ( [chessGame isOver] == NO && [chessGame whoseTurn] == YOU )
     [chessGame yourMove];

A more practical example of nested if statements would be if you added an else clause to the previous example, as shown in the following:

if ( [chessGame isOver] == NO )
     if ( [chessGame whoseTurn] == YOU )
          [chessGame yourMove];
     else
          [chessGame myMove];

Execution of this statement proceeds as described previously. However, if the game is not over and it's not your move, the else clause is executed. This sends the message myMove to chessGame. If the game is over, the entire if 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 returned from the whoseTurn method, and not with the if statement that tests whether the game is over. The general rule is that an else clause is always associated with the last if statement that does not contain an else.

You can go one step further and add an else clause to the outermost if statement in the preceding example. This else clause would be executed if the game is over:

if ( [chessGame isOver] == NO )
     if ( [chessGame whoseTurn] == YOU )
          [chessGame yourMove];
     else
          [chessGame myMove];
else
     [chessGame finish];

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

if ( [chessGame isOver] == NO )
     if ( [chessGame whoseTurn] == YOU )
          [chessGame yourMove];
  else
     [chessGame finish];

will not result in the statement being interpreted as indicated by its format. Instead, this statement will be interpreted as follows:

if ( [chessGame isOver] == NO )
     if ( [chessGame whoseTurn] == YOU )
          [chessGame yourMove];
     else
          [chessGame finish];

This is because the else clause is associated with the last un-elsed if. You could 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 ( [chessGame isOver] == NO ) {
     if ( [chessGame whoseTurn] == YOU )
          [chessGame yourMove];
}
else
     [chessGame finish];

achieves the desired effect.

The else if Construct

You have 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 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 whether the number that is keyed in is negative, zero, or positive. The simple if-else construct will not work. Of course, in this case, you could always resort to three separate if statements, but this solution will not always work—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. We mentioned that the statement that follows an else could be any valid Objective-C program statement, so why not another if? Thus, in the general case, you could write the following:

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

This 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, like so:

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.

The next program illustrates the use of the else if construct by implementing the sign function discussed earlier.

Program 6.6.


// Program to implement the sign function

#import <stdio.h>

int main (int argc, char *argv[])
{
  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 ", sign);
  return 0;
}


Program 6.6. Output


Please type in a number: 1121
Sign = 1


Program 6.6. Output (Rerun)


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


Program 6.6. Output (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.

The next program 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 6.7.


// This program categorizes a single character
//       that is entered at the terminal

#import <stdio.h>

int main (int argc, char *argv[])
{
  char c;

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

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


Program 6.7. Output


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


Program 6.7. Output (Rerun)


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


Program 6.7. Output (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 whether the character is a lowercase letter or an uppercase letter. The former test is made by the following expression:

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

This expression 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 this expression:

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

This expression 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 computer systems that store characters inside the machine in a format known as ASCII.1

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 whether the character is a digit. Note that this test compares the character c against 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, in ASCII, 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 at the terminal. 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 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.

Let's suppose for the next example that you want to write a program that allows the user to type in simple expressions of the following form:

number  operator  number

The program will evaluate the expression and display the results at the terminal. The operators you want to have recognized are the normal operators for addition, subtraction, multiplication, and division. Let's use the Calculator class from Program 4.6 in Chapter 4, “Data Types and Expressions,” here. So, each expression will be given to the calculator for computation.

The following program uses a large if statement with many else if clauses to determine which operation is to be performed.

Program 6.8.


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

// Implement a Calculator class

#import <objc/Object.h>
#import <stdio.h>


@interface Calculator: Object
{
  double accumulator;
}

// accumulator methods
-(void)  setAccumulator: (double) value;
-(void)  clear;
-(double) accumulator;

// arithmetic methods
-(void)  add: (double) value;
-(void)  subtract: (double) value;
-(void)  multiply: (double) value;
-(void)  divide: (double) value;
@end

@implementation Calculator;
-(void) setAccumulator: (double) value
{
    accumulator = value;
}

-(void) clear
{
    accumulator = 0;
}

-(double) accumulator
{
    return accumulator;
}

-(void) add: (double) value
{
    accumulator += value;
}

-(void) subtract: (double) value
{
    accumulator -= value;
}

-(void) multiply: (double) value
{
    accumulator *= value;
}


-(void) divide: (double) value
{
    accumulator /= value;
}
@end


int main (int argc, char *argv[])
{
  double      value1, value2;
  char        operator;
  Calculator  *deskCalc = [[Calculator alloc] init];
  printf ("Type in your expression. ");
  scanf ("%lf %c %lf", &value1, &operator, &value2);

  [deskCalc setAccumulator: value1];
  if ( operator == '+' )
    [deskCalc add: value2];
  else if ( operator == '-' )
    [deskCalc subtract: value2];
  else if ( operator == '*' )
    [deskCalc multiply: value2];
  else if ( operator == '/' )
    [deskCalc divide: value2];

  printf ("%.2f ", [deskCalc accumulator]);
  [deskCalc free];

  return 0;
}


Program 6.8. Output


Type in your expression.
123.5 + 59.3
182.80


Program 6.8. Output (Rerun)


Type in your expression.
198.7 / 26
7.64


Program 6.8. Output (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 double value can be read in with the %lf format characters. This is the format used to read in the value of the variable value1, which is the first operand of the expression.

Next, you 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.

After the two values and the operator have been read in, the program stores the first value into the calculator's accumulator. Next, you test the value of operator against the four permissible operators. When a correct match is made, the corresponding message is sent to the calculator to perform the operation. In the last printf, the value of the accumulator is retrieved for display. Execution of the program is then complete.

A few words about program thoroughness are in order at this point. Although the preceding program does accomplish the task that we set out to perform, the program is not really complete because it does not account for mistakes made on the part of the user. For example, what would happen if the user were to type in a ? for the operator by mistake? The program would simply fall through the if statement and no messages would ever appear at the terminal to alert the user that he had 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 Objective-C. The program should check for this case.

Trying to predict the ways in which 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 can often point a 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 ask, “What would happen if…?” and to insert the necessary program statements to handle the situation properly.

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

Program 6.8A.


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

#import <stdio.h>
#import <objc/Object.h>

// Insert interface and implementation sections for
// Calculator class here

int main (int argc, char *argv[])
{
  double     value1, value2;
  char       operator;
  Calculator *deskCalc = [[Calculator alloc] init];

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

  [deskCalc setAccumulator: value1];

  if ( operator == '+' )
       [deskCalc add: value2];
  else if ( operator == '-' )
       [deskCalc subtract: value2];
  else if ( operator == '*' )
       [deskCalc multiply: value2];
  else if ( operator == '/' )
       if ( value2 == 0 )
            printf ("Division by zero. ");
       else
            [deskCalc divide: value2];
  else
       printf ("Unknown operator. ");


  printf ("%.2f ", [deskCalc accumulator]);
  [deskCalc free];

  return 0;
}


Program 6.8A. Output


Type in your expression.
123.5 + 59.3
182.80


Program 6.8A. Output (Rerun)


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


Program 6.8A. Output (Rerun)


Type in your expression.
125 $ 28
Unknown operator.
125


When the operator that is typed in is the slash, for division, another test is made to determine whether 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 if statements 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. at the terminal.

A better way to handle the division by zero problem is to perform the test inside the method that handles division. So, you can modify your divide: method as shown here:

-(void) divide: (double) value
{
  if (value != 0.0)
     accumulator /= value;
     else {
     printf ("Division by zero. ");
     accumulator = 99999999.;
     }
}

If value is nonzero, you perform the division; otherwise, you display the message and set the accumulator to 99999999. This is arbitrary; you could have set it to zero or perhaps set a special variable to indicate an error condition. In general, it's better to have the method handle special cases rather than rely on the resourcefulness of the programmer using the method.

The switch Statement

The type of if-else statement chain you encountered in the last program example—where 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 Objective-C language for performing precisely this function. The name of the statement is the switch statement, and its general format is as follows:

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. You will 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 is executed. Sometimes this is done intentionally; if you elect to do so, be sure to insert comments to alert others of your purpose.

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 catchall else 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 the previous code in mind, you can translate the big if statement from Program 6.8A into an equivalent switch statement. We will call this new program Program 6.9.

Program 6.9.


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

#import <stdio.h>
#import <objc/Object.h>

// Insert interface and implementation sections for
// Calculator class here

int main (int argc, char *argv[])
{
  double  value1, value2;
  char    operator;
  Calculator *deskCalc = [[Calculator alloc] init];


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

[deskCalc setAccumulator: value1];

switch ( operator ) {
     case '+':
      [deskCalc add: value2];
      break;
     case '-':
      [deskCalc subtract: value2];
      break;
     case '*':
      [deskCalc multiply: value2];
       break;
     case '/':
[deskCalc divide: value2];
break;
     default:
     printf ("Unknown operator. ");
     break;
  }
printf ("%.2f ", [deskCalc accumulator]);
[deskCalc free];
return 0;
}


Program 6.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 completed. If none of the cases matches 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, you should 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 a colon after the value in each case) before the common statements that are to be executed. As an example, in the switch statement

switch ( operator )
{
     ...
   case '*':
   case 'x':
       [deskCalc multiply: value2];
       break;
     ...
}

the multiply: method is executed if operator is equal to an asterisk or to the lowercase letter x.

Boolean Variables

Just about anyone learning to program soon finds herself 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.

You could take several approaches to generate a table of prime numbers. If you had the task of generating all prime numbers up to 50, for example, then the most straightforward (and simplest) algorithm to generate such a table would simply test each integer, p, for divisibility by all integers from 2 through p-1. If any such integer evenly divided p, then p would not be prime; otherwise, it would be a prime number.

Program 6.10.


// Program to generate a table of prime numbers

#import <stdio.h>

int main (int argc, char *argv[])
{
   int   p, d, 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 (" ");
   return 0;
}


Program 6.10. Output


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


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

A second loop is set up to divide p by the integers 2–p-1. Inside the loop, a test is performed to see whether 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 evenly divides 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 divided 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. 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. Variables used in such a manner are generally referred to as Boolean variables. A flag typically assumes only one of two different values. Furthermore, the value of a flag usually is tested at least once in the program to see whether it is on (TRUE or YES) or off (FALSE or NO) and some particular action is taken based on the results of the test.

In Objective-C, the notion of a flag being TRUE or FALSE is most naturally translated into the values 1 and 0, respectively. So, in Program 6.10, when you set the value of isPrime to 1 inside the loop, you are effectively setting it 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 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 is used 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 Objective-C, there is an even more convincing argument in favor of these logic values. It has to do with the way the Objective-C language treats the concept of TRUE and FALSE.

When we began our discussions in this chapter, we noted that if the conditions specified inside the if statement were satisfied, the program statement that immediately followed would be executed. But what exactly does satisfied mean? In the Objective-C language, satisfied means nonzero, and nothing more. Thus, the statement

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

results in the 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” were used. This is because, whenever a relational expression is evaluated in Objective-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: The relational expression number < 0 is evaluated. If the condition is satisfied, that is, if number is less than 0, the value of the expression is 1; otherwise, its value is 0.

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 the 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 6.10 and the notion of flags, it is perfectly valid in Objective-C, to test whether the value of a flag is TRUE by an expression such as

if ( isPrime )

which is equivalent to

if ( isPrime != 0 )

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

if ( ! isPrime )

the logical negation operator is used to test whether 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 0, 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 following expression:

my_move = ! my_move;

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 whether 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 statement as

x >= y

A couple of built-in features in Objective-C make working with Boolean variables a little easier. One is the special type BOOL, which can be used to declare variables that will contain either a true or false value.2 The other is the built-in values YES and NO. Using these predefined values in your programs can make them easier to write and read. Here is Program 6.10 rewritten to take advantage of these features.

Program 6.10A.


// Program to generate a table of prime numbers
// second version using BOOL type and predefined values

#import <stdio.h>
#import <objc/Object.h>

int main (int argc, char *argv[])
{
   int    p, d;
   BOOL  isPrime;

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

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

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

   printf (" ");
   return 0;
}


Program 6.10A. Output


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


Even though you haven't defined any classes in Program 6.10A, you need to import objc/Object.h because BOOL, YES, and NO are defined there.

The Conditional Operator

Perhaps the most unusual operator in the Objective-C language is one called the conditional operator. Unlike all other operators in Objective-C—which are either unary or binary operators—the conditional operator is a ternary operator; that is, it takes three operands. The two symbols 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 expression is

condition ? expression1 : expression2

In this syntax, condition is an expression, usually a relational expression, that is evaluated by the Objective-C system first whenever the conditional operator is encountered. If the result of the evaluation of condition is TRUE (that is, nonzero), expression1 is evaluated and the result of the evaluation becomes the result of the operation. If condition evaluates FALSE (that is, zero), expression2 is evaluated and its result becomes the result of the operation.

A conditional expression is most often used to assign one of two values to a variable depending on some condition. For example, suppose you have an integer variable x and another integer variable s. If you wanted to assign -1 to s if x were less than 0, and the value of x2 to s otherwise, the following statement could be written:

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

The condition x < 0 is first tested when the previous statement is executed. Parentheses are generally placed around the condition expression to aid in the statement's readability. This is usually not required, though, because the precedence of the conditional operator is very low—lower, in fact, than all other operators but 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 max_value the maximum of a and b:

max_value = ( 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 implemented in Program 6.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 previous 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 follows:

e1 ? e2 : ( e3 ? e4 : e5 )

Conditional expressions don't have to be used on the right side of an assignment—they can be used in any situation in which expressions could be used. This means you could display the sign of the variable number without first assigning it to a variable using a printf statement as shown here:

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

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

Exercises

  1. Write a program that asks the user to type in two integer values at the terminal. Test these two numbers to determine whether the first is evenly divisible by the second, and then display an appropriate message at the terminal.
  2. Program 6.8A displays the value in the accumulator even if an invalid operator is entered or division by zero is attempted. Fix that problem.
  3. Modify the print method from the Fraction class so that whole numbers are displayed as such (so the fraction 5/1 should display as simply 5). Also modify the method to display fractions with a numerator of 0 as simply zero.
  4. Write a program that acts as a simple printing calculator. The program should allow the user to type in expressions of the following 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, and 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 sure that the program detects division by 0 and also checks for unknown operators. Use the Calculator class developed in Program 6.8 for performing your calculations.

  5. We developed Program 5.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. By correctly handled, we mean that if the number –8645 were typed in, for example, 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 the following:

    nine three two

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

  7. Program 6.10 has several inefficiencies. One inefficiency results from checking even numbers. Because any even number greater than 2 obviously 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 if you added 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 6.10 to incorporate these two changes; then run the program to verify its operation. Note: In a later chapter, you will learn even more efficient ways of generating prime numbers.
..................Content has been hidden....................

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