Structures in C — structs

Introduction

Most modern computer programming languages provide mechanisms for grouping data together into logical units. Already in C, we have seen the use of 1-dimensional arrays to store and retrieve data of the same type using an index. This course segment introduces two additional mechanisms for collections: a struct to store data of different types, and 2-dimensional arrays to store tables. This session discusses the struct concept; the next session covers two-dimensional arrays, and the third session puts the ideas of a struct and 2-dimensional arrays together to address the processing of image data.


The struct Concept

C groups variables together into a "structure", called a struct. Conceptually, structs allow a programmer to group related data together; pragmatically, the programmer needs to be able to work with the collection of data at some times and with individual pieces at other times. Using a struct, you can simplify parameters, organize data, and protect information.

In C, a structure definition has three basic parts:

Structures in Other Programming Languages

Although most modern programming languages allow the grouping of data, terminology and details differ.


For example, a program for keeping track of students might use the following collection of variables:

    struct student {
        int number;
        double testGrades[2];
        double grade;
    };

Jargon

The struct is named student while its members or fields are number, testGrades, and grade. The name of a struct is also called a tag.


Declaring a Variable or an Array as a struct


Once a struct is defined, variables and arrays are declared following a reasonably familiar syntax:

    struct student hannah;    
    struct student csc161[30];

In this example,

With these declarations, space is allocated on the run-time stack for the variable or array, but the fields are not initialized.


Alternatively, C allows a struct to be initialized when it is declared, by giving values for each field in order, just as with number variables and with arrays, .

For example, the following declaration initializes a struct student jackson, with student number 2718281828, with two grades (98.6 and 83.4) for the testGrades array, and a numeric value (91.0) for grade

  struct student jackson = {2718281828, {98.6, 83.4}, 91};  

Using a struct

Once a struct variable is declared, it can be used within a program from at least two perspectives:

For example, given the declaration struct student hannah from above,


Extended Example: Using a struct for Robot Motion

To illustrate the use of a struct as a collection of related data, consider how to package data related to the movement of a robot. Many MyroC motion commands require two basic values: a speed and a duration. Since these parts relate to a single motion, it is natural to define them as a package:

  struct movement{
    double speed;
    double time;
  };

Version 1: Working with individual movement fields

With this definition, a program could initialize a robot's movement data and then utilize the pieces to command a robot to move forward. The full program is available as square-move-1.c.

  rConnect ("/dev/rfcomm0");

  /* Declare and initialize data for one robot movement */
  struct movement action = {0.8, 2.0};

  /* Command to move forward according to the speed and time stored
     in the struct */
  rForward ( action.speed, action.time);

  /* beep after movement */
  rBeep (1, 600);

  rDisconnect();

Notes


Version 2: structs as parameters

Although Version 1 of this program illustrates some syntactic elements when using a struct, Version 1 takes little advantage of the capability to group data elements.

In Version 2, the struct construction allows the main procedure to highlight the program's logical overall structure. In this approach, the variable action represents an overall robot movement, and the main procedure outlines the high-level steps required for processing this movement.


int main()
{
  rConnect ("/dev/rfcomm0");

  /* Declare information for one robot struct movement */
  struct movement action;

   /* Initialize, print, and execute a robot movement */
  initialize (&action);  // address allows this variable to change

  printMove (action);

  moveRobot (action);

  /* beep after movement */
  rBeep (1, 600);

  rDisconnect();

  return 0;
} // main

Turning to the procedures for printMove and moveRobot, a movement parameter provides data as a collection. Within each procedure, the data fields are used separately as needed by printf or by MyroC functions.

/* print values in struct movement struct */
void printMove (struct movement move)
{
  printf ("robot action:  time = %lf,   speed = %lf\n", 
          move.time, move.speed);
}

/* move robot */
void moveRobot (struct movement move)
{
  rForward (move.speed, move.time);
}

To initialize the action variable in the main procedure, the address &action is passed to the initialize procedure. Within initialize, the parameter move is declared as a pointer to a struct: struct movement *.

/* set speed and action for a move by the Scribbler 2 robot */
/* must pass address of pointer to get values out of procedure */
void initialize (struct movement *move)
{
  (*move).speed = 0.8;
  (*move).time = 2.0;
}

Given the struct pointer, *move references the memory allocated for the action variable in main. That is, *move refers to the action struct.

Since *move is a struct, (*move).time and (*move).speed refer to the fields within the struct.

Summary:


The full, version 2 program is available as square-move-2.c.


Version 3: Use of a typedef declaration

Although version 2 of our program works fine, the frequent repetition of the phrase struct movement can feel somewhat tedious.

As an alternative, we can define a new data type to describe our student information, using the instruction:

   typedef struct {
     double speed;
     double time;
   } movement_t;
and then declare our variables using this new type, with instructions like:
   movement_t action; // a typedef allows a simple, clean declaration

Similarly, procedure headers from Version 2 can now be simplified somewhat.

   void initialize (movement_t *move)
   void printMove (movement_t move)
   void moveRobot (movement_t move)\

You might find it helpful to think of the typedef instruction as giving a "blueprint" for the creation of a movement_t struct variable, while the declarations cause the "construction" of variables having type movement_t by setting aside memory.

The full, version 3 program, using the typedef construction, is available as square-move-3.c.

In the definition of movement_t, we have chosen not to give a name after the keyword struct. Although we could provide a name:

   typedef struct movement {
     double speed;
     double time;
   } movement_t;

the purpose of the typedef is to define a new type movement_t, and there is no need to refer to this type of structure as struct movement when the simple label movement_t is simpler.

A Common Naming Convention

Although any almost name can be specified as a type using a typedef statement, a common approach uses a struct followed by an underscore and a t. For example, in the movement example,

With this convention movement_t is a new type for a movement.


Version 4: An array of movements

As with int, float, and double types, C allows arrays of structs. And, as with all arrays, an array variable identifies the base address of a sequence of struct elements.

As an example, consider program square-move-4.c that defines and uses an array of eight movements. Using the same typedef for movement_t defined above, the heart of the main procedure follows:


  rConnect ("/dev/rfcomm0");

  int i; 

  /* Declares an array of 8 movement structs */
  movement_t actions[8];

  /* Loop to initialize the values in the structs */
  for (i = 0; i < 8; i++)
    {
      /* Sets a constant speed for all actions */
      actions[i].speed = 1 - (.1 * i);

      /* Sets the time for each action to increase by half a second */
      actions[i].time = (i / 2.0) + 0.5;
    }

  /* Loop to move the Scribbler according to the structs stored times
     and speed(s). */
  for (i = 0; i < 8; i++)
    {
      /* Command to move forward according to the speed and time stored
         in the struct in the array position i */
      rForward ( actions[i].speed, actions[i].time);

      /* Command to make a (roughly) 90 degree turn to the left */
      rTurnLeft (1, 0.8);
    }

  rBeep (1, 600);

  rDisconnect();

Version 5: structs, arrays, and functions

Once again, Version 4 utilizes an array, but the program takes little advantage of a struct as a collection of data.

Version 5 uses functions to organize processing. For initialization and printing, square-move-5.c uses similar functions, with struct parameters, that we used in Version 3. For illustration in this discussion, the moveRobot function takes the entire array as parameter. In this version, the main has these elements:


int main()
{
  rConnect ("/dev/rfcomm0");

  int i; 

  /* Declares an array of 8 movement structs */
  movement_t actions[8];

  /* Loop to initialize the values in the structs */
  for (i = 0; i < 8; i++)
    {
      initialize (&actions[i], i);
    }

  /* Loop to print the Scribbler times and speed(s). */
  for (i = 0; i < 8; i++)
    {
      printMove (actions[i]);
    }

    moveRobot (actions, 8);

  rBeep (1, 600);

  rDisconnect();

  return 0;
} // main

In this program, a function performs each primary processing step, as described in Version 4.

/* set speed and action for a move by the Scribbler 2 robot */
/* must pass address of pointer to get values out of procedure */
void initialize (movement_t *move, int index)
{
  (*move).speed = 1 - (.1 * index);
  (*move).time = (index / 2.0) + 0.5;
}

/* print values in movement struct */
void printMove (movement_t move)
{
  printf ("robot action:  time = %lf,   speed = %lf\n", 
          move.time, move.speed);
}

/* move robot */
void moveRobot (movement_t move [], int size)
{
  int i;
  for (i = 0; i < size; i++)
    {
      rForward (move[i].speed, move[i].time);
      /* Command to make a (roughly) 90 degree turn to the left */
      rTurnLeft (1, 0.8);
    }
}

Version 6: struct arrays and initialization

As a final example illustrating robot motion with structs and arrays, consider a program in which each function operates on the entire array of movements.

In this version square-move-6.c, the main program is particularly streamlined

int main()
{
  rConnect ("/dev/rfcomm0");

  int i; 

  /* Declares an array of 8 movement structs */
  movement_t actions[8];

  initSpeedTime(actions);
  moveScribbler (actions);
  rBeep (1, 600);

  rDisconnect();

  return 0;
} // main

Within the function initSpeedTime and moveScribbler, the actions array comes in as a parameter. Thus, inside the functions, processing proceeds as with any declared array of structs.

void initSpeedTime (movement_t actions []) 
{ int i;
  /* Loop to initialize the values in the structs */
  for (i = 0; i < 8; i++)
    {
      /* Sets a constant speed for all actions */
      actions[i].speed = 1 - (.1 * i);

      /* Sets the time for each action to increase by half a second */
      actions[i].time = (i / 2.0) + 0.5;
    }
}

void moveScribbler (movement_t actions [])
{ int i;
  /* Loop to move the Scribbler according to the structs stored times
     and speed(s). */
  for (i = 0; i < 8; i++)
    {
      /* Command to move forward according to the speed and time stored
         in the struct in the array position i */
      rForward ( actions[i].speed, actions[i].time);

      /* Command to make a (roughly) 90 degree turn to the left */
      rTurnLeft (1, 0.8);
    }
}

As described earlier in this reading:


A New Example: Representing Time with a Struct

As a completely separate example of a struct, the following structure may be used to represent a time value in hours, minutes and seconds format (e.g., 12:34:56.123):

        typedef struct {
            int hours;
            int mins;
            double secs;
        } timeinfo_t;

The timeinfo_t identifier is the struct "tag". A new type called timeinfo_t is created. We did not call it time, because there is already a C library function called time.

Structure types may be used as return types or argument types in functions. A function that converts time values given in seconds (e.g., 12345.67) to time values given in hh:mm:ss.sss format might have the prototype:

    timeinfo_t convertTime( double realTime )

and would look like:

    timeinfo_t convertTime( double realTime )
    {
        timeinfo_t result;
        .
        .
        .
        return result;
    }


created 11 April 2008 by Marge Coahran
revised 3 August 2011 by Erik Opavsky
full revision 14 November 2011 by Erik Opavsky
minor editing 14 November 2011 by Henry Walker
naming convention subsection added 17 August 2012 by Henry Walker
minor editing 26 October 2013 by Henry Walker
Valid HTML 4.01! Valid CSS!
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu.