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

The following is part of a series of lessons designed to help teach people to program in C for the TI-89, 92+, and V200 calculators using the TIGCC development environment.

If you wish, you can download the program source code, project files, and binaries here.

Lesson 7: Structures, Unions, and Enumerations

Step 1 - An Introduction to Structures

In nearly every programming language, it becomes necessary to group data together to form larger constructs. We do this for many reasons, but mainly because it's easier for us to think of a group of information as part of a larger item. For example, when you think of someone's date of birth, you are really thinking of at least 3 numbers, the day of the month, the month, and the year. But encapsulating all this data into a single place, we can start thinking of it as a single unit, and that's very advantageous over keeping track of variables that are related by yourself.

So, now that we have some belief that it's a good idea to put data members together, how do we go about accomplishing this task? Easy! C has a built-in method for working with sets of data, and they are called structures. Structures, as the name implies, are containers for varying kinds of data. You can put any kind of data within a structure: integers, characters, pointers, arrays, even other structures if you like. Let's take a look at a small example to see how structures work:

struct dob {
	int month;
	int day;
	int year;
};

struct dob birthday;
  
birthday.month = 6;
birthday.day = 2;
birthday.year = 1973;

This may look at little complicated at first, but trust me, once we get to the explanation, everything will become very simple. So, let's take a look at our various parts. All structures have 2 parts, the definition and the declaration. Once they are defined and declared, we can use them. So, let's examine them piece by piece. I highlighted the various parts in different colors. The definition is in red, the declaration is in blue, and the usage is in teal. We'll start with the definition, since we must first define a structure before we can declare it or use it.

Structures are defined using the struct keyword. That keyword is followed by the name of the structure. The name of a structure is just like a variable type name, like int or char, so it must be unique. The name of the structure we defined is called 'dob', short for date of birth. Once we have the name of the structure, we need to open a brace { to enclose the members of the structure. This is where we put all the variables we will have as members of the structure. We use the same procedure for declaring variables inside a structure as we do for declaring variables in a function, except we don't initialize any of the values. This is because these variables do not actually exist yet, because we haven't created any structures yet, we're only telling the compiler how to create a structure so that when we tell it to build one, it will know how. Finally, when we are finished declaring the members of the structure, we close the brace } and signify the end with a semi-colon ;. The semi-colon is very important, for reasons we will discuss later on.

Now that we have defined a structure, the compiler will know how to build one when we ask it to. To actually declare a structure, like we declare a variable, we use the struct keyword again with the name of the structure (so the compiler knows which structure to create), and then give it a variable name. So, in the blue above, we used struct dob to tell the compiler we want it to create a dob structure, and then give it a variable name we can use to access the structure, which we called birthday. This is just like a standard variable declaration (like int or char), except we need to add the struct keyword because the compiler won't know what a 'dob' is without it.

Finally, we get down to the how of structures: How do we access the members of a structure? Well, that's easy. C has a built-in operator, called the dot operator. The dot operator is just that, a dot, or more properly, a period. So, we use the name of the structure (the variable name, not the structure name), followed by the dot operator, and then the name of the variable structure member we want to access. This is just like using any other variable, so you can do anything with a structure member that you can do with a variable.

See, structures are easy! So let's take it a step further and work through a small example program.

Step 2 - Using Structures in C

Start TIGCC and create a new project. Create a new C Source File named struct. Modify the file so that it looks like this:

struct.c


#include <tigcclib.h>

struct dob {
    int month, day, year;
};

struct Person {
    char name[20];
    int height;
    struct dob birthday;
};

void printPerson(struct Person person) {
    // clear the screen
    clrscr();

    // print the structure information
    printf("Name:     %s\n", person.name);
    printf("Height:   %d inches\n", person.height);
    printf("Birthday: %d-%d-%d\n", person.birthday.month,
        person.birthday.day, person.birthday.year);

    // wait for user to press a key to see the results
    ngetchx();
}

void _main(void) {
    struct Person person;
    short int result = 1;
    const char *title = "DMA Error", *error = "Not enough free memory";
    char *input = NULL;
    HANDLE dlg = H_NULL;

    // allocate memory for the input buffer
    if ((input = (char *)calloc(35,sizeof(char))) == NULL) {
        DlgMessage(title,error,BT_OK,BT_NONE);
        return;
    }

    // allocate memory for the dialog box
    if ((dlg = DialogNewSimple(140,85)) == H_NULL) {
        DlgMessage(title,error,BT_OK,BT_NONE);

        // free input buffer memory before exiting
        free(input);

        return;
    }

    // design the dialog box
    DialogAddTitle(dlg,"Personal Information",BT_OK,BT_NONE);
    DialogAddRequest(dlg,5,15,"Name:",0,20,14);
    DialogAddRequest(dlg,5,25,"Height (inches):",21,2,3);
    DialogAddRequest(dlg,5,35,"Birth Month:",24,2,3);
    DialogAddRequest(dlg,5,45,"Day of Month:",27,2,3);
    DialogAddRequest(dlg,5,55,"Birth Year:",30,4,5);

    // prompt the user for dialog input
    while (result != KEY_ENTER && result > 0) {
        result = DialogDo(dlg,CENTER,CENTER,input,NULL);
    }

    // free the memory used by the dialog box
    HeapFree(dlg);

    // copy information from the input buffer
    sprintf(person.name,"%s",input);
    person.height = atoi(input+21);
    person.birthday.month = atoi(input+24);
    person.birthday.day = atoi(input+27);
    person.birthday.year = atoi(input+30);

    // free the memory used by the input buffer
    free(input);

    // print the structure information
    printPerson(person);
}

Step 2a - Compile and Run the Program

Save the program source and build the project. Send the program to TiEmu and run it. It will look something like this:

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

Step 2b - Program Analysis

We are getting past the point of writing completely trivial programs. The programs we start writing now will be a little more complex, but you should be reaching the level where you no longer need explanations for most of the code we write. If you haven't started yet, it would be a really good idea to start taking at look at the TIGCC library documentation. It has tons of useful information in it, and any function that I would use in a program that we haven't already seen is probably documented there.

struct Person person;
short int result = 1;
const char *title = "DMA Error", *error = "Not enough free memory";
char *input = NULL;
HANDLE dlg = H_NULL;

Our variable declaration section is quite diverse this time. Integers, character arrays with and without string literals, dialog handles, and of course structures. The only thing of note in this list is the structure defined at the time. Recall from our little intro above that we declare structures with the keyword struct, the name of the structure, and the variable name we want to use. If we haven't mentioned it before, we should now: C is a case sensitive language. This means that 'person' and 'Person' are two different things to the C compiler, so we can name our struct Person and name our variable person and the C compiler will have no trouble differentiating between them.

// allocate memory for the input buffer
if ((input = (char *)calloc(35,sizeof(char))) == NULL) {
	DlgMessage(title,error,BT_OK,BT_NONE);
	return;
}

// allocate memory for the dialog box
if ((dlg = DialogNewSimple(140,85)) == H_NULL) {
	DlgMessage(title,error,BT_OK,BT_NONE);
		
	// free input buffer memory before exiting
	free(input);
		
	return;
}

I hope you are getting used to dynamic memory allocation by now. It's a very powerful tool when used properly. We probably went slightly overboard with the small character array we have, but it's never a bad idea to try and save memory.

Remember that in allocating space for a dialog box, we use the DialogNewSimple() function, and instead of checking to see if it returned NULL, we make sure it didn't return H_NULL, or HANDLE NULL. Remember that if we allocate the memory for the input array, but cannot allocate the dialog box, we need to free the input array before we exit the program.

// design the dialog box
DialogAddTitle(dlg,"Personal Information",BT_OK,BT_NONE);
DialogAddRequest(dlg,5,15,"Name:",0,20,14);
DialogAddRequest(dlg,5,25,"Height (inches):",21,2,3);
DialogAddRequest(dlg,5,35,"Birth Month:",24,2,3);
DialogAddRequest(dlg,5,45,"Day of Month:",27,2,3);
DialogAddRequest(dlg,5,55,"Birth Year:",30,4,5);

Remember from lesson 6, dialog boxes need to be created, designed, and then displayed. We only had a single input box on the last dialog box, but this demonstrates how we can have more than one request box using the offset parameter we talked about before. Remember that we always need one more character than the maximum length of a string to account for the terminator. Also keep in mind that the AMS dialog boxes can only input strings, they can't input integers or other kinds of data, so we will need to convert our strings to integers later.

It's not too hard to see how the offset parameter works. Note that for the first input box, the offset is 0. This is because we are going to add data to the beginning of our input string. Now see that the next field has an offset of the max length + 1, or 21. We repeat this process going down the request boxes, adding the max length + 1 to the following offset field, plus the offset position of the current field. So the first field is 0 with a max length of 20, so the next field will be max length of the previous field + 1 plus the previous field offset, or 20 + 1 + 0, or 21. So, our offset here is 21, and our max length is 2, so the next fields offset should be 2 (max length) + 1 (terminator) + 21 (offset), or 24. I think you can see the mathematical pattern.

The last parameter, the width just shows us how many characters can be displayed in the text field at once. The first field was chosen fairly arbitrarily (I took the value from the TIGCC library docs), the rest of the values are one character more in length than the max length of the field, so we can see all the characters at once.

// prompt the user for dialog input
while (result != KEY_ENTER && result > 0) {
	result = DialogDo(dlg,CENTER,CENTER,input,NULL);
}

Last time we did dialog boxes, I had the while loop keep looping until we got the ENTER key returned, but I said that was a bad idea, because if there was insufficient memory to display the dialog box, and an error was returned, that would create an infinite loop. So, instead of that, this time we check to see either if we got the enter key returned, or if an error was returned. An error return is any number less than zero. Remember from our variable declaration section, we defined the result to be 1 initially, so both of these conditions will be false initially, so the while loop will happen at least once.

// copy information from the input buffer
sprintf(person.name,"%s",input);

The next part of our code assigns values to our structure. We talked about the sprintf() function briefly in lesson 5 when we created a limited facsimile of it, but I think this may be the first time we have used the function. Remember that sprintf() is like printf(), but instead of printing to the screen, we print to a string. In this case, we are printing to the name member of our person structure. Since we cannot copy an array using the assignment operator =, we must copy the array character by character. This is where the sprintf() function can help us out, by copying our input string (until it reaches the terminator of the first string) to our name member.

person.height = atoi(input+21);
person.birthday.month = atoi(input+24);
person.birthday.day = atoi(input+27);
person.birthday.year = atoi(input+30);

The next set of assignments are a little less complex. Since they are integers, we can use the assignment operator, but we must convert the strings to integers. Remember that the dialog request box can only input string values, so to get integers, we need to convert to integers. To convert a string to an integer, we use the atoi() function, which I believe is short for array to integer, but don't quote me on that. In any event, the atoi() function converts a string (character array) to an integer value, and sends that integer value back as its return type. Keep in mind since the strings we are looking for are not at the beginning of the string, we need to use pointer arithmetic to find the correct place. Remember the offsets we used above when declaring the request boxes? These are the values we need to increment by to find the correct place to start the conversion.

Remember that input + 21 is the beginning of the 21st character in the array, so by advancing by the offset, we start looking at the proper string. The Request Box takes care of adding the terminating character for us, but since we used calloc(), all the non-used spaces are zero anyway, and zero is the terminating character.

The other thing to notice here is that we used a structure within our person structure. We defined a date of birth (dob) structure inside our Person structure. So, to get to the individual data members of the inner structure, we just use the dot operator twice. There is no special trick, because once we use the dot operator the first time, we treat it just like any normal variable.

// free the memory used by the input buffer
free(input);

Up until now, we have always freed our memory at the very end of our _main() method. While there is nothing wrong with this approach, the idea is to share the memory between the other programs and other parts of your own program, so to be a good memory manager, you should really free up memory as soon as you are no longer using it. But, so long as you free it before you exit the program, you will probably be okay. It's just good programming practice.

// print the structure information
printPerson(person);

The last part of our program calls the printPerson() function which we defined to print the data members of the structure. We could have embedded this in our _main() method, but it's really something we should abstract into a function. This is just one of the concepts in functional programming. If you have a task that will be done more than once, it should probably be a function. If we were writing a real program to deal with people, a printPerson() function would probably be used more than once in the course of the program. So let's take a look at the function real quick.

void printPerson(struct Person person) {
	// clear the screen
	clrscr();
	
	// print the structure information
	printf("Name:     %s\n", person.name);
	printf("Height:   %d inches\n", person.height);
	printf("Birthday: %d-%d-%d\n", person.birthday.month,
		person.birthday.day, person.birthday.year);
	
	// wait for user to press a key to see the results
	ngetchx();
}

This part of the program is very straight forward, we just print the data members using printf() functions.

I think you can see where structures would be very useful when creating larger programs. The capability to combine several smaller data members into larger constructs is very important in C.

Continue the Lesson in Part II

 

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

Get Firefox!    Valid HTML 4.01!    Made with jEdit