17 Beginner's Lesson 11: Advanced Types

Lesson 11 attached.
Greetings,

In lesson ten we took a brief look at the bitwise operators ( &, |,
^, ~ ). In this lesson we'll look at Advanced Types: struct, union,
typedef, and enum; as well as Bit Fields or Packed Structures, and
arrays of structures. We're adding four new C keywords to our bag of
tools!

Advanced Types
--------------
Through the use of structures, unions, and enumerated types, the C
programmer can extend the language with new types.

Structures
----------
An array is used for storing a group of similar data types. But what
if you want to store different data types, such as integers and strings?
Use a structure! In an array, all the elements are of the same type and
are numbered. In a structure, each element or "field" is named and has
its own type.

The general form of a structure is:

struct struct_name {
field_type field_name /* comment */
field_type field_name /* comment */
....
} variable_name

Here's an example which defines a structure for a simple parts database:

struct part {
int number; /* part number */
char name[30]; /* name of part */
} item;

This definition tells C two things. First, it tells C what a `struct part'
looks like. This statement defines a new data type that can
be used in declaring other variables. The variable `item' is also
declared by this statement. Since the structure of a `part' has been
defined, we can use it to declare additional variables.

struct part list; /* parts list */

The struct_name part of the definition may be omitted:

struct {
int number; /* part number */
char name[30]; /* name of part */
} item;

Now the variable `item' still has to be defined, but no data type
has been created. The data type for this variable is an "anonymous
structure".

The variable_name may also be omitted:

struct {
int number; /* part number */
char name[30]; /* name of part */
};

In an extreme case, both the variable_name and the struct_name may be
omitted. This creates a section of correct, but totally useless code.

We've defined the variable `item' containing two named fields: number
and name. To access them we use the syntax:

variable.field

For example, to add a number to the numbers we can use this:

item.number = 1234;

Here is an example:
/*************************************************/
/* part.c */
#include <stdio.h>

struct part {
int number;
char name[30];
} item;

int main(void)
{
item.number = 1234;
strcpy(item.name, "Disk drive");

printf("Part number: %d\n", item.number);
printf("Part name: %s\n", item.name);

return 0;
}
/*************************************************/

Structures may be initialized at declaration time by putting the list
in curly braces:

struct part {
int number;
char name[30];
} item = { 1234, "Disk drive" };

A structure is used to define a data type with several fields. Each
field takes up a separate storage location in memory. For example,
the structure in this program uses 8 bytes of memory:

/*************************************************/
/* struct-size.c */
#include <stdio.h>
int main(void)
{
struct rectangle {
float width;
double height;
} size;

printf("%d <-struct\n",sizeof(size)); /* size of struct */
printf("%d <-field1\n",sizeof(size.width)); /* size of field */
printf("%d <-field2\n",sizeof(size.height)); /* size of field */

return 0;
}
/*************************************************/

Here is what the output looks like on my computer:
$ ./struct-size
12 <-struct
4 <-field1
8 <-field2

Unions
------

A 'union' is similar to a structure; however, it defines a single
location that can be given many different field names. In a structure,
the fields do not interact. Changing one field does not change any
others. In a union, all fields occupy the same space, so only one may
be active at a time. Here is an example of a union:

/*************************************************/
/* union-size.c */
#include <stdio.h>
int main(void)
{
union rectangle {
float width;
double height;
} size;

printf("%d <-union\n",sizeof(size));
printf("%d <-field\n",sizeof(size.width));
printf("%d <-field\n",sizeof(size.height));

return 0;
}
/*************************************************/

The output from this program is:

$ ./union-size
8 <-union
4 <-field
8 <-field

We can easily see the difference in size of a struct and union.

typedef
-------

C allows the programmer to define her own variable types using the
typedef statement. The general form of the typedef statement is:

typedef type-declaration

where type-declaration is the same as a variable declaration except
a type name is used instead of a variable name:

typedef int boolean;

defines a new type `boolean' that is the same as an integer. So the
declaration:

boolean flag;

is the same as:

int flag;

It really doesn't seem all that different from:

#define boolean int
boolean flag;

However, typedef can be used to define more complex objects that are
beyond the scope of a simple #define statement:

typedef int group[10];

There is now a new type named `group' denoting an array of ten integers:

/*************************************************/
int main(void)
{
typedef int group[10]; /* create new type "group" */
group totals; /* declare a variable with the new type */
for (i = 0; i < 10; i++)
totals[i] = 0;
return 0;
}
/*************************************************/

enumerated type
---------------

The enumerated type `enum' is designed for variables that contain only
a limited set of values. These values are referenced by a tag. The
compiler assigns each tag an integer value internally. We could use a
#define to create values; for example, week_day:

#define week_day int /* define the type for week_day */
#define SUNDAY 0
#define MONDAY 1
#define TUESDAY 2
#define WEDNESDAY 3
#define THURSDAY 4
#define FRIDAY 5
#define SATURDAY 6

now to use it:

week_day today = TUESDAY;

But that is cumbersome. It is better to use the enum type:

enum week_day {SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
SATURDAY};

now use it:

enum week_day today = TUESDAY;

The general form of enum is:

enum enum_name { tag1, tag2, . . .} variable_name

enum tags are usually all uppercase. enum variables can't be used to
index an array.

Bit Fields or Packed Structures
-------------------------------

Packed structures allow us to declare structures in a way that takes up
a minimum amount of storage. For example, the following structure takes
up a total of six bytes (on a 16-bit machine):

struct item {
unsigned int list; /* TRUE if item is in the list */
unsigned int seen; /* TRUE if this item has been seen */
unsigned int number; /* item number */
};

The layout for this structure looks like this:

0| 1| 2| 3| 4| 5| 6| 7| 8| 9| 10| 11| 12| 13| 14| 15|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| | | | | | | list | | | | | | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| | | | | | | seen | | | | | | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| | | | | | | number| | | | | | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

Each structure uses six bytes of storage (two bytes for each integer).

However, the fields `list' and `seen' can only have two values, 0 and 1.
So only one bit is needed to represent them. We never plan on having
more than 16383 items (0x3fff or 14 bits). We can redefine this
structure using bit fields, so it takes only two bytes, by following
each field with a colon and the number of bits to be used for that
field:

struct item {
unsigned int list:1; /* TRUE if item is in the list */
unsigned int seen:1; /* TRUE if this item has been seen */
unsigned int number:14; /* item number */
};

Now we can pack our data in only two bytes:

| 0 | 1 | 2| 3| 4| 5| 6| 7| 8| 9| 10| 11| 12| 13| 14| 15|
+------+------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| list | seen | | | | | number| | | | | | | | |
+------+------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

Packed structures should be used with care. The code to extract data
from bit fields is relatively large and slow. Unless storage is a
problem, packed structures shouldn't be used. This might be a good
technique to use on embedded Linux problems where resources are small?

Arrays of Structures
--------------------

Structurea and arrays can be combined. For example, what if we want to
record the time a runner completes each lap of a four-lap race? We
define a structure to store the time:

struct time {
int hour /* hour -- 24 hour clock */
int minute; /* 0-59 */
int second; /* 0-59 */
};
#define MAX_LAPS 4 /* only 4 laps */
struct time lap[MAX_LAPS];

We can use the structure like this:

/*
** runner just past the timing point
*/
lap[count].hour = hour;
lap[count].minute = minute;
lap[count].second = second;
count++;

The array can be initialized at run time.

struct time start_stop[2] = {
{10, 0, 0},
{12, 0, 0}
};

What if we want to write a program to handle a mailing list?
Mailing labels are 5 lines high and 60 characters wide. We
need a structure to store names and addresses. The mailing
list will be sorted by name for most printouts, and sorted in
postal zip code order for actual mailings. It looks like this:

struct mailing {
char name[60]; /* last name, first name */
char address1[60]; /* two lines of */
char address2[60]; /* street address */
char city[40];
char state[2]; /* abbreviated state */
long int zip; /* numeric zip code */
};

We can now declare an array to hold our mailing list:

struct mailing list[MAX_ENTRIES];

/**********************************************************/
/* books.c -- example of using an array of structures */
#include <stdio.h>

#define MAX_TITLE 30 /* line length for book title */
#define MAXAUTHOR 30 /* line length for book author */
#define MAX_BOOKS 3 /* maximum number of books */

struct book {
char title[MAX_TITLE]; /* book title */
char author[MAXAUTHOR]; /* book author */
int year; /* year published */
};

int main(void)
{
struct book libry[MAX_BOOKS]; /* array of book structures */

char line[81];
int count = 0;
int index;

while (count < MAX_BOOKS)
{
printf("Please enter book title: ");
fgets(line, sizeof(line), stdin);
line[strlen(line)-1] = '\0'; /* remove '\n' from line */
strcpy(libry[count].title, line);

printf("Please enter author: ");
fgets(line, sizeof(line), stdin);
line[strlen(line)-1] = '\0';
strcpy(libry[count].author, line);

printf("Enter year published: ");
fgets(line, sizeof(line), stdin);
sscanf(line, "%d", &libry[count].year);

if (count < MAX_BOOKS) count++;
}
printf("Here is the list of your books:\n");
for(index = 0; index < count; index++)
printf("%s by %s published %d\n", libry[index].title,
libry[index].author,
libry[index].year);
return 0;
}
/**********************************************************/

Later on, we'll learn how to combine structures and pointers to create
very complex and powerful data structures.

No exercises for this lesson. Play with this stuff and share anything
interesting that you come up with! 8^D

Lesson 12 will be an introduction to simple pointers.

Happy Programming!
--
K