C for Yourself - part 14

This month Steve Mumford investigates some alternative methods of data storage.

Up till now, if we've wanted to store a set of related data items such as names or telephone numbers, we've used arrays - David described how to set them up back in the December 1994 issue. As a brief reminder, here's a few examples of array declarations:

float numbers[100];
int grid[5][10];
char names[10][25];
char *places[20];

It's important to remember that when you declare your arrays, you're specifying the maximum number of elements you'll be using - in the first example, you've defined ten 'cells' starting at numbers[0] and goint to numbers[9].

Arrays are quite versatile, but they do have their limitations. If you're writing a simple database to store the contents of your address book, you'd need several arrays to hold the different types of information - one for the names, another for the telephone numbers, and so on. In order to perform an operation on an individual record, you have to examine each array in turn and manually reconstitute the information. It's simple enough to do, but when each record contains a lot of data fields, it quickly becomes tiresome, especiallyfor routine operations like copying one record to another.

Another consequence of arranging the data in separate arrays according to its type is that the natural structure of information is lost - in an address book, all the information for one address is stored in one space; you'd never dream of storing all the phone numbers in a separate chapter and cross-referencing them to the names whenever you wanted to call someone. It's this philosophy that brought about a mjor addition to the language of C, and it's this addition that I'll be looking at next.

Structures

A structure can be thought of as a group of variables, similar to an entry in an address book. Once you've defined the contents of a structure, you can declare a structure of that type with a single command. Here's how you define a structure:

struct mode
{
  char name[15];
  int xres;
  int yres;
  int colours;
};

Essentially, you put together a list of the items you want the structure to hold, and initialise them as if they were separate variables - the only difference is that you surround the whole lot in braces at the end of a struct command - and since it's a command we're dealing with we need to include the semicolon after the closing brace. The example above assigns the name mode as a new variable type. No structures have been created in memory yet; we've just told the compiler how to make one when it needs to. The structure mode contains four elements - a block of 15 bytes in which a name can be stored, the x and y resolutions of the mode, and finally the number of colours. In order to declare a structure of this type that we can use, we need to do the following:

struct mode games;

or

struct mode hires;

It's similar to declaring a variable of any other type, but because mode is a type we've just created - a pseudotype, if you like - we need to remind the compiler of that fact by prefixing it with the struct keyword.

If you're going to be declaring several structures with the same definition, it's command practice to use the typedef command to cut down on the typing. The following example instructs the compiler to treat any MODE instruction it finds as if it were struct mode.

typedef struct mode MODE;

MODE games;

You can even use the typedef command at the same time as the definition of the structure, which accounts for why you'll see some pretty big differences between structure definitions.

typedef struct
{
  int numerator;
  int denominator;
} FRACTION;

FRACTION frac1;

So, you've defined and declared your structure, so how do you use it? In the same way that you use an index to look at different elements in an array, you use the names of the members of the structure to access them, by using the dot operator.

gets(games.name);
games.xres = 640;
games.yres = 256;
games.colours = 256;

In the example above, I'm storing information in the structure named games by adding the appropriate element onto the end of the structure name.

Arrays of structures

It's all very well being able to create a compound variable that can take a whole chunk of related data in one go, but it's not much use unless you can create whole stacks of them in one go. However, since we can do almost anything with a structure name that we could do with a variable, there's no problem - we just create an array of structures.

MODE modes[10];

The above statement declares an array of 10 mode structures, and each one can contain a name, the x and y resolutions, and the number of colours as before.

A structure in an array is accessed in almost the same way as before, using the dot operator. The following example steps through the array of structures and reads in a name for each of them.

for (x=0; x<10; x++)
{
  printf("Please enter the name for mode %d:\n", x);
  gets(mode[x].name);
}

This allows use to store our list of names and addresses in one large array, and operations such as copying, sorting or deleting records are made much easier by the fact that we can access a whole chunk of data in one go. The code fragment belows creates a new structure named chosen and copies the fourth structure from the modes array into it.

MODE chosen;
chosen = modes[3];

Note that this copies the array of characters in modes[3].name, which would have needed the strcpy() function outside a structure.

Advanced structures

So far, we've created individual structures, made arrays of them, and learned how to access separate elements within them. However, that's only the tip of the proverbial iceberg - since you can do almost anything to a structure that you can do with a variable, you can manufacture some pretty interesting data maps.

Firstly, you're not limited to including single variables in structures; you can put arrays within them too. If you want to progress further, you can also include other structures, arrays of structures - the list goes on. There are limitations; if you're defining a structure named address, you're not allowed to refer to structures of that type within its own definition. It's mostly because you fall into a recursion trap - the compiler would try to put the address structure inside itself, and get confused. However, as with most limitations, there's a way round it, and this leads on to such marvels as linked lists and binary trees - more on which next month.

struct example
{
  int array[10][20];
  MODE games;
  MODE strucarray[5];
};

struct example ex1;

Once this rather complex structure shown above has been set up, you can access all the elements within it following the rules laid out earlier. All the following assignments are possible.

ex1.array[5][5] = 10;
ex1.games.xres = 640;
ex1.strucarray[3].colours = 16;

Pointers to structures

Another important similarity between structures and other variables is the ability to refer to them by their location in memory - by a pointer. This allows structures to be allocated memory dynamically, which is a neater, although more complicated, arrangement than declaring a huge array of them at the start of the program. Taking the example of the address book database, we could either guess at the maximum number of records the user would ever create and set aside enough memory before the program ran, or using pointers, we could wait until the user needed another empty record before allocating the memory. The only snag is that we have to use malloc(), so if you need to brush up on the function, I suggest you browse over David's article in the Christmas 1994 issue of Acorn User.

typedef struct
{
  int x;
  float y;
} SIMPLE;

SIMPLE *var1;

The above fragment defines a structure and creates the variable type SIMPLE for it. The last line declares a pointer to a structure of type SIMPLE, but doesn't allocate any memory yet. That's done by using the malloc() function, and the sizeof() function calculates the amount of memory the structure takes up. If it's a more complicated structure, you might have to determine its size for yourself.

var1 = malloc(sizeof(SIMPLE));

You can't access these structures in quite the same way as the ones mentioned earlier in the column, but a special operator is provided to avoid all those stars and ampersands that are normally associated with pointers. I'll cover that next month, so I'll see you then.


Source: Acorn User - 158 - August 1995
Publication: Acorn User
Contributor: Steve Mumford