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.
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 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;
}
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 F2–F14 (or, equivalently, Fibonacci[2]
through Fibonacci[14]
).
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.
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?
#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;
}
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.
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:
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:
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.)
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.
#import <stdio.h>
void printMessage (void)
{
printf ("Programming is fun.
");
}
int main (int argc, char *argv[])
{
printMessage ();
return 0;
}
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.
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.
#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;
}
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.
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).
#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;
}
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.
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.
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.
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.
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.
#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;
}
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 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;
}
Enter today's date (mm dd yyyy): 2 28 2004
Tomorrow's date is 2/29/04.
Enter today's date (mm dd yyyy): 10 2 2005
Tomorrow's date is 10/3/05.
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 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.
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.
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;
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.
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.
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 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.
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 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;
}
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.
#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;
}
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.
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 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;
}
Today's date is 9/25/04.
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.
// 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;
}
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.
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.
#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;
}
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.
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 i
th 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 int
s” 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.
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.
#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;
}
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
.
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 char
s, 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."
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.
// 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;
}
A string to be copied.
So is this.
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
.
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.
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.
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).
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!
Some language features didn't fit well into any of the other chapters, so they've been included here.
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.
goto
StatementExecution 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 goto
s in a program can make it impossible to decipher. For this reason, goto
statements are not considered part of good programming style.
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.
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.
sizeof
OperatorAlthough 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 int
s, 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.
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.
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
.
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?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.
Fraction
s passed to it in an array and to return the result as a Fraction
.typedef
definition for a struct date
called Date
that will allow you to make declarations like
Date todaysDate;
in your program.
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?
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);
18.227.111.208