TIGCC Programming Lessons
Lesson 7: Structures, Unions, and Enumerations
Step 3 - Dynamic Structures
We have already seen some advanced uses for structures, like containing
a structure within another structure, but there are more things we can
do with structures. Remember from lesson 6 that we allocate memory space
as a certain number of bytes. Since there is no distinction (in memory)
between an integer, a character, or a floating point number, as they are
all stored as a series of bytes, we can also allocate dynamic memory to
use with structures. We do this by creating pointer structures, like
this:
struct dob {
int month, day, year;
};
struct dob *birthday = NULL;
if ((birthday = (struct dob *)malloc(sizeof(struct dob))) == NULL) {
DlgMessage("DMA Error","Not enough memory for a dob structure",BT_OK,BT_NONE);
return;
}
This is essentially the same as any other memory allocation operation we
have performed. The sizeof() operator knows how to calculate the size of
a structure by summing the sizes of all the member variables. So, in
this instance, assuming integers take 2 bytes, the sizeof(struct dob)
will be 6 bytes.
Remember though that pointers are not the same thing as variables, so we
must always make sure to dereference a pointer before we try to access
its values. We can do the same thing with pointers to structures, and we
must do so before we use the . dot operator. For example, if we were to
access the data members of the dob structure we created above, we would
need to do this:
(*birthday).month = 6;
(*birthday).day = 3;
(*birthday).year = 1981;
This gets a little confusing at times though, and since we often have
large structures which are allocated through dynamic memory, we can use
a special kind of operator to access the data members instead of the .
dot operator. We can use the -> structure indirection operator. So,
instead of what we did above, we could do this instead:
birthday->month = 6;
birthday->day = 3;
birthday->year = 1981;
The ->, called the structure indirection operator, accesses the
dereferenced value of a pointer rather than the pointer itself. It is
the counterpart to the . dot operator. But I think you can see how this
is easier than the first method we described.
Never forget to free the memory allocated for structures before you exit
the program. Since structures usually comprise several variables, it is
very bad to forget this, because you will lose many bytes of memory this
way. But we free structure pointer memory in exactly the same way as we
free other pointer memory:
free(birthday);
Step 4 - Arrays of Structures
It is often very useful to create a large number of structures and
access them collectively. Just as we used arrays of characters to create
strings, we can use arrays of structures to create more useful
structures. Imagine a deck of cards. The deck is composed of a set of 52
cards (American playing card decks at least), and the card itself can be
represented as a structure, with an integer for the suit and an integer
for the face. So, if we had a structure for a Card, it might look
something like this:
struct Card {
int face, suit;
};
Now imagine we wanted to create a deck. Well, we would simply use an
array of Card structures to accomplish this task. So, our deck would be
something like this:
struct Card deck[52];
Declaring arrays of structures is no different than declaring arrays of
any other data type. We can even use pointers to allocate multiple
structures dynamically, like so:
struct Card *deck = NULL;
if ((deck = (struct Card *)calloc(52,sizeof(struct Card))) == NULL) {
DlgMessage("DMA Error","Not enough memory for the Deck of Cards",BT_OK,BT_NONE);
return;
}
Now, when using arrays of dynamically created structures, we can either
use array notation and the . dot operator, or we can use pointer
notation and the -> indirection operator to access the member
elements. If we use the -> indirection operator though, we must
remember to change our pointer when we want to get to the other array
nodes. So, if we were going to initialize our deck of cards, we could do
either of the following:
int loop;
for (loop = 0; loop < 52; loop++) {
deck[loop].face = loop % 13;
deck[loop].suit = loop / 13;
}
Or, if we choose to use pointer notation, we could do this instead:
int loop;
for (loop = 0; loop < 52; loop++) {
(*deck).face = loop % 13;
(*deck).suit = loop / 13;
// increment the card pointer
deck++;
}
I prefer the first option, but they are completely equivalent.
Step 5 - Creating New Variable Types with Structures
The last thing we will cover about structures is how to create new
variable types using structures. The one very annoying thing about using
structures is that we must constantly use the struct keyword to tell the
compiler we are using a structure. Wouldn't it be easier if we could use
a structure we have defined just as if it were a built-in type like int
or char? Well, we can do that. It's called type definition, using the
typedef keyword. So, imagine we had the dob structure from above, but
instead of using struct dob, let's call it a DOB variable type. Let's
just see how that would work:
typedef struct {
int month, day, year;
} DOB;
DOB dob;
dob.month = 6;
dob.day = 3;
dob.year = 1981;
I think you will agree this looks a little more compact than using the
struct keyword everywhere. In this version, we only have to create a
variable type of structure and then type define it with a new variable
type name. Then we can use it anywhere in our code.
We can couple this with using pointers, too.
DOB *dob = NULL;
if ((dob = (DOB *)malloc(sizeof(DOB))) == NULL) {
DlgMessage("DMA Error","Not enough memory for a DOB structure",BT_OK,BT_NONE);
return;
}
dob->month = 6;
dob->day = 3;
dob->year = 1981;
Just don't forget to free the memory when you are done with it.
Step 6 - Advanced Structure Usage
Start TIGCC and create a new project. Create a new C Source File called
card. Modify the file so that it looks like this:
card.c
#include <tigcclib.h>
#define DECK_SIZE 52
typedef struct {
int face;
int suit;
} Card;
void initDeck(Card *deck) {
int loop;
// assign initial values to the deck of cards
for (loop = 0; loop < DECK_SIZE; loop++) {
deck[loop].face = loop % 13;
deck[loop].suit = loop / 13;
}
}
void shuffle(Card *deck) {
int loop, randNum;
Card temp;
// shuffle the deck
for (loop = 0; loop < DECK_SIZE; loop++) {
randNum = rand() % DECK_SIZE;
// swap the card at the current position with
// the one at the randomly selected position
temp = deck[loop];
deck[loop] = deck[randNum];
deck[randNum] = temp;
}
}
void printDeck(Card *deck, const char *faces[], const char *suits[]) {
int loop;
// clear the screen
clrscr();
// print the deck one card at a time
for (loop = 0; loop < 52; loop++) {
// print the face and suit of the card
printf("%s of %s\n",faces[deck[loop].face],suits[deck[loop].suit]);
// pause for each 8 cards displayed
if ((loop % 8) == 0 && loop != 0) {
printf("Press a key to continue...\n");
ngetchx();
}
}
}
void _main(void) {
Card *deck = NULL;
const char *faces[] = {"Ace","Two","Three","Four","Five","Six","Seven","Eight",
"Nine","Ten","Jack","Queen","King"};
const char *suits[] = {"Spades","Diamonds","Clubs","Hearts"};
// allocate memory for the deck of cards
if ((deck = (Card *)calloc(DECK_SIZE,sizeof(Card))) == NULL) {
DlgMessage("DMA Error","Unable to allocate card structure",BT_OK,BT_NONE);
return;
}
// initialize the random number generator
randomize();
// initialize the deck of cards
initDeck(deck);
// shuffle the deck
shuffle(deck);
// print the deck of cards
printDeck(deck,faces,suits);
// free the memory used by the deck of cards
free(deck);
// wait for user input before exiting the program
ngetchx();
}
Step 6a - Compile and Run the Program
Save the card.c file and build the project. Send it to TiEmu and it
should look something like this:
Step 6b - Program Analysis
Structures are not complicated concepts in C, but they do have many
nuances that the new programmer must get accustomed to before they
become second nature, but structures are a very powerful concept in C,
and prove a valuable building in advanced C programming.
Card *deck = NULL;
const char *faces[] = {"Ace","Two","Three","Four","Five","Six","Seven","Eight",
"Nine","Ten","Jack","Queen","King"};
const char *suits[] = {"Spades","Diamonds","Clubs","Hearts"};
Our _main() method begins like any other method, with our variable
declarations. Our deck of cards will be dynamically allocated. We create
two string literal arrays to map the names of the cards to the numbers
we store internally to the structure.
// allocate memory for the deck of cards
if ((deck = (Card *)calloc(DECK_SIZE,sizeof(Card))) == NULL) {
DlgMessage("DMA Error","Unable to allocate card structure",BT_OK,BT_NONE);
return;
}
Dynamic memory allocation should be becoming second nature to you by
now, but let's go over it real quick just to make sure. We allocate the
memory using the calloc() function. Remember that calloc() sets all the
memory we allocate to zero before it returns our pointer, and we must
still always check to make sure we got the memory we requested.
// initialize the random number generator
randomize();
// initialize the deck of cards
initDeck(deck);
This part of the code represents our initialization code for the
program. We need to seed the random number generator so it will generate
random numbers properly, and we need to initialize our deck of cards so
we can use them.
Next we need to create the deck of cards by initializing our card
structure values. We use the initDeck() function to handle this for us,
and it's a relatively simple function.
void initDeck(Card *deck) {
int loop;
// assign initial values to the deck of cards
for (loop = 0; loop < DECK_SIZE; loop++) {
deck[loop].face = loop % 13;
deck[loop].suit = loop / 13;
}
}
Our deck initialization is a fairly simple process. We loop through the
entire deck from 0 to 51 (we defined the DECK_SIZE constant to be 52),
and we assign the face values between 0 and 12 for Ace, Two, ..., Jack,
Queen, King respectively. Remember that the modulo operation takes the
remainder of integer division, so if we divide the value of the loop by
13, the remainder of that operation will be assigned to the face. For
the suit values, we take the quotient of that division, which will
return values between 0 and 3, for our suits. We defined the suits to be
Spades, Diamonds, Clubs, and Hearts respectively.
// shuffle the deck
shuffle(deck);
Of course, before we can use a deck of cards in any kind of card game,
we need to shuffle the deck. This shuffle function will accomplish that
for us.
void shuffle(Card *deck) {
int loop, randNum;
Card temp;
// shuffle the deck
for (loop = 0; loop < DECK_SIZE; loop++) {
randNum = rand() % DECK_SIZE;
// swap the card at the current position with
// the one at the randomly selected position
temp = deck[loop];
deck[loop] = deck[randNum];
deck[randNum] = temp;
}
}
Shuffling the deck is fairly easy. We just need to loop through the
deck, pick a random card for each position and swap the card at the
position in the deck we are at with the randomly selected card. This is
why it was necessary to seed the random number generator in the _main()
function. Remember that DECK_SIZE is defined to be 52, as in 52 cards in
a standard American playing card deck.
// print the deck of cards
printDeck(deck,faces,suits);
Since we are only making a small test program, we will just print out
the deck of cards to show you the Card structure works. Since we want to
map the numbers we store in our Card structures to actual suit and face
names, we give this function the character arrays we defined above as
well as the deck of cards.
void printDeck(Card *deck, const char *faces[], const char *suits[]) {
int loop;
// clear the screen
clrscr();
// print the deck one card at a time
for (loop = 0; loop < 52; loop++) {
// print the face and suit of the card
printf("%s of %s\n",faces[deck[loop].face],suits[deck[loop].suit]);
// pause for each 8 cards displayed
if ((loop % 8) == 0 && loop != 0) {
printf("Press a key to continue...\n");
ngetchx();
}
}
}
The printDeck() function simply loops through our deck and prints out
the Card at the position in the deck. We pause the screen after every 8
cards because that's roughly all the information we can fit on the TI-89
screen using the standard font size. We use the face and suit members of
the Card structure in the current deck position to act as pointer
indexes for the two character arrays 'suits' and 'faces' so we can print
out the proper card name.
This is pretty much the end of the program. The final two lines in the
_main() method free up the memory used by our deck of cards and pauses
so the user can see the final cards in the deck before the screen is
restored.
Continue with Unions in Part III
|