Total grandeur of a total edifice, Chosen by an inquisitor of structures.
C provides the programmer with a rich set of data types. Through the use of structures, unions, and enumerated types, the programmer can extend the language with new types.
Suppose we are writing an inventory program for a warehouse. The warehouse is filled with bins that contain various parts. All the parts in a bin are identical, so we don’t have to worry about mixed bins.
For each bin, we need to know:
The name of the part it holds (string 30 characters long)
The quantity on hand (integer)
The price (integer cents)
In previous chapters, we have used arrays for storing a group of similar data types. However, in this example, we have a mixed bag: two integers and a string.
Instead of an array, we will use a new data type called 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 data type.
The general form of a structure definition is:
structstructure-name
{field-type
field-name;
/*comment
*/field-type
field-name;
/*comment
*/ . . . . }variable-name
;
For example, we want to define a bin to hold printer cables. The structure definition is:
struct bin { char name[30]; /* name of the part */ int quantity; /* how many are in the bin */ int cost; /* The cost of a single part (in cents) */ } printer_cable_bin; /* where we put the print cables */
This definition actually tells C two things. The first is what a
struct bin
looks like. This
statement defines a new data type that can be used in declaring other
variables. The variable printer_cable_bin
is also declared by this
statement. Because the structure of a bin
has been defined, we can use it to
declare additional variables:
struct bin terminal_cable_box; /* Place to put terminal cables */
The structure-name part of the definition may be omitted:
struct { char name[30]; /* name of the part */ int quantity; /* how many are in the bin */ int cost; /* The cost of a single part (in cents) */ } printer_cable_bin; /* where we put the print cables */
The variable printer_cable_bin
has been defined, but no
data type has been created. In other words, printer_cable_bin
is the only variable of
this structure you want in the program. The data type for this
variable is an anonymous structure.
The variable-name may also be omitted. The following example would define a structure type, but no variables:
struct bin { char name[30]; /* name of the part */ int quantity; /* how many are in the bin */ int cost; /* The cost of a single part (in cents) */ };
You can now use the new data type (struct bin
) to define variables such as
printer_cable_bin
.
In an extreme case, both the variable-name and the structure-name may be omitted. This syntax creates a section of correct, but totally useless code.
We have defined the variable printer_cable_bin
containing three named
fields: name
, quantity
, and cost
. To access them, we use the
syntax:
variable
.field
For example, if we just found out that the price of the cables went up to $12.95, we would do the following:
printer_cable_bin.cost = 1295; /* $12.95 is the new price */
To compute the value of everything in the bin, we can use the following:
total_cost = printer_cable_bin.cost * printer_cable_bin.quantity;
Structures may be initialized at declaration time by putting the list of elements in curly braces ({ }):
/* * Printer cables */ struct bin { char name[30]; /* name of the part */ int quantity; /* how many are in the bin */ int cost; /* The cost of a single part (in cents) */ } printer_cable_bin = { "Printer Cables", /* Name of the item in the bin */ 0, /* Start with empty box */ 1295 /* cost -- $12.95 */ };
A structure is used to define a data type with several fields. Each field takes up a separate storage location. For example, the structure:
struct rectangle { int width; int height; };
appears in memory. A union is similar to a structure; however, it defines a single location that can be given many different field names:
union value { long int i_value; /* integer version of value */ float f_value; /* floating version of value */ };
The fields i_value
and
f_value
share the same
space.
You might think of a structure as a large box divided up into several different compartments, each with its own name. A union is a box, not divided at all, with several different labels placed on the single compartment inside.
Figure 12-1 illustrates a structure with two fields. Each field is assigned a different section of the structure. A union contains only one compartment, which is assigned different 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. In other words, if you put
something in i_value
, assigning
something to f_value
wipes out the
old value of i_value
.
Example 12-1 shows how a union can be used.
/* * Define a variable to hold an integer or * a real number (but not both) */ union value { long int i_value; /* The real number */ float f_value; /* The floating-point number */ } data; int i; /* Random integer */ float f; /* Random floating-point number */ main() { data.f_value = 5.0; data.i_value = 3; /* data.f_value overwritten */ i = data.i_value; /* legal */ f = data.f_value; /* not legal, will generate unexpected results */ data.f_value = 5.5; /* put something in f_value/clobber i_value */ i = data.i_value; /* not legal, will generate unexpected results */ return(0); }
Unions are frequently used in the area of communications. For example, suppose we have a remote tape and we want to send it four messages: open, close, read, and write. The data contained in these four messages is vastly different depending on the message.
The open message needs to contain the name of the tape; the write message needs to contain the data to write; the read message needs to contain the maximum number of characters to read; and the close message needs no additional information.
#define DATA_MAX 1024 /* Maximum amount of data for a read and write */ struct open_msg { char name[30]; /* Name of the tape */ }; struct read_msg { int length; /* Max data to tranfer in the read */ }; struct write_msg { int length; /* Number of bytes to write */ char data[DATA_MAX]; /* Data to write */ }; struct close_msg { }; const int OPEN_CODE=0; /* Code indicating an open message */ const int READ_CODE=1; /* Code indicating a read message */ const int WRITE_CODE=2; /* Code indicating a write message */ const int CLOSE_CODE=3; /* Code indicating a close message */ struct msg { int msg; /* Message type */ union { struct open_msg open_data; struct read_msg read_data; struct write_msg write_data; struct close_msg close_data } msg_data; };
C allows the programmer to define her own variable types through the typedef statement. This statement provides a way for the program to extend C’s basic types. The general form of the typedef statement is:
typedeftype-declaration
;
where type-declaration is the same as a variable declaration except that a type name is used instead of a variable-name. For example:
typedef int count;
defines a new type count
that
is the same as an integer.
So the declaration:
count flag;
is the same as:
int flag;
At first glance, this statement is not much different from:
#define count int count flag;
However, typedefs can be used to define more complex objects that are beyond the scope of a simple #define statement. For example:
typedef int group[10];
A new type named group
now
exists, denoting an array of ten integers:
main() { typedef int group[10]; /* Create a new type "group" */ group totals; /* Use the new type for a variable */ for (i = 0; i < 10; i++) totals[i] = 0; return (0); }
One frequent use of typedef’s is in the definition of a new structure. For example:
struct complex_struct { double real; double imag; }; typedef struct complex_struct complex; complex voltag1 = {3.5, 1.2};
The enumerated data type is designed for variables that
contain only a limited set of values. These values are referenced by
name (tag). The compiler assigns each tag an integer value internally.
Consider an application, for example, in which we want a variable to
hold the days of the week. We could use the const declaration to create values for the
week_days
, as follows:
typedef int week_day; /* define the type for week_days */ const int SUNDAY = 0; const int MONDAY = 1; const int TUESDAY = 2; const int WEDNESDAY = 3; const int THURSDAY = 4; const int FRIDAY = 5; const int SATURDAY = 6; /* now to use it */ week_day today = TUESDAY;
This method is cumbersome. A better method is 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 an enum statement is:
enumenum-name
{tag-1
,tag-2
, . . .}variable-name
Like structures, the enum-name or the variable-name may be omitted. The tags may be any valid C identifier; however, they are usually all uppercase.
C implements the enum type as compatible with integer. So in C, it is perfectly legal to say:
today = 5; /* 5 is not a week_day */
although some compilers will issue a warning when they see this line. In C++, enum is a separate type and is not compatible with integer.
Sometimes you must convert one type of variable to another type. This is accomplished through the cast or typecast operation. The general form of a cast is:
(type
)expression
This operation tells C to compute the value of the expression, and then convert it to the specified type. This operation is particularly useful when you work with integers and floating-point numbers:
int won, lost; /* # games won/lost so far */ float ratio; /* win/lose ratio */ won = 5; lost = 3; ratio = won / lost; /* ratio will get 1.0 (a wrong value) */ /* The following will compute the correct ratio */ ratio = ((float) won) / ((float) lost);
Another common use of this operation is for converting pointers from one type to another.
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 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 storage layout for this structure can be seen in Figure 12-2. Each structure uses six bytes of storage (two bytes for each integer).
However, the fields list
and
seen
can only have two values, 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 that 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 */ };
In this example, we tell the compiler to use one bit for
list
, one bit for seen,
and 14 bits for number
. Using this method, we can pack our
data into only two bytes, as seen in Figure 12-3.
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 should not be used.
In Chapter 10, we needed to store character data and five status flags for 8,000 characters. In this case, using a different byte for each flag would eat up a lot of storage (five bytes for each incoming character). We used bitwise operations to pack the five flags into a single byte. Alternatively, a packed structure could have accomplished the same thing:
struct char_and_status { char character; /* Character from device */ int error:1; /* True if any error is set */ int framing_error:1;/* Framing error occurred */ int parity_error:1; /* Character had the wrong parity */ int carrier_lost:1; /* The carrier signal went down */ int channel_down:1; /* Power was lost on the channel */ };
Using packed structures for flags is clearer and less error prone than using bitwise operators. However, bitwise operators give the programmer additional flexibility. You should use the one that is clearest and easiest for you to use.
Structures and arrays can be combined. For example, suppose 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 */ }; const int MAX_LAPS = 4; /* we will have only 4 laps */ /* the time of day for each lap*/ struct time lap[MAX_LAPS];
We can use this structure as follows:
/* * Runner just passed the timing point */ lap[count].hour = hour; lap[count].minute = minute; lap[count].second = second; ++count;
This array can also be initialized at run time.
Initialization of an array of structures is similar to the initialization of multi-dimensional arrays:
struct time start_stop[2] = { {10, 0, 0}, {12, 0, 0} };
Suppose 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 zip code order for actual mailings. Our mailing list structure looks like this:
struct mailing { char name[60]; /* Last name, first name */ char address1[60];/* Two lines of street address */ char address2[60]; char city[40]; char state[2]; /* Two character abbreviation */ long int zip; /* Numeric zip code */ };
We can now declare an array to hold our mailing list:
/* Our mailing list */ struct mailing list[MAX_ENTRIES];
The state field is two elements because it is designed to hold
two characters. The field is not a string because we have not
allocated enough space for the end of string character (' '
).
Structures and unions are some of the more powerful features of the C language. No longer are you limited to C’s built-in data types—you can create your own. As we will see in later chapters, structures can be combined with pointers to create very complex and powerful data structures.
Exercise 12-1: Design a structure to hold the data for a mailing list. Write a function to print out the data.
Exercise 12-2: Design a structure to store time and date. Write a function to find the difference between two times in minutes.
Exercise 12-3: Design an airline reservation data structure that contains the following data:
Flight number
Originating airport code (three characters)
Destination airport code (three characters)
Starting time
Arrival time
Exercise 12-4: Write a program that lists all the planes that leave from two airports specified by the user.
18.191.223.123