7. More on Classes

In this chapter you'll continue learning how to work with classes and write methods. You'll also apply some of the concepts you've learned in the previous chapter, such as program looping, making decisions, and working with expressions. First, we'll talk about splitting your program into multiple files to make working with larger programs easier.

Separate Interface and Implementation Files

It's time to get used to putting your class declarations and definitions in separate files. Typically, a class declaration (that is, the @interface section) is placed in its own file, called class.h. The definition (that is, the @implementation section) is normally placed in a file of the same name, using the extension .m instead. So, let's put the declaration of the Fraction class into the file Fraction.h and the definition into Fraction.m. (see Program 7.1).

Program 7.1. Interface File Fraction.h


#import <objc/Object.h>

// The Fraction class

@interface Fraction : Object
{
  int  numerator;
  int   denominator;
}
-(void)    print;
-(void)    setNumerator: (int) n;
-(void)    setDenominator: (int) d;
-(int)     numerator;
-(int)     denominator;
-(double)  convertToNum;

@end


The interface file tells the compiler (and other programmers as you'll learn later) what a Fraction looks like—it contains two instance variables called numerator and denominator, which are both integers. It also has six instance methods: print, setNumerator:, setDenominator:, numerator, denominator, and convertToNum. The first three methods don't return a value; the next two return an int; and the last one returns a double. The setNumerator: and setDenominator: methods each take an integer argument.

The details of the implementation for the Fraction class are in the file Fraction.m.

Program 7.1. Implementation File: Fraction.m


#import "Fraction.h"
#import <stdio.h>

@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 1.0;
}
@end


Note that the interface file is imported into the implementation file with the following statement:

#import "Fraction.h"

This is done so that the compiler knows about the class and methods you declared for your Fraction class, and it can also ensure consistency between the two files. Recall also that you don't normally (although you can) redeclare the class's instance variables inside the implementation section, so the compiler needs to get that information from the interface section contained in Fraction.h.

The other thing you should note is that the file that is imported is enclosed in a set of double quotes and not < and > characters, as was the case with <obj/Object.h> and <stdio.h>. The double quotes are used for local files (files you create yourself), as opposed to system files, and they tell the compiler where to look for the specified file. When you use double quotes, the compiler typically looks inside your current directory first for the specified file and then in a list of other places. The actual places that are searched can be specified to the compiler if necessary.

Here is the test program for our example, which we have typed into the file main.m.

Program 7.1. Main Test Program: main.m


#import "Fraction.h"
#import <stdio.h>

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

  // set fraction to 1/3

  [myFraction setNumerator: 1];
  [myFraction setDenominator: 3];

  // display the fraction

  printf ("The value of myFraction is:");
  [myFraction print];
  printf (" ");
  [myFraction free];

  return 0;
}


Note again that the test program, main.m, includes the interface file Fraction.h and not the implementation file Fraction.m. This file is specified on the command line for the compiler to process.

Now you have your program split into three separate files. This might seem like a lot of work for a small program example, but the usefulness will become apparent when you start dealing with larger programs and sharing class declarations with other programmers.

To compile this program, give the Objective-C compiler both “.m” filenames on the command line. Using gcc, the command line looks like this:

gcc Fraction.m main.m –o fractions –l objc

This builds an executable file called fractions. Here's the output after running the program:

Program 7.1. Output


The value of myFraction is: 1/3


Multiple Arguments to Methods

Let's continue to work with the Fraction class and make some additions. You have defined six methods. It would be nice to have a method to set both the numerator and denominator with a single message. You define methods that take multiple arguments simply by listing each successive argument followed by a colon. This becomes part of the method name. For example, the method name addEntryWithName:andEmail: is a method that takes two arguments, presumably a name and an email address. The method addEntryWithName:andEmail:andPhone: is a method that takes three arguments: a name, an email address, and a phone number.

A method to set both the numerator and denominator could be named setNumerator:andDenominator:, and you might use it like this:

[myFraction setNumerator: 1 andDenominator: 3];

That's not bad. And that was actually the first choice for the method name. But we can come up with a more readable method name. For example, how about setTo:over:? That might not look too appealing at first glance, but compare this message to set myFraction to 1/3 with the previous one:

[myFraction setTo: 1 over: 3];

I think that reads a little better, but the choice is up to you (some might actually prefer the first name because it explicitly references the instance variable names contained in the class). Again, choosing good method names is important for program readability. Writing out the actual message expression can help you pick a good one.

Let's put this new method to work. First, add the declaration of setTo:over: to the interface file, as shown in Program 7.2.

Program 7.2. Interface File: Fraction.h


#import <objc/Object.h>

// Define the Fraction class

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

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


Next, add the definition for the new method to the implementation file.

Program 7.2. Implementation File: Fraction.m


#import "Fraction.h"
#import <stdio.h>

@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 1.0;
}

-(void) setTo: (int) n over: (int) d
{
  numerator = n;
  denominator = d;
}
@end


The new setTo:over: method simply takes its two integer arguments, n and d, and assigns them to the corresponding fields of the fraction, numerator and denominator.

Here's a test program to try your new method.

Program 7.2. Test File: main.m


#import "Fraction.h"
#import <stdio.h>

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

  [aFraction setTo: 100 over: 200];
  [aFraction print];

  [aFraction setTo: 1 over: 3];
  [aFraction print];
  [aFraction free];

  return 0;
}


Program 7.2. Output


100/200
1/3


Methods Without Argument Names

When creating the name for a method, the argument names are actually optional. For example, you can declare a method like this:

-(int) set: (int) n: (int) d;

Note that, unlike previous examples, no name is given for the second argument to the method here. This method is named set::, and the two colons mean the method takes two arguments, even though they're not all named.

To invoke the set:: method, you use the colons as argument delimiters, as shown here:

[aFraction set: 1 : 3];

It's generally not good programming style to omit argument names when writing new methods because it makes the program harder to follow and makes it less intuitive when you're using a method as to what the actual parameters to the method signify.

Operations on Fractions

Let's continue to work with the Fraction class. First, you'll write a method that will enable you to add one fraction to another. You'll name the method add:, and you have it take a fraction as an argument. Here's the declaration for the new method:

-(void) add: (Fraction *) f;

Note the declaration for the argument f:

(Fraction *) f

This says that the argument to the add: method is of type class Fraction. The asterisk is necessary, so the declaration

(Fraction) f

is not correct. You will be passing one fraction as an argument to your add: method, and you'll have the method add it to the receiver of the message, so the message expression

[aFraction add: bFraction];

will add the Fraction bFraction to the Fraction aFraction. Just as a quick math refresher, to add the fractions a/b and c/d, you perform the calculation as follows:

image

Here is the code for the new method that you will put into the @implementation section:

// add a Fraction to the receiver

- (void) add: (Fraction *) f
{
  // To add two fractions:
  // a/b + c/d = ((a*d) + (b*c)) / (b * d)

  numerator = (numerator * [f denominator])
              + (denominator * [f numerator]);
  denominator = denominator * [f denominator];
}

Don't forget that you can refer to the Fraction that is the receiver of the message by its fields: numerator and denominator. On the other hand, you can't directly refer to the instance variables of the argument f that way. Instead, you have to obtain them by applying the numerator and denominator methods. Note that this is the first time you have used a method (numerator and denominator) from within another method (add:). Obviously, this is perfectly valid.

Let's assume that you added the previous declarations and definitions for your new add: method to your interface and implementation files. Program 7.3 is a sample test program and output.

Program 7.3. Test File: main.m


#import "Fraction.h"
3import <stdio.h>

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

  // Set two fractions to 1/4 and 1/2 and add them together

  [aFraction setTo: 1 over: 4];
  [bFraction setTo: 1 over: 2];

  // Print the results

  [aFraction print];
  printf (" + ");
  [bFraction print];
  printf (" = ");

  [aFraction add: bFraction];
  [aFraction print];
  printf (" ");
  [aFraction free];
  [bFraction free];

  return 0;
}


Program 7.3. Output


1/4 + 1/2 = 6/8


The test program is straightforward enough. Two Fractions, called aFraction and bFraction, are allocated and initialized. Then they are set to the values 1/4 and 1/2, respectively. Next, the Fraction bFraction is added to the Fraction aFraction; the result of the addition is then displayed. Note again that the add: method adds the argument to the object of the message, so the object gets modified. This is verified when you print the value of aFraction at the end of main. You should realize that you had to print the value of aFraction before invoking the add: method so that you could get its value displayed before it was changed by the method. Later in this chapter, you'll redefine the add: method so that add: does not affect the value of its argument.

Local Variables

You might have noticed that the result of adding 1/4 to 1/2 was displayed as 6/8, and not as 3/4, which you might have preferred (or even expected!). That's because your addition routine just does the math and no more—it doesn't worry about reducing the result. So, to continue with our exercise of adding new methods to work with fractions, let's make a new reduce method to reduce a fraction to its simplest terms.

Reaching back to your high school math again, you can reduce a fraction by finding the largest number that evenly divides both the numerator and denominator of your fraction and then dividing them by that number. Technically, you want to find the greatest common divisor (gcd) of the numerator and denominator. You already know how to do that from Program 5.7. You might want to refer to that program example just to refresh your memory.

With the algorithm in hand, you can now write your new reduce method:

- (void) reduce
{
  int  u = numerator;
  int  v = denominator;
  int  temp;

  while (v != 0) {
    temp = u % v;
    u = v;
    v = temp;
  }

  numerator /= u;
  denominator /= u;
}

Notice something new about this reduce method: It declares three integer variables called u, v, and temp. These variables are local variables, meaning their values exist only during execution of the reduce method and that they can only be accessed from within the method in which they are defined. In that sense, they are similar to the variables you have been declaring inside your main routine; those variables were also local to main and could be accessed directly only from within the main routine. None of the methods you developed could directly access those variables defined in main.

Local variables have no default initial value, so you must set them to some value before using them. The three local variables in the reduce method are set to values before they are used, so that's not a problem here. And, unlike your instance variables (which retain their values through method calls), these local variables have no memory. Therefore, after the method returns, the values of these variables disappear. Each time a method is called, each local variable defined in that method is initialized to the value specified (if any) with the variable's declaration.

Method Arguments

The names you use to refer to a method's arguments are also local variables. When the method is executed, whatever arguments are passed to the method are copied into these variables. Because the method is dealing with a copy of the arguments, it cannot change the original values passed to the method. This is an important concept. Suppose you had a method calculate: defined as follows:

-(void) calculate: (double) x
{
  x *= 2;
  ...
}

Also suppose you used the following message expression to invoke it:

[myData calculate: ptVal];

Whatever value contained in the variable ptVal would be copied into the local variable x when the calculate method was executed. So, changing the value of x inside calculate: would have no effect on the value of ptVal—only on the copy of its value stored inside x.

Incidentally, in the case of arguments that are objects, you can change the instance variables stored in that object. You'll learn more about that in the next chapter.

The static Keyword

You can have a local variable retain its value through multiple invocations of a method by placing the keyword static in front of the variable's declaration. So, for example

static int hitCount = 0;

declares the integer hitCount to be a static variable. Unlike other normal local variables, a static one does have an initial value of 0, so the initialization shown previously is redundant. Further, they are initialized only once when program execution begins and retain their values through successive method calls.

So the code sequence

-(void) showPage
{
   static int pageCount = 0;
   ...
   ++pageCount;
   ...
}

might appear inside a showPage method that wanted to keep track of the number of times it was invoked (or in this case, perhaps the number of pages that have been printed, for example). The local static variable would be set to 0 only once when the program started and would retain its value through successive invocations of the showPage method.

Note the difference between making pageCount a local static variable and making it an instance variable. In the former case, pageCount could count the number of pages printed by all objects that invoked the showPage method. In the latter case, the variable would count the number of pages printed by each individual object because each object would have its own copy of pageCount.

Remember that static or local variables can be accessed only from within the method in which they're defined. So, even the static pageCount variable can be accessed only from within showPage. You can move the declaration of the variable outside any method declaration (typically near the beginning of your implementation file) to make it accessible to any methods, like so:

#import "Printer.h"
static int pageCount;

@implementation Printer;
  ...
@end

The pageCount variable can now be accessed by any instance or class method contained in the file. Chapter 10, “More on Variables and Data Types,” covers this topic of variable scope in greater detail.

Returning to our fractions, incorporate the code for the reduce method into your Fraction.m implementation file. Don't forget to declare the reduce method in your Fraction.h interface file, as well. With that done, you can test your new method in Program 7.4.

Program 7.4. Test File main.m


#import "Fraction.h"
#import <stdio.h>

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

  [aFraction setTo: 1 over: 4];   // set 1st fraction to 1/4
  [bFraction setTo: 1 over: 2];   // set 2nd fraction to 1/2

  [aFraction print];
  printf (" + ");
  [bFraction print];
  printf (" = ");

  [aFraction add: bFraction];

  // reduce the result of the addition and print the result

  [aFraction reduce];
  [aFraction print];
  printf (" ");
  [aFraction free];
  [bFraction free];

  return 0;
}


Program 7.4. Output


1/4 + 1/2 = 3/4


That's better!

The self Keyword

In Program 7.4 we decided to reduce the fraction outside of the add: method. We could have done it inside add: as well. The decision was completely arbitrary. However, how would we go about identifying the fraction to be reduced to our reduce method? We know how to identify instance variables inside a method directly by name, but we don't know how to directly identify the receiver of the message.

The keyword self can be used to refer to the object that is the receiver of the current method. If inside your add: method you were to write

[self reduce];

the reduce method would be applied to the Fraction that was the receiver of the add: method, which is what you want. You will see throughout this book how useful the self keyword can be. For now, use it in your add: method. Here's what the modified method looks like:

- (void) add: (Fraction *) f
{
  // To add two fractions:
  // a/b + c/d = ((a*d) + (b*c)) / (b * d)

  numerator = (numerator * [f denominator]) +
        (denominator * [f numerator]);
  denominator = denominator * [f denominator];

  [self reduce];
}

So, after the addition is performed, the fraction is reduced.

Allocating and Returning Objects from Methods

We noted that the add: method changes the value of the object that is receiving the message. Let's create a new version of add: that will instead make a new fraction to store the result of the addition. In this case, we will need to return the new Fraction to the message sender. Here is the definition for the new add: method:

-(Fraction *) add: (Fraction *) f
{
  // To add two fractions:
  // a/b + c/d = ((a*d) + (b*c)) / (b * d)

  // result will store the result of the addition
  Fraction   *result = [[Fraction alloc] init];
  int        resultNum, resultDenom;

  resultNum = (numerator * [f denominator]) +
        (denominator * [f numerator]);
  resultDenom = denominator * [f denominator];

  [result setTo: resultNum over: resultDenom];
  [result reduce];

  return result;
}

The first line of your method definition is

-(Fraction *) add: (Fraction *) f;

This says that your add: method will return a Fraction object and that it will take one as its argument as well. The argument will be added to the receiver of the message, which is also a Fraction.

The method allocates and initializes a new Fraction object called result and then defines two local variables called resultNum and resultDenom. These will be used to store the resulting numerator and denominators from your addition.

After performing the addition as before and assigning the resulting numerators and denominators to your local variables, you then proceed to set result with the following message expression:

[result setTo: resultNum over: resultDenom];

After reducing the result, you return its value to the sender of the message with the return statement.

Note that the memory occupied by the Fraction result that is allocated inside the add: method is returned and does not get released. You can't release it from the add: method because the invoker of the method needs it. It is therefore imperative that the user of this method knows that the object being returned is a new instance and must be subsequently released. This can be communicated to the user through suitable documentation that is made available to users of the class.

Program 7.5 tests your new add: method.

Program 7.5. Test File main.m


#import "Fraction.h"

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

  Fraction *resultFraction;

  [aFraction setTo: 1 over: 4];  // set 1st fraction to 1/4
  [bFraction setTo: 1 over: 2];  // set 2nd fraction to 1/2


  [aFraction print];
  printf (" + ");
  [bFraction print];
  printf (" = ");

  resultFraction = [aFraction add: bFraction];
  [resultFraction print];
  printf (" ");

  // This time give the result directly to print
  // memory leakage here!

  [[aFraction add: bFraction] print];
  printf (" ");


  [aFraction free];
  [bFraction free];
  [resultFraction free];

  return 0;
}


Program 7.5. Output


1/4 + 1/2 = 3/4
3/4


Some explanation is in order here. First, you define two Fractions—aFraction and bFraction—and set their values to 1/4 and 1/2, respectively. You also define a Fraction called resultFraction (why doesn't it have to be allocated and initialized?). This variable will be used to store the result of your addition operations that follow.

The following lines of code

resultFraction = [aFraction add: bFraction];
[resultFraction print];
printf (" ");

first send the add: message to aFraction, passing along the Fraction bFraction as its argument. The resulting Fraction that is returned by the method is stored in resultFraction and then displayed by passing it a print message. Note that you must be careful at the end of the program to release resultFraction, even though you didn't allocate it yourself in main. It was allocated by the add: method, but it's still your responsibility to clean it up. The message expression

[[aFraction add: bFraction] print];

might look nice, but it actually creates a problem. Because you take the Fraction that add: returns and send it a message to print, you have no way of subsequently releasing the Fraction object that add: created. This is an example of memory leakage. If you did this type of nested messaging many times in your program, you would end up accumulating storage for fractions whose memory would not be released. Each time, you would be adding, or leaking, just a little bit more memory that you could not directly recover.

One solution to the problem is to have the print method return its receiver, which you could then free. But that seems a little roundabout. A better solution is to divide the nested messages into two separate messages, as was done earlier in the program.

By the way, you could have avoided using the temporary variables resultNum and resultDenom completely in your add: method. Instead, the single message call

[result setTo: (numerator * [f denominator]) +
   (denominator * [f numerator])
         over: denominator * [f denominator]];

would have done the trick! We're not suggesting you write such concise code. However, you might see it when you examine other programmers' code, so it is useful to learn how to read and understand these powerful expressions.

Let's take one last look at fractions in this chapter. For our example, let's consider calculation of the following series:

image

The sigma notation is shorthand for a summation. Its use here means to add the values of 1/2i, where i varies from 1 to n. That is, add 1/2 + 1/4 + 1/8…. If you make the value of n large enough, the sum of this series should approach 1. Let's experiment with different values for n to see how close we get.

Program 7.7 prompts for the value of n to be entered and performs the indicated calculation.

Program 7.7. main.m


#import "Fraction.h"

int main (int argc, char *argv[])
{
   Fraction *aFraction = [[Fraction alloc] init];
   Fraction *sum = [[Fraction alloc] init], *sum2;
   int i, n, pow2;

   [sum setTo: 0 over: 1]; // set 1st fraction to 0

   printf ("Enter your value for n: ");
   scanf ("%i", &n);

   pow2 = 2;
   for (i = 1; i <= n; ++i) {
     [aFraction setTo: 1 over: pow2];
     sum2 = [sum add: aFraction];
     [sum free];  // release previous sum
     sum = sum2;
     pow2 *= 2;
}

   printf ("After %i iterations, the sum is %g ", n, [sum convertToNum]);
  [aFraction free];
  [sum free];

  return 0;
}


Program 7.7. Output


Enter your value for n: 5
After 5 iterations, the sum is 0.96875


Program 7.7. Output (Rerun)


Enter your value for n: 10
After 10 iterations, the sum is 0.999023


Program 7.7. Output (Rerun)


Enter your value for n: 15
After 15 iterations, the sum is 0.999969


The Fraction sum is set to the value of 0 by setting its numerator to 0 and its denominator to 1 (what would happen if you set both its numerator and denominator to 0?). The program then prompts the user to enter her value for n and reads it using scanf. You then enter a for loop to calculate the sum of the series. First, you initialize the variable pow2 to 2. This variable is used to store the value of 2i. So, each time through the loop it's value is multiplied by 2.

The for loop starts at 1 and goes through n. Each time through the loop, you set aFraction to 1/pow2, or 1/2i. This value is then added to the cumulative sum by using the previously defined add: method. The result from add: is assigned to sum2 and not to sum to avoid memory leakage problems. (What would happen if you assigned it directly to sum instead?) The old sum is then freed, and the new sum, sum2, is assigned to sum for the next iteration through the loop. Study the way the fractions are freed in the code so that you feel comfortable with the strategy that is used to avoid memory leakage. And realize that if this were a for loop that was executed hundreds or thousands of times and you weren't judicious about freeing your fractions, you would quickly start to accumulate a lot of wasted memory space.

When the for loop is completed, you display the final result as a decimal value using the convertToNum method. You have just two objects left at that point to release—aFraction and your final Fraction object stored in sum. Program execution is then complete.

The output shows what happens when we ran the program three separate times on a PowerBook G4. You might get slightly different results from your computer. The first time, the sum of the series is calculated and the resulting value of 0.96875 is displayed. The third time we ran the program with a value of 15 for n, which gave us a result very close to 1.

Extending Class Definitions and the Interface File

You've now developed a small library of methods for working with fractions. In fact, here is the interface file, listed in its entirety, so you can see all you've accomplished with this class:

#import <objc/Object.h>

// Define the Fraction class

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

-(void)   print;
-(void)   setNumerator: (int) n;
-(void)   setDenominator: (int) d;
-(void)   setTo: (int) n over: (int) d;
-(int)    numerator;
-(int)    denominator;
-(double) convertToNum;
-(Fraction *) add: (Fraction *) f;
-(void)  reduce;
@end

You might not have a need to work with fractions, but these examples have shown how you can continually refine and extend a class by adding new methods. Someone else working with fractions could be handed this interface file and that should be sufficient for him to be able to write his own programs to deal with fractions. If he needed to have a new method added, that could be done either directly by extending the class definition or indirectly by defining his own subclass and adding his own new methods. You'll learn how to do that in the next chapter.

Exercises

  1. Add the following methods to the Fraction class to round out the arithmetic operations on fractions. Reduce the result within the method in each case:

    // Subtract argument from receiver
    –(Fraction *) subtract (Fraction *) f;
    // Multiply receiver by argument
    –(Fraction *) multiply (Fraction *) f;
    // Divide receiver by argument
    –(Fraction *) divide (Fraction *) f;

  2. Modify the print method from your Fraction class so that it takes an optional BOOL argument that indicates whether the fraction should be reduced for display. If it is to be reduced, be sure you don't make any permanent changes to the fraction itself.
  3. Modify Program 7.7 to also display the resulting sum as a fraction and not just as a real number.
  4. Will your Fraction class work with negative fractions? For example, can you add –1/4 and –1/2 and get the correct result? After you think you have the answer, write a test program to try it.
  5. Modify the Fraction's print method to display fractions greater than 1 as mixed numbers. So, the fraction 5/3 should be displayed as 1 2/3.
  6. Exercise 7 in Chapter 4, “Data Types and Expressions,” defined a new class called Complex for working with complex imaginary numbers. Add a new method called add: that can be used to add two complex numbers. To add two complex numbers, you simply add the real parts and the imaginary parts, as in:

    (5.3 + 7i) + (2.7 + 4i) = 10 + 11i

    Have the add: method store and return the result as a new Complex number, based on the following method declaration:

    -(Complex *) add: (Complex * complexNum);

    Make sure you address any potential memory leakage issues in your test program.

  7. Given the Complex class developed in exercise 7 of Chapter 4 and the extension made in exercise 6 of this chapter, create separate Complex.h and Complex.m interface and implementation files. Create a separate test program file to test everything.
..................Content has been hidden....................

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