CS202 Computer Science II
FAQ Newsboard  Telnet Email TA Lectures Assignments Man pages Help

Classes

Chapter 3 in textbook.....

We know that functions are a nice way to get control abstraction. But how do we get data abstraction. What is data abstraction anyway? The concept of data abstraction depends on the concept of abstract data types (ADTs). So let's study ADTs then get into data abstraction using classes.

A data type in any language has two properties.

  1. A set of values
  2. A set of allowable operations on those values.
Consider integers in C/C++. The range of allowable integer values on the DEC workstations is given in the file /usr/include/limits.h by the following declaration.
/* Minimum and maximum values a `signed int' can hold.  */
#define INT_MIN (- INT_MAX - 1)
#define INT_MAX 2147483647
So we know the set of values. Allowable operations include +, -, *, /, % and so on.

Now pay attention. Because int is a data type we can create several instances (or objects) of type int

int i, j, k;
declares three integers named i, j, and k. These objects (or variables) of type int can be used like integers are supposed to be used in C/C++

The problem is that the predefined (standard) types in a programming language are too primitive to model real world data. However, if a language provides a way for the programmer to create a programmer defined data type by composing standard data types and other programmer defined data types, we can cater to real world complexity.

A programmer defined data type is an Abstract Date Type (ADT). To implement an ADT a programmer must:

  1. Choose a concrete data representation of the abstract data, using data types that already exist.
  2. Implement each allowable operation in terms of program instructions (usually in the form of functions).
Consider the stack module that I implemented for you for assignment one. The implementation uses a concrete data representation (I used an array of char), as well as function code for allowable operations on the stack (Push, Pop, ...).

However, in contrast to int, the stack module only exports only ONE stack. I cannot create/declare many stacks and if I want to use more than one stack in a program I am out of luck.

C++ provides a language mechanism called a CLASS. A Class is a programmer defined TYPE that is used for creating abstract data types.


In C++, a class is programmer-defined type whose components are called class-members or members. Class members can be variables or functions. We could, for example, reimplement a stack as a class and declare it as:

class Stack {
public:
  int Push(char);
  int Pop(char &);
  Boolean Empty();
  Boolean Full();
  StackPrint();
//.....

private:
  char contents[100];
  int top;

};

Note the semicolon at the end of the last curly brace. This is required in C++ class declarations. Date or function declared after the keyword public make up the public interface. Data or functions declared private are hidden from the user. Using such a stack module we could declare
Stack parenStack, curlyBraceStack, squareBraceStack;
three distinct stacks.

The Stack class has five public members. These are the member functions Push, Pop, Empty, Full, and StackPrint. It has two private members. These are the member variables contents (an array) and top. All private members are inaccessible to client code, only the class's member functions can access them. This separation of class members into public and private parts enforces information hiding.


A Class is a Type

Let's work with an illustratively useful (but practically negligible) example of a class.

class JarType {
public:
  void InitToEmpty(); 
  void Add(int n);
  int Quantity() const;			       
// const means Quantity() inspects private member but does not change it
       
private:
  int numUnits;
  
};
The declaration
JarType jar1;
JarType jar2;
creates two instances of the TYPE JarType. Each instance of this class has its own copy of numUnits, the private member of JarType.

Classes are very much like built-in types. You can declare as many instances of a class as you like. You can pass class instances as parameters to functions and return them as function values. A class instance, like any other variable, follows the scope rules of the language.

On the other hand, most of the built in operators do not apply to classes. You cannot add (+) two JarType instances (example: ( jar1 + jar2 is not allowed). You cannot check for equality with == (example: jar1 == jar2 is not allowed). All classes do have two built-in operators. These are member selection (.) and assignment (=). With the former you select an individual member of the class by writing the variable (class instance) name, then a dot, then the member name.

	jar1.InitToEmpty();
invokes the InitToEmpty function of the jar1 instance of the JarType class. Assignment and selection are illustrated with the following examples
JarType jar1, jar2;
JarType myJar;

jar1.InitToEmpty();
jar1.Add(10);
// ASSERT: jar1 contains 10 items

jar2.InitToEmpty();
jar2.Add(40);
jar2.Add(15);
// ASSERT: jar2 contains 55 items


if(jar2.Quantity() > 100)
{
   DoSomething();
}

myJar = jar2;
// ASSERT: myjar contains 55 items
The scope of a class member is within the class. Thus if the class SomeType had a member named Add and there was also a global Add function defined.
JarType jar1;
SomeType someInstance;
int n;

....

jar1.Add(10);
someInstance.Add(24);
Add(28, n);
The compiler will have no trouble distinguishing between the three Add's.

The fact that numUnits has been declared private means that clients cannot access numUnits directly. Thus:

	myJar.numUnits = 50;
is not legal in C++. You cannot do that and the C++ compiler will complain and not produce an object file. Only the class's member functions can access numUnits. If it is neccessary for clients to inspect (but not modify) numUnits the class programmer can provide an access function. The JarType member function Quantity is such a function and has the keyword const appended to the declaration to state and ensure that Quantity can inspect but not modify members of JarType. Quantity is called a const member function


Specification and Implementation

Here is the specification file for the JarType class in the file jar.h

// SPECIFICATION FILE jar.h
// This module exports a JarType ADT

class JarType {
public:
  void InitToEmpty();
  // PRE: 

  // POST: Number of Units in jar is 0
  // Must be called before other member functions

  void Add(int n);
  //PRE: InitToEmpty has been invoked at least once
  && N >= 0

  //POST: n units have been added to jar

  int Quantity() const;
  // PRE: InitToEmpty has been invoked at least once
  // POST: FCTVAL == number of units in jar

private:
  int numUnits;
};
Here's the implementation file jar.cpp
// Implementation file
// This module exports a JarType ADT

#include "jar.h"

// Private members 
// int numUnits

void JarType::InitToEmpty()
  //
  // POST: numUnits == 0
  // Must be called before Add and Quantity
{
  numUnits = 0;
}

void JarType::Add(int n)
  // PRE: numUnits >= 0 && n >= 0
  // POST: numUnits == numUnits + n
{
  numUnits += n;
}

int JarType::Quantity() const
  // PRE: numUnits >= 0
  // POST: FCTVAL == numUnits
{
  return numUnits;
}
Things to note about classes in C++
  1. C++'s scope resolution operator "::" with the class name prefixing the :: and the member name succeeding the ::.
  2. Members of a class do not use the . to refer to other members.
  3. Quantity is a const member function and must be declared in both the specification and implementation files.


Constructors

Just like with the stack specification and implementation there is an initialization function InitToEmpty for JarType that must be called before other class members can be used. If the client does not do the initialization -- unpleasantness ensues :-)

What you need is guaranteed initialization. In C++ we have CONSTRUCTORS to guarantee initialization. A constructor is a member function that is implicitly invoked whenever a class instance is created.

  1. A constructor function's name is the same as that of the class.
  2. It also has no return type and is intended only to initialize a class instance's private data.
Let's change JarType by removing InitToEmpty and adding a couple of constructors.
class JarType {
public:
  void Add(int n);
  int Quantity() const;

  JarType(int n);
  JarType();


private:
  int numUnits;
};
This has two constructors, differentiated by their parameter lists. The first constructor has one parameter, n, and fills a jar with n units when created. The second constructor is parameterless and initializes the jar with some default value, say zero. A parameterless constructor in C++ is called a DEFAULT CONSTRUCTOR.

Here's how the constructors look for JarType

JarType::JarType(int n)
{
   numUnits = n;
}

JarType::JarType()
{
   numUnits = 0;
}
We don't invoke a constructor like other member functions using the dot (.) notation. Instead a constructor is automatically invoked whenever a client creates a class instance.
JarType myJar = JarType(20);
// created myJar with 20 units

JarType myJar(20);
// creates myJar with 20 units

JarType myJar;
// default constructor invoked
// creates myJar with 0 units
And here's some client code that uses the JarType module.
cout << "Enter Initial number of items:";
cin >> itemCount;

JarType myJar(itemCount);

cout << "How many items to add:";
cin >> itemCount;

myJar.Add(itemCount);


Class Invariants

For the JarType class to behave as the implementer intends,

	numUnits >= 0
must always be true. This is called the class invariant. A class invariant is a universal pre and post condition for every member function (except the constructors) of a class. Each member function (including constructors) must ensure that the class invariant is true upon exit from the member. Guidelines for constructor construction (use).
  1. Does not return a value so is declared without a type.
  2. SomeClass anObject(p1, p2, ...);
    
    is an abbreviation for
    SomeClass anObject = SomeClass((p1, p2, ...);
    
  3. A class may provide several constructors. The compiler chooses an appropriate constructor according to the number and type of actual parameters to the constructor.
  4. SomeClass  anObject;
    
    If the class has no constructors, then memory is allocated for anObjectbut no initialization is done

    If the class has constructors, the default constructor is invoked.

    If there is no default constructor we have a syntax error.

  5. If a class has at least one constructor and a vector of class instances is declared
    SomeClass vec[10];
    
    then one of the constructors must be the default and it will be invoked for each element of the vector. No parameters can be passed in this case.