MacDevCenter    
 Published on MacDevCenter (http://www.macdevcenter.com/)
 See this if you're having trouble printing code examples


Learning Cocoa: Repurposing Variables

by Seth Roby
01/13/2004

Joey from Friends is the slow one. Tim Curry seems to be the bad guy in all his new movies. And Macaulay Culkin will forever be the kid from Home Alone. We, the audience, have come to know these people in these roles, and the actors can do little to escape their fate: they will forever be typecast.

The variables in your Cocoa program have it a little easier. Today we’ll see a few new powerful ways we can use our variables to do things we never could before, building on the concepts we learned in the last lesson.

Bits of Knowledge

By now we know that every variable is really just a bunch of bits, which we can look at a certain way and read as an integer, a pointer, or whatever else we need to remember. Well, what else do we want to remember?

We noted when we learned about int that an integer can’t hold any decimal places, so we can’t shove pi into an int. There’s a different data type to put such numbers in, and it’s called a float, because it stores one “floating-point” number, so named because the decimal point can be anywhere inside the number, floating about between any two digits, not trapped to the right of the ones column like it is in an int.

Let’s look at some simple code for using floats; I’ve upgraded the previous main method to allow for favorite numbers that contain decimal points, for all the math geeks of the world.

int main (int argc, const char * argv[]) {
	//Computes our favorite number
	float divideBy = 3.0;
	float favoriteNumber = (3.14159265 * integerForSeedValue(8)) / divideBy;
	favoriteNumber--;

	/* now let’s tell the world
		what our favorite number is! */
	printf("My favorite number is %f", favoriteNumber);
	return 0;
} 

None of this should be at all surprising. We can do math, transfer the number around, use the decrement operator to count down and lots of other operations that are totally normal. But there are a few things to note that might not seem obvious at first.

The first is that we can mix and match integers and floats in our code. We see this in the line where favoriteNumber is declared; we multiply a float by an integer, which is being returned by our function.

Secondly, note that we can use a float to store an integer, when we store 3.0 into the divideBy variable. This makes perfect sense; an int is just a float with the decimal point pushed to the side, with nothing to the right of it.

Last, note that we use a new conversion specification to print out our float. Naturally enough, floats are represented with an ‘f’ character.

This code should compile and run fine. If you’re on the bleeding edge and running Panther and Xcode, you should get a nice window that opens to display the output. If you’re in Project Builder on Jaguar, the output should appear in the Run tab.

Now let’s regress a little and explore some new territory. I’ve put a few twists on things:

int main (int argc, const char * argv[]) {
	//Computes our favorite number
	int favoriteNumber = (3.14159265 * 4.321) / 2.92;
	
	/* now let's tell the world
		what our favorite number is! */
	countTo(favoriteNumber);
	
	return 0;
}

Related Reading

Learning Cocoa with Objective-C
By James Duncan Davidson, Apple Computer, Inc.

Note that favoriteNumber is back to storing an int on the heap. When we assign a value to favoriteNumber, we assign using only floats that we have written into our code. So what does favoriteNumber actually contain?

You might guess that it contains 4.64891159, which is what Google’s Calculator tells us it should equal. But we know that an int can’t store that kind of detail; it is limited to doing only whole numbers. Indeed, if we take a look, favoriteNumber contains the simple value 4. It has taken the float we gave it and done an automatic conversion into what it understands, by simply truncating the number to an int, throwing away the decimal places. Note that it didn’t round; 4.99999 will still be truncated to 4.

You might ask why we ever use int. After all, it drops all this information we gave it that might be useful. Those numbers after the decimal could make a difference. The reason here is that we’re probably using the wrong type; if we cared, we’d use a float like we do in the first snippet in this lesson. However, there are plenty of times when you simply don’t care about those extra decimal places, and this is one of those times; our countTo() function takes an integer, not a float; it won’t count to 4.6; it will only count to 4. Since we don’t need the extra data, we ignore it.

One last word on float and then we’ll move on. Computers find it very easy to count in whole numbers, just like you and I do. They’re simple. Decimals are harder, because you never know how to split up the space. There are very complex rules for doing so, but even so float will sometimes play little games with you, and you’ll find out that the variable you set to 4.0 is now 3.9999998. That’s because float stores not the data you give it, but a close approximation of that data. It’s close enough for most needs, and if you need more precision there are other data types that provide them. What this means for you is that you can’t check to see if floats equal each other, because they sometimes won’t even when they should. Instead, you’ll find yourself using code like this:

if (fabs(varISetToFourPointOh - 4.0) > .01)
//close enough for government work!

We’re using the fabs function, which computes the absolute value of a float. Floats really aren’t that hard once we understand these limitations, but there’s another type of data we use a lot that we don’t know how to store yet.

Strings

Probably the most obvious thing we don’t know how to store yet is text. We can hardcode text into our programs, but we can’t store it, and we certainly can’t manipulate it. To do that, we need a few new types.

The first one we’ll learn is called char, and it stores a single character of text. It does this through a little bit of clever trickery called encoding. Basically, the idea is the same as the old decoder cards that used to come in Cracker Jacks: each letter is given an appropriate number, and you can just look at the numbers and convert them into characters as you go. So the string ‘COCOA’ becomes the sequence of numbers 67, 79, 67, 79, 65 (Yes, the letter ‘A’ is the number 65. Why? Because that’s how it is). You can find a complete listing of all the conversions here.

It’s easy to understand how this fits into what we know about C so far; the variable stores this new type in the same 32 bits, and now we just read it as a letter instead of as a number. Simple enough.

The trick comes when we have to represent a sequence of numbers. How do we do that? Well, there’s the hard way (note the %c is the conversion specification for a _c_haracter).

int main (int argc, const char * argv[]) {
	//print some letters the hard way
	char letterC = 'c';
	char letterO = 'o';
	char letterA = 'a';
	
	printf("%c%c%c%c%c", letterC, letterO, letterC, letterO, letterA);
	return 0;
}

But this is obviously ridiculous. There’s a much better way, and it involves some of the magic we learned last week, together with a concept that’s new this week:

int main (int argc, const char * argv[]) {
	//print some letters the easy way
	char* string = "cocoa";
	
	printf("%s", string);	
	//yes, we could just put them in the format string. But that's no fun.
	return 0;
}

Quite a bit better. But what’s going on here? Well, we’ve just made a string and stored it into a variable, appropriately enough called string. This variable, as you can see, is referenced by a pointer. But it’s a pointer to a char, not a new type. How does that work?

A Vast Array of Possibilities

It works because programs deal with strings all the time, and C gives us a little shortcut to working with them. Instead of defining every character we print, we can define an array of them, and use them all together as a group. So what in the world is an array?

An array is not a new data type; you can use it with any data type. With strings, we’re making an array of characters. But you can just as easily make an array of ints or floats or pointers or whatever. But basically, an array is a list of variables, all the same type, which you want to work with as a group. Let’s look at some code to see an example:

int main (int argc, const char * argv[]) {
	int numbers[5];		//define an array
	numbers[0] = 67;	//put some data in
	numbers[1] = 79;
	numbers[2] = 67;
	numbers[3] = 79;
	numbers[4] = 65;
	
	//show the data off
	printf("My favorite numbers are %d, %d, %d, %d, %d",
		numbers[0],	//get some data out
		numbers[1],
		numbers[2],
		numbers[3],
		numbers[4]);
	return 0;
}

Here we see pretty much everything we need to know about using arrays. We can define one by putting a number in square brackets after a variable name. This number is the capacity of the array; it defines how many elements it can hold. In doing this, we have made an array with that name, with room for the specified number of elements, and each element in the array is of the specified type. Note that the capacity must be a constant; it cannot be a variable. This is because the compiler allocates memory for the arrays; they are just normal variables in the run time.

From now on, we can look at the whole array by referring to it by name, in this case numbers. But if we want to use one of the elements of the array, we must refer to it by index. Indexes specify position by their distance after the start, so the start is always 0 (being 0 places after itself) and the last index is the one less than capacity we specified when we created the array. Note that when specifying indexes we are free to use a variable if we want. The index is placed inside square brackets and put after the array name; so numbers[2] refers to the element at index 2 (the third element) in the array numbers.

If we want to assign a value to an element, we can put data into it just like we do a normal variable, with the assignment operator, but we must use an index to make sure we’re assigning the value to one of the array’s elements, not the array itself. Likewise, we can get the data out of the array by just appending the brackets and an appropriate index.

It is important to note that in C, unlike in many more modern languages like Java and Python, arrays have no idea how many elements they contain; you must keep track of that and make sure that you don’t try to put data into elements that don’t exist (This is how buffer overruns – a too-common security hole – work).

Stringing it all Together

So we understand a little about characters and about arrays, and that together we get strings. But how? Let’s examine a few ways we can more simply make our arrays. The code from the previous snippet is compacted into the following:

int main (int argc, const char * argv[]) {
	int numbers[5] = {67, 79, 67, 79, 65};
	
	//show the data off
	printf("My favorite numbers are %d, %d, %d, %d, %d",
		numbers[0],
		numbers[1],
		numbers[2],
		numbers[3],
		numbers[4]);
	return 0;
}

We can use this curly-bracket formation to make things a little more terse. You can build and run this snippet and see that it does the exact same thing as the previous snippet. Now let’s do a neat trick. Change the format string so the first line of printf looks like this:

printf("My favorite programming environment is %c%c%c%c%c!",

Build and run again and see encoding in action. The computer has taken the integers we’re using and displayed them as characters; we’ve made an array of numbers into a string. To make our lives even simpler than it is with the bracket notation, C gives us the double-quotes format we saw first, and we see again here:

int main (int argc, const char * argv[]) {
	char numbers[] = "COCOA";
	
	//show the data off
	printf("My favorite programming environment is %c%c%c%c%c!",
		numbers[0],
		numbers[1],
		numbers[2],
		numbers[3],
		numbers[4]);
	return 0;
}

The double-quotes format is really just a simple way of making an array of characters that we can use just like any other array. Note that we can even edit the string in code:

int main (int argc, const char * argv[]) {
	char numbers[] = "COCOA";
	numbers[0] = 'S';
	numbers[2] = numbers[4];
	numbers[2] = 'M';

	//show the data off
	printf("My favorite island is %c%c%c%c%c!",
		numbers[0],
		numbers[1],
		numbers[2],
		numbers[3],
		numbers[4]);
	return 0;
}

Cast Off

Before, when we had printf convert integers into characters, we were using what is known as cast. Now let’s using casting to do something interesting with our code. Let’s put main back to where it was before this article, and change countTo():

int main (int argc, const char * argv[]) {
	//Computes our favorite number
	int* favoriteNumber = malloc(sizeof(int));
	*favoriteNumber = (3 * 4) / 2;
	*favoriteNumber = integerForSeedValue(*favoriteNumber + 2);;
	
	/* now let's tell the world
		what our favorite number is! */
	countTo(*favoriteNumber);
	
	free(favoriteNumber);
	return 0;
}

void countTo(int loopsToPerform) {
	char* msg;
	int loopsToGo = loopsToPerform;
	
	if (loopsToGo > 26) {
		msg = "All the letters of the alphabet!\n";
		loopsToGo = 26;
	} else if (loopsToGo < 0) {
		msg = "None of the letters of the alphabet!\n";
		loopsToGo = 0;
	} else {
		msg = "The first %d letters of the alphabet!\n";
	}
	
	printf(msg, loopsToPerform);
	while (loopsToGo) {
		char thisChar = (char) (65 + (loopsToPerform-loopsToGo));
		printf("%c!\n", thisChar);
		--loopsToGo;
	}
	
	printf("Next time won't you cast with me?\n");
}

main is provided for reference; the new stuff is all in countTo.

There are three new things in this snippet; we’ll note the boring ones first.

Notice that we’ve done something slightly different in our first call to printf by passing it a variable instead of a constant string. This is totally fine; the program will use the passed-in string without difficulty. You will actually use this method quite often in your Cocoa programming, because passing in constants makes your program harder to localize, and so you pass in variables instead.

Secondly, note that we use the -- operator again, but now it’s before the variable (prefixed) instead of after (suffixed). Either one works here, where the variable change is all that’s going on. But you can actually use these operators anywhere you’d normally have the variable, and then their behavior is slightly different. When the operator is prefixed, the variable is changed before it is used. When it is suffixed, the variable is changed after it is used. If you have more than one such operator on the same line, you’re probably best off splitting your code across lines.

The last new thing to note is what the snippet is all about: casting. The first line of our while loop contains the cast. That line is declaring a char called thisChar, but the equation it’s using returns an int. To convert into a char, we put the type we want in between some parentheses, and toss that in front of the code that would normally evaluate to be an int.

But how does it know how to translate numbers into letters? Well, it doesn’t, really, and that’s the beauty of the system: what we’re doing here is not translating, we’re just reading the value we already have a little differently. The ones and zeros remain the same, but we can now use them as a char. If the value really can be read that way, great! If not, you see problems: we could just as easily cast to a char*, which the computer would think is a pointer. But when we used that pointer, the computer would look for a char in memory at address 65, and find out that it can’t read kernel space. Crash goes the program.

Casting is incredibly powerful, and can be used to work out neat tricks like the one above (which you should compile and run now), and much more complex tricks that will save you time and effort as you program. However, it is a tool that is easily misused, and must be used with care.

Stringing You On

So we’ve seen how we can massage our string in lots of different ways -- we can print it, change its contents, reconstruct it, and lots of other things to mutate it from one state into another. The thing we can’t do (for now) is make our string longer. That requires a bit more knowledge than we have at present, but we’ll remedy that situation in the next article, when we talk about NULL and all its cousins, and make our first steps toward using Objects, the building blocks of modern programs.

Seth Roby graduated in May of 2003 with a double major in English and Computer Science, the Macintosh part of a three-person Macintosh, Linux, and Windows graduating triumvirate.


Return to the Mac DevCenter

Copyright © 2009 O'Reilly Media, Inc.