Skip to content
Physics and Astronomy
Home Our Teaching Resources C programming PHY3134 structures.html
Back to top

Computational Physics
Structures organise your data

Show me your flow charts and conceal your tables and I shall continue to be mystified, show me your tables and I won't usually need your flow charts; they'll be obvious.   Brooks

[ Flow charts were once popular methods of showing the organisation and structure of your code and functions. Tables are used in other languages to show the organisation and structure of your data; they are the equivalent of structures in C. ]

Structures do for variables what functions do for code

We have already discussed the fact that nearly every program is so large that it is completely impossible to hold in all in your head at the same time. Functions organise code into self contained units, enabling you to think at a higher level without having to keep track of the details. Structures do exactly the same for variables.

Structures reduce the number of arguments to functions

Imagine a function to calculate and print out the height and velocity of a projectile thrown into the air. Its height and velocity are known at time t=0 and we need print out its position and velocity at time t + dt. Its prototype would look something like:
void height_velocity(float y, float vy, float mass,
                     float drag_coeff, float ywind,
                     float viscosity, float dt);
This is a very simple problem but the function has seven arguments. Worse, they are all floats so it would be very easy to get two in the wrong order and the compiler would not notice. There are over five thousand different legal ways of ordering seven arguments of the same type but only one of them is the right one!

C structures give us a way of rolling up several different variables into a single package, just as a carrier bag allows us to carry several items together at the supermarket, thus making it easier to pass them to functions.

Structures organise and package variables into groups

Representation is the essence of programming.   Brooks

If we take a look at the arguments to the function above we see they split into three groups: y, vy, mass and drag_coeff are properties of the projectile, ywind and viscosity are properties of the air and time is a physical quantity in its own right. This suggests we want two structures, one for the projectile and one for the air, and to leave time as it is.

The following code declares what are in effect two new types of variables:

struct projectile {
  float y;
  float vy;
  float mass;
  float drag_coeff;
};  /* Notice the semi-colon! */

struct airprop {
  float ywind;
  float viscosity;
};
This code does not actually create any structures, it just tells the compiler what we mean by 'struct projectile' and 'struct airprop'.

Creating structures

We can create actual instances of these structures - including arrays of structures - just as we would floats or ints:
void myfun(void) {
  struct airprop air;
  struct projectile myproj, projarray[20];

  myproj.mass = 1.0;
  myproj.y = 1.5;
  myproj.vy = 11.6;
  projarray[0].mass = 1.3;
 /* Whatever else here */
}
The function height_velocity is now declared as:
void height_velocity(struct projectile proj,
                     struct airprop air, float dt);

Not only do we now only have three variables rather than seven, all three are of a different type so if we were to call the function with two of its arguments in the wrong order the compiler would notice and tell us.

The function would then contain lines like:

proj.y += proj.vy * dt;
Notice:
  • The first code fragment defined the meaning of 'struct projectile', the second fragment used this information to actually create some structures.

  • The individual members of the structure are accessed as proj.mass, etc. You can treat each member as an ordinary variable in exactly the same way that you can treat each element of an array as an ordinary variable.

  • We were careful to divide the data into different structures according to what object the data was a property of.

  • Just as a function prototype must precede the first reference to that function in the file, the declaration of what the structure means must precede the first use of one of those structures.

  • Again, just as in the function prototype, if we were to use a structure in two different files we would not manually type the declaration into each file; we would create a header file and include it in every file where it was needed.

  • If we pass a pointer to a function then when we change proj.mass inside the function we are changing the original object;
It so happens that all of the members of the above were of the same type (float), but structures can contain all types including arrays and other structures. Also, we can use a little piece of syntactic sugar to ease our mental model:
typedef struct aardvark {
  float mass;
  float age;
  float inside_leg[4];
  int social_security_number;
  struct ant diet;  /* Struct ant must already have been declared */
} Aardvark;

// 'Aardvark' is now a synonym for 'struct aardvark':
main() {
  Aardvark pinky;
  struct aardvark perky;
}
  

Structures allow extensibility

Our simple example only has one dimension, y. But if our program is successful somebody is bound to ask us to extend it to three dimensions in which case y, vy and ywind will be replaced by arrays. If we used the "separate variables" approach we would have to go through each of our functions changing the number (and type) of arguments.

With the "structure" approach, we just add some more members to the structure definition, change the part of the code responsible for calculating the acceleration and recompile.

Did somebody mention spin?

Real objects spin in the air and have texture. Even if we assume a spherical shape, that's several more variables. And somebody is sure to want to throw non-spherical objects. In this case our height_velocity function is going to split into two parts:
  • An up-market numerical algorithm for moving the projectile.

  • An function for calculating the force on the object (and hence its acceleration), taking into account shape, spin, etc.
But if we use structures, the function definition remains unchanged.

Structures help make our functions more modular

The spin/texture/shape argument above shows that, if we chose to go that way, our acceleration function could become very complicated but our function call remains unchanged. Thus,the rest of our code doesn't need to know about it.

A simple example

This rather simple example shows a function whose job is to calculate and print out the area of an ellipse. (In practice this function is so simple it probably isn't worth making a separate function.)
#include <stdio.h>

typedef struct ellipse {
  float centre[2];
  float axes[2];
  float orientation;
} Ellipse;

void print_area(Ellipse el);

int main() {
  Ellipse ellie;
  int i;

  printf("Centre? (x,y)\n");
  scanf("%f %f", &ellie.centre[0], &ellie.centre[1]);
  
  for(i = 0; i < 2; ++i) {
    do {
      printf("Length of axis number %d ( > 0 )?\n", i + 1);
      scanf("%f", &ellie.axes[i]);
      } while (ellie.axes[i] <= 0);
  }
  
  printf("Orientation to the vertical?\n");
  scanf("%f", &ellie.orientation);

  print_area(ellie);
  return 0;
}

void print_area(Ellipse el) {
#define  PI 3.14159265358979

  float area = PI * el.axes[0] * el.axes[1];
  printf("The area is %f\n", area);
}

Executive summary

  • If you have a whole load of variables referring to the same object stick them all into a structure.

  • Organise the data into structures according to the object they refer to, along the same principles as using functions to organise your code and according to the same criteria: how to best minimise the amount you have to keep in your head at any one time.

  • A structure contains everything you need to know about that object. When you add new properties to an object you can easily extend your structure definition and the parts of the code that don't deal with that aspect of it don't need to know about it.

There is, of course, an obvious weakness with what we have learnt so far: we know how to pass the values of a structure into a function but we have not yet learnt how to change them within the function and to pass them back to the calling function. We shall see how to do this next week.

Reference

Brooks, F. R. Jr, The Mythical Man-Month (1975).

                                                                                                                                                                                                                                                                       

Validate   Link-check © Copyright & disclaimer Privacy & cookies Share
Back to top