Structures

It is sometimes useful to have a collection of values of different types that may be treated as a single entity.

Consider a student records example. For each student:

student in class:

student id (integer)
first and last name (two strings)
score on homework (integer)
score on midterm (integer)
score on final (integer)
final letter grade (char)

Note: Various types, hence an array may not be used.

A structure allows us to group together variables of different types:

struct StudentRecord
{
  int id_num;
  char first_name[31];
  char last_name [31];
  int hw_score;
  int midterm_score;
  int final_score;
  char letter_grade;
};

The keyword struct indicates a structure. StudentRecord is referred to as the structure tag (may be any legal identifier) and may be thought of as the type of the structure. The identifiers inside the braces are called member names and need to be legal identifiers.

Once a structure definition has been given, the structure type may be used as you would any predefined type (int, char, double etc.):

StudentRecord Jill, John;

We assign values to structure variables by using a dot operator:

Jill.id_num = 11111;
John.midterm_score = 68;
Jill.final_score = 91;

You may use member variables anywhere you would use other variables.

strcpy(Jill.first_name, "Jill");

Two or more structures may use the same member names

struct Student323
{
  int id_num;
  char letter_grade;
  int quiz_score;
  int hw_score;
};

struct Student215
{
  int id_num;
  char letter_grade;
  int hw_score;
};

Note: Semicolon immediately after the last brace. Variables may be declared immediately after the structure definition but we will not use that technique.

You may also cluster variables of the same type. Considering a date structure:

struct date
{
  int month, day, year;
};

A structure variable may be viewed as a collection of member variables or as a single variable with several parts. Consider the following:

StudentRecord Jill, John;
Jill.id_num = 111111;
strcpy(Jill.first_name, "Jill");
strcpy(Jill.last_name, "Doe");
Jill.hw_score = 95;
Jill.midterm_score = 98;
Jill.final_score = 88;
Jill.letter_grade = 'A';

John = Jill;

The assignment statement above is equivalent to:

John.id_num = Jill.id_num;
strcpy(John.first_name, Jill.firs_name);
strcpy(John.last_name, Jill.last_name);
John.hw_score = Jill.hw_score;
John.midterm_score = Jill.midterm_score;
John.final_score = Jill.final_score;
John.letter_score = Jill.letter_grade;

Testing for equality as below is not allowed:

if (John == Jill)
  cout << "These are the same\n";
else
  cout << "Not the same\n";

 

Structures as function arguments

Since instances of structures work like variables we may make them call-by-value or call-by-reference arguments to functions as well as the return value of functions:

void print_record( StudentRecord student)
{
  cout << "Student ID: " << student.id_num
   << " Name: " << student.first_name
   << " Homework: " << student.hw_score
   << " Midterm: " << student.midterm_score
   << " Final: " << student.final_score
   << " Letter Grade: " << student.letter_grade
   << endl << endl;
}

Classes and Objects

A class extends the structure construct by allowing member functions as well as member variables. To define classes we need to specify the values that the object can take on as well as associated member functions. Consider a simple class, which implements "student":

class Student
{
  public:
  void output();

void setscore(int scr, char scrtype);
void setid(int sid);
int stud_id;
int hw_score;
int midterm_score;
  int final_score;
};

Notice the similarity to structures. The differences are:

To define a member function:

void Student::output()
// outputs the grade
{
  cout << "A\n";
}

Student::output

john.final_score

Recall the mechanism for referencing structure members as well as the invocation of member functions eof, get, etc. for streams. We use the same mechanism to reference member variables and functions:

Student john, jill, jack;
john.final_score = 91;
jill.midterm_score = 92;
int score1;
cout << "Enter the midterm for jack: ";
cin >> score1;
// setting a score using the setscore function
jack.setscore(score1, ‘m’);
if (jack.midterm >= 90)
 cout << "Excellent\n";

Let us consider the temperature conversion program.

Define a class called temperature:

Public and Private members

In order to allow the separation of the details of implementation from usage, C++ provides two ways to declare member variables in a class, public and private.

Consider the following class definition:

class DayOfYear
{
  public:
   void input(); // input day of year from keyboard
   void output(); // output day of year to screen
   void set(int new_month, int new_day); // set date
   int get_month(); // return month, 1=Jan, etc.
   int get_day(); // return day of month
  private:
   int month;
   int day;
};

Consider the following declaration:

DayofYear today;

Since month and day are private, the following statements are illegal:

Today.month = 5; today.day = 20;

To set the date, we now have to call the member function ‘set

today.set(5,20);

Also, to get the value of the day and month:

if (today.get_month()==11 && today.get_day()==20)
   cout << "Good Luck!\n";

To input values:

today.input();

The member function input reads two values from the keyboard for month and day and assigns them to the corresponding private variables.

void DayOfYear::input()
{
  cout << "Enter the month as a number: ";
  cin >> month;
  cout << "Enter the day of the month: ";
  cin >> day;
  return;
}
void DayOfYear::set(int new_month, int new_day)
{
  month = new_month;
  day = new_day;
  return;
}
int DayOfYear::get_month()
{
    return (month);
}
int DayOfYear::get_day()
{
  return (day);
}

As a general rule we do not want users of the class to be able to directly access internal data structures. This is why we make all member variables private.

We typically divide member variables into two sections. One for private and the other for public members. This is not a C++ requirement, you may several public or private sections as shown below.

class Sample
{
  public:
   void do_this();
   int stuff;
  private:
   void do_that();
   int another_thing;
  public:
   int do_the_other();
   double a_third_var;
};

Variables are by default private, but it is a good practice to explicitly indicate membership.

Tips for Dealing with Classes

  1. Make all member variables private. This is a safeguard that ensures that our classes will behave nicely.
  2. Define accessor functions. If all member variables are private, then you should provide functions for accessing these variables. It is good practice to define accessor functions for each member variable.
  3. You cannot apply == to objects (yet). (sign up for CSC310).
  4. DayOfYear today, examday;
    if (today == examday)
      cout << "Good luck!\n";

    Accessor functions resolves this problem since:

    if (today.get_day()==examday.get_day() && today.get_month()==examday.get_month())
       cout << " Good luck!\n";

  5. You can use the assignment operator with objects. As with structures it is perfectly legal to use the assignment operator with objects
  6. DayOfYear today, classday;
    classday.input();
    today = classday;

    This is equivalent to:

    today.day = classday.day;
    today.month = classday.month;

    (assuming our first example where month is an int) This is true despite the fact that day and month are private member variables.

Constructors for initialization

A good way to initialize an object is to declare a member function called a constructor. A constructor is a member function that is automatically called when an object of that class is declared. It is used to initialize the values of some or all of the member variables (and do any other sort of initialization that might be necessary). You define a constructor the same way as you define any other member function for the class except for two differences.

  1. A constructor must have the same name as the class.
  2. A constructor definition cannot return a value and hence has no type (void is not required).
  3. A constructor should be a public member function (otherwise it would not be usable when declaring objects of that class).

To add a constructor to the DayOfYear class

class DayOfYear
{
  public:
   DayOfYear(int init_month, int init_day);
   void input();
   void output();
   int get_month(); // returns month, 1 = Jan, etc.
   int get_day(); // returns the day of the month
  private:
   int month;
   int day;
};

The constructor:

DayOfYear::DayOfYear(int init_month, int init_day)
{
  day = init_day;
  month = init_month;
  return;
}

To use this constructor?

DayOfYear today(5, 21), tomorrow(5, 22);

Note that we must do it this way; we cannot make a call to a constructor like an ordinary member function, that is, the following is illegal:

DayOfYear today;
today.DayOfYear(5,21);

It is useful to include a constructor that doesn't take any arguments. Such a constructor is called a default constructor.

Modify the change the DayOfYear class to have the following public function

DayOfYear::DayOfYear()
{
  day = 0;
  month = 0;
  return;
}

This will serve the purpose of defining the date to be something when we don't have a value for it. We may invoke it thus:

DayOfYear examday;

Note that the following is invalid:

DayOfYear examday();

We no longer need the set member function since we can also use the constructor to change the values of the member variables once it's been defined.

DayOfYear today;

today = DayOfYear(5,21);

This is legal whereas today.DayOfYear(5,21) is wrong? The constructor DayOfYear returns an object, which is of type DayOyYear so we need to assign it to an object of the right type.

The variable can also have values when we change it.

DayOfYear tomorrow(5,22);

tomorrow = DayOfYear(5,23);

Note that now we do not need the set functions that we had before since we can now use the constructors to achieve the same purpose. You can also make a call to the default constructor to change the values, but if you do, then you need the parentheses.

today = DayOfYear();

This resets the values of the member variables to the defaults.

Always Include a Default Constructor

If you have constructors then you must have a default constructor. The DayOfYear class may not have a default constructor, but instead a constructor that takes two arguments. The following would then be illegal:

DayOfYear today;

This is because C++ interprets the above to be a call to a constructor with no arguments which does not exist, so it gives an error. You can declare a default constructor to do nothing if you want to get around this problem.

DayOfYear::DayOfYear()
{
  return;
}

The final version of the DayOfYear class follows:

#include <iostream.h>
class DayOfYear
{
public:
// constructors (note: no types!!!!)
DayOfYear(); // (default) sets date to 1/1;
DayOfYear(int m, int d); // set date to m/d;
void ask_user(); // input day of year from keyboard
void output(); // output the day of year to the screen
int get_month(); // returns the month, 1 = Jan, etc.
int get_day(); // returns the day of the month
private:
int month; // These are not accessible
int day; // from outside the class
void set(int new_month, int new_day);
// set the date to a new month and day
};
DayOfYear::DayOfYear()
{
month = 1;
day = 1;
}
DayOfYear::DayOfYear(int m, int d)
{
set(m,d);
}
void DayOfYear::ask_user()
{
int d, m;
cout << "Enter the month as a number: ";
cin >> m;
cout << "Enter the day of the month: ";
cin >> d;
set(m,d);
return;
}
void DayOfYear::output()
{
cout << month << "/" << day << endl;
return;
}
void DayOfYear::set(int new_month, int new_day)
{
if (new_month < 1)
{
new_month = 1;
cout << "(Corrected month: " << new_month << ")\n";
}
else if (new_month > 12)
{
new_month = 12;
cout << "(Corrected month: " << new_month << ")\n";
}
month = new_month;
day = new_day;
return;
}
int DayOfYear::get_month()
{
return (month);
}
int DayOfYear::get_day()
{
return (day);
}
#include <iostream.h>
#include "day_of_year_3.h"
int main ()
{
DayOfYear today;
cout << "'today' is currently set to: ";
today.output();
cout << endl;
today.ask_user();
cout << "\nYou entered the following date: ";
today.output();
cout << "\nTime passes...\n" << "\nNow it's: ";
// Using constructors instead of 'set'
today = DayOfYear(6,25);
today.output();
if ((today.get_month()==12) && (today.get_day()==25))
cout << "Merry Christmas!!\n";
else
cout << "Have a nice day!\n";
cout << endl;
return 0;
}

Summary of the Properties of Classes

Programming Example: Bank Account Class

See pages 322-323. Highlights of the example.