The second aggregate data type is the structure: a group of related data items under one name. While array elements are all the same type, the elements of a structure, known as its “members,” can be of different types.
Structures are equivalent to records in QuickPascal or user-defined types in QuickBasic. As in those languages, the ability to group different types in the same construct provides powerful, very flexible data-handling capabilities.
We'll write a simple program to demonstrate the basics of structures. Suppose you want to write a payroll program that records these facts about an employee:
Name
Number of months of service
Hourly wage
Each of these data items requires a different data type. The name can be stored in a string (character array), while an integer will do for the months of service. The hourly wage may contain a fraction; we'll store it in a floating-point variable.
Although each of these variables has a different type, we can group all of them in a single structure. The EMPLOYEE.C program below contains the structure.
/* EMPLOYEE.C: Demonstrate structures. */
#include <stdio.h>
#include <string.h>
struct employee
{
char name[10];
int months;
float wage;
};
void display( struct employee show );
main()
{
struct employee jones;
strcpy( jones.name, "Jones, J" );
jones.months = 77;
jones.wage = 13.68;
display( jones );
}
void display( struct employee show )
{
printf( "Name: %s\n", show.name );
printf( "Months of service: %d\n", show.months );
printf( "Hourly wage: %6.2f\n", show.wage );
}
Here is the output of the EMPLOYEE.C program:
Name: Jones, J
Months of service: 77
Hourly wage: 13.68
Since a structure can (and normally does) contain different data types, creating it is a little more complicated than making an array or simple variable. Before you can create a structure variable, you must declare a structure type that tells the compiler how many members the structure contains and what types they are.
A structure-type declaration starts with the keyword struct, which is followed by a list of the structure's members enclosed in braces. Between the struct and the list of members, you can also specify a “structure tag”—a name that other parts of the program can use to refer to the type.
The structure declaration from EMPLOYEE.C,
struct employee
{
char name[10];
int months;
float wage;
};
creates a “template” for an employee structure that structure variables
of this type can use. It's as if you created a brand new data type, tagging it
employee. Figure 4.6 illustrates the employee structure type.
Once you have declared a structure, you can create variables of that type using the structure tag. Each variable can contain values of the types defined in the structure type. In EMPLOYEE.C, the statement
struct employee jones;
declares a structure variable of the type employee named jones. The struct states that the variable is a structure. The employee tag specifies the variable's structure type, and jones is the variable's name.
You can also declare the variable in the same statement that declares the structure type. The following code declares the employee structure type and a variable of that type named jones:
struct employee
{
char name[10];
int months;
float wage;
} jones;
The variable name (jones) appears at the end of the declaration.
Summary: Use the member-of operator (.) to specify structure members.
You specify structure members by name, using the “member-of” operator (.) to separate the variable name and the member name. These are the names of the members of the jones structure variable in EMPLOYEE.C:
jones.name
jones.months
jones.wage
Like other variables, structure variables should be initialized before use. After jones is declared in EMPLOYEE.C, the statements
strcpy( jones.name, "Jones, J" );
jones.months = 77;
jones.wage = 13.68;
initialize the members of the jones variable. The first statement initializes the jones.name member by calling the strcpy (“string copy”) library function; for a description of this function, see Chapter 11, “Input and Output.”
Figure 4.7 shows how the jones structure is stored in memory. Again, since computer memory is linear, the members of the structure are laid out end-to-end.
You can initialize a structure when you declare it. The following code would
perform both operations in EMPLOYEE.C:
struct employee jones =
{
"Jones, J",
77,
13.68
};
This code declares the jones structure variable and lists the initializing value for each of its members.
A structure member can be treated like any other variable of its type. You can assign a value to it, change its value, and so on. For instance, the statement
jones.months = 83;
would change the value of the jones.months member in EMPLOYEE.C.
Summary: Assigning one structure to another copies the entire structure.
You can also assign an entire structure to another structure of the same type. This copies the entire contents of the first structure to the second. You might do this to save time when creating a new structure whose contents differ only slightly from those of an existing structure.
To illustrate, let's modify the EMPLOYEE.C program. Say you have a second employee named Lavik whose wage rate and months of service are the same as those of Jones and you want to create a second structure. You could begin by declaring a second employee structure variable named lavik in this fashion:
struct employee lavik = jones;
Now the members of the lavik structure contain the same data as the members of the jones structure. The lavik.name member contains the string Jones, J, the lavik.months member contains the value 77, and the lavik.wage member contains the value 13.68. You could add the statement
strcpy( lavik.name, "Lavik, B" );
to place a new string in the lavik.name member.
Summary: Structure variables can be passed as function arguments.
When you pass a structure name to a function, the function creates a local structure variable of that type. Like all local variables, the new variable is private to the function that includes it.
For example, if you add the statements
strcpy( show.name, "King, M" );
printf( "%s\n", show.name );
to the end of the display function in EMPLOYEE.C, then a new string is copied into the show.name member of the function's structure variable. The printf statement in the second line prints
King, M
Since this structure is local to the display function, the change doesn't affect the structure defined in the main function. If you add the statement
printf( "%s\n", jones.name );
to the end of the main function, the program prints
Jones, J
The original structure is unchanged.
While you can pass a structure name to a function as we did above, it's more common to pass the function a pointer to the structure. This not only permits the function to access a structure defined elsewhere in the program, but it conserves memory (since the function doesn't create a local copy of the structure). For an explanation of how to access structures using pointers, see Chapter 9, “Advanced Pointers.”
Summary: An array of structures is a group of structures of the same type.
Since it's rare for a company to have a single employee, a more practical version of the EMPLOYEE.C program would have an array of structures—one structure per employee. The concept may sound intimidating, but this is a common use of structures.
The following statement declares a 50-element array named payroll, with each element a structure of the type employee:
struct employee payroll[50];
To specify members in such an array, you combine array notation and structure notation, giving the array name, a subscript, and a member name. For instance, the name
payroll[0].months
specifies the months member of the first structure in the payroll array. The first part of the name (payroll[0]) contains the array name and subscript that identify the structure. The second part (months) identifies the member within that structure.
Figure 4.8 depicts the first three elements of the payroll array.
Once you grasp the basic idea, it's easy to imagine practical uses for an array of structures. Many programs, from an address book to a library card catalog, might use a structure to store different types of information about an individual item, then store many such structures in an array.
As noted earlier, a structure can contain members of any data type—including other structures. So you can create a structure of structures: a structure whose members are structures.
To illustrate, suppose you write a group of functions that draw various kinds of graphic windows and message boxes. You could define a small structure something like the following:
struct title
{
char text[70]; /* Title text */
int color; /* Color of title text */
short justify; /* Left, center, or right */
};
to aid in drawing titles. The title structure's three members specify the title's text, its color, and how its text is justified.
Once the title structure is defined, you can make it part of other, larger structures that use titles. If you define a window structure type to draw windows, for example, that structure could include a title along with other structure members:
struct window
{
struct title wintitle; /* Window title */
/* Other structure members go here... */
};
In this structure type, the title member is named wintitle.
You specify members of such structures using member-of operators and
the appropriate names. If you create a variable of the window type named
mywindow, the name
mywindow.wintitle.color
specifies the color member of the wintitle member of the mywindow structure.
A “bit field” is a specialized structure that provides a way to manipulate individual bits or groups of bits. One use for this advanced feature is to access hardware addresses such as the computer's video memory.
Summary: The members of a bit-field structure are groups of bits.
You declare and use a bit-field structure much as you would any other structure. The difference is that every one of its members must be a bit or group of bits. You can't include other data types in a bit field.
The following statement declares a bit-field structure type with the tag SCREEN:
struct SCREEN
{
unsigned character : 8;
unsigned fgcolor : 3;
unsigned intensity : 1;
unsigned bgcolor : 3;
unsigned blink : 1;
} screenbuf[25][80];
The colons in the declaration tell QuickC these are bit fields rather than normal structure members. The number following each colon tells how many bits the field contains. In the SCREEN type the character member has 8 bits, intensity has 1 bit, and so on. Figure 4.9 illustrates the SCREEN type.
Figure 4.10 illustrates memory allocation for the SCREEN type. The members of the SCREEN type mirror the arrangement of bits in screen memory.
Take another look at the structure declaration. In addition to declaring a structure type, the statement declares a two-dimensional array variable screenbuf, of the same structure type. You could use this array as an alternate video buffer. Many graphics programs use a similar arrangement to switch between an alternate video buffer and the computer's video memory.
The five members of the SCREEN type happen to take up a full int (two bytes, on DOS machines). A bit field need not fill up a byte or int; the bit field can contain as many bits as you need up to the maximum number of bits for the field's base type. The base type for each field in the example is unsigned (unsigned int), so each field can contain a maximum of 16 bits.
The members of a bit-field structure are accessed with the structure-member operator—like other structure members. For instance, the name
screenbuf[13][53].blink = 1;
specifies the blink member of element 13, 53 of the screenbuf array.
The range of values you can assign to a bit-field member depends on the member's size. Since the blink member of the SCREEN type contains one bit, blink is limited to the value 0 or 1. The fgcolor member contains three bits and can have any value from 0–7.