Techno-Plaza
Site Navigation [ News | Our Software | Calculators | Programming | Assembly | Downloads | Links | Cool Graphs | Feedback ]
 Main
   Site News

   Our Software

   Legal Information

   Credits

 Calculators
   Information

   C Programming

     Introduction to C

     Keyboard Input

     Graphics Intro

     Slider Puzzle 1

     Functions

     Pointers

     Dynamic Memory

     Slider Puzzle 2

     Structures

       Part I

       Part II

       Part III

     Bit Manipulation

     Advanced Pointers

     File I/O

     Graduate Review

   Assembly

   Downloads

 Miscellaneous
   Links

   Cool Graphs

   Feedback Form

C Programming Lessons

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:

TI-89 AMS 2.05 card.89z TI-92+ AMS 2.05 card.9xz

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

 

Copyright © 1998-2007 Techno-Plaza
All Rights Reserved Unless Otherwise Noted

Get Firefox!    Valid HTML 4.01!    Made with jEdit