Up to now we have been using simple variables - integers, floating points, unsigned longs etc. The most complicated variables that we have been using have been arrays. Structures offer a way of joining these simple variables together to form complex structures.
Let's suppose that we want to store data about people inside the computer. Each person has a name, an address, a height, eye colour - you can think of dozens of pieces of information that you may want to store about them. It is always possible to store these as a series of simple variables - a string for the name, an integer for the eye colour (1 could be brown, 2 blue etc.) It is even possible to store all the pieces of information as an array (with each element being a different piece of information) or as a single string (with all the data strung together). However, it would be nice if we could encapsulate all that information in an easy-to-read form inside one data structure. This is where structures come in.
struct person
{ string name;
int eye_colour;
float height;
};
person friend,mother;
person spouse;
This sets up a structure called person and declares three variables of that type. Each of these people will have a name, a height and an eye colour. The declaration does not set up these values - they start off as random values and characters, whatever happens to be in the memory allocated at the time. The data is set up as follows:
friend.name = "Diane"; friend.eye_colour = 1; friend.height = 1.61; mother.name = "Mary"; mother.eye_colour = 2; mother.height = 1.44; spouse.name = "Helen"; spouse.eye_colour = 1; spouse.height = 1.7;
The individual variables that make up the structure (the fields) are referenced using a full stop to separate them from the name of the variable. They can be treated just like normal variables, used in arithmetic expressions etc. For example, Diane was kidnapped by aliens, as a result of which, her eyes changed colour:
friend.eye_colour++
Displaying the value of friend.eye_colour now produces the value 2, which is what you would expect.
You can pass structures across as function parameters pretty easily. The type of the parameter is the same as the name of the structure type (i.e. person in this case). Here's a little function that displays the details for a person:
void display_details (person someone_I_know)
{ cout << "Name : " << someone_I_know.name << endl;
cout << "Eye colour : ";
switch (someone_I_know.eye_colour)
{ case 1 : cout << "Brown"; break;
case 2 : cout << "Blue"; break;
case 3 : cout << "Green"; break;
default : cout << "Unknown";
}
cout << endl;
cout << "Height : " << someone_I_know.height
<< " metres" << endl;
}
In this case, the parameter is called someone_I_know, and the fields are referred to as per normal. One of the fields is even used as the variable in a switch statement. You would call the function as per normal:
display_details(friend); display_details(mother); display_details(spouse);
This is going to surprise you! You can actually define a function inside a structure and it becomes part of the structure itself. A function which is linked to a structure like this is called a member function of that structure. Here's an example. Let's suppose we have a structure which is designed to hold data about rectangles. A rectangle is defined by its length and width. You might also think that the area and perimeter of the rectangle are important - so they are - but they can be calculated from the length and width by member functions:
struct rectangle
{ double length, width;
double area()
{ return length * width;
}
double perimeter()
{ return 2 * (length + width);
}
};
rectangle my_lawn;
rectangle hankerchief;
rectangle football_field;
This structure has two fields and two member functions. You could set the data fields the same way as before:
my_lawn.length = 30; // All dimensions in metres my_lawn.width = 25.51; hankerchief.length = 2.1; // We have big hankies hankerchief.width = 1.923; // in our house! football_field.length = 350; football_field.width = 250;
You would use the functions as though they were data fields inside the structure. Just separate them from the name of the structure using a full stop. You do still need to put in the brackets to show that they are functions, but in this case there are no parameters so you don't need to put anything between those brackets.
cout << "The perimeter of my lawn is "
<< my_lawn.perimeter() << endl;
if (hankerchief.area() > 10)
cout << "That\'s one heck of a hankie!"
<< endl;
double ratio = football_field.area()
/ hankerchief.area();
cout << "A football field is " << ratio
<< " times as big as my hankie!" << endl;
Apart from the structure part at the beginning they are just like normal functions. One thing to note is that in the function definitions themselves within the structure you just refer to the data fields of that function as normal variables, e.g. length and width rather than using a full stop and the name of the structure. Because this code is within the structure definition itself, the compiler will know that the length and width referred to are those of that particular variable i.e. within the definition of my_lawn, the length and width referred to are my_lawn.length and my_lawn.width, within the hankerchief variable, the length and width are hankerchief.length and hankerchief.width etc.
Of course, there's nothing to stop you passing parameters to a member function. Here's a rather contrived example:
struct player
{ string symbol; // top hat, car, boot etc.
int cash,current_square;
void get_money (int howmuch)
{ cash += howmuch;
cout << "The bank balance of " <<
symbol << " is now £" << cash << endl;
}
void lose_money (int howmuch)
{ if (cash > howmuch)
{ cash -= howmuch;
cout << "The bank balance of " <<
symbol << " is now £" << cash << endl;
}
else
{ cash = 0;
cout << symbol << " is bankrupt!" << endl;
}
}
};
player Richard,Diane,Helen;
Richard.cash = 500;
Richard.symbol = "Top hat";
Diane.cash = 500;
Diane.symbol = "Boot";
Richard.get_money(200);
Diane.lose_money(150);
The bank balance of Top hat is now £700 The bank balance of Boot is now £350
It's ages since I played Monopoly, so I have only made a token stab at defining a Monopoly player. I know they have houses, hotels, "Get out of Jail free" cards etc. but as far as I am concerned, they are defined by their symbol, their current position on the board and how much of the folding stuff they possess.
The structure above has two member functions, one to add to a player's bank balance and one to subtract from it. Each takes a parameter which is how much to alter the bank balance by.
It is perfectly possible to define one structure as part of another. Here's an example - a snooker table. Again, I know less about snooker than I do about Monopoly, so forgive me if I get some of the details wrong! A snooker table has a length and a width. It also has six pockets, one at each corner and one in the middle of the longest sides. I believe there are 10 red balls, and one each of the following colours: white, black, green, yellow, brown, blue and pink, each worth different numbers of points. Each ball is defined by its position on the table, or it could be off the table (in somebody's pint) or down one of the pockets.
Right. Let's start by defining a position as a structure. This is defined as an x-co-ordinate (horizontal position) and a y-co-ordinate.
struct position
{ float x,y; };
Now we move up one step and define a ball. This has a position, a colour, a point value and a status (1 meaning on the table, 0 meaning in a pocket and -1 meaning off the table for some reason).
struct ball
{ position p;
int colour; // 0=black, 1=white etc.
int points;
int status;
};
This contains a reference to the previous structure that we defined. Finally, the table itself is defined as having 17 balls and 6 pockets. Each pocket is defined solely by its position.
struct table
{ ball balls[17]; // First ball is balls[0]
position pockets[6];
float length,width;
};
table snooker;
The structures within structures are accessed using multiple full stops. The table length is set using snooker.length = 2.4; and the pocket positions are set using snooker.pockets[0].x = 0; and snooker.pockets[0].y = 0;. The ball positions are set using even more full stops: snooker.balls[4].p.x = 1.466; and snooker.balls[4].p.y = 0.782;.