Wednesday, November 10, 2010

Structures Unions and Enumerations


Structures


Out of the three data types structures are the most important, so I'll start by going over them. Structures are similar to arrays in that they hold data. However, a structure's data is referenced by name rather than numerical index. The data inside structures is private; each new structure provides a new scope. Here is an example of how to declare a structure with a couple variables of that structure type:

struct {
  int ident;
  char name[LEN + 1];
  int val;
} part1, part2;

part1 and part2 are now both the exact same data-type, and each have the same members as each other. A structure can also be initialized while it is declared, like so:

struct {
  int ident;
  char name[LEN + 1];
  int val;
} part1 = {1, "Mobo", 200},
  part2 = {2, "PSU", 75};

Alternatively you can use the designator operator "." to assign values to structure members, like so:

struct {
  int ident;
  char name[LEN + 1];
  int val;
} part1 = {.ident = 1, .name = "Mobo", .val = 200},
  part2 = {.ident = 2, .name = "PSU", .val = 75};

It doesn't matter where in the initializer that a member is given a value if it is given a value by a designator. However, is there is no designator, then the value will be given to the corresponding member in the order from top to bottom of the structure, and from left to right of the initializer. LEN is just a constant defined in a preprocessor directive, and could be whatever the coder chose. You must add one to compensate for an end of line null character.

Structures of the same type can be copied with the = operator. Although, you cannot use the == or != whether or not structures are of the same type. part1 and part2 from the above example are both the exact same structure type. However, in the following example, part1 and part2 are not the exact same, and cannot be used with the = operator:

struct {
  int ident;
  char name[LEN + 1];
  int val;
} part1;

struct {
  int ident;
  char name[LEN + 1];
  int val;
} part2;

Since you can use the assignment operator with structures it is a slight surprise that arrays within structures will be copied from one to another. This can be useful for creating "dummy" structures that are used just for the purpose of copying one array to another.


So far I've shown two examples of declaring structures without using a structure tag. Once you've created a tag for a structure, you can treat a structure as a data type, like so:

struct pc_Hardware{
  int ident;
  char name[LEN + 1];
  int val;
};

struct pc_Hardware part1 =  {.ident = 1, .name = "Mobo", .val = 200};

It is also possible for a function to use a structure as an argument. However, using a structure as an argument can cause a lot of overhead. It's actually usually better to pass a pointer to a structure to a function, and then use the pointer to modify the members as needed. Functions can also return structures. Similarly, you might have a function return a pointer to a structure instead of an actual structure.

A structure can also be nested within another structure, as a data member. This is useful to create "categories" of members within a structure. Suppose you have a structure that holds data about computer hardware, and there are a total of four different brands of hardware. You could have each member of the structure represent a type of hardware, and within each structure you could hold information about the hardware brand.


Unions


A union is similar to a structure in all ways, except in that the compiler will allocate enough space for only the largest of all the union members. This means that all members of a union will all share the same space in memory. Altering one member of a union will overwrite the data of all others. This means that only one member of a union can hold data at any given time. Unions are usually used as a means of saving space. In my last computer hardware example, a union could have been used in place of a structure for holding the names of the brand, as the hardware usually wouldn't be made by two different companies at once (as long as the brand name doesn't go over the LEN limit, which is just a constant that can be defined as any amount you want to specify).


Arrays of unions can also be useful. Suppose you need an array that can hold either integers or floats. You can't simply create an array that can hold either, since an array must be universally one type. You can however create an array of unions rather easily. Consider this example:

union {
  int i;
  float f;
} Number;

Number a[100];

Now the array a can hold in each element either a float or integer type. Here is how one could assign either a float or integer into the array:

a[0].i = 1;
a[1].f = 10.01;

The biggest problem with using unions is that there is no way to tell which data member was last altered, and thus knowing which member actually holds a value. Often times programmers will nest a union within a structure, having the structure have one other data member. This other member within the structure will act as a "tag field" so that the programmer can keep track of which member holds a value.


Enumerations


Enumerations are good for creating new definitions of data types that have only a few different possible values. An enumeration would be good for creating a boolean variable, or perhaps a variable to represent suit in a deck of cards. The benefits of using an enumeration over preprocessor directives for defining such things is that anyone reading your code can easily see all the possible variants of your variable, and see that each one is of the same type. Enumerations can increase readability and code cleanliness.

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

The above example shows how set up an enumeration for the different suits of a deck. This is much better than using #define directives, in that it obeys C's scope rules; an enumeration declared within a function won't affect the rest of the program. The members of this enumeration can be used just the same as #define directives.


The members of an enumeration are actually integers. CLUBS is equivalent to the integer 0, and HEARTS 3. One could use the suits defined in the above enumeration just as if SPADES were the integer 1, and so on. You can also specify exactly the integer amount that a member will equal, like so:

enum suit { CLUBS, SPADES = 7, DIAMONDS, HEARTS = 20 } s;

s = CLUBS;
s++;

CLUBS would default to 0, although DIAMONDS would default to 8, which is one more than the previous member. s was assigned the value of CLUBS (zero), then in the next line was incremented to 1.


Enumerations are perfect creating "tag fields" for unions to determine which of the members of a union were last modified. Here is an example:

struct {
  enum { INT, FLOAT } kind;
  union {
    int i;
    float f;
  } u;
} Number;

The above struct can be used in our original union example where an array of unions was created. This structure has advantages in that the programmer will be able to tell whether or not each array element holds an integer or float with the "tag field" kind.


Sources:  C Programming: A Modern Approach 2nd Ed. (particularly chapter 16)

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.