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 7 - An Introduction to Unions

Well, we spent a lot of time covering Structures, the first and probably most popular way to group data members together in C. But it is not the only way of grouping data together. Suppose we want to use either an integer, or a floating point number to store a number, but we don't want to use both. Well, instead of declaring two variables, we could put them together in what is called a union in C. Unions are like structures in that they combine data members together in a single block. But the difference is that we can only use one variable in the union at a time. This is because the variable space for all the variables are defined on top of one another. So using any one of the variables will overwrite the value of all the other values.

You may be asking yourself, why would anyone want to create a structure where you could only use one variable? Well, the answer is easy: memory management. If we need to store two different kinds of data, there is no reason to reserve space for both kinds. We can just reserve space for the largest one, and save the space used by the smaller one. It is also used to allowing certain kind of data to be defined in two different ways. For example, the SCR_RECT union defined by AMS will let you define a rectangle on the screen either using 4 characters (4 bytes), or a single long integer (4 bytes). This doesn't seem to have a big advantage, but what if you had a flag variable, where each bit represented a different flag. So, we could define it either as a single byte, and just remember internally what each bit represents, or we could define it as a whole byte and as a segment of 8 bits, each with their own name. In this way, we could pass the the entire flag byte when we need to give it to a function, or manipulate several bits at once, or we could use a single bit flag to modify each flag one at a time. This explanation makes more sense when you see the VAT Symbol Entry table, so let's take a look at that real quick:

typedef struct {
	char name[8];
	unsigned short compat;

	union {
		unsigned short flags_n;

		struct {
			unsigned short busy:1,local:1,flag1_5:1,flag1_4:1,
					collapsed:1,twin:1,archived:1,in_view:1;
			unsigned short folder:1,overwritten:1,checked:1,hidden:1,
					locked:1,statvar:1,graph_ref_1:1,graph_ref_0:1;
		} bits;
	} flags;

	unsigned short handle;
} SYM_ENTRY;

In the Symbol Entry table here, used by the AMS to keep track of files, we can see that the structure has an embedded union. I didn't mention this before when we talked about structures, but you can create an unnamed union or structure and declare an instance of the structure by putting the name you want to call the structure or union before the closing semi-colon after the ending brace. So, in this example, the unnamed structure inside the union will have a variable called bits, which is the structure inside the union. Now, we haven't covered bit operations yet, but the variables inside the structure use one bit each. This is a common use for unions, because we can now access each of the flags as part of the union or we can address the entire flags_n variable at once, if we need to.

So, instead of using 16 integers to keep track of the different parts of a symbol entry, we can use just a single integer and address it one bit at a time, and we still have the luxury of addressing the entire flag variable as a single piece. This is a very powerful concept in memory management, because rather than wasting 32 bytes of memory on this one entry, we only use 2 bytes.

As far as using unions, they are used in exactly the same manner as structures. The dot operator accesses their member variables, or in this case their member variable, singular. Remember we can only use one piece of the union at any one time. We will get to an example program after we talk about the next concept, and you will see how easy unions are. So, let's hold off on any more union talk for now.

Step 8 - Enumerations, the Pseudo-Structures

The last kind of 'structures' we will be talking about here are called enumerations. An enumeration literally means a series, like 0, 1, 2, ..., etc. So, using enumerations in C is just an easy way to declare constants.

For example, instead of declaring 4 #define constants that all have arbitrary values, we could define them inside an enumeration, like this:

enum Suits {SPADES, DIAMONDS, HEARTS, CLUBS};

This is the same thing as using 4 define statements, like this:

#define	SPADES		0
#define	DIAMONDS	1
#define	HEARTS		2
#define	CLUBS		3

But I think you can see the space saving constraints of using an enumeration. It's customary to name enumerations so it's clear what kind of constants we are referring to, but there is no requirement that enumerations be named. In fact, we can get the same results by doing this:

enum {SPADES, DIAMONDS, HEARTS, CLUBS};

We can assign specific values to the constants in enumerations, if it is necessary to do so, but if we are using arbitrary constants, and just want to use names to represent values that just need to be different, like card suits, we can do this. But for sport, let's examine how we would assign values to the enumeration constants:

enum {SPADES = 1, DIAMONDS = 2, HEARTS = 3, CLUBS = 4};

By using the assignment operator =, we can assign values to our constants different than the defaults. This is good if we have an enumeration whose values are specific, like defining common key code values, like in the TIGCC CommonKeys enumeration:

enum CommonKeys {KEY_F1 = 268, KEY_F2 = 269, KEY_F3 = 270, KEY_F4 = 271, 
  			KEY_F5 = 272, KEY_F6 = 273, KEY_F7 = 274, KEY_F8 = 275,
  			KEY_ESC = 264, KEY_QUIT = 4360, KEY_APPS = 265, KEY_SWITCH = 4361,
  			KEY_MODE = 266, KEY_BACKSPACE = 257, KEY_INS = 4353, KEY_CLEAR = 263, 
  			KEY_VARLNK = 4141, KEY_CHAR = 4139, KEY_ENTER = 13, KEY_ENTRY = 4109, 
  			KEY_STO = 258, KEY_RCL = 4354, KEY_SIGN = 173, KEY_MATH = 4149, 
  			KEY_MEM = 4150, KEY_ON = 267, KEY_OFF = 4363
  };

Each of the key codes is unique, and we can still use our constants names in place of the values, which is easier for us to remember. Computers may like numbers, but humans like names.

The last property of enumerations is that, if we define a value, but don't define other values, the other values will be assigned sequential values, so, if we did this:

enum {SPADES = 1, DIAMONDS, CLUBS, HEARTS};

Then, the SPADES constant would be 1, DIAMONDS would be 2, CLUBS would be 3, and HEARTS would be 4. This is just one of the many flexibilities of the enumeration verses #define constants, along with their ease of use in defining many constants at once.

There is one more thing about enumerations, and that is that we can define an enum variable type when we name our enumerations.

enum Suits suit = SPADES;

This is not often done though because C treats enumerations the same as integers. There is no prohibition on assigning suit some random integer value like 12. The one reason you might do something like this is to make your program more readable.

Step 9 - Some More Concrete Code using Unions and Enumerations

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


#include <tigcclib.h>

#define YEAR    2002

enum {JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC};

typedef union {
    char date[25];

    struct {
        short int month;
        short int day;
    } dayValues;
} DAY;

void pickDays(DAY *days) {
    int loop, randNum;

    for (loop = 0; loop < 7; loop++) {
        // select a random month
        randNum = rand() % 12;
        days[loop].dayValues.month = randNum;

        // get a random day taking into account the max number of
        // days in the random month we just selected
        switch (randNum) {
            case JAN:
            case MAR:
            case MAY:
            case JUL:
            case AUG:
            case OCT:
            case DEC:
                randNum = rand() % 31;
                break;
            case FEB:
                randNum = rand() % 28;
                break;
            case APR:
            case JUN:
            case SEP:
            case NOV:
                randNum = rand() % 30;
                break;
        }

        // assign our randomly selected day to our structure
        days[loop].dayValues.day = randNum + 1;
    }
}

void convertDays(DAY *days) {
    int loop;
    short int day, month;
    const char *monthnames[] = {"January","February","March","April","May","June",
                "July","August","September","October",
                "November","December"};

    // loop through the days and assign a date string value
    for (loop = 0; loop < 7; loop++) {
        day = days[loop].dayValues.day;
        month = days[loop].dayValues.month;

        sprintf(days[loop].date,"%s %d, %d",monthnames[month],day,YEAR);
    }
}

void printDays(DAY *days) {
    int loop;

    // clear the screen
    clrscr();

    // print each day in our days array
    for (loop = 0; loop < 7; loop++) {
        printf("%s\n",days[loop].date);
    }
}

void _main(void) {
    DAY days[7];

    // seed the random number generator
    randomize();

    // pick 7 random days in the year 2002
    pickDays(days);

    // convert the numeric dates to a string
    convertDays(days);

    // print our randomly selected days
    printDays(days);

    // wait for user input before exiting the program
    ngetchx();
}

Step 9a - Compile and Run the Program

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

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

Step 9b - Program Analysis

I realize this program wasn't very useful, but I couldn't find any helpful examples that would illustrate these concepts in a useful manner. These concepts are so unique and specific, the only examples are either extremely complex and powerful, or extremely trivial. But, if it makes you feel any better, this example is much better than any of the ones I found in my C programming books, and at least I put some thought into it. Anyway, let's start the analysis.

typedef union {
	char date[25];
	
	struct {
		short int month;
		short int day;
	} dayValues;
} DAY;

The first thing to take a look at is the definition of the union we will be using. Remember that unions can be used exactly like structures, except that we can only use one of their variables. In this case, we can either use the character array, or the dayValues structure. I don't like using the union and struct keywords, so I always type define my structures and unions.

enum {JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC};

Here we define our enumeration. The constants are the month abbreviations, with JAN being month 0, and DEC being month 11 (1 - 12). I don't really see the need for a name, since we will be using the constants directly. Okay, now let's take a look at the _main() function.

DAY days[7];

Here is our DAY union array. We could have allocated it dynamically, but there is nothing wrong with doing it this way, and I don't want you to forget that we have other methods than dynamic memory allocation for using memory. Remember that we use unions just like we use structures.

// pick 7 random days in the year 2002
pickDays(days);

// convert the numeric dates to a string	
convertDays(days);
	
// print our randomly selected days
printDays(days);

The three functions we use in this program are pickDays(), convertDays(), and printDays(). We choose 7 random days in the pickDays() function. We convert the numeric values we randomly selected into a date string in the convertDays() function. Finally, we print our date strings in the printDays() function.

void pickDays(DAY *days) {
	int loop, randNum;
	
	for (loop = 0; loop < 7; loop++) {
		// select a random month
		randNum = rand() % 12;
		days[loop].dayValues.month = randNum;
		
		// get a random day taking into account the max number of 
		// days in the random month we just selected
		switch (randNum) {
			case JAN:
			case MAR:
			case MAY:
			case JUL:
			case AUG:
			case OCT:
			case DEC:
				randNum = rand() % 31;
				break;
			case FEB:
				randNum = rand() % 28;
				break;
			case APR:
			case JUN:
			case SEP:
			case NOV:
				randNum = rand() % 30;
				break;
		}

		// assign our randomly selected day to our structure
		days[loop].dayValues.day = randNum + 1;
	}
}

This function is not difficult. We loop through the union array and select a random month number and random day of that month. To determine how many days are in each month, we use the enumeration to see which month we selected, and select from the appropriate random number range, either 0-30, 0-27, or 0-29. To that number, we add one to adjust the date to something printable. I hope you remember how to use the switch statement. If not, take a look back at lesson 4 where we introduced them.

Although we cannot use more than one variable inside the union at a time, we can group variables using structures and access all the pieces of a structure inside the union, because a structure is considered as one piece. This is one of the advantages of using structures.

void convertDays(DAY *days) {
	int loop;
	short int day, month;
	const char *monthnames[] = {"January","February","March","April","May","June",
				"July","August","September","October",
				"November","December"};
	
	// loop through the days and assign a date string value
	for (loop = 0; loop < 7; loop++) {
		day = days[loop].dayValues.day;
		month = days[loop].dayValues.month;
	
		sprintf(days[loop].date,"%s %d, %d",monthnames[month],day,YEAR);
	}
}

The convert days function is just as easy as the pickDays() function. We loop through the union array and create a date string based on the integer values stored in the union's internal structure. It is a good idea to make a copy of the month and day variables from our structure before we run the sprintf() function, because it's possible we would start overwriting them when copying the month name before we got to the day value. So, we make a copy of the month and day, then create a string of MONTH NAME DAY, YEAR. The year is a constant defined to be 2002. Remember that once we write the date string, we have overwritten and lost the values contained in the dayValues structure.

void printDays(DAY *days) {
	int loop;
	
	// clear the screen
	clrscr();
	
	// print each day in our days array
	for (loop = 0; loop < 7; loop++) {
		printf("%s\n",days[loop].date);
	}
}

This function is so simple, it doesn't even really need explanation. All we do is clear the screen, and print the date strings by looping through the day union array. Since the _main() method pauses the program before exiting, we don't need to do that here.

Remember that since we have the date string in use, the dayValues member variables are gone. They have been overwritten.

Well, I know that wasn't the most enthralling, or even useful example program, but I think it demonstrated the concept. We will revisit these concepts later when we are doing more complex programs. For now, just keep these concepts in mind, because we will revisit them and use them in much more detail later.

Step 10 - Conclusion

We learned some very useful concepts today. We also learned about unions and enumerations. I'm kidding of course. Unions and enumerations are very useful, even if it doesn't seem so right now. They are reserved for tasks that are a bit more advanced than a simple example can cover.

You have been using enumerations since lesson 2. The KEY_XX constants you have been using to check for key presses are all defined in enumerations. They are located in the kbd.h file which is included automatically by including the tigcclib.h include header which we have been using for all our programs thus far.

You should be able to see how useful structures can be in programs you want to write right now. Unions and enumerations will become more important later on. Enumerations are very useful for defining arbitrary constant names, which you have probably already had need of, so you may be able to replace some of the things you are writing in your own programs with them already. They are a little less bulky than #define constants.

Have fun with structures. You are well on your way to becoming an expert C programmer. But experience is the key. Try to do things you are interested in. Break the program into small parts and write them as functions. Then string all the functions together. That's what I do.


Lesson 7: Structures, Unions, and Enumerations
Questions or Comments? Feel free to contact us.


 

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

Get Firefox!    Valid HTML 4.01!    Made with jEdit