3. Classes, Objects, and Methods

In this chapter you learn about some key concepts in object-oriented programming and start working with classes in Objective-C. You'll need to learn a little bit of terminology, and we'll keep it fairly informal. We'll also cover only some of the basic terms here because you can easily get overwhelmed. Refer to Appendix A, “Glossary,” at the end of this book for more precise definitions of these terms.

What Is an Object, Anyway?

An object is a thing. Think about object-oriented programming as a thing and something you want to do to that thing. This is in contrast to a programming language such as C, known as a procedural programming language. In C, you typically think about what you want to do first and then you worry about the objects…almost the opposite from object-orientation.

Let's take an example from everyday life. Let's assume you own a car, which is obviously an object, and one that you own. You don't have just any car; you have a particular car that was manufactured in a factory, maybe in Detroit, maybe in Japan, or maybe someplace else. Your car has a vehicle identification number (VIN), which uniquely identifies that car.

In object-oriented parlance, your car is an instance of a car. And continuing with the terminology, car is the name of the class from which this instance was created. So, each time a new car is manufactured, a new instance from the class of cars is created, and each instance of the car is referred to as an object.

Your car might be silver, have a black interior, and be a convertible or hardtop, and so on. Additionally, you perform certain actions with your car. For example, you drive your car, fill it with gas, (hopefully) wash it, take it in for service, and so on. This is depicted in Table 3.1.

Table 3.1. Actions on Objects

image

The actions listed in Table 3.1 can be done with you car, and they can also be done with other cars as well. For example, your sister drives her car, washes it, fills it with gas, and so on.

Instances and Methods

A unique occurrence of a class is an instance, and the actions that are performed on the instance are called methods. In some cases, a method can be applied to an instance of the class or to the class itself. For example, washing you car applies to an instance (in fact, all the methods listed in Table 3.1 would be considered instance methods). Finding out how many types of cars a manufacturer makes would apply to the class, so it would be a class method. Suppose you have two cars that came off the assembly line that are seemingly identical: They both have the same interior, same paint color, and so on. They might start out the same, but as each car is used by its respective owner, it acquires its own unique characteristics. For example, one car might end up with a scratch on it and the other might have more miles on it. Each instance or object contains not only information about its initial characteristics it acquired from the factory, but also its current characteristics. Those characteristics can change dynamically. As you drive your car, the gas tank becomes depleted, the car gets dirtier, and the tires get a little more worn.

Applying a method to an object can affect the state of that object. If your method is to “fill up my car with gas,” after that method is performed your car's gas tank will be full. The method will have affected the state of the car's gas tank.

The key concepts here are that objects are unique representations from a class, and each object contains some information (data) that is typically private to that object. The methods provide the means of accessing and changing that data.

The Objective-C programming language has the following particular syntax for applying methods to classes and instances:

[ ClassOrInstance method ];

In this syntax, a left bracket is followed by the name of a class or instance of that class, which is followed by one or more spaces, which is followed by the method you want to perform. Finally, it is closed off with a right bracket and a terminating semicolon. When you ask a class or an instance to perform some action, you say that you are sending it a message; the recipient of that message is called the receiver. So, another way to look at the general format described previously is as follows:

[ receiver message ] ;

Let's go back to the previous list and write everything in this new syntax. Before you do that, though, you need to get your new car. Go to the factory for that, like so:

yourCar = [Car new];     get a new car

You send a message to the Car class (the receiver of the message) asking it to give you a new car. The resulting object (which represents your unique car), is then stored in the variable yourCar. From now on, yourCar can be used to refer to your instance of the car, which you got from the factory.

Because you went to the factory to get the car, the method new is called a factory or class method. The rest of the actions on your new car will be instance methods because they apply to your car. Here are some sample message expressions you might write for your car:

[yourCar prep];      get it ready for first-time use
[yourCar drive];     drive your car
[yourCar wash];      wash your car
[yourCar getGas];    put gas in your car if you need it
[yourCar service];   service your car

[yourCar topDown];   if it's a convertible
[yourCar topUp];

Your sister Sue can use the same methods for her own instance of a car:

[suesCar drive];
[suesCar wash];
[suesCar getGas];

This is one of the key concepts behind object-oriented programming (that is, applying the same methods to different objects), and you'll learn more about that later.

You probably won't need to work with cars in your programs. Your objects are likely to be computer-oriented things, such as windows, rectangles, pieces of text, or maybe even a calculator or a playlist of songs. And just like the methods used for your cars, your methods might look similar, as in the following:

image

An Objective-C Class for Working with Fractions

Now it's time to define an actual class in Objective-C and learn how to work with instances of the class.

Once again, you'll learn procedure first. As a result, the actual program examples might not seem very practical. We'll get into more practical stuff later.

Suppose you needed to write a program to work with fractions. Maybe you needed to deal with adding, subtracting, multiplying them, and so on. If you didn't know about classes, you might start with a simple program that looked like this:

Program 3.1.


// Simple program to work with fractions
#import <stdio.h>

int main (int argc, char *argv[])
{
  int  numerator = 1;
  int  denominator = 3;
  printf ("The fraction is %i/%i ", numerator, denominator);

return 0;
}


Program 3.1. Output


The fraction is 1/3


In Program 3.1 the fraction is represented in terms of its numerator and denominator. The first two lines in main both declare the variables numerator and denominator as integers and assign them initial values of 1 and 3, respectively. This is equivalent to the following lines:

int numerator, denominator;

numerator = 1;
denominator = 3;

We represented the fraction 1/3 by storing 1 into the variable numerator and 3 into the variable denominator. If you needed to store a lot of fractions in your program, this could be cumbersome. Each time you wanted to refer to the fraction, you'd have to refer to the corresponding numerator and denominator. And performing operations on these fractions would be just as awkward.

It would be better if you could define a fraction as a single entity and collectively refer to its numerator and denominator with a single name, such as myFraction. You can do that in Objective-C, and it starts by defining a new class.

Program 3.2 duplicates the functionality of Program 3.1 using a new class called Fraction. Here, then, is the program followed by a detailed explanation of how it works.

Program 3.2.


// Program to work with fractions – class version

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

//------- @interface section -------

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

-(void)   print;
-(void)   setNumerator: (int) n;
-(void)   setDenominator: (int) d;

@end

//------- @implementation section -------


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

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

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

@end

//------- program section -------


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

  // Create an instance of a Fraction

  myFraction = [Fraction alloc];
  myFraction = [myFraction init];

  // Set fraction to 1/3

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

  // Display the fraction using the print method

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

  return 0;
}


Program 3.2. Output


The value of myFraction is: 1/3


As you can see from the comments in Program 3.2, the program is logically divided into three sections, as depicted in Figure 3.1:

@interface section

@implementation section

program section

Figure 3.1. Program structure.

image

The @interface section describes the class, its data components, and its methods, whereas the @implementation section contains the actual code that implements these methods. Finally, the program section contains the program code to carry out the intended purpose of the program.

Each of these sections is a part of every Objective-C program, even though you might not need to write each section yourself. As you'll see, each section is typically put in its own file. For now, however, we'll keep it all together in a single file.

The @interface Section

When you define a new class, you have to do a few things. First, you have to tell the Objective-C compiler where the class came from. That is, you have to name its parent class. Second, you have to specify what type of data is to be stored in the objects of this class. That is, you have to describe the data that members of the class will contain. These members are called the instance variables. Finally, you need to define the type of operations, or methods, that can be used when working with objects from this class. This is all done in a special section of the program called the @interface section. The general format of this section looks like this:

@interface NewClassName: ParentClassName
{
  memberDeclarations;
}

methodDeclarations;
@end

By convention, class names begin with an uppercase letter, even though it's not required. This enables someone reading your program to distinguish class names from other types of variables by simply looking at the first character of the name. Let's take a short diversion to talk a little bit about forming names in Objective-C.

Choosing Names

In Chapter 2, “Programming in Objective-C,” you used several variables to store integer values. For example, you used the variable sum in Program 2.4 to store the result of the addition of the two integers, 50 and 25.

The Objective-C language allows data types other than just integers to be stored in variables as well, provided the proper declaration for the variable is made before it is used in the program. Variables can be used to store floating-point numbers, characters, and even objects (or more precisely, references to objects).

The rules for forming names are quite simple: They must begin with a letter or underscore (_), and they can be followed by any combination of letters (upper- or lowercase), underscores, or the digits 0–9. The following is a list of valid names:

sum

pieceFlag

i

myRectangle

numberOfMoves

_sysFlag

ChessPiece

On the other hand, the following names are not valid for the stated reasons:

sum$value$ is not a valid character.

piece flagEmbedded spaces are not permitted.

3SpencerNames can't start with a number.

intThis is a reserved word.

int cannot be used as a variable name because its use has a special meaning to the Objective-C compiler. This use is known as a reserved name or reserved word. In general, any name that has special significance to the Objective-C compiler cannot be used as a variable name. Appendix B, “Objective-C Language Summary,” provides a complete list of such reserved names.

You should always remember that upper- and lowercase letters are distinct in Objective-C. Therefore, the variable names sum, Sum, and SUM each refer to a different variable. As noted, when naming a class, start it with a capital letter. Instance variables, objects, and method names, on the other hand, typically begin with lowercase letters. To aid readability, capital letters are used inside names to indicate the start of a new word, as in the following examples:

image

When deciding on a name, keep one recommendation in mind: Don't be lazy. Pick names that reflect the intended use of the variable or object. The reasons are obvious. Just as with the comment statement, meaningful names can dramatically increase the readability of a program and will pay off in the debug and documentation phases. In fact, the documentation task will probably be greatly reduced because the program will be more self-explanatory.

Here again is the @interface section from Program 3.2:

//------- @interface section -------

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

-(void) print;
-(void) setNumerator: (int) n;
-(void) setDenominator: (int) d;

@end

The name of the new class (NewClassName) is Fraction, and its parent class is Object. (We'll talk in greater detail about parent classes in Chapter 8, “Inheritance.”) The Object class is defined in the file objc/Object.h, which is why that file (along with stdio.h) is imported at the beginning of Program 3.2:

#import <objc/Object.h>

Instance Variables

The memberDeclarations section specifies what types of data are stored in a Fraction, as well as the names of those data types. As you can see, this section is enclosed inside its own set of curly braces. For your Fraction class, the declarations

int  numerator;
int  denominator;

say that a Fraction object has two integer members called numerator and denominator.

The members declared in this section are known as the instance variables. As you'll see, each time you create a new object, a new and unique set of instance variables also is created. Therefore, if you have two Fractions, one called fracA and another called fracB, each will have its own set of instance variables. That is, fracA and fracB each will have its own separate numerator and denominator. The Objective-C system automatically keeps track of this for you, which is one of the nicer things about working with objects.

Class and Instance Methods

You have to define methods to work with your Fractions. You'll need to be able to set the value of a fraction to a particular value. Because you won't have direct access to the internal representation of a fraction (in other words, direct access to its instance variables), you must write methods to set the numerator and denominator. You'll also write a method called print that will display the value of a fraction. Here's what the declaration for the print method looks like in the interface file:

-(void) print;

The leading minus sign (-) tells the Objective-C compiler that the method is an instance method. The only other option is a plus sign (+), which indicates a class method. A class method is one that performs some operation on the class itself, such as creating a new instance of the class. This is similar to manufacturing a new car, in that the car is the class and you want to create a new one—which would be a class method.

An instance method performs some operation on a particular instance of a class, such as setting its value, retrieving its value, displaying its value, and so on. Referring to the car example, after you have manufactured the car, you might need to fill it with gas. The operation of filling it with gas is performed on a particular car, so it would be analogous to an instance method.

Return Values

When you declare a new method, you have to tell the Objective-C compiler whether the method returns a value, and if it does, what type of value it returns. This is done by enclosing the return type in parentheses after the leading minus or plus sign. So, the declaration

-(int) getNumerator;

specifies that the instance method called getNumerator returns an integer value. Similarly, the line

-(double) getDoubleValue;

declares a method that returns a double precision value. (You'll learn more about this data type in Chapter 4, “Data Types and Expressions.”)

A value is returned from a method using the Objective-C return statement, similar to the way in which we returned a value from main in previous program examples.

If the method returns no value, you indicate that using the type void, as in the following:

-(void) print;

This declares an instance method called print that returns no value. In such a case, you do not need to execute a return statement at the end of your method. Alternatively, you can execute a return without any specified value, as in the following:

return;

You don't need to specify a return type for your methods, although it's better programming practice if you do. If none is specified, id is the default. You'll learn more about the id data type in a later chapter. Basically, the id type can be used to refer to any type of object.

Method Arguments

Two other methods are declared in the @interface section from Program 3.2:

-(void) setNumerator: (int) n;
-(void) setDenominator: (int) d;

These are both instance methods that return no value. Each method takes an integer argument, which is indicated by the (int) in front of the argument name. In the case of setNumerator, the name of the argument is n. This name is arbitrary, and it is the name the method uses to refer to the argument. Therefore, the declaration of setNumerator specifies that one integer argument called n will be passed to the method and that no value is returned. This is similar for setDenominator, except the name of its argument is d.

Notice the syntax of the declaration for these methods. Each method name ends with a colon, which tells the Objective-C compiler that the method expects to see an argument. Next, the type of the argument is specified, enclosed in a set of parentheses, in much the same way the return type is specified for the method itself. Finally, the symbolic name to be used to identify that argument in the method is specified. The entire declaration is terminated with a semicolon. This syntax is depicted in Figure 3.2.

Figure 3.2. Declaring a method.

image

When a method takes an argument, you also append a colon to the method name when referring to the method. setNumerator: and setDenominator: would therefore be the correct way to identify these two methods—each of which takes a single argument. Also, the identification of the print method without a trailing colon indicates that this method does not take any arguments. In Chapter 7, “More on Classes,” you'll see how methods that take more than one argument are identified.

The @implementation Section

As noted, the @implementation section contains the actual code for the methods you declared in the @interface section. Just as a point of terminology, you say that you declare the methods in the @interface section and that you define them (that is, give the actual code) in the @implementation section.

The general format for the @implementation section is as follows:

@implementation NewClassName;
methodDefinitions;
@end

NewClassName is the same name that was used for the class in the @interface section. You can use the trailing colon followed by the parent class name, as we did in the @interface section:

@implementation Fraction: Object;

However, it's optional and typically not done.

The methodDefinitions part of the @implementation section contains the code for each method specified in the @interface section. Similar to the @interface section, each method's definition starts by identifying the type of method (class or instance), its return type, and its arguments and their types. However, instead of the line ending with a semicolon, the code for the method follows, enclosed inside a set of curly braces.

Here's the @implementation section from Program 3.2:

//------- @implementation section -------


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

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

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

@end

The print method uses printf to display the values of the instance variables numerator and denominator. But to which numerator and denominator does this method refer? It refers to the instance variables contained in the object that is the receiver of the message. That's an important concept, and we'll return to it shortly.

The setNumerator: method takes the integer argument you called n and simply stores it in the instance variable numerator. Similarly, setDenominator: stores the value of its argument d in the instance variable denominator.

The Program Section

The program section contains the code to solve your particular problem, which can be spread out across many files, if necessary. Somewhere you must have a routine called main, as we've previously noted. That's where your program always begins execution. Here is the program section from Program 3.2:

//------- program section -------

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

  // Create an instance of a Fraction

  myFraction = [Fraction alloc];
  myFraction = [myFraction init];

  // Set fraction to 1/3

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

  // Display the fraction using the print method

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

  [myFraction free];

  return 0;
}

Inside main you define a variable called myFraction with the following line:

Fraction *myFraction;

This line says that myFraction is an object of type Fraction; that is, myFraction is used to store values from your new Fraction class. The asterisk (*) in front of myFraction is required, but don't worry about its purpose now. Technically, it says that myFraction is actually a reference (or pointer) to a Fraction.

Now that you have an object to store a Fraction, you need to create one, just like you ask the factory to build you a new car. This is done with the following line:

myFraction = [Fraction alloc];

alloc is short for allocate. You want to allocate memory storage space for a new fraction. The expression

[Fraction alloc]

sends a message to your newly created Fraction class. You are asking the Fraction class to apply the alloc method, but you never defined an alloc method, so where did it come from? The method was inherited from a parent class. Chapter 8 deals with this topic in detail.

When you send the alloc message to a class, you get back a new instance of that class. In Program 3.2, the returned value is stored inside your variable myFraction. The alloc method is guaranteed to zero out all of an object's instance variables. However, that does not mean that the object has been properly initialized for use. You need to initialize an object after you allocate it.

This is done with the next statement in Program 3.2, that reads as follows:

myFraction = [myFraction init];

Again, you are using a method here that you didn't write yourself. The init method initializes the instance of a class. Note that you are sending the init message to myFraction. That is, you want to initialize a specific Fraction object here, so you don't send it to the class—you send it to an instance of the class. Make sure you understand this point before continuing.

The init method also returns a value, namely the initialized object. You take the return value and store it in your Fraction variable myFraction.

The two-line sequence of allocating a new instance of class and then initializing it is done so often in Objective-C that the two messages are typically combined, as follows:

myFraction = [[Fraction alloc] init];

The inner message expression

[Fraction alloc]

is evaluated first. As you know, the result of this message expression is the actual Fraction that is allocated. Instead of storing the result of the allocation in a variable as you did before, you directly apply the init method to it. So, again, first you allocate a new Fraction and then you initialize it. The result of the initialization is then assigned to the myFraction variable.

As a final shorthand technique, the allocation and initialization is often incorporated directly into the declaration line, as in the following:

Fraction *myFraction = [[Fraction alloc] init];

This coding style is used often throughout the remainder of this book, so it's important that you understand it.

Returning to Program 3.2, you are now ready to set the value of your fraction. The program lines

// Set fraction to 1/3

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

do just that. The first message statement sends the setNumerator: message to myFraction. The argument that is supplied is the value 1. Control is then sent to the setNumerator: method you defined for your Fraction class. The Objective-C system knows that it is the method from this class to use because it knows that myFraction is an object from the Fraction class.

Inside the setNumerator: method, the passed value of 1 is stored inside the variable n. The single program line in that method takes that value and stores it in the instance variable numerator. So, you have effectively set the numerator of myFraction to 1.

The message that invokes the setDenominator: method on myFraction follows next. The argument of 3 is assigned to the variable d inside the setDenominator: method. This value is then stored inside the denominator instance variable, thus completing the assignment of the value 1/3 to myFraction. Now you're ready to display the value of your fraction, which is done with the following lines of code from Program 3.2:

// display the fraction using the print method

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

The first printf call simply displays the following text:

The value of myFraction is:

A newline is not appended to the end of the line so that the value of the fraction as displayed by the print method appears on the same line. The print method is invoked with the following message expression:

[myFraction print];

Inside the print method, the values of the instance variables numerator and denominator are displayed, separated by a slash character. The final printf call back in main simply displays a newline character.

The last message in the program,

[myFraction free];

frees the memory that was used for the Fraction object. This is a critical part of good programming style. Whenever you create a new object, you are asking for memory to be allocated for that object. Also, when you're done with the object, you are responsible for releasing the memory it uses. Although it's true that the memory will be released when your program terminates anyway, after you start developing more sophisticated applications, you can end up working with hundreds (or thousands) of objects that consume a lot of memory. Waiting for the program to terminate for the memory to be released is wasteful of memory, can slow your program's execution, and is not good programming style. So, get into the habit right now!

It seems as if you had to write a lot more code to duplicate in Program 3.2 what you did in Program 3.1. That's true for this simple example here; however, the ultimate goal in working with objects is to make your programs easier to write, maintain, and extend. You'll realize that later.

The last example in this chapter shows how you can work with more than one fraction in your program. In Program 3.3, you set one fraction to 2/3, set another to 3/7, and display them both.

Program 3.3.


// Program to work with fractions – cont'd

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

//------- @interface section -------

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

-(void) print;
-(void) setNumerator: (int) n;
-(void) setDenominator: (int) d;

@end

//------- @implementation section -------


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

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

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

@end

//------- program section -------

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

  // Set 1st fraction to 2/3

  [frac1 setNumerator: 2];
  [frac1 setDenominator: 3];

  // Set 2nd fraction to 3/7

  [frac2 setNumerator: 3];
  [frac2 setDenominator: 7];

  // Display the fractions

  printf ("First fraction is:");
  [frac1 print];
  printf (" ");

  printf ("Second fraction is:");
  [frac2 print];
  printf (" ");

  [frac1 free];
  [frac2 free];

  return 0;
}


Program 3.3. Output


First fraction is: 2/3
Second fraction is: 3/7


The @interface and @implementation sections remain unchanged from Program 3.2. The program creates two Fraction objects called frac1 and frac2 and then assigns the value 2/3 to the first fraction and 3/7 to the second. Realize that, when the setNumerator: method is applied to frac1 to set its numerator to 2, the instance variable frac1 gets its instance variable numerator set to 2. Also, when frac2 uses the same method to set its numerator to 3, its distinct instance variable numerator is set to the value 3. Each time you create a new object, it gets its own distinct set of instance variables. This is depicted in Figure 3.3.

Figure 3.3. Unique instance variables.

image

Based on which object is getting sent the message, the correct instance variables are referenced. Therefore, in

[frac1 setNumerator: 2];

it is frac1's numerator that is referenced whenever setNumerator: uses the name numerator inside the method. That's because frac1 is the receiver of the message.

Accessing Instance Variables and Data Encapsulation

You've seen how the methods that deal with fractions can access the two instance variables numerator and denominator directly by name. In fact, an instance method can always directly access its instance variables. A class method can't, however, because it's dealing only with the class itself and not with any instances of the class (think about that for a second). But what if you wanted to access your instance variables from someplace else—for example, from inside your main routine? You can't do that directly because they are hidden. The fact that they are hidden from you is a key concept called data encapsulation. It enables someone writing class definitions to extend and modify his class definitions without worrying about whether programmers (that is, users of the class) are tinkering with the internal details of the class. Data encapsulation provides a nice layer of insulation between the programmer and the class developer.

You can access your instance variables in a clean way by writing special methods to retrieve their values. For example, you'll create two new methods called, appropriately enough, numerator and denominator to access the corresponding instance variables of the Fraction that is the receiver of the message. The result will be the corresponding integer value, which you will return. Here are the declarations for your two new methods:

-(int) numerator;
-(int) denominator;

And here are the definitions:

-(int) numerator
{
  return numerator;
}

-(int) denominator
{
  return denominator;
}

Note that the names of the methods and the instance variables they access are the same. There's no problem doing this; in fact, it is common practice. Program 3.4 tests your two new methods.

Program 3.4.


// Program to access instance variables – cont'd

#import <stdio.h>
#import <objc/Object.h>
//------- @interface section -------

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

-(void) print;
-(void) setNumerator: (int) n;
-(void) setDenominator: (int) d;
-(int) numerator;
-(int) denominator;

@end

//------- @implementation section -------


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

@end


//------- program section -------

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 using our two new methods

  printf ("The value of myFraction is: %i/%i ",
   [myFraction numerator], [myFraction denominator]);
  [myFraction free];

  return 0;
}


Program 3.4. Output


The value of myFraction is 1/3


The printf statement

printf ("The value of myFraction is: %i/%i ",
  [myFraction numerator], [myFraction denominator]);

displays the results of sending two messages to myFraction: the first to retrieve the value of its numerator, and the second the value of its denominator.

Incidentally, methods that set the values of instance variables are often collectively referred to as setters, and methods used to retrieve the values of instance variables are called getters. For the Fraction class, setNumerator: and setDenominator: are the setters and numerator and denominator are the getters.

It should also be pointed out here that there is also a method called new that combines the actions of an alloc and init. So, the line

Fraction *myFraction = [Fraction new];

could be used to allocate and initialize a new Fraction. It's generally better to use the two-step allocation and initialization approach so you conceptually understand that two distinct events are occurring: You're first creating a new object and then you're initializing it.

Now you know how to define your own class, create objects or instances of that class, and send messages to those objects. We'll return to the Fraction class in later chapters. You'll learn how to pass multiple arguments to your methods, how to divide your class definitions into separate files, and also about key concepts such as inheritance and dynamic binding. However, now it's time to learn more about data types and writing expressions in Objective-C. First, try the exercises that follow to test your understanding of the important points covered in this chapter.

Exercises

  1. Which of the following are invalid names? Why?

    Int                      playNextSong        6_05
    _calloc                  Xx                  alphaBetaRoutine
    clearScreen              _1312               z
    ReInitialize             _                   A$

  2. Based on the example of the car in this chapter, think of an object you use every day. Identify a class for that object and write five actions you do with that object.
  3. Given the list in exercise 2, use the following syntax:

    [instance method];

    to rewrite your list in this format.

  4. Imagine that you owned a boat and a motorcycle in addition to a car. List the actions you would perform with each of these. Do you have any overlap between these actions?
  5. Based on question 4, imagine you had a class called a Vehicle and an object called myVehicle that could be either a Car, a Motorcycle, or a Boat. What if you wrote the following:

    [myVehicle prep];
    [myVehicle getGas];
    [myVehicle service];

    Do you see any advantages of being able to apply an action to an object that could be from one of several classes?

  6. In a procedural language such as C, you think about actions and then write code to perform the action on various objects. Referring to the car example, you might write a procedure in C to wash a vehicle and then inside that procedure write code to handle washing a car, washing a boat, washing a motorcycle, and so on. If you took that approach and then wanted to add a new vehicle type (see the previous exercise), do you see advantages or disadvantages to using this procedural approach over an object-oriented approach?
  7. Define a class called Point that will hold a Cartesian coordinate (x, y), where x and y are integers. Define methods to individually set the x and y coordinates of a point and retrieve their values. Write an Objective-C program to implement your new class and test it.
..................Content has been hidden....................

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