13. Underlying C Language Features

This chapter describes features of the Objective-C language that you don't necessarily need to know to write Objective-C programs. In fact, most of these come from the underlying C programming language. Features such as functions, structures, pointers, unions, and arrays are best learned on a need-to-know basis. Because C is a procedural language, some of these features go against the grain of object-oriented programming. They can also interfere with some of the strategies implemented by the Foundation framework, such as the memory allocation methodology or working with character strings containing multibyte characters.1

On the other hand, some applications can require you to use a lower-level approach, perhaps for the sake of optimization. If you're working with large arrays of data for example, you might want to use Objective-C's built-in array data structures rather than Foundation's array objects (which are described in Chapter 15, “Numbers, Strings, and Collections”). Functions can also come in handy if used properly to group repetitive operations together and modularize a program.

It is recommended that you just skim through this chapter to get an overview of the material and come back after you've finished reading Part II, “The Foundation Framework.” Or you can skip it all together and go on to Part II, which covers the Foundation framework. If you end up supporting someone else's code or start digging through some of the Foundation framework header files, you will encounter some of the constructs covered in this chapter. Several of the Foundation data types, such as NSRange, NSPoint, and NSRect, require a rudimentary understanding of structures, which are described here. In such cases, you can return to this chapter and read the appropriate section to gain an understanding of the concepts.

Arrays

The Objective-C language provides a capability that enables the user to define a set of ordered data items known as an array. This section describes how arrays can be defined and manipulated. In later sections, we'll include further discussions on arrays to illustrate how they work together with functions, structures, character strings, and pointers.

Suppose you had a set of grades that you wanted to read into the computer, and suppose that you wanted to perform some operations on these grades, such as rank them in ascending order, compute their average, or find their median. If you think about the process of ranking a set of grades, you will quickly realize that you cannot perform such an operation until each and every grade has been entered.

In Objective-C, you can define a variable called grades, which represents not a single value of a grade but an entire set of grades. Each element of the set can then be referenced by means of a number called an index number, or subscript. Whereas in mathematics a subscripted variable, xi, refers to the ith element x in a set, in Objective-C the equivalent notation is this:

x[i]

So, the expression

grades[5]

(read as “grades sub 5”) refers to element number 5 in the array called grades. In Objective-C, array elements begin with the number 0, so

grades[0]

actually refers to the first element of the array.

An individual array element can be used anywhere that a normal variable can be. For example, you can assign an array value to another variable with a statement such as follows:

g = grades[50];

This statement takes the value contained in grades[50] and assigns it to g. More generally, if i is declared to be an integer variable, the statement

g = grades[i];

takes the value contained in element number i of the grades array and assigns it to g.

A value can be stored into an element of an array simply by specifying the array element on the left side of an equal sign. In the statement

grades[100] = 95;

the value 95 is stored into element number 100 of the grades array.

You can easily sequence through the elements in the array by varying the value of a variable that is used as a subscript into the array. Therefore, the for loop

for ( i = 0; i < 100; ++i )
  sum += grades[i];

sequences through the first 100 elements of the array grades (elements 0–99) and adds the value of each grade into sum. When the for loop is finished, the variable sum contains the total of the first 100 values of the grades array (assuming sum was set to 0 before the loop was entered).

Just as with other types of variables, arrays must also be declared before they are used. The declaration of an array involves declaring the type of element that will be contained in the array, such as int, float, or an object, as well as the maximum number of elements that will be stored inside the array.

The definition

Fraction *fracts [100];

defines fracts to be an array containing 100 fractions. Valid references to this array can be made by using subscripts 0–99.

The expression

fracts[2] = [fracts[0] add: fracts[1]];

invokes the Fraction's add: method to add the first two fractions from the fracts array and stores the result in the third location of the array.

Program 13.1 generates a table of the first 15 Fibonacci numbers. Try to predict its output. What relationship exists between each number in the table?

Program 13.1.


// Program to generate the first 15 Fibonacci numbers
#import <stdio.h>

int main (int argc, char *argv[])
{
  int Fibonacci[15], i;

      Fibonacci[0] = 0;  /* by definition */
      Fibonacci[1] = 1;  /*   ditto   */

  for ( i = 2; i < 15; ++i )
      Fibonacci[i] = Fibonacci[i-2] + Fibonacci[i-1];

  for ( i = 0; i < 15; ++i )
      printf ("%i ", Fibonacci[i]);

  return 0;
}


Program 13.1. Output


0
1
1
2
3
5
8
13
21
34
55
89
144
233
377


The first two Fibonacci numbers, which we will call F0 and F1, are defined to be 0 and 1, respectively. Thereafter, each successive Fibonacci number Fi is defined to be the sum of the two preceding Fibonacci numbers Fi-2 and Fi-1. So, F2 is calculated by adding the values of F0 and F1. In the preceding program, this corresponds directly to calculating Fibonacci[2] by adding the values Fibonacci[0] and Fibonacci[1]. This calculation is performed inside the for loop, which calculates the values of F2F14 (or, equivalently, Fibonacci[2] through Fibonacci[14]).

Initializing Array Elements

Just as you can assign initial values to variables when they are declared, you can also assign initial values to the elements of an array. This is done by simply listing the initial values of the array, starting from the first element. Values in the list are separated by commas and the entire list is enclosed in a pair of braces.

The statement

int integers[5] = { 0, 1, 2, 3, 4 };

sets the value of integers[0] to 0, integers[1] to 1, integers[2] to 2, and so on.

Arrays of characters are initialized in a similar manner; thus the statement

char letters[5] = { 'a', 'b', 'c', 'd', 'e' };

defines the character array letters and initializes the five elements to the characters 'a', 'b', 'c', 'd', and 'e', respectively.

You don't have to completely initialize an entire array. If fewer initial values are specified, only an equal number of elements are initialized and the remaining values in the array are set to zero. Thus, the declaration

float sample_data[500] = { 100.0, 300.0, 500.5 };

initializes the first three values of sample_data to 100.0, 300.0, and 500.5 and sets the remaining 497 elements to 0.

By enclosing an element number in a pair of brackets, specific array elements can be initialized in any order. For example

int x = 1233;
int a[] = { [9] = x + 1, [2] = 3, [1] = 2, [0] = 1 };

defines a 10-element array called a (based on the highest index into the array) and initializes the last element to the value of x + 1 (1234). In addition, it initializes the first three elements to 1, 2, and 3, respectively.

Character Arrays

The purpose of Program 13.2 is to simply illustrate how a character array can be used. However, there is one point worthy of discussion. Can you spot it?

Program 13.2.


#import <stdio.h>

int main (int argc, char *argv[])
{
  char word[] = { 'H', 'e', 'l', 'l', 'o', '!' };
  int  i;

  for ( i = 0; i < 6; ++i )
        printf ("%c", word[i]);

  printf (" ");
  return 0;
}


Program 13.2. Output


Hello!


The most notable point in the preceding program is the declaration of the character array word. There is no mention of the number of elements in the array. The Objective-C language enables you to define an array without specifying the number of elements. If this is done, the size of the array is determined automatically based on the number of initialization elements. Because Program 13.2 has six initial values listed for the array word, the Objective-C language implicitly dimensions the array to six elements.

This approach works fine as long as you initialize every element in the array at the point that the array is defined. If this is not to be the case, you must explicitly dimension the array.

If you put a terminating null character ('') at the end of a character array, you create what is often-called a character string. If you substituted the initialization of word in Program 13.2 with this line

char word[] = { 'H', 'e', 'l', 'l', 'o', '!', '' };

you could have subsequently displayed the string with a single printf call, like this:

printf ("%s ", word);

This works because the %s format characters tell printf to keep displaying characters until a terminating null character is reached. That's the character you put at the end of your word array.

Multidimensional Arrays

The types of arrays you've seen thus far are all linear arrays—that is, they all deal with a single dimension. The language enables arrays of any dimension to be defined. This section takes a look at two-dimensional arrays.

One of the most natural applications for a two-dimensional array arises in the case of a matrix. Consider the 4 × 5 matrix shown here:

image

In mathematics, it is common to refer to an element of a matrix by using a double subscript. If the preceding matrix was called M, the notation Mi,j would refer to the element in the ith row, jth column, where i ranges from 1 through 4 and j ranges from 1 through 5. The notation M3,2 would refer to the value 20, which is found in the third row, second column of the matrix. In a similar fashion, M4,5 would refer to the element contained in the fourth row, fifth column (the value 6).

In Objective-C, an analogous notation is used when referring to elements of a two-dimensional array. However, because Objective-C likes to start numbering things at 0, the first row of the matrix is actually row 0 and the first column of the matrix is column 0. The preceding matrix would then have row and column designations as shown in the following diagram:

image

Whereas in mathematics the notation Mi,j is used, in Objective-C the equivalent notation is as follows:

M[i][j]

Remember, the first index number refers to the row number, whereas the second index number references the column. The statement

sum = M[0][2] + M[2][4];

therefore adds the value contained in row 0, column 2 (which is -3) to the value contained in row 2, column 4 (which is 14) and assigns the result of 11 to the variable sum.

Two-dimensional arrays are declared the same way that one-dimensional arrays are; thus

int M[4][5];

declares the array M to be a two-dimensional array consisting of 4 rows and 5 columns, for a total of 20 elements. Each position in the array is defined to contain an integer value.

Two-dimensional arrays can be initialized in a manner analogous to their one-dimensional counterparts. When listing elements for initialization, the values are listed by row. Brace pairs are used to separate the list of initializers for one row from the next. Thus, to define and initialize the array M to the elements listed in the preceding table, a statement such as the following could be used:

int M[4][5] = {
              { 10, 5, -3, 17, 82 },
              { 9, 0, 0, 8, -7 },
              { 32, 20, 1, 0, 14 },
              { 0, 0, 8, 7, 6 }
    };

Pay particular attention to the syntax of the previous statement. Note that commas are required after each brace that closes off a row, except in the case of the last row. The use of the inner pairs of braces is actually optional. If it's not supplied, initialization proceeds by row. Therefore, the previous statement could also have been written as follows:

int M[4][5] = { 10, 5, -3, 17, 82, 9, 0, 0, 8, -7, 32,
                20, 1, 0, 14, 0, 0, 8, 7, 6 };

As with one-dimensional arrays, the entire array need not be initialized. A statement such as

int M[4][5] = {
              { 10, 5, -3 },
              { 9, 0, 0 },
              { 32, 20, 1 },
              { 0, 0, 8 }
    };

initializes only the first three elements of each row of the matrix to the indicated values. The remaining values are set to 0. Note that, in this case, the inner pairs of braces are required to force the correct initialization. Without them, the first two rows and the first two elements of the third row would have been initialized instead. (Verify for yourself that this would be the case.)

Functions

The printf routine is an example of a function that you have used in every program so far. Indeed, each and every program also has used a function called main. Let's go back to the very first program you wrote (Program 2.1), which displayed the phrase Programming is fun. at the terminal:

#import <stdio.h>
int main (int argc, char *argv[])
{
   printf ("Programming is fun. ");
   return 0;
}

Here is a function called printMessage that produces the same output:

void printMessage (void)
{
    printf ("Programming is fun. ");
}

The only difference between printMessage and the function main from Program 2.1 is in the first line. The first line of a function definition tells the compiler four things about the function:

• Who can call it

• The type of value it returns

• Its name

• The number and type of arguments it takes

The first line of the printMessage function definition tells the compiler that printMessage is the name of the function and that it returns no value (the first use of the keyword void). Unlike methods, you don't put the function's return type inside a set of parentheses. In fact, you'll get a compiler error message if you do!

After telling the compiler that printMessage doesn't return a value, the second use of the keyword void says that it takes no arguments.

You will recall that main is a specially recognized name in the Objective-C system that always indicates where the program is to begin execution. There always must be a main. So, you can add a main function to the preceding code to end up with a complete program, as shown in Program 13.3.

Program 13.3.


#import <stdio.h>
void printMessage (void)
{
         printf ("Programming is fun. ");
}

int main (int argc, char *argv[])
{
         printMessage ();
         return 0;
}


Program 13.3. Output


Programming is fun.


Program 13.3 consists of two functions: printMessage and main. As mentioned earlier, the idea of calling a function is not new. Because printMessage takes no arguments, you call it simply by listing its name followed by a pair of open and close parentheses.

Arguments and Local Variables

In Chapter 5, “Program Looping,” you developed programs for calculating triangular numbers. Here you'll define a function to generate a triangular number and call it, appropriately enough, calculateTriangularNumber. As an argument to the function, you'll specify which triangular number to calculate. The function will then calculate the desired number and display the results at the terminal. Program 13.4 shows the function to accomplish the task and a main routine to try it.

Program 13.4.


#import <stdio.h>
// Function to calculate the nth triangular number

void calculateTriangularNumber (int n)
{
   int i, triangularNumber = 0;

   for ( i = 1; i <= n; ++i )
       triangularNumber += i;

   printf ("Triangular number %i is %i ", n, triangularNumber);
}

int main (int argc, char *argv[])
{
    calculateTriangularNumber (10);
    calculateTriangularNumber (20);
    calculateTriangularNumber (50);
    return 0;
}


Program 13.4. Output


Triangular number 10 is 55
Triangular number 20 is 210
Triangular number 50 is 1275


The first line of the calculateTriangularNumber function is

void calculateTriangularNumber (int n)

and it tells the compiler that calculateTriangularNumber is a function that returns no value (the keyword void) and that it takes a single argument, called n, which is an int. Note again that you can't put the argument type inside parentheses, as you are accustomed to doing when you write methods.

The beginning of the function's definition is indicated by the opening curly brace. Because you want to calculate the nth triangular number, you have to set up a variable to store the value of the triangular number as it is being calculated. You also need a variable to act as your loop index. The variables TriangularNumber and i are defined for these purposes and are declared to be of type int. These variables are defined and initialized in the same manner that you defined and initialized your variables inside the main routine in previous programs.

Local variables in functions behave the same way they do in methods: If an initial value is given to a variable inside a function, that initial value is assigned to the variable each time the function is called.

Variables defined inside a function (as in methods) are known as automatic local variables because they are automatically “created” each time the function is called and because their values are local to the function.

Static local variables are declared with the keyword static, retain their values through function calls, and have default initial values of 0.

The value of a local variable can be accessed only by the function in which the variable is defined. Its value cannot be accessed from outside the function.

Returning to our program example, after the local variables have been defined, the function calculates the triangular number and displays the results at the terminal. The closed brace then defines the end of the function.

Inside the main routine, the value 10 is passed as the argument in the first call to calculateTriangularNumber. Execution is then transferred directly to the function where the value 10 becomes the value of the formal parameter n inside the function. The function then calculates the value of the 10th triangular number and displays the result.

The next time that calculateTriangularNumber is called, the argument 20 is passed. In a similar process, as described earlier, this value becomes the value of n inside the function. The function then calculates the value of the 20th triangular number and displays the answer at the terminal.

Returning Function Results

As with methods, a function can return a value. The type of value returned with the return statement must be consistent with the return type declared for the function. A function declaration that starts like this

float kmh_to_mph (float km_speed)

begins the definition of a function kmh_to_mph, which takes one float argument called km_speed and returns a floating-point value. Similarly

int gcd (int u, int v)

defines a function called gcd with integer arguments u and v and returns an integer value.

Let's rewrite the greatest common divisor algorithm used in Program 5.7 in function form. The two arguments to the function are the two numbers whose greatest common divisor (gcd) you want to calculate (see Program 13.5).

Program 13.5.


#import <stdio.h>
// This function finds the greatest common divisor of two
//  nonnegative integer values and returns the result

int gcd (int u, int v)
{
    int temp;

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

   return u;
}

main ()
{
     int result;

     result = gcd (150, 35);
     printf ("The gcd of 150 and 35 is %i ", result);

     result = gcd (1026, 405);
     printf ("The gcd of 1026 and 405 is %i ", result);

     printf ("The gcd of 83 and 240 is %i ", gcd (83, 240));
     return 0;
}


Program 13.5. Output


The gcd of 150 and 35 is 5
The gcd of 1026 and 405 is 27
The gcd of 83 and 240 is 1


The function gcd is defined to take two integer arguments. The function refers to these arguments through their formal parameter names: u and v. After declaring the variable temp to be of type int, the program displays the values of the arguments u and v, together with an appropriate message at the terminal. The function then calculates and returns the greatest common divisor of the two integers.

The statement

result = gcd (150, 35);

says to call the function gcd with the arguments 150 and 35 and to store the value that is returned by this function into the variable result.

If the return type declaration for a function is omitted, the compiler assumes the function will return an integer—if it returns a value at all. Many programmers take advantage of this fact and omit the return type declaration for functions that return integers. This, however, is a bad programming habit that should be avoided.

The default return type for functions differs from that for methods. You'll recall that, if no return type is specified for a method, the compiler assumes it returns a value of type id. Once again, you should always declare the return type for a method and not rely on this fact.

Declaring Return Types and Argument Types

We mentioned earlier that the Objective-C compiler assumes that a function returns a value of type int as the default case. More specifically, whenever a call is made to a function, the compiler assumes that the function returns a value of type int unless either of the following has occurred:

• The function has been defined in the program before the function call is encountered.

• The value returned by the function has been declared before the function call is encountered. Declaring the return and argument types for a function is known as a prototype declaration.

Not only is the function declaration used to declare the function's return type, but it is also used to tell the compiler how many arguments the function takes and what their types are. This is analogous to declaring methods inside the @interface section when defining a new class.

To declare absoluteValue as a function that returns a value of type float and that takes a single argument, also of type float, you could use the following prototype declaration:

float absoluteValue (float);

As you can see, you just have to specify the argument type inside the parentheses, and not its name. You can optionally specify a “dummy” name after the type if you like:

float absoluteValue (float x);

This name doesn't have to be the same as the one used in the function definition—the compiler ignores it anyway.

A foolproof way to write a prototype declaration is to simply use your text editor to make a copy of the first line from the actual definition of the function. Remember to place a semicolon at the end.

If the function takes a variable number of arguments (such as is the case with printf and scanf), the compiler must be informed. The declaration

int printf (char *format, ...);

tells the compiler that printf takes a character pointer as its first argument (more on that later) and is followed by any number of additional arguments (the use of the ...). printf and scanf are declared in the special file stdio.h, which is why you have been placing the following line at the start of each of your programs:

#import <stdio.h>

Without this line, the compiler can assume printf takes a fixed number of arguments, which can result in incorrect code being generated.

The compiler automatically converts your numeric arguments to the appropriate types when a function is called only if you have placed the function's definition or have declared the function and its argument types before the call.

Here are some reminders and suggestions about functions:

• By default, the compiler assumes that a function returns an int.

• When defining a function that returns an int, define it as such.

• When defining a function that doesn't return a value, define it as void.

• The compiler converts your arguments to agree with the ones the function expects only if you have previously defined or declared the function.

To be safe, declare all functions in your program, even if they are defined before they are called. (You might decide later to move them someplace else in your file or even to another file.) A good strategy is to put your function declarations inside a header file and then just import that file into your modules.

Functions are external by default. That is, the default scope for a function is that it can be called by any functions or methods contained in any files that are linked together with the function. You can limit the scope of a function by making it static. You do this by placing the keyword static in front of the function declaration, as shown here:

static int gcd (int u, int v)
{
  ...
}

A static function can be called only by other functions or methods that appear in the same file that contains the function's definition.

Functions, Methods, and Arrays

To pass a single array element to a function or method, the array element is specified as an argument in the normal fashion. So, if you had a squareRoot function to calculate square roots and wanted to take the square root of averages[i] and assign the result to a variable called sq_root_result, a statement such as this one would work:

sq_root_result = squareRoot (averages[i]);

Passing an entire array to a function or method is an entirely new ball game. To pass an array, you only need to list the name of the array, without any subscripts, inside the call to the function or method invocation. As an example, if you assume that grade_scores has been declared as an array containing 100 elements, the expression

minimum (grade_scores)

in effect passes the entire 100 elements contained in the array grade_scores to the function called minimum. Naturally, on the other side of the coin, the minimum function must be expecting an entire array to be passed as an argument and must make the appropriate formal parameter declaration.

Here is a function that finds the minimum integer value in an array containing a specified number of elements:

// Function to find the minimum in an array

int minimum (int values[], int numElements)
{
    int minValue, i;

    minValue = values[0];

    for ( i = 1; i < numElements; ++i )
        if ( values[i] < minValue )
           minValue = values[i];

    return (minValue);
}

The function minimum is defined to take two arguments: first, the array whose minimum you want to find and, second, the number of elements in the array. The open and close brackets that immediately follow values in the function header serve to inform the Objective-C compiler that values is an array of integers. The compiler doesn't care how large it is.

The formal parameter numElements serves as the upper limit inside the for statement. Thus, the for statement sequences through the array from values[1] through the last element of the array, which is values[numElements - 1].

If a function or method changes the value of an array element, that change is made to the original array that was passed to the function or method. This change remains in effect even after the function or method has completed execution.

The reason an array behaves differently from a simple variable or an array element—whose value cannot be changed by a function or method—is worthy of a bit of explanation. We stated that, when a function or method is called, the values passed as arguments are copied into the corresponding formal parameters. This statement is still valid. However, when dealing with arrays, the entire contents of the array are not copied into the formal parameter array. Instead, a pointer is passed indicating where in the computer's memory the array is located. So, any changes made to the formal parameter array are actually made to the original array and not to a copy of the array. Therefore, when the function or method returns, these changes still remain in effect.

Multidimensional Arrays

A multidimensional array element can be passed to a function or method just as any ordinary variable or single-dimensional array element can. The statement

result = squareRoot (matrix[i][j]);

calls the squareRoot function, passing the value contained in matrix[i][j] as the argument.

An entire multidimensional array can be passed as an argument the same way in which a single-dimensional array can: You simply list the name of the array. For example, if the matrix measuredValues is declared to be a two-dimensional array of integers, the Objective-C statement

scalarMultiply (measuredValues, constant);

could be used to invoke a function that multiplies each element in the matrix by the value of constant. This implies, of course, that the function itself can change the values contained inside the measuredValues array. The discussion pertaining to this topic for single-dimensional arrays also applies here: An assignment made to any element of the formal parameter array inside the function makes a permanent change to the array that was passed to the function.

When declaring a single-dimensional array as a formal parameter, it was stated that the actual dimension of the array was not needed. It suffices to simply use a pair of empty brackets to inform the Objective-C compiler that the parameter is in fact an array. This does not totally apply in the case of multidimensional arrays. For a two-dimensional array, the number of rows in the array can be omitted but the declaration must contain the number of columns in the array. The declarations

int arrayValues[100][50]

and

int arrayValues[][50]

are both valid declarations for a formal parameter array called arrayValues containing 100 rows by 50 columns; but the declarations

int arrayValues[100][]

and

int arrayValues[][]

are not because the number of columns in the array must be specified.

Structures

The Objective-C language provides another tool for grouping elements together besides arrays. Structures can also be used, and they form the basis for the discussions in this section.

Suppose you wanted to store a date—say, 7/18/03—inside a program, perhaps to be used for the heading of some program output or even for computational purposes. A natural method for storing the date would be to simply assign the month to an integer variable called month, the day to an integer variable day, and the year to an integer variable year. So the statements

int month = 7, day = 18, year = 2003;

would work just fine. This is a totally acceptable approach. But what if your program also needed to store several dates? It would be much better if you could somehow group these sets of three variables together.

You can define a structure called date in the Objective-C language that consists of three components that represent the month, day, and year. The syntax for such a definition is rather straightforward, as shown by the following:

struct date
{
  int month;
  int day;
  int year;
};

The date structure just defined contains three integer members called month, day, and year. The definition of date in a sense defines a new type in the language in that variables can subsequently be declared to be of type struct date, as in the following definition:

struct date today;

You can also define a variable called purchaseDate to be of the same type by a separate definition, such as follows:

struct date purchaseDate;

Or, you can simply include the two definitions on the same line, as in this line:

struct date today, purchaseDate;

Unlike variables of type int, float, or char, a special syntax is needed when dealing with structure variables. A member of a structure is accessed by specifying the variable name, followed by a period (called the dot operator), and then the member name. For example, to set the value of day in the variable today to 21, you write

today.day = 21;

Note that no spaces are permitted between the variable name, period, and member name. To set year in today to 2003, the expression

today.year = 2003;

can be used. Finally, to test the value of month to see whether it is equal to 12, a statement such as

if ( today.month == 12 )
  next_month = 1;

will work.

Program 13.6 incorporates the preceding discussions into an actual program.

Program 13.6.


#import <stdio.h>
int main (int argc, char *argv[])
{
    struct date
    {
        int month;
        int day;
        int year;
    };

    struct date today;

    today.month = 9;
    today.day = 25;
    today.year = 2004;

    printf ("Today's date is %i/%i/%.2i. ", today.month,
              today.day, today.year % 100);

    return 0;
}


Program 13.6. Output


Today's date is 9/25/04.


The first statement inside main defines the structure called date to consist of three integer members called month, day, and year. In the second statement, the variable today is declared to be of type struct date. So, the first statement simply defines what a date structure looks like to the Objective-C compiler and causes no storage to be reserved inside the computer. The second statement declares a variable to be of type struct date and therefore does cause memory to be reserved for storing the three integer members of the structure variable today.

After the assignments have been made, the values contained inside the structure are displayed by an appropriate printf call. The remainder of today.year divided by 100 is calculated prior to being passed to the printf function so that just 04 is displayed for the year. The %.2i format characters in the printf call specify a minimum of two characters to be displayed, thus forcing the display of the leading zero for the year.

When it comes to the evaluation of expressions, structure members follow the same rules as do ordinary variables in the Objective-C language. Division of an integer structure member by another integer is therefore performed as an integer division, as shown here:

century = today.year / 100 + 1;

Suppose you wanted to write a simple program that accepted today's date as input and displayed tomorrow's date to the user? Now, at first glance, this seems a perfectly simple task to perform. You can ask the user to enter today's date and then calculate tomorrow's date by a series of statements, like so:

tomorrow.month = today.month;
tomorrow.day  = today.day + 1;
tomorrow.year = today.year;

Of course, the previous statements would work fine for the majority of dates, but the following two cases would not be properly handled:

• If today's date fell at the end of a month

• If today's date fell at the end of a year (that is, if today's date were December 31)

One way to easily determine whether today's date falls at the end of a month is to set up an array of integers that corresponds to the number of days in each month. A lookup inside the array for a particular month then gives the number of days in that month (see Program 13.7).

Program 13.7.


// Program to determine tomorrow's date

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

struct date
{
    int month;
    int day;
    int year;
};

// Function to calculate tomorrow's date

struct date dateUpdate (struct date today)
{
    struct date tomorrow;
    int numberOfDays (struct date d);

    if ( today.day != numberOfDays (today) )
  {
      tomorrow.day = today.day + 1;
      tomorrow.month = today.month;
      tomorrow.year = today.year;
  }
    else if ( today.month == 12 )   // end of year
  {
    tomorrow.day = 1;
    tomorrow.month = 1;
    tomorrow.year = today.year + 1;
  }
  else
  {                // end of month
    tomorrow.day = 1;
    tomorrow.month = today.month + 1;
    tomorrow.year = today.year;
  }

  return (tomorrow);
}

// Function to find the number of days in a month

int numberOfDays (struct date d)
{
   int answer;
   BOOL isLeapYear (struct date d);
   int daysPerMonth[12] =
    { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

   if ( isLeapYear (d) == YES && d.month == 2 )
     answer = 29;
   else
    answer = daysPerMonth[d.month - 1];

   return (answer);
}

// Function to determine if it's a leap year

BOOL isLeapYear (struct date d)
{
if ( (d.year % 4 == 0 && d.year % 100 != 0) ||
         d.year % 400 == 0 )
    return YES;
    else
    return NO;
}

int main (int argc, char *argv[])
{
   struct date dateUpdate (struct date today);
   struct date thisDay, nextDay;

   printf ("Enter today's date (mm dd yyyy): ");
   scanf ("%i%i%i", &thisDay.month, &thisDay.day,
              &thisDay.year);

   nextDay = dateUpdate (thisDay);

   printf ("Tomorrow's date is %i/%i/%.2i. ",nextDay.month,
              nextDay.day, nextDay.year % 100);
   return 0;
}


Program 13.7. Output


Enter today's date (mm dd yyyy): 2 28 2004
Tomorrow's date is 2/29/04.


Program 13.7. Output (Rerun)


Enter today's date (mm dd yyyy): 10 2 2005
Tomorrow's date is 10/3/05.


Program 13.7. Output (Rerun)


Enter today's date (mm dd yyyy): 12 31 2005
Tomorrow's date is 1/1/06.


Even though you're not working with any classes in this program, the file Object.h was imported nevertheless because you wanted to use the BOOL type and the defines YES and NO. They're defined in that file.

You'll notice that the definition of the date structure appears first and outside of any function. This is because structure definitions behave very much like variables: If a structure is defined within a particular function, only that function knows of its existence. This is a local structure definition. If you define the structure outside any function, that definition is global. A global structure definition enables any variables that are subsequently defined in the program (either inside or outside a function) to be declared to be of that structure type. Structure definitions that are shared among more than one file are typically centralized in a header file and then imported into the files that want to use the structure.

Inside the main routine, the declaration

struct date dateUpdate (struct date today);

tells the compiler that the dateUpdate function takes a date structure as its argument and returns one as well. You don't need the declaration here because the compiler has already seen the actual function definition earlier in the file. However, it's still good programming practice. For example, consider if you subsequently separated the function definition and main into separate source files. In that case, the declaration would be necessary.

As with ordinary variables—and unlike arrays—any changes made by the function to the values contained in a structure argument have no effect on the original structure. They affect only the copy of the structure that is created when the function is called.

After a date has been entered and stored inside the date structure variable thisDay, the dateUpdate function is called like this:

nextDay = dateUpdate (thisDay);

This statement calls dateUpdate, passing it the value of the date structure thisDay.

Inside the dateUpdate function, the prototype declaration

int numberOfDays (struct date d);

informs the Objective-C compiler that the numberOfDays function returns an integer value and takes a single argument of type struct date.

The statement

if ( today.day != numberOfDays (today) )

specifies that the structure today is to be passed as an argument to the numberOfDays function. Inside that function, the appropriate declaration must be made to inform the system that a structure is expected as an argument, like so:

int numberOfDays (struct date d)

The numberOfDays function begins by determining whether it is a leap year and whether the month is February. The former determination is made by calling another function called isLeapYear.

The isLeapYear function is straightforward enough; it simply tests the year contained in the date structure given as its argument and returns YES if it is a leap year and NO if it is not.

Make sure that you understand the hierarchy of function calls in the Program 13.7: The main function calls dateUpdate, which in turn calls numberOfDays, which itself calls the function isLeapYear.

Initializing Structures

Initializing structures is similar to initializing arrays—the elements are simply listed inside a pair of braces, with each element separated by a comma.

To initialize the date structure variable today to July 2, 2004, the statement

struct date today = { 7, 2, 2004 };

can be used.

As with the initialization of an array, fewer values can be listed than are contained in the structure. So, the statement

struct date today = { 7 };

sets today.month to 7 but gives no initial value to today.day or today.year. In such a case, their default initial values are undefined.

Specific members can be designated for initialization in any order with the notation

.member = value

in the initialization list, as in

struct date today = { .month = 7, .day = 2, .year = 2004 };

and

struct date today = { .year = 2004 };

The last statement just sets the year in the structure to 2004. As you know, the other two members are undefined.

Arrays of Structures

Working with arrays of structures is pretty straightforward. The definition

struct date birthdays[15];

defines the array birthdays to contain 15 elements of type struct date. Referencing a particular structure element inside the array is quite natural. To set the second birthday inside the birthdays array to February 22, 1996, the sequence of statements

birthdays[1].month = 2;
birthdays[1].day  = 22;
birthdays[1].year = 1996;

will work just fine. The statement

n = numberOfDays (birthdays[0]);

sends the first date in the array to the numberOfDays function to find out how many days are contained in the month specified by that date.

Structures Within Structures

Objective-C provides an enormous amount of flexibility in defining structures. For instance, you can define a structure that itself contains other structures as one or more of its members, or you can define structures that contain arrays.

You have seen how to logically group the month, day, and year into a structure called date. Suppose you had an analogous structure called time that you used to group the hour, minutes, and seconds representing a time. In some applications, you might need to logically group both a date and a time together. For example, you might need to set up a list of events that are to occur at a particular date and time.

The previous discussion implies that you want to have a convenient means for associating both the date and the time together. You can do this in Objective-C by defining a new structure (called, perhaps, date_and_time), which contains as its members two elements: the date and the time. It is shown here:

struct date_and_time
{
    struct date  sdate;
    struct time  stime;
};

The first member of this structure is of type struct date and is called sdate, and the second member of the date_and_time structure is of type struct time and is called stime. This definition of a date_and_time structure requires that a date structure and a time structure have been previously defined to the compiler.

Variables can now be defined to be of type struct date_and_time, like so:

struct date_and_time event;

To reference the date structure of the variable event, the syntax is the same:

event.sdate

You could therefore call your dateUpdate function with this date as the argument and assign the result back to the same place by a statement, like so:

event.sdate = dateUpdate (event.sdate);

You can do the same type of thing with the time structure contained within your date_and_time structure:

event.stime = time_update (event.stime);

To reference a particular member inside one of these structures, a period followed by the member name is added onto the end:

event.sdate.month = 10;

This statement sets the month of the date structure contained within event to October, and the statement

++event.stime.seconds;

adds one to the seconds contained within the time structure.

The event variable can be initialized in the expected manner:

struct date_and_time event =
     { { 12, 17, 1989 }, { 3, 30, 0 } };

This sets the date in the variable event to December 17, 1989, and sets the time to 3:30:00.

Naturally, you can set up an array of date_and_time structures, as is done with the following declaration:

struct date_and_time events[100];

The array events is declared to contain 100 elements of type struct date_and_time. The 4th date_and_time contained within the array is referenced in the usual way as events[3], and the 25th date in the array can be sent to your dateUpdate function as follows:

events[24].sdate = dateUpdate (events[24].sdate);

To set the first time in the array to noon, the following series of statements can be used:

events[0].stime.hour  = 12;
events[0].stime.minutes = 0;
events[0].stime.seconds = 0;

Additional Details About Structures

There is some flexibility in defining a structure that we should mention here. First, it is valid to declare a variable to be of a particular structure type at the same time that the structure is defined. This is done simply by including the variable name(s) before the terminating semicolon of the structure definition. For example, the statement

struct date
{
    int month;
    int day;
    int year;
} todaysDate, purchaseDate;

defines the structure date and also declares the variables todaysDate and purchaseDate to be of this type. You can also assign initial values to the variables in the normal fashion. Thus

struct date
{
    int month;
    int day;
    int year;
} todaysDate = { 9, 25, 2004 };

defines the structure date and the variable todaysDate with initial values as indicated.

If all the variables of a particular structure type are defined when the structure is defined, the structure name can be omitted. So the statement

struct
{
    int month;
    int day;
    int year;
} dates[100];

defines an array called dates to consist of 100 elements. Each element is a structure containing three integer members: month, day, and year. Because you did not supply a name to the structure, the only way to subsequently declare variables of the same type would be by explicitly defining the structure again.

Bit Fields

Two methods in Objective-C can be used to pack information together. One way is to simply represent the data inside an integer and then access the desired bits of the integer using the bit operators described in Chapter 4, “Data Types and Expressions.”

Another way is to define a structure of packed information using an Objective-C construct known as a bit field. This method uses a special syntax in the structure definition that enables you to define a field of bits and assign a name to that field.

To define bit field assignments, you can define a structure called packedStruct, for example, as follows:

struct packedStruct
{
  unsigned int f1:1;
  unsigned int f2:1;
  unsigned int f3:1;
  unsigned int type:4;
  unsigned int index:9;
};

The structure packedStruct is defined to contain five members. The first member, called f1, is an unsigned int. The :1 that immediately follows the member name specifies that this member is to be stored in 1 bit. The flags f2 and f3 are similarly defined as being a single bit in length. The member type is defined to occupy 4 bits, whereas the member index is defined as being 9 bits long.

The compiler automatically packs the preceding bit field definitions together. The nice thing about this approach is that the fields of a variable defined to be of type packedStruct can now be referenced in the same convenient way that normal structure members are referenced. So, if you were to declare a variable called packedData as follows:

struct packedStruct packedData;

you could easily set the type field of packedData to 7 with this simple statement:

packedData.type = 7;

You could also set this field to the value of n with this similar statement:

packedData.type = n;

In this last case, you needn't worry about whether the value of n is too large to fit into the type field; only the low-order 4 bits of n are assigned to packedData.type.

Extraction of the value from a bit field is also automatically handled, so the statement

n = packedData.type;

extracts the type field from packedData (automatically shifting it into the low-order bits as required) and assigns it to n.

Bit fields can be used in normal expressions and are automatically converted to integers. The statement

i = packedData.index / 5 + 1;

is therefore perfectly valid, as is the following:

if ( packedData.f2 )
...

This tests whether flag f2 is on or off. One thing worth noting about bit fields is that there is no guarantee whether the fields are internally assigned from left to right or from right to left. So, if bit fields are assigned from right to left, f1 would be in the low-order bit position, f2 in the bit position immediately to the left of f1, and so on. This should not present a problem unless you are dealing with data that was created by a different program or by a different machine.

You can also include normal data types within a structure that contains bit fields. So, if you wanted to define a structure that contains an int, a char, and two 1-bit flags, the following definition would be valid:

struct table_entry
{
  int      count;
  char     c;
  unsigned int f1:1;
  unsigned int f2:1;
};

Bit fields are packed into units as they appear in the structure definition, where the size of a unit is defined by the implementation and is most likely a word. The Objective-C compiler does not rearrange the bit field definitions to try to optimize storage space.

A bit field that has no name can be specified to cause bits inside a word to be skipped, meaning the definition

struct x_entry
{
  unsigned int type:4;
  unsigned int :3;
  unsigned int count:9;
};

defines a structure, x_entry, that contains a 4-bit field called type and a 9-bit field called count. The unnamed field specifies that 3 bits separate the type from the count field.

A final point concerning the specification of fields concerns the special case of an unnamed field of length 0. This can be used to force alignment of the next field in the structure at the start of a unit boundary.

Don't Forget About Object-Oriented Programming!

Now you know how to define a structure to store a date. And you've written various routines to manipulate that date structure. But what about object-oriented programming? Shouldn't you have made a class called Date instead and then developed methods to work with a Date object? Wouldn't that be a better approach? Well, the answer is yes. Hopefully, that's what entered your mind when we led the discussion in this section about storing dates in your program.

Certainly, if you had to work with a lot of dates in your programs, defining a class and methods to work with dates would be a better approach. In fact, the Foundation framework has a couple of classes called NSDate and NSCalendarDate defined for such purposes. It's left as an exercise for you to implement a Date class to deal with dates as objects instead of as structures.

Pointers

Pointers enable you to effectively represent complex data structures, change values passed as arguments to functions and methods, and more concisely and efficiently deal with arrays. At the end of this chapter, we'll also clue you in about how important they are to the implementation of objects in the Objective-C language.

We introduced the concept of a pointer in Chapter 8, “Inheritance,” when we talked about the Point and Rectangle classes and how you can have multiple references to the same object.

To understand the way in which pointers operate, you first must understand the concept of indirection. We are used to this concept in our everyday life. For example, suppose I needed to buy a new toner cartridge for my printer. In the company that I work for, all purchases are handled by the purchasing department. So, I would call Jim in purchasing and ask him to order the new cartridge for me. Jim, in turn, would call the local supply store to order the cartridge. The approach that I would take in obtaining my new cartridge would actually be an indirect one because I would not be ordering the cartridge directly from the supply store myself.

This same notion of indirection applies to the way pointers work in Objective-C. A pointer provides an indirect means of accessing the value of a particular data item. And just as there are reasons it makes sense to go through the purchasing department to order new cartridges (I don't have to know which particular store the cartridges are being ordered from, for example), so are there good reasons why, at times, it makes sense to use pointers in Objective-C.

But enough talk; it's time to see how pointers actually work. Suppose you've defined a variable called count as follows:

int count = 10;

You can define another variable, called intPtr, that will enable you to indirectly access the value of count by the following declaration:

int *intPtr;

The asterisk defines to the Objective-C system that the variable intPtr is of type pointer to int. This means that intPtr will be used in the program to indirectly access the value of one or more integer variables.

You have seen how the & operator was used in the scanf calls of previous programs. This unary operator, known as the address operator, is used to make a pointer to a variable in Objective-C. So, if x is a variable of a particular type, the expression &x is a pointer to that variable. The expression &x can be assigned to any pointer variable, if desired, that has been declared to be a pointer to the same type as x.

Therefore, with the definitions of count and intPtr as given, you can write a statement such as

intPtr = &count;

to set up the indirect reference between intPtr and count. The address operator has the effect of assigning to the variable intPtr not the value of count, but a pointer to the variable count. The link that has been made between intPtr and count is conceptualized in Figure 13.1. The directed line illustrates the idea that intPtr does not directly contain the value of count but a pointer to the variable count.

Figure 13.1. Pointer to an integer.

image

To reference the contents of count through the pointer variable intPtr, you use the indirection operator, which is the asterisk (*). If x were defined to be of type int, the statement

x = *intPtr;

would assign the value that is indirectly referenced through intPtr to the variable x. Because intPtr was previously set pointing to count, this statement would have the effect of assigning the value contained in the variable count—which is 10—to the variable x.

The previous statements have been incorporated into Program 13.8, which illustrates the two fundamental pointer operators: the address operator (&) and the indirection operator (*).

Program 13.8.


// Program to illustrate pointers

#import <stdio.h>

int main (int argc, char *argv[])
{
   int  count = 10, x;
   int  *intPtr;

   intPtr = &count;
   x = *intPtr;

   printf ("count = %i, x = %i ", count, x);

   return 0;
}


Program 13.8. Output


count = 10, x = 10


The variables count and x are declared to be integer variables in the normal fashion. On the next line, the variable intPtr is declared to be of type “pointer to int.” Note that the two lines of declarations could have been combined into the single line, like so:

int count = 10, x, *intPtr;

Next, the address operator is applied to the variable count, which has the effect of creating a pointer to this variable, which is then assigned by the program to the variable intPtr.

Execution of the next statement in the program

x = *intPtr;

proceeds as follows: The indirection operator tells the Objective-C system to treat the variable intPtr as containing a pointer to another data item. This pointer is then used to access the desired data item, whose type is specified by the declaration of the pointer variable. Because you told the compiler that intPtr points to integers when you declared the variable, the compiler knows that the value referenced by the expression *intPtr is an integer. Also, because you set intPtr to point to the integer variable count in the previous program statement, the value of count is indirectly accessed by this expression.

Program 13.9 illustrates some interesting properties of pointer variables. Here, a pointer to a character is used.

Program 13.9.


#import <stdio.h>

int main (int argc, char *argv[])
{
   char c = 'Q';
   char *charPtr = &c;

   printf ("%c %c ", c, *charPtr);

   c = '/';
   printf ("%c %c ", c, *charPtr);

   *charPtr = '(';
   printf ("%c %c ", c, *charPtr);

   return 0;
}


Program 13.9. Output


Q Q
/ /
( (


The character variable c is defined and initialized to the character 'Q'. In the next line of the program, the variable charPtr is defined to be of type “pointer to char,” meaning that whatever value is stored inside this variable should be treated as an indirect reference (pointer) to a character. You will notice that you can assign an initial value to this variable in the normal fashion. The value you assign to charPtr in the program is a pointer to the variable c, which is obtained by applying the address operator to the variable c. (Note that this initialization would have generated a compiler error had c been defined after this statement because a variable must always be declared before its value can be referenced in an expression.)

The declaration of the variable charPtr and the assignment of its initial value could have been equivalently expressed in two separate statements as follows:

char *charPtr;
charPtr = &c;

(and not by the statements

char *charPtr;
*charPtr = &c;

as might be implied from the single line declaration).

Always remember that the value of a pointer in Objective-C is meaningless until it is set pointing to something.

The first printf call simply displays the contents of the variable c and the contents of the variable referenced by charPtr. Because you set charPtr to point to the variable c, the value displayed is the contents of c, as verified by the first line of the program's output.

In the next line of the program, the character '/' is assigned to the character variable c. Because charPtr still points to the variable c, displaying the value of *charPtr in the subsequent printf call correctly displays this new value of c at the terminal. This is an important concept. Unless the value of charPtr is changed, the expression *charPtr will always access the value of c. Thus, as the value of c changes, so does the value of *charPtr.

The previous discussion can help you understand how the program statement that appears next in the program works. We mentioned that unless charPtr were changed, the expression *charPtr would always reference the value of c. Therefore, in the expression

*charPtr = '(';

the left parenthesis character is being assigned to c. More formally, the character '(' is assigned to the variable that is pointed to by charPtr. You know that this variable is c because you placed a pointer to c in charPtr at the beginning of the program.

The previous concepts are the key to your understanding of the operation of pointers. Please review them at this point if they still seem a bit unclear.

Pointers and Structures

You have seen how a pointer can be defined to point to a basic data type such as an int or a char. But pointers can also be defined to point to structures. Earlier in this chapter, you defined your date structure as follows:

struct date
{
   int month;
   int day;
   int year;
};

Just as you defined variables to be of type struct date, as in

struct date  todaysDate;

so can you define a variable to be a pointer to a struct date variable:

struct date *datePtr;

The variable datePtr, as just defined, can then be used in the expected fashion. For example, you can set it to point to todaysDate with the following assignment statement:

datePtr = &todaysDate;

After such an assignment has been made, you can then indirectly access any of the members of the date structure pointed to by datePtr in the following way:

(*datePtr).day = 21;

This statement will have the effect of setting the day of the date structure pointed to by datePtr to 21. The parentheses are required because the structure member operator . has higher precedence than the indirection operator *.

To test the value of month stored in the date structure pointed to by datePtr, a statement such as

if ( (*datePtr).month == 12 )
     ...

can be used.

Pointers to structures are so often used that a special operator exists in the language. The structure pointer operator ->, which is the dash followed by the greater than sign, permits expressions that would otherwise be written as

(*x).y

to be more clearly expressed as

x->y

So, the previous if statement can be conveniently written as follows:

if ( datePtr->month == 12 )
   ...

Program 13.6, which was the first program that illustrated structures, was rewritten using the concept of structure pointers. This program is presented here as Program 13.10.

Program 13.10.


// Program to illustrate structure pointers
#import <stdio.h>

int main (int argc, char *argv[])
{
   struct date
   {
     int month;
     int day;
     int year;
   };

   struct date today, *datePtr;

   datePtr = &today;
   datePtr->month = 9;
   datePtr->day = 25;
   datePtr->year = 2004;

   printf ("Today's date is %i/%i/.2i. ",
           datePtr->month, datePtr->day, datePtr->year % 100);
   return 0;
}


Program 13.10. Output


Today's date is 9/25/04.


Pointers, Methods, and Functions

You can pass a pointer as an argument to a method or function in the normal fashion, and you can have a function or method return a pointer as its result. When you think about it, that's what your alloc and init methods have been doing all along— returning pointers. That's covered in more detail at the end of this chapter.

Now consider Program 13.11.

Program 13.11.


// Pointers as arguments to functions
#import <stdio.h>

void exchange (int *pint1, int *pint2)
{
   int temp;

   temp = *pint1;
   *pint1 = *pint2;
   *pint2 = temp;
}


int main (int argc, char *argv[])
{
   void exchange (int *pint1, int *pint2);
   int  i1 = -5, i2 = 66, *p1 = &i1, *p2 = &i2;

   printf ("i1 = %i, i2 = %i ", i1, i2);

   exchange (p1, p2);
   printf ("i1 = %i, i2 = %i ", i1, i2);

   exchange (&i1, &i2);
   printf ("i1 = %i, i2 = %i ", i1, i2);

   return 0;
}


Program 13.11. Output


i1 = -5, i2 = 66
i1 = 66, i2 = -5
i1 = -5, i2 = 66


The purpose of the exchange function is to interchange the two integer values pointed to by its two arguments. The local integer variable temp is used to hold one of the integer values while the exchange is made. Its value is set equal to the integer that is pointed to by pint1. The integer pointed to by pint2 is then copied into the integer pointed to by pint1, and the value of temp is then stored into the integer pointed to by pint2, thus making the exchange complete.

The main routine defines integers i1 and i2 with values of -5 and 66, respectively. Two integer pointers, p1 and p2, are then defined and set to point to i1 and i2, respectively. The program next displays the values of i1 and i2 and calls the exchange function, passing the two pointers (p1 and p2) as arguments. The exchange function exchanges the value contained in the integer pointed to by p1 with the value contained in the integer pointed to by p2. Because p1 points to i1, and p2 to i2, the values of i1 and i2 are exchanged by the function. The output from the second printf call verifies that the exchange worked properly.

The second call to exchange is a bit more interesting. This time, the arguments passed to the function are pointers to i1 and i2 that are manufactured right on the spot by applying the address operator to these two variables. Because the expression &i1 produces a pointer to the integer variable i1, this is in line with the type of argument your function expects for the first argument (a pointer to an integer). The same applies for the second argument as well. And as you can see from the program's output, the exchange function did its job and switched the values of i1 and i2 back to their original values.

Study Program 13.11 in detail. It illustrates with a small example the key concepts to be understood when dealing with pointers in Objective-C.

Pointers and Arrays

If you have an array of 100 integers called values, you can define a pointer called valuesPtr, which can be used to access the integers contained in this array with the following statement:

int *valuesPtr;

When you define a pointer that will be used to point to the elements of an array, you don't designate the pointer as type “pointer to array”; rather you designate the pointer as pointing to the type of element contained in the array.

If you had an array of Fraction objects called fracts, you could similarly define a pointer to be used to point to elements in fracts with the following statement:

Fraction *fractsPtr;

Note that this is the same declaration used to define a Fraction object.

To set valuesPtr to point to the first element in the values array, you simply write

valuesPtr = values;

The address operator is not used in this case because the Objective-C compiler treats the occurrence of an array name without a subscript as a pointer to the first element of the array. Therefore, simply specifying values without a subscript has the effect of producing a pointer to the first element of values.

An equivalent way of producing a pointer to the start of values is to apply the address operator to the first element of the array. Thus the statement

valuesPtr = &values[0];

can be used to serve the same purpose of placing a pointer to the first element of values in the pointer variable valuesPtr.

To display the Fraction object in the array fracts that is pointed to by fractsPtr, you could write this statement:

[fractsPtr print];

The real power of using pointers to arrays comes into play when you want to sequence through the elements of an array. If valuesPtr is defined as mentioned previously and is set pointing to the first element of values, the expression

*valuesPtr

can be used to access the first integer of the values array—that is, values[0]. To reference values[3] through the valuesPtr variable, you can add three to valuesPtr and then apply the indirection operator:

*(valuesPtr + 3)

In general, the expression

*(valuesPtr + i)

can be used to access the value contained in values[i].

So, to set values[10] to 27, you could obviously write the following expression:

values[10] = 27;

Or, using valuesPtr, you could write this:

*(valuesPtr + 10) = 27;

To set valuesPtr to point to the second element of the values array, you can apply the address operator to values[1] and assign the result to valuesPtr:

valuesPtr = &values[1];

If valuesPtr points to values[0], you can set it to point to values[1] by simply adding one to the value of valuesPtr:

valuesPtr += 1;

This is a perfectly valid expression in Objective-C and can be used for pointers to any data type.

So, in general, if a is an array of elements of type x, px is of type “pointer to x,” and i and n are integer constants of variables, the statement

px = a;

sets px to point to the first element of a, and the expression

*(px + i)

subsequently references the value contained in a[i]. Furthermore, the statement

px += n;

sets px to point n elements further in the array, no matter what type of element is contained in the array.

Suppose fractsPtr points to a fraction stored inside an array of fractions. Further suppose you want to add it to the fraction contained in the next element of the array and assign the result to the Fraction object result. You could do this by writing the following:

result = [fractsPtr add: fractsPtr + 1];

The increment and decrement operators (++ and --) are particularly handy when dealing with pointers. Applying the increment operator to a pointer has the same effect as adding one to the pointer, whereas applying the decrement operator has the same effect as subtracting one from the pointer. So, if textPtr were defined as a char pointer and were set pointing to the beginning of an array of chars called text, the statement

++textPtr;

would set textPtr pointing to the next character in text, which is text[1]. In a similar fashion, the statement

--textPtr;

would set textPtr pointing to the previous character in text, assuming of course that textPtr was not pointing to the beginning of text prior to the execution of this statement.

Comparing two pointer variables in Objective-C is perfectly valid. This is particularly useful when comparing two pointers into the same array. For example, you could test the pointer valuesPtr to see whether it points past the end of an array containing 100 elements by comparing it to a pointer to the last element in the array. So, the expression

valuesPtr > &values[99]

would be TRUE (nonzero) if valuesPtr was pointing past the last element in the values array, and it would be FALSE (zero) otherwise. From our earlier discussions, you can replace the previous expression with its equivalent:

valuesPtr > values + 99

This is possible because values used without a subscript is a pointer to the beginning of the values array. (Remember that it's the same as writing &values[0].)

Program 13.12 illustrates pointers to arrays. The arraySum function calculates the sum of the elements contained in an array of integers.

Program 13.12.


#import <stdio.h>
// Function to sum the elements of an integer array

int arraySum (int array[], int n)
{
   int sum = 0, *ptr;
   int *arrayEnd = array + n;

   for ( ptr = array; ptr < arrayEnd; ++ptr )
       sum += *ptr;

   return (sum);
}

int main (int argc, char *argv[])
{
   int arraySum (int array[], int n);
   int values[10] = { 3, 7, -9, 3, 6, -1, 7, 9, 1, -5 };

   printf ("The sum is %i ", arraySum (values, 10));
   return 0;
}


Program 13.12. Output


The sum is 21


Inside the arraySum function, the integer pointer arrayEnd is defined and set pointing immediately after the last element of array. A for loop is then set up to sequence through the elements of array; then the value of ptr is set to point to the beginning of array when the loop is entered. Each time through the loop, the element of array pointed to by ptr is added into sum. The value of ptr is then incremented by the for loop to set it pointing to the next element in array. When ptr points past the end of array, the for loop is exited and the value of sum is returned to the caller.

Is It an Array, or Is It a Pointer?

To pass an array to a function, you simply specify the name of the array, as you did previously with the call to the arraySum function. But we also mentioned in this section that to produce a pointer to an array, you need only specify the name of the array. This implies that in the call to the arraySum function, what was passed to the function was actually a pointer to the array values. This is precisely the case and explains why you are able to change the elements of an array from within a function.

But if it is indeed the case that a pointer to the array is passed to the function, then why isn't the formal parameter inside the function declared to be a pointer? In other words, in the declaration of array in the arraySum function, why isn't the declaration

int *array;

used? Shouldn't all references to an array from within a function be made using pointer variables?

To answer these questions, we must first reiterate what we have said before about pointers and arrays. We mentioned that, if valuesPtr points to the same type of element as contained in an array called values, the expression *(valuesPtr + i) is in all ways equivalent to the expression values[i], assuming that valuesPtr has been set to point to the beginning of values. What follows from this is that you can also use the expression *(values + i) to reference the ith element of the array values, and, in general, if x is an array of any type, the expression x[i] can always be equivalently expressed in Objective-C as *(x + i).

As you can see, pointers and arrays are intimately related in Objective-C, which is why you can declare array to be of type “array of ints” inside the arraySum function or to be of type “pointer to int.” Either declaration works just fine in the preceding program—try it and see.

If you will be using index numbers to reference the elements of an array, declare the corresponding formal parameter to be an array. This more correctly reflects the usage of the array by the function. Similarly, if you will be using the argument as a pointer to the array, declare it to be of type pointer.

Pointers to Character Strings

One of the most common applications of using a pointer to an array is as a pointer to a character string. The reasons are ones of notational convenience and efficiency. To show how easily pointers to character strings can be used, let's write a function called copyString to copy one string into another. If you were writing this function using your normal array indexing methods, the function might be coded as follows:

void copyString (char to[], char from[])
{
   int i;

   for ( i = 0; from[i] != ''; ++i )
       to[i] = from[i];

   to[i] = '';
}

The for loop is exited before the null character is copied into the to array, thus explaining the need for the last statement in the function.

If you write copyString using pointers, you no longer need the index variable i. A pointer version is shown in Program 13.13.

Program 13.13.


#import <stdio.h>
void copyString (char *to, char *from)
{
     for ( ; *from != ''; ++from, ++to )
         *to = *from;

     *to = '';
}

int main (int argc, char *argv[])
{
     void copyString (char *to, char *from);
     char string1[] = "A string to be copied.";
     char string2[50];

   copyString (string2, string1);
   printf ("%s ", string2);

   copyString (string2, "So is this.");
   printf ("%s ", string2);
   return 0;
}


Program 13.13. Output


A string to be copied.
So is this.


The copyString function defines the two formal parameters, to and from, as character pointers and not as character arrays as was done in the previous version of copyString. This reflects how these two variables will be used by the function.

A for loop is then entered (with no initial conditions) to copy the string pointed to by from into the string pointed to by to. Each time through the loop, the from and to pointers are each incremented by one. This sets the from pointer pointing to the next character that is to be copied from the source string and sets the to pointer pointing to the location in the destination string where the next character is to be stored.

When the from pointer points to the null character, the for loop is exited. The function then places the null character at the end of the destination string.

In the main routine, the copyString function is called twice—the first time to copy the contents of string1 into string2 and the second time to copy the contents of the constant character string "So is this." into string2.

Constant Character Strings and Pointers

The fact that the call

copyString (string2, "So is this.");

works in the previous program implies that when a constant character string is passed as an argument to a function, what is actually passed is a pointer to that character string. Not only is this true in this case, but it can also be generalized by saying that whenever a constant character string is used in Objective-C, a pointer to that character string is produced.2

So, if textPtr is declared to be a character pointer, as in

char *textPtr;

then the statement

textPtr = "A character string.";

assigns to textPtr a pointer to the constant character string "A character string." Be careful to make the distinction here between character pointers and character arrays because the type of assignment shown previously is not valid with a character array. For example, if text were defined instead to be an array of chars, with a statement such as

char text[80];

you could not write a statement such as this:

text = "This is not valid.";

The only time Objective-C lets you get away with performing this type of assignment to a character array is when initializing it, like so:

char text[80] = "This is okay.";

Initializing the text array in this manner does not have the effect of storing a pointer to the character string "This is okay." inside text, but rather the actual characters themselves followed by a terminating null character, inside corresponding elements of the text array.

If text were a character pointer, initializing text with the statement

char *text = "This is okay.";

would assign to it a pointer to the character string "This is okay."

The Increment and Decrement Operators Revisited

Up to this point, whenever you used the increment or decrement operator it was the only operator that appeared in the expression. When you write the expression ++x, you know that this adds one to the value of the variable x. And as you have just seen, if x is a pointer to an array, this sets x to point to the next element of the array.

The increment and decrement operators can be used in expressions in which other operators also appear. In such cases, it becomes important to know more precisely how these operators work.

Whenever you used the increment and decrement operators, you always placed them before the variables that were being incremented or decremented. So, to increment a variable i, you simply wrote the following:

++i;

Actually, it is also valid to place the increment operator after the variable, like so:

i++;

Both expressions are valid and both achieve the same result—namely, incrementing the value of i. In the first case, where the ++ is placed before its operand, the increment operation is more precisely identified as a pre-increment. In the second case, where the ++ is placed after its operand, the operation is identified as a post-increment.

The same discussion applies to the decrement operator. So, the statement

--i;

technically performs a pre-decrement of i, whereas the statement

i--;

performs a post-decrement of i. Both have the same net result of subtracting one from the value of i.

When the increment and decrement operators are used in more complex expressions, the distinction between the pre- and post- nature of these operators is realized.

Suppose you have two integers called i and j. If you set the value of i to 0 and then write the statement

j = ++i;

the value assigned to j is 1—not 0 as you might expect. In the case of the pre-increment operator, the variable is incremented before its value is used in an expression. Therefore, in the previous expression, the value of i is first incremented from 0 to 1 and then its value is assigned to j, as if the following two statements had been written instead:

++i;
j = i;

If you use the post-increment operator in the statement

j = i++;

i is incremented after its value has been assigned to j. So, if i were 0 before the previous statement were executed, 0 would be assigned to j and then i would be incremented by 1, as if the statements

j = i;
++i;

were used instead.

As another example, if i is equal to 1, the statement

x = a[--i];

has the effect of assigning the value of a[0] to x because the variable i is decremented before its value is used to index into a. The statement

x = a[i--];

used instead assigns the value of a[1] to x because i would be decremented after its value had been used to index into a.

As a third example of the distinction between the pre- and post- increment and decrement operators, the function call

printf ("%i ", ++i);

increments i and then sends its value to the printf function, whereas the call

printf ("%i ", i++);

increments i after its value has been sent to the function. So, if i were equal to 100, the first printf call would display 101 at the terminal, whereas the second printf call would display 100. In either case, the value of i would be equal to 101 after the statement had been executed.

As a final example on this topic before we present a program, if textPtr is a character pointer, the expression

*(++textPtr)

first increments textPtr and then fetches the character it points to, whereas the expression

*(textPtr++)

fetches the character pointed to by textPtr before its value is incremented. In either case, the parentheses are not required because the * and ++ operators have equal precedence but associate from right to left.

Let's go back to the copyString function from Program 13.13 and rewrite it to incorporate the increment operations directly into the assignment statement.

Because the to and from pointers are incremented each time after the assignment statement inside the for loop is executed, they should be incorporated into the assignment statement as post-increment operations. The revised for loop of Program 13.13 then becomes

for ( ; *from != ''; )
   *to++ = *from++;

Execution of the assignment statement inside the loop would proceed as follows. The character pointed to by from would be retrieved, and then from would be incremented to point to the next character in the source string. The referenced character would be stored inside the location pointed to by to; then to would be incremented to point to the next location in the destination string.

The previous for statement hardly seems worthwhile because it has no initial expression and no looping expression. In fact, the logic would be better served when expressed in the form of a while loop. This has been done in Program 13.14. This program presents the new version of the copyString function. The while loop uses the fact that the null character is equal to the value 0, as is commonly done by experienced Objective-C programmers.

Program 13.14.


// Function to copy one string to another
//          pointer version 2
#import <stdio.h>

void copyString (char *to, char *from)
{
     while ( *from )
           *to++ = *from++;
     *to = '';
}


int main (int argc, char *argv[])
{
   void copyString (char *to, char *from);
   char string1[] = "A string to be copied.";
   char string2[50];

   copyString (string2, string1);
   printf ("%s ", string2);

   copyString (string2, "So is this.");
   printf ("%s ", string2);
   return 0;
}


Program 13.14. Output


A string to be copied.
So is this.


Operations on Pointers

As you have seen in this chapter, you can add or subtract integer values from pointers. Furthermore, you can compare two pointers to see whether they are equal or whether one pointer is less than or greater than another pointer. The only other operation permitted on pointers is the subtraction of two pointers of the same type. The result of subtracting two pointers in Objective-C is the number of elements contained between the two pointers. Thus, if a points to an array of elements of any type and b points to another element somewhere further along in the same array, the expression b - a represents the number of elements between these two pointers. For example, if p points to some element in an array x, the statement

n = p - x;

assigns to the variable n (assumed here to be an integer variable) the index number of the element inside x that p points to. Therefore, if p had been set pointing to the 100th element in x by a statement such as

p = &x[99];

the value of n after the previous subtraction was performed would be 99.

Pointers to Functions

Of a slightly more advanced nature, but presented here for the sake of completeness, is the notion of a pointer to a function. When working with pointers to functions, the Objective-C compiler needs to know not only that the pointer variable points to a function, but also the type of value returned by that function as well as the number and types of its arguments. To declare a variable, fnPtr, to be of type “pointer to function that returns an int and that takes no arguments,” the declaration

int (*fnPtr) (void);

can be written. The parentheses around *fnPtr are required; otherwise, the Objective-C compiler would treat the preceding statement as the declaration of a function called fnPtr that returns a pointer to an int (because the function call operator () has higher precedence than the pointer indirection operator *).

To set your function pointer pointing to a specific function, you simply assign the name of the function to it. Therefore, if lookup were a function that returned an int and that took no arguments, the statement

fnPtr = lookup;

would store a pointer to this function inside the function pointer variable fnPtr. Writing a function name without a subsequent set of parentheses is treated in an analogous way to writing an array name without a subscript. The Objective-C compiler automatically produces a pointer to the specified function. An ampersand is permitted in front of the function name, but it's not required.

If the lookup function has not been previously defined in the program, you must declare the function before the previous assignment can be made. A statement such as

int lookup (void);

would be needed before a pointer to this function could be assigned to the variable fnPtr.

You can call the function indirectly referenced through a pointer variable by applying the function call operator to the pointer, listing any arguments to the function inside the parentheses. For example

entry = fnPtr ();

calls the function pointed to by fnPtr, storing the returned value inside the variable entry.

One common application for pointers to functions is passing them as arguments to other functions. The Standard Library uses this in the function qsort, which performs a quick sort on an array of data elements. This function takes as one of its arguments a pointer to a function that is called whenever qsort needs to compare two elements in the array being sorted. In this manner, qsort can be used to sort arrays of any type because the actual comparison of any two elements in the array is made by a user-supplied function, and not by the qsort function itself.

In the Foundation framework some methods take a function pointer as an argument. For example, the method sortUsingFunction:context: is defined in the NSMutableArray class and calls the specified function whenever two elements in an array to be sorted need to be compared.

Another common application for function pointers is to create dispatch tables. You can't store functions themselves inside the elements of an array. However, you can store function pointers inside an array. Given this, you can create tables that contain pointers to functions to be called. For example, you might create a table for processing different commands that will be entered by a user. Each entry in the table could contain both the command name and a pointer to a function to call to process that particular command. Now, whenever the user entered a command, you could look up the command inside the table and invoke the corresponding function to handle it.

Pointers and Memory Addresses

Before we end this discussion of pointers in Objective-C, we should point out the details of how they are actually implemented. A computer's memory can be conceptualized as a sequential collection of storage cells. Each cell of the computer's memory has a number, called an address, associated with it. Typically, the first address of a computer's memory is numbered 0. On most computer systems, a cell is one byte.

The computer uses memory for storing the instructions of your computer program and for storing the values of the variables associated with a program. So, if you declare a variable called count to be of type int, the system would assign location(s) in memory to hold the value of count while the program is executing. This location might be at address 1000FF16, for example, inside the computer's memory.

Luckily, you don't need to concern yourself with the particular memory addresses assigned to variables—they are automatically handled by the system. However, the knowledge that associated with each variable is a unique memory address will help you to understand the way pointers operate.

Whenever you apply the address operator to a variable in Objective-C, the value generated is the actual address of that variable inside the computer's memory. (Obviously, this is where the address operator gets its name.) So, the statement

intPtr = &count;

assigns to intPtr the address in the computer's memory that has been assigned to the variable count. Thus, if count were located at address 1000FF16, this statement would assign the value 0x1000FF to intPtr.

Applying the indirection operator to a pointer variable, as in the expression

*intPtr

has the effect of treating the value contained in the pointer variable as a memory address. The value stored at that memory address is then fetched and interpreted in accordance with the type declared for the pointer variable. So, if intPtr were of type pointer to int, the value stored in the memory address given by *intPtr would be interpreted as an integer by the system.

Unions

One of the more unusual constructs in the Objective-C programming language is the union. This construct is used mainly in more advanced programming applications where you need to store different types of data into the same storage area. For example, if you wanted to define a single variable called x that could be used to store a single character, a floating-point number, or an integer, you would first define a union, called (perhaps) mixed, as follows:

union mixed
{
  char  c;
  float f;
  int  i;
};

The declaration for a union is identical to that of a structure, except the keyword union is used where the keyword struct is otherwise specified. The real difference between structures and unions has to do with the way memory is allocated. Declaring a variable to be of type union mixed, as in

union mixed x;

does not define x to contain three distinct members called c, f, and i; rather it defines x to contain a single member that is called either c, f, or i. In this way, the variable x can be used to store either a char, a float, or an int, but not all three (or not even two of the three). You can store a character into the variable x with the following statement:

x.c = 'K';

To store a floating-point value into x, the notation x.f is used:

x.f = 786.3869;

Finally, to store the result of dividing an integer count by 2 into x, the statement

x.i = count / 2;

could be used.

Because the float, char, and int members of x coexist in the same place in memory, only one value can be stored into x at a time. Furthermore, it is your responsibility to ensure that the value retrieved from a union is consistent with the way it was last stored in the union.

When defining a union, the name of the union is not required and variables can be declared at the same time that the union is defined. Pointers to unions can also be declared, and their syntax and rules for performing operations are the same as for structures. Finally, a union variable can be initialized like so:

union mixed x = { '#' };

This sets the first member of x, which is c, to the character #. A particular member can also be initialized by name, like this

union mixed x = {.f=123.4;};

An automatic union variable can also be initialized to another union variable of the same type.

The use of a union enables you to define arrays that can be used to store elements of different data types. For example, the statement

struct
{
    char *name;
    int  type;
    union
    {
        int   i;
        float  f;
        char  c;
    } data;
} table [kTableEntries];

sets up an array called table, consisting of kTableEntries elements. Each element of the array contains a structure consisting of a character pointer called name, an integer member called type, and a union member called data. Each data member of the array can contain an int, a float, or a char. The integer member type might be used to keep track of the type of value stored in the member data. For example, you could assign it the value INTEGER (defined appropriately, we assume) if it contained an int, FLOATING if it contained a float, and CHARACTER if it contained a char. This information would enable you to know how to reference the particular data member of a particular array element.

To store the character '#' into table[5], and subsequently set the type field to indicate that a character is stored in that location, the following two statements could be used:

table[5].data.c = '#';
table[5].type = CHARACTER;

When sequencing through the elements of table, you could determine the type of data value stored in each element by setting up an appropriate series of test statements. For example, the following loop would display each name and its associated value from table at the terminal:

enum symbolType { INTEGER, FLOATING, CHARACTER };
  ...

for ( j = 0; j < kTableEntries; ++j )
{
  printf ("%s ", table[j].name);

  switch ( table[j].type )
  {
     case INTEGER:
          printf ("%i ", table[j].data.i);
          break;
     case FLOATING:
          printf ("%g ", table[j].data.f);
          break;
     case CHARACTER:
          printf ("%c ", table[j].data.c);
          break;
     default:
          printf ("Unknown type (%i), element %i ",
                     table[j].type, j );
          break;
  }
}

The type of application illustrated previously might be practical for storage of a symbol table, which might contain the name of each symbol, its type, and its value (and perhaps other information about the symbol as well).

They're Not Objects!

Now you know how to define arrays, structures, character strings, and unions and how to manipulate them in your program. Just remember one fundamental thing: They're not objects. That means you can't send messages to them. It also means they can't be used to take maximal advantage of nice things such as the memory allocation strategy provided by the Foundation framework. That's one of the reasons I encouraged you to skip this chapter and return to it later. In general, I'd rather you learned how to use the Foundation's classes that define things like arrays and strings as objects than the ones built in to the language. You should resort to using the types defined in this chapter only if you really need to. And hopefully you won't!

Miscellaneous Language Features

Some language features didn't fit well into any of the other chapters, so they've been included here.

Compound Literals

A compound literal is a type name enclosed in parentheses followed by an initialization list. It creates an unnamed value of the specified type, which has scope limited to the block in which it is created or global scope if defined outside of any block. In the latter case, the initializers must all be constant expressions.

Here's an example:

(struct date) {.month = 7, .day = 2, .year = 2004}

This is an expression that produces a structure of type struct date with the specified initial values. This can be assigned to another struct date structure, like so:

theDate = (struct date) {.month = 7, .day = 2, .year = 2004};

Or, it can be passed to a function or method expecting an argument of struct date, like so:

setStartDate ((struct date) {.month = 7, .day = 2, .year = 2004});

Types other than structures can be defined as well—for example, if intPtr is of type int *, the statement

intPtr = (int [100]) {[0] = 1, [50] = 50, [99] = 99 };

(which can appear anywhere in the program) sets intptr pointing to an array of 100 integers, whose 3 elements are initialized as specified.

If the size of the array is not specified, it is determined by the initializer list.

The goto Statement

Execution of a goto statement causes a direct branch to be made to a specified point in the program. To identify where in the program the branch is to be made, a label is needed. A label is a name formed with the same rules as variable names and must be immediately followed by a colon. The label is placed directly before the statement to which the branch is to be made and must appear in the same function or method as the goto.

For example, the statement

goto out_of_data;

causes the program to branch immediately to the statement that is preceded by the label out_of_data;. This label can be located anywhere in the function or method, before or after the goto, and might be used as shown here:

out_of_data: printf ("Unexpected end of data. ");
  ...

Programmers who are lazy frequently abuse the goto statement to branch to other portions of their code. The goto statement interrupts the normal sequential flow of a program. As a result, programs are harder to follow. Using many gotos in a program can make it impossible to decipher. For this reason, goto statements are not considered part of good programming style.

The null Statement

Objective-C permits a solitary semicolon to be placed wherever a normal program statement can appear. The effect of such a statement, known as the null statement, is that nothing is done. This might seem quite useless, but it is often used by programmers in while, for, and do statements. For example, the purpose of the following statement is to store all the characters read in from standard input (your terminal by default) into the character array pointed to by text until a newline character is encountered. It uses the library routine getchar, which reads and returns a single character at a time from standard input:

while ( (*text++ = getchar ()) != ' ' )
  ;

All the operations are performed inside the looping conditions part of the while statement. The null statement is needed because the compiler takes the statement that follows the looping expression as the body of the loop. Without the null statement, whatever statement that follows in the program would be treated as the body of the program loop by the compiler.

The Comma Operator

At the bottom of the precedence totem pole, so to speak, is the comma operator. In Chapter 5,”Program Looping,” we pointed out that inside a for statement you could include more than one expression in any of the fields by separating each expression with a comma. For example, the for statement that begins

for ( i = 0, j = 100; i != 10; ++i, j -= 10 )
  ...

initializes the value of i to 0 and j to 100 before the loop begins, and it increments the value of i and subtracts 10 from the value of j each time after the body of the loop is executed.

Because all operators in Objective-C produce a value, the value of the comma operator is that of the rightmost expression.

The sizeof Operator

Although you should never make any assumptions about the size of a data type in your program, sometimes you will need to know this information. This might be needed when performing dynamic memory allocation using library routines such as malloc or when writing or archiving data to a file. Objective-C provides an operator called sizeof that you can use to determine the size of a data type or object. The sizeof operator returns the size of the specified item in bytes. The argument to the sizeof operator can be a variable, an array name, the name of a basic data type, an object, the name of a derived data type, or an expression. For example, writing

sizeof (int)

gives the number of bytes needed to store an integer. On a Mac with a G5 processor, this produces a result of 4 (or 32 bits). If x is declared as an array of 100 ints, the expression

sizeof (x)

would give the amount of storage required to store the 100 integers of x.

Given that myFract is a Fraction object that contains two int instance variables (numerator and denominator), the expression

sizeof (myFract)

produces the value 4 on any system that represents pointers using 4 bytes. In fact, this is the value that sizeof yields for any object because here you are asking for the size of the pointer to the object's data. To get the size of the actual data structure to store an instance of a Fraction object, you would instead write the following:

sizeof (*myFract)

On my PowerBook G4 OS X system, this gives me a value of 12. That's 4 bytes each for the numerator and denominator plus another 4 bytes for the inherited isa member mentioned in the section “How Things Work” at the end of this chapter.

The expression

sizeof (struct data_entry)

has as its value the amount of storage required to store one data_entry structure. If data is defined as an array of struct data_entry elements, the expression

sizeof (data) / sizeof (struct data_entry)

gives the number of elements contained in data (data must be a previously defined array and not a formal parameter or externally referenced array). The expression

sizeof (data) / sizeof (data[0])

also produces the same result.

Use the sizeof operator wherever possible to avoid having to calculate and hard-code sizes into your programs.

Command-Line Arguments

Many times a program is developed that requires the user to enter a small amount of information at the terminal. This information might consist of a number indicating the triangular number you want to have calculated or a word you want to have looked up in a dictionary.

Rather than having the program request this type of information from the user, you can supply the information to the program at the time the program is executed. This capability is provided by what is known as command-line arguments.

We have pointed out that the only distinguishing quality of the function main is that its name is special; it specifies where program execution is to begin. In fact, the function main is actually called upon at the start of program execution by the runtime system, just as you would call a function from within your own program. When main completes execution, control is returned to the runtime system, which then knows that your program has completed execution.

When main is called by the runtime system, two arguments are passed to the function. The first argument, which is called argc by convention (for argument count), is an integer value that specifies the number of arguments typed on the command line. The second argument to main is an array of character pointers, which is called argv by convention (for argument vector). In addition, argc + 1 character pointers are contained in this array. The first entry in this array is either a pointer to the name of the program that is executing or a pointer to a null string if the program name is not available on your system. Subsequent entries in the array point to the values specified in the same line as the command that initiated execution of the program. The last pointer in the argv array, argv[argc], is defined to be null.

To access the command-line arguments, the main function must be appropriately declared as taking two arguments. The conventional declaration we have used in all the programs in this book will suffice:

int main (int argc, char *argv[])
{
  ...
}

Remember, the declaration of argv defines an array that contains elements of type “pointer to char.” As a practical use of command-line arguments, suppose you had developed a program that looks up a word inside a dictionary and prints its meaning. You can use command-line arguments so that the word whose meaning you want to find can be specified at the same time that the program is executed, as in the following command:

lookup aerie

This eliminates the need for the program to prompt the user to enter a word because it is typed on the command line.

If the previous command were executed, the system would automatically pass to the main function a pointer to the character string "aerie" in argv[1]. As you will recall, argv[0] would contain a pointer to the name of the program, which in this case would be "lookup".

The main routine might appear as shown:

#include <stdio.h>

int main (int argc, char *argv[])
{
   struct entry dictionary[100] =
     { { "aardvark", "a burrowing African mammal"    },
       { "abyss",  "a bottomless pit"         },
       { "acumen",  "mentally sharp; keen"       },
       { "addle",  "to become confused"        },
       { "aerie",  "a high nest"            },
       { "affix",  "to append; attach"         },
       { "agar",   "a jelly made from seaweed"     },
       { "ahoy",   "a nautical call of greeting"    },
       { "aigrette", "an ornamental cluster of feathers" },
       { "ajar",   "partially opened"         } };

   int  entries = 10;
   int  entryNumber;
   int  lookup (struct entry dictionary [], char search[],
                int entries);

   if ( argc != 2 )
   {
        printf ("No word typed on the command line. ");
        return (1);
   }

   entryNumber = lookup (dictionary, argv[1], entries);

   if ( entryNumber != -1 )
       printf ("%s ", dictionary[entryNumber].definition);
   else
       printf ("Sorry, %s is not in my dictionary. ", argv[1]);

   return (0);
}

The main routine tests to ensure that a word was typed after the program name when the program was executed. If it wasn't, or if more than one word was typed, the value of argc is not equal to 2. In that case, the program writes an error message to standard error and terminates, returning an exit status of 1.

If argc is equal to 2, the lookup function is called to find the word pointed to by argv[1] in the dictionary. If the word is found, its definition is displayed.

It should be remembered that command-line arguments are always stored as character strings. So, execution of the program power with the command-line arguments 2 and 16, as in

power 2 16

stores a pointer to the character string "2" inside argv[1] and a pointer to the string "16" inside argv[2]. If the arguments are to be interpreted as numbers by the program (as we suspect is the case in the power program), they must be converted by the program itself. Several routines are available in the program library for doing such conversions, such as sscanf, atof, atoi, strtod, and strotol. In Part II, “The Foundation Framework,” you'll learn how to use a class called NSProcessInfo to access the command-line arguments as string objects instead of as C strings.

How Things Work

We would be remiss if we finished this chapter without first trying to tie a couple of things together. Because the Objective-C language has the C language lying underneath, it's worthwhile mentioning some of the connections between the former and the latter. These are implementation details you can ignore or can use to perhaps give you a better understanding of how things work, in the same way learning that pointers are actually memory addresses can help give you a better understanding about pointers. We won't get too detailed here; we'll just state four facts about the relationship between Objective-C and C.

Fact #1: Instance variables are stored in structures. When you define a new class and its instance variables, those instance variables are actually stored inside a structure. That's how you can manipulate objects; they're really structures whose members are your instance variables. So, the inherited instance variables plus the ones you added in your class all comprise a single structure. When you alloc a new object, enough space is reserved to hold one of these structures.

One of the inherited members (it comes from the root object) of the structure is a protected member called isa that identifies the class to which the object belongs. Because it's part of the structure (and therefore part of the object), it is carried around with the object. In that way the runtime system can always identify the class of an object (even if you assign it to a generic id object variable) by just looking at its isa member.

You can gain direct access to the members of an object's structure by making them @public (see the discussion in Chapter 10, “More on Variables and Data Types”). If you did that with the numerator and denominator members of your Fraction class, for example, you could write expressions such as

myFract->numerator

in your program to directly access the numerator member of the Fraction object myFract. But we strongly advise against your doing that! As we mentioned in Chapter 10, it goes against the grain of data encapsulation.

Fact #2: An object variable is really a pointer. When you define an object variable like a Fraction, as in

Fraction *myFract;

you're really defining a pointer variable called myFract. This variable is defined to point to something of type Fraction, which is the name of your class. When you allocate a new instance of a Fraction, with

myFract = [Fraction alloc];

you're allocating space to store a new Fraction object in memory (that is, space for a structure) and then taking the pointer to that structure that is returned and storing it inside the pointer variable myFract.

When you assign one object variable to another, as in

myFract2 = myFract1;

you're simply copying pointers. Both variables end up pointing to the same structure stored somewhere in memory. Making a change to one of the members referenced (that is, pointed to) by myFract2 therefore changes the same instance variable (that is, structure member) referenced by myFract1.

Fact #3: Methods are functions and message expressions are function calls. Methods are really functions. When you invoke a method, you are calling a function associated with the class of the receiver. The arguments passed to the function are the receiver (self) and the method's arguments. So, all the rules about passing arguments to functions, return values, and automatic and static variables are the same whether you're talking about a function or a method. The Objective-C compiler creates a unique name for each function using a combination of the class name and the method name.

Fact #4: The id type is a generic pointer type. Because objects are referenced through pointers, which in turn are just memory addresses, you can freely assign them back and forth between id variables. A method that returns an id type consequently just returns a pointer to some object in memory. You can then take that value and assign it to any object variable you like. Because the object always carries around its isa member wherever it goes, its class can always be identified, even if you store it in a generic object variable of type id.

Exercises

  1. Write a function that calculates the average of an array of 10 floating-point values and returns the result.
  2. The reduce method from your Fraction class finds the greatest common divisor of the numerator and denominator to reduce the fraction. Modify that method so that it uses the gcd function from Program 13.5 instead. Where do you think you should place the function definition? Are there any benefits to making the function static? Which approach do you think is better—using a gcd function or incorporating the code directly into the method as you did previously? Why?
  3. Prime numbers can be generated by an algorithm known as the Sieve of Erastosthenes. The algorithm for this procedure is presented here. Write a program that implements this algorithm. Have the program find all prime numbers up to n = 150. What can you say about this algorithm as compared to the ones used in the text for calculating prime numbers?

    Step 1. Define an array of integers P. Set all elements Pi to 0, 2 <= i <= n.

    Step 2. Set i to 2.

    Step 3. If i > n, the algorithm terminates.

    Step 4. If Pi is 0, i is prime.

    Step 5. For all positive integer values of j, such that i × j < ≠ n, set Pixj to 1.

    Step 6. Add 1 to i and go to step 3.

  4. Write a function to add all the Fractions passed to it in an array and to return the result as a Fraction.
  5. Write a typedef definition for a struct date called Date that will allow you to make declarations like

    Date todaysDate;

    in your program.

  6. As noted in the text, defining a Date class instead of a date structure would be more consistent with the notion of object-oriented programming. Define such a class with appropriate setter and getter methods. Also, add a method called dateUpdate to return the day after its argument.

    Do you see any advantages of defining a Date as a class instead of as a structure? Do you see any disadvantages?

  7. Given the following definitions:

    char *message = "Programming in Objective-C is fun ";
    char message2[] = "You said it ";
    char *format = "x = %i ";
    int  x = 100;

    determine whether each printf call from the following sets is valid and produces the same output as other calls from the set.

    /*** set 1 ***/
    printf ("Programming in Objective-C is fun ");
    printf ("%s", "Programming in Objective-C is fun ");
    printf ("%s", message);
    printf (message);
    /*** set 2 ***/
    printf ("You said it ");
    printf ("%s", message2);
    printf (message2);
    printf ("%s", &message2[0]);
    /*** set 3 ***/
    printf ("said it ");
    printf (message2 + 4);
    printf ("%s", message2 + 4);
    printf ("%s", &message2[4]);
    /*** set 4 ***/
    printf ("x = %i ", x);
    printf (format, x);

  8. Write a program that prints all its command-line arguments, one per line at the terminal. Notice the effect of enclosing arguments containing space characters inside quotation marks.
..................Content has been hidden....................

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