SI204 Class 30: Structs I

Reading

Section 6.1 of Absolute C++

 

Lecture

First: A Compelling Introduction

This class begins a big new topic for us --- user defined types, which for us will be structs (or classes as they are called in programming languages like C++ or Java).  Structs provide some capabilities that you might have already felt the need for. Let's consider a few example problems:

  • Suppose we want a function midPoint that takes two points and returns their midpoint.

  • Suppose we want to read in a list of 12 midshipmen names and alpha codes, and print out the midshipmen names ordered by class year.

  • Suppose we want to store a bunch of student names along with their grades on 10 homework assignments.

Each of these are things we can do (think about how), but only with difficulty. The problem is that in each case we are working "physical" objects that do not have a corresponding built-in type in C++. It would be natural to write a midpoint function if there were a type called point that encapsulated both the x and y coordinates --- it's prototype would be point midPoint(point a, point b);. It would be natural to sort 12 midshipmen ordered by alpha codes (which would order by class year) if there were a type mid that encapsulated both alpha code and name --- we'd have an array mid *mySquad = new mid[12]. Finally, it'd be natural to store student names along with homework info if there was a type student --- we'd just store it in an array of student objects. Clearly, all of these problems scream out for the ability of the user to wrap up one or more existing types into one orderly syntactic little package and call it a new user-defined type. In C++, struct is the mechanism that allows you to do this.

A simple example: A struct called point

Let's take the simple example of our midpoint function. We decided that the existence of a type point would make such a function simple and natural. We need to wrap up a double for the x-coordinate and a double for the y-coordinate into a single object of a new type - point. Here's how that's accomplished in C++:

struct point      <--- Declares a new type called "point"
{
  double x, y;   <--- Says that a "point" contains two doubles named x and y
};               <--- Don't forget the ;

This struct definition, like function definitions, appears outside of main or of any other function definitions, and it must appear before you try to use an object of type point. From the point of this definition onwards you can use point as a new type. If you want to access the double x within a point object named myPoint, you write myPoint.x --- note that myPoint.x is an object of type double, so anything you can do with a double you can do with myPoint.x! The objects packaged together in a new struct are called data members. We'll start off simple by creating an object of type point, reading values into the object, and printing it out:

int main()
{
  // Creates an object myPoint of type point
  point myPoint;
 
  // Reads & stores coordinate values
  cout << "Enter x-coord: ";
  cin >> myPoint.x;
  cout << "Enter y-coord: ";
  cin >> myPoint.y;
 
  // Writes out point P
  cout << "Point is (" << myPoint.x
       << ',' << myPoint.y << ")" << endl;
 
  return 0;
}

One very important thing to note here is that we can't, yet, say cin >> myPoint in order to read into point myPoint. Nor can we say cout << myPoint in order to write out point myPoint. Why? Because cin and cout know nothing about the type point! On the other hand, cin >> myPoint.x works perfectly well, because cin is just reading into a double, which we know it does just fine. Now, let's look at defining the function midPoint:

point midPoint(point left, point right)
{
  point middle;
  middle.x = (left.x + right.x)/2;
  middle.y = (left.y + right.y)/2;
  return middle;
}

Hopefully this code is pretty much self-explanatory. Notice that by wrapping up two doubles in the type point I can, in a sense, return two objects from a function! Take a look at this complete program that reads two points from the user and prints out their midpoint.

 

What does the compiler know how to do with our new types?

As we just saw, when we define a new type using struct, cin doesn't know how to read that type, and cout doesn't know how to write that type. In fact, the compiler doesn't know how to do any of the implicit or explicit conversions with your new types, so that neither double(myPoint), where myPoint is an object of type point, nor point(j), where j is an object of type int will be recognized. The only things the compiler knows how to do with your user defined type are assignment using the = operator, and copy for pass-by-value arguments in function calls. These are done by assigning/copying each data member independently. Most importantly, however, object of user defined type are created and destroyed and passed around just like any other type: scoping rules are the same, creation with new is the same, parameter passing is the same, parameter type matching for function overloading is the same ... all of these things you've already learned still apply.

 

Heterogeneous Data

Although we could imagine implementing our midPoint function with arrays of two doubles --- believe me, it'd be painful! Where classes really find utility are when we need to wrap up objects of different types in one object. Let's think about our example of midshipmen names and alpha codes. We might define a mid as follows:

struct mid
{
  int alpha;
  string first, last;
};

Notice that this class has three data members, one of type int and two of type string. Let's consider using this type in the following problem: The file Mids.txt contains the names and alpha codes of the Midshipmen in my two sections. The first entry in the file is how many mids there are.  We want to write a program that will read that data and store it in an array for later processing. To test what we've done, we'll simply allow the user to enter an alpha, and we'll return the name of the Mid with that alpha, or an error message if none is found. Creating the array and reading in data from the file is easy (notice how each cell of the array someMids is an object of type mid:

  // Create and array of n Mids
  mid *someMids = new mid[n];
 
  // Open file
  ifstream fin("Mids.txt");
 
  // Read in mids
  for(int i = 0; i < n; i++)
    fin >> someMids[i].alpha >> someMids[i].last 
         >> someMids[i].first;

Similarly, if int variable a holds the value of the alpha code to search for, the code that does the searching and prints the response is straightforward:

  // search for alpha a
  int k = 0;
  while(k < n && someMids[k].alpha != a)
    k++;
 
  // print result of search
  if (k == n)
    cout << "No Mid with that alpha was found!" << endl;
  else
    cout << someMids[k].first << " " << someMids[k].last << endl;

Here is a complete program.

Problems

1.      Write a program that reads in three points describing the vertices of a triangle and computes the midpoint triangle they define, i.e. the triangle whose vertices are the three midpoints of the previous triangle. A typical run of your program should look like:

Enter triangle vertices: (0,0) (0,1) (1,0)
Midpoint triangle verts: (0,0.5)(0.5,0.5)(0.5,0)   

Notice how my solution defines functions for writing and reading points!

2.      We can plot the two triangles from the previous problem using Excel's scatter plot feature. This screenshot gives you an example of an Excel file and the plot it produces. Modify your program to produce a text file that we can import into Excel to plot these triangles. Notice how my solution makes use of overloaded functions ... I probably should have given my new writepoint function a different name though.

3.      We could do the same thing we just did for any polygon. Write a program that reads in n vertices defining an n-gon, and produces a text file that we can import into Excel to plot the n-gon and its "midpoint n-gon".


Assoc Prof Christopher Brown

Last modified by LT M. Johnson 11/06/2007 03:15 PM