9. Arrays


What a wide array of information we have for you in this section. *BLAM*! We're sorry--that pun has been taken out and shot.

An array: a linear collection of related data. Eh? It's a continuous chunk of memory that holds a number of identical data types. Why would we want to do that? Well, here's an example without arrays:

int age0;
int age1;
int age2;
int age3;
int age4;

age1 = 10;
printf("age 1 is %d\n", age1);

And here is that same example using the magical power of arrays:

int age[5];

age[1] = 10;
printf("age 1 is %d\n", age[1]);

Ooooo! See how much prettier that is? You use the square-brackets notation ([]) to access elements in the array by putting an int variable in there or, like we did in the example, a constant number. Since you can index the array using a variable, it means you can do things in loops and stuff. Here's a better example:

int i;
int age[5] = {10,20,25,8,2};

for(i = 0; i < 5; i++) {
    printf("age %d is %d\n", i, age[i]);
}

whoa--we've done a couple new things here. for one, you'll notice in the array definition that we've initialized the entire array at once. this is allowed in the definition only. once you get down to the code, it's too late to initialize the array like this, and it must be done an element at a time.

the other thing to notice is how we've accessed the array element inside the for loop: using age[i]. so what happens in this for is that i runs from 0 to 4, and so each age is printed out in turn, like so:

age 0 is 10
age 1 is 20
age 2 is 25
age 3 is 8
age 4 is 2

why does it run from 0 to 4 instead of 1 to 5? good question. the easiest answer is that the array index numbers are zero-based instead of one-based. that's not really an answer, i know...well, it turns out that most (if not all) processors use zero-based indexing in their machine code for doing memory access, so this maps very well to that.

You can make arrays of any type you desire, including structs, and pointers.

9.1. Passing arrays to functions

it's pretty effortless to pass an array to a function, but things get a little wierd. the weirdest part is that when you pass the "whole" array by putting its name in as a parameter to the function, it's actually a pointer to the first element of the array that gets passed in for you.

now, inside the function, you can still access the array using array notation ([]). here's an example:

void init_array(int a[], int count)
{
    int i;

    /* for each element, set it equal to its index number times 10: */

    for(i = 0; i < count; i++)
        a[i] = i * 10;
}

int main(void)
{
    int mydata[10];

    init_array(my_data, 10);  /* note lack of [] notation */

    return 0;
}

A couple things to note here are that we didn't have to specify the array dimension (that is, how many elements are in the array) in the declaration for init_array(). We could have, but we didn't have to.

Also, since an array in C has no built-in concept of how big it is (i.e. how many elements it holds), we have to be nice and cooperative and actually pass in the array size separately into the function. We use it later in the for to know how many elements to initialize.

Hey! We didn't use the address-of operator in the call! Wouldn't that make a copy of the array onto the stack? Isn't that bad? Well, no.

When you have an array, leaving off the and square brackets gives you a pointer to the first element of the array. (You can use the address-of operator if you want, but it actually results in a different pointer type, so it might not be what you expect.) So it is, in fact, a pointer to the array that's getting copied onto the stack for the function call, not the entire array.

Right about now, you should be recognizing that you can use arrays to hold a lot of things, and that could serve you quite well in your projects which often involve collections of data. For instance, let's say:

We have a virtual world where we have a number of virtual creatures that run around doing virtual things. Each creature has a real X and Y coordinate. There are 12 creatures. Each step of the simulation, the creatures will process their behavior algorithm and move to a new position. The new position should be displayed.

Uh oh! Time for building blocks! What do we have? Ok, we need an X and Y coordinate for the creatures. We need 12 creatures. We need a construct that repeatedly processes the behavior for each. And we need output.

So the coordinates of the creature. There are a few ways to do this. You could have two arrays of 12 elements, one to hold the X and one to hold the Y coordinate. (These are known as parallel arrays.) But let's instead try to think of a way that we could bundle those data together. I mean, they're both related to the same virtual creature, so wouldn't it be nice to have them somehow logically related? If only there were a way...but wait! That's right! A struct!

struct creature {
    float x;
    float y;
};

There we go. Now we need 12 of them; 12 items of type struct creature. What's a good way of holding a collection of identical types? Yes, yes! Array!

    struct creature guys[12];

So what else--we need to be able to repeatedly execute the behavior of these creatures. Like looping over and over and over...yes, a loop would be good for that. But how many times should we loop? I mean, the specification didn't tell us that, so it's an excellent question. Often we'll loop until some exit condition is true (like the user presses ESCAPE or something like that), but since the spec writer didn't say, let's just loop forever.

    for(;;) {  /* loop forever */

Now what? We need to write the behavior of the creatures and put it in the loop, right? Since this is a lot of self-contained code we're about to write, we might as well stick it in a function and call that function for each of the creatures. But I'll leave the actual implementation of that function up to you, the reader. :-)

        for(i = 0; i < 12; i++) {
            execute_behavior(&(guy[i]));
        }

You notice that we did use the address-of operator there in the call. In this case, we're not passing the whole array; we're just passing a pointer to a single element in the array. It's not always necessary to do that (you could have it copy the single element in the call), but since this is a struct, I pass a pointer to keep the memory overhead low.

The last thing to do was to output the information. How this is done should be in the spec, but isn't. It would be cool to do a hi-resolution screen with little icons for where the creatures are, but that is currently beyond the scope of what we're doing here, so we'll just write a simple thing to print out the creatures.

One final note--it's always a good idea to initialize the data before using it, right? So I'll write a function that initializes the creatures, too, before we use it. How it initializes them is also undefined in the spec, so I'll arbitrarily set them up in a diagonal line.

Completed (except for the behavior) code:

#include <stdio.h>

struct creature {
    float x;
    float y;
};

void execute_behavior(struct creature *c)
{
    /* does nothing now */
    /* --you'll have to code it if you want them to ever move! */
}

main(int main(void)
{
    int i;
    struct creature guys[12];

    /* initialize them--could be its own function: */

    for(i = 0; i < 12; i++) {
        guys[i].x = (float)i; /* (float) is a "cast"--it changes the type! */
        guys[i].y = (float)i;
    }

    /* main loop */

    for(;;) {   /* loop forever */

        /* have them do their thing: */

        for(i = 0; i < 12; i++) {
            execute_behavior(&(guy[i]));
        }

        /* output the result */
        for(i = 0; i < 12; i++) {
            printf("creature %d: (%.2f, %.2f)\n", i, guys[i].x, guys[i].y);
        }
    }

    return 0;
}

I threw in a cast there in the code: (float). See i is an int, but each of the fields guys[i].x and guys[i].y is a float. The cast changes the expression right after it, in this case "i" to the specified type. It's always a good idea to have the same types on both sides of the assignment operator (=).

Another new thing is the "%.2f" in the printf() format string. A plain "%f" means to print a float, which is what we're passing it. The addtional ".2" means print it using two decimal places. You can leave the ".2" off and see what happens. :-)