CSC 306 Introduction to Programming with C++

Using Classes in C++

Chapter 6


Objectives

Important Definitions


Using Classes in C++

We have learned that Bjarne Stroustrup developed the C++ language to improve upon C, but so far we have not yet learned much about the differences between these two languages. A simple answer to this implicit question is that C is a subset of C++; in other words, most C programs will run when compiled with a C++ compiler, but the reverse is not true. Unlike C, C++ is an object-oriented language in that it provides features that support object-oriented programming. In this assignment, we will begin to experiment with object-oriented programming.

One of the most important programming elements in C++ is called a class. In most object-oriented programming languages such as C++, a class is a user-defined type that includes both (1) a set of variables associated with it, as well as (2) a set of member functions. Note that the C structure or "struct" that we saw in our previous assignment only includes a set of variables but does not have any functions contained in it. However, the idea of including member functions make intuitive sense. For example, a "Time" structure should correspond to the way people record the time of day, and the operations we defined correspond to the sorts of things people do with recorded times. Similarly, a "Point" structure could correspond to the mathematical concept of a point, and operations such as calculating the distance between the two are important and should be closely associated with it.

In object oriented programming, programs are made up of a collection of classes and member function definitions, where the functions operate on specific kinds of objects of that class. Each class definition corresponds to some concept in the real world, and the functions that operate on an object of that class correspond to the ways real-world objects interact.

Member functions of a class differ from the other C++ functions in only two main ways:

  1. When we call a member function, we will invoke it on a specific class object. People sometimes describe this process as "performing an operation on an object," or "sending a message to an object."
  2. The member function is declared inside the class definition to make the relationship between the class and the function explicit.
Unfortunately, the syntax of the C++ class differs somewhat from the syntax of a structure, but we will be able to make a simple translation. The process to take some functions and transform them into member functions is mechanical, and one simply follows a sequence of steps.

Classes verses Structures

Consider the following code:
  1. a structure that could handle times

    // Define a structure that records the minutes, hours and seconds for 
    // a time period
    struct Time {
      int hour, minute;
      double second;
    };
    

  2. a function that could print times:

    // A function that takes as input a Time structure and outputs the time 
    // stored in it.
    void printTime(Time mytime) {
      cout << mytime.hour << ":" << mytime.minute << ":" << mytime.second << endl;
    }
    

To display the time in currentTime which is of type Time, we would have to pass currentTime as an argument to the printTime function:

Time currentTime = { 9, 14, 30.0 };
printTime( currentTime );
We could convert this structure into a class named Time that includes a member function called print() that does the exact same thing.

  1. The first step is to change the name of the function from printTime (that is currently independent of any classes) to Time::print. The scope resolution operator, ::, separates the name of the class from the name of the function; together they indicate that this is a function named print() that is closely associated with the Time class object.

  2. The second step is to realize that we no longer need the myTime parameter because print is a member of the class that is printing itself, so we eliminate the parameter. In doing this, we are going to invoke the function on an object rather than pass an object as an argument to the function. This function will no longer have a parameter named myTime but will refer to the object as current object. The kind of member variable access seen in the print() member function is called implicit because the name of the class object does not appear in the member function explicitly. Features like this are one reason member functions are often more concise than other functions.

  3. Finally, note here that the implicit time parameter is not modified in this function, so it should be held constant (hence the keyword "const"). It is not obvious where we should put information about a parameter that does not exist, but it is after the parameter list (which is empty in this case).
The resulting class definition is as follows:

class Time {
  int hour, minute;
  double second;

  void Time::print() const {
    cout << hour << ":" << minute << ":" << second << endl;
  }
};
Note that a class definition looks like a structure definition with a semicolon at the end, except it can also contain member function definitions. As is the case with regular non-member functions, a member function definition describes the type of the return value (if it has one), and the types and number of its parameters (remember that it does not need parameters for variables defined in the class itself). Also like regular functions, one can include a member function declaration in the class definition and then write the function definition later:

class Time {
  int hour, minute;
  double second;

  void Time::print()	const;
};

// some extra code

// Function definition. Notice that the variables hour, minute and second are
// resolved using the Time:: resolution, so the compiler knows where they are
// defined
void Time::print() const {
  cout << hour << ":" << minute << ":" << second << endl;
}
In order to invoke the new version of the print() function, we will have to invoke it on a Time object:

  currentTime.print(); // This tells the object to "print itself"

Another Example

Suppose you want to have a function that will convert a time (with hours, minutes and seconds). One version of a convertToSeconds(...) function that works on a Time structure could look like this:

double convertToSeconds(Time myTime) {
  int minutes = myTime.hour * 60 + myTime.minute;
  double seconds = minutes * 60 + myTime.second;
  return seconds;
}
It is straightforward to convert this to a class function:

double Time::convertToSeconds() const {
  int minutes = hour * 60 + minute;
  double seconds = minutes * 60 + second;
  return seconds;
}
Notice that (1) there are no parameters to the member function, and that (2) the the keyword const indicates that none of the member variables are modified.

Constructors

There are two ways to give values to the member variables in structures or classes. One way we have already encountered is by initialization when the structure is declared:

  Time testTime = {9, 14, 30.0};  // initial values of 9 hours, 14 minutes, 30.0 seconds
This by itself is very limiting because (1) we may not know the values when we declare an object or structure, (2) we may know the values in a format that will not work (such as when we only how many seconds and want the program to automatically figure out the hours, minutes and seconds), and (3) these values may change.

If Time is a structure, we can give use a makeTime(...) function to take as input the number of seconds and create a Time structure with the appropriate values:

Time makeTime(double secs) {
  Time mytime;
  mytime.hour = int(secs / 3600.0);
  secs -= mytime.hour * 3600.0;
  mytime.minute = int(secs / 60.0);
  secs -= mytime.minute * 60.0;
  mytime.second = secs;
  return mytime;    // Note this is what the function should return
}
In the case of classes, the corresponding function is so common that there is a special syntax for these constructors, and it looks like:

Time::Time(double secs) {
  hour = int(secs / 3600.0);
  secs -= hour * 3600.0;
  minute = int(secs / 60.0);
  secs -= minute * 60.0;
  second = secs;
}
First, notice that the constructor must have the SAME NAME AS THE CLASS, and no return type because the instance variables of the class is modified in the function. We can refer to the new object---the one we are constructing---using the keyword this, or implicitly, as is the case here. When we write values to hour, minute and second, the compiler knows we are referring to the instance variables of the new object. Second, notice that there is no need to create a new time object as in the case with structures, and nothing is returned. Both of these steps are handled automatically.

To invoke the constructor, you use syntax that is a cross between a variable declaration and a function call:

  Time bedtime(seconds);
This statement declares that the variable bedtime is of type Time and calls the constructor, passing the value of seconds as an argument. The system allocates space for the new object and the constructor initializes its instance variables.

Initialize or Construct?

We can initialize Time structures using curly-braces:

  Time bedTime = { 3, 35, 0.0 };
Using constructors for a class, we have a different way to declare and initialize:

  Time bedtime(seconds);
These two functions represent different programming styles, and different points in the history of C++. The Dev-C++ compiler requires that a program use the constructor and not the curly braces.

Fortunately, it is legal to overload constructors in the same way as overloading functions. In other words, there can be more than one constructor with the same "name," as long as they take different parameters. Initializing a new object involves finding a constructor that takes the appropriate parameters. For example, it is common to have a constructor that takes one parameter for each instance variable and assigns them the appropriate values:

Time::Time(int h, int m, double s) {
  hour = h;
  minute = m;
  second = s;
}
To invoke this constructor, we use the same syntax as before, except that the arguments have to be two integers and a double:

  Time currentTime(9, 14, 30.0);

Public and Private

Consider the Time class defined so far. It contains three instance variables for the hour, minute and second values and functions one can use to initialize, modify or display this information:

class Time {
  int hour, minute;
  double second;

  Time(int h, int m, double s);		// constructor
  Time(double secs);			// constructor
  void print() const;			// print out information
  double convertToSeconds() const;	// output the time in number of seconds 
};
One advantage of object-oriented programming is to be able to control (1) how instance variables can be modified and (2) which functions are accessible to anything that is not part of an object. This is done through encapsulation; for functions, this refers to the process of separating a function's interface (how to use it) from its implementation (how it does what it does). In this example, the program may know that a function called print() exists for the Time object that takes no parameters and returns no values, but the details of how this function works is hidden. Encapsulation for the data (member variables) essentially hide what they are from anything that is external to the object.

There are often two sections of a class definition, a part indicated using the keyword private that is restricted from outside access and the part indicated by the public keyword that is available to the outside world. Member functions are usually public, which means that they can be invoked by anything outside the function. The member variables are usually private, which means that they can be read and written only by class member functions. Totally hiding member variables without any way to modify them is too restrictive, so one often creates accessor functions that returns or sets the value of a variable. Of course, constructors must be public functions.

Note that the default designation for any parts of a class that is not explicity set to be private nor public. Thus, our Time class in its entirety could be declared as:

class Time {
private:
	int hour, minute;
	double second;

public:
	Time(int h, int m, double s);		// constructor
	Time(double secs);			// constructor
	void print() const;			// print out information
	double convertToSeconds() const;	// output the time in number of seconds 

};


Assignment Specifics

This assignment must be completed individually.

Your task is to explore and use a C++ class that manages dates.

  1. Obtain a copy of DateDemo.cpp and read through it. Test the program using various birth dates.

  2. Use the given Date class to compute a person's exact age in years, months, and days after their birthday information is input from the keyboard.
    Note: You are to learn to use a given class here, so do not try to write your own class or to circumvent using the given Date class.

    Again, USE the given Date class--this will not only make the assignment easier but it will give you experience using a class.

    In later chapters, we will learn how to locate a C++ class outside of the file that contains the main program, so users would typically use the class without modifying it. Thus, use the given Date class WITHOUT ANY MODIFICATION TO THE CLASS or to its public functions. Note that you will need to copy the entire class as well as the public functions used by the class.

    Of course, you are free to modify the existing or create your own main() or any new function that is not part of the Date class.

  3. Output the result in readable format.

Notes:

When you are finished writing and testing your assignment, drop the file with your source code, YourLastName_306A13.cpp, into the CSC306_A13 dropbox on the Academic server.


Back to Introduction to Computer Programming with C++ Homepage