Section 4.1
Classes
cplusplus.com

A class is a logical method to organize data and functions in the same structure. They are declared using keyword class, whose functionality is similar to that of the C keyword struct, but with the possibility of including functions as members, instead of only data.

Its form is:

class class_name {
  permission_label_1:
    member1;
  permission_label_2:
    member2;
  ...
  } object_name;
where class_name is a name for the class (user defined type) and the optional field object_name is one, or several, valid object identifiers. The body of the declaration can contain members, that can be either data or function declarations, and optionally permission labels, that can be any of these three keywords: private:, public: or protected:. They make reference to the permission which the following members acquire: If we declare members of a class before including any permission label, the members are considered private, since it is the default permission that the members of a class declared with the class keyword acquire.

For example:

class CRectangle {
    int x, y;
  public:
    void set_values (int,int);
    int area (void);
  } rect;
Declares class CRectangle and an object called rect of this class (type). This class contains four members: two variables of type int (x and y) in the private section (because private is the default permission) and two functions in the public section: set_values() and area(), of which we have only included the prototype.

Notice the difference between class name and object name: In the previous example, CRectangle was the class name (i.e., the user-defined type), whereas rect was an object of type CRectangle. Is the same difference that int and a have in the following declaration:

int a;
int is the class name (type) and a is the object name (variable).

On successive instructions in the body of the program we can refer to any of the public members of the object rect as if they were normal functions or variables, just by putting the object's name followed by a point and then the class member (like we did with C structs). For example:

rect.set_value (3,4);
myarea = rect.area();
but we will not be able to refer to x or y since they are private members of the class and they could only be referred from other members of that same class. Confused? Here is the complete example of class CRectangle:

// classes example
#include <iostream.h>

class CRectangle {
    int x, y;
  public:
    void set_values (int,int);
    int area (void) {return (x*y);}
};

void CRectangle::set_values (int a, int b) {
  x = a;
  y = b;
}

int main () {
  CRectangle rect;
  rect.set_values (3,4);
  cout << "area: " << rect.area();
}
area: 12

The new thing in this code is the operator :: of scope included in the definition of set_values(). It is used to declare a member of a class outside it. Notice that we have defined the behavior of function area() within the definition of the CRectangle class - given its extreme simplicity. Whereas set_values() has only its protype declared within the class but its definition is outside. In this outside declaration we must use the operator of scope ::.

The scope operator (::) specifies the class to which the member being declared belongs, granting exactly the same scope properties as if it was directly defined within the class. For example, in the function set_values() of the previous code, we have referred to the variables x and y, that are members of class CRectangle and that are only visible inside it and its members (since they are private).

The only difference between defining a class member function completely within its class and to include only the prototype, is that in the first case the function will automatically be considered inline by the compiler, while in the second it will be a normal (not-inline) class member function.

The reason why we have made x and y private members (remember that if nothing else is said all members of a class defined with keyword class have private access) it is because we have already defined a function to introduce those values in the object (set_values()) and therefore the rest of the program does not have a way to directly access them. Perhaps in a so simple example as this you do not see a great utility protecting those two variables, but in greater projects it may be very important that values cannot be modified in an unexpected way (unexpected from the point of view of the object).

One of the greater advantages of a class is that we can declare several different objects from it. For example, following with the previous example of class CRectangle, we could have declared the object rectb in addition to the object rect :

// class example
#include <iostream.h>

class CRectangle {
    int x, y;
  public:
    void set_values (int,int);
    int area (void) {return (x*y);}
};

void CRectangle::set_values (int a, int b) {
  x = a;
  y = b;
}

int main () {
  CRectangle rect, rectb;
  rect.set_values (3,4);
  rectb.set_values (5,6);
  cout << "rect area: " << rect.area() << endl;
  cout << "rectb area: " << rectb.area() << endl;
}
rect area: 12
rectb area: 30

Notice that the call to rect.area() does not give the same result as the call to rectb.area(). This is because each object of class CRectangle has its own variables x and y, and its own functions set_value() and area().

On that is based the concept of object and object-oriented programming. In that data and functions are properties of the object, instead of the usual view of objects as function parameters in structured programming. In this and the following sections we will discuss advantages of this methodology.

In this concrete case, the class (type of object) to which we were talking about is CRectangle, of which there are two instances, or objects: rect and rectb, each one with its own member variables and member functions.

Constructors and destructors

Objects generally need to initialize variables or assign dynamic memory during their process of creation to become totally operative and to avoid returning unexpected values during their execution. For example, what would happen if in the previous example we called the function area() before having called function set_values? Probably an indetermined result since the members x and y would have never been assigned a value.

In order to avoid that, a class can include a special function: a constructor, which can be declared by naming a member function with the same name as the class. This constructor function will be called automatically when a new instance of the class is created (when declaring a new object or allocating an object of that class) and only then. We are going to implement CRectangle including a constructor:

// classes example
#include <iostream.h>

class CRectangle {
    int width, height;
  public:
    CRectangle (int,int);
    int area (void) {return (width*height);}
};

CRectangle::CRectangle (int a, int b) {
  width = a;
  height = b;
}

int main () {
  CRectangle rect (3,4);
  CRectangle rectb (5,6);
  cout << "rect area: " << rect.area() << endl;
  cout << "rectb area: " << rectb.area() << endl;
}
rect area: 12
rectb area: 30

As you can see, the result of this example is identical to the previous one. In this case we have only replaced the function set_values, that no longer exists, by a class constructor. Notice the way in which the parameters are passed to the constructor at the moment at which the instances of the class are created:

CRectangle rect (3,4);
CRectangle rectb (5,6);
You can also see how neither the prototype nor the later constructor declaration includes a return value, not even void type. This must always be thus. A constructor never returns a value nor does the void have to be specified, as we have shown in the previous example.

The Destructor fulfills the opposite functionality. It is automatically called when an object is released from the memory, either because its scope of existence has finished (for example, if it was defined as a local object within a function and the function ends) or because it is an object dynamically assigned and it is released using operator delete.

The destructor must have the same name as the class with a tilde (~) as prefix and it must return no value.

The use of destructors is specially suitable when an object assigns dynamic memory during its life and at the moment of being destroyed we want to release the memory that it has used.

// example on constructors and destructors
#include <iostream.h>

class CRectangle {
    int *width, *height;
  public:
    CRectangle (int,int);
    ~CRectangle ();
    int area (void) {return (*width * *height);}
};

CRectangle::CRectangle (int a, int b) {
  width = new int;
  height = new int;
  *width = a;
  *height = b;
}

CRectangle::~CRectangle () {
  delete width;
  delete height;
}

int main () {
  CRectangle rect (3,4), rectb (5,6);
  cout << "rect area: " << rect.area() << endl;
  cout << "rectb area: " << rectb.area() << endl;
  return 0;
}
rect area: 12
rectb area: 30

Overloading Constructors

Like any other function, a constructor can also be overloaded with several functions that have the same name but different types or numbers of parameters. Remember that the compiler will execute the one that matches at the moment at which a function with that name is called (Section 2.3, Functions-II). In this case, at the moment at which a class object is declared.

In fact, in the cases where we declare a class and we do not specify any constructor the compiler automatically assumes two overloaded constructors ("default constructor" and "copy constructor"). For example, for the class:

class CExample {
  public:
    int a,b,c;
    void multiply (int n, int m) { a=n; b=m; c=a*b; };
  };
with no constructors, the compiler automatically assumes that it has the following constructor member functions:

It is important to realize that both default constructors: the empty construction and the copy constructor exist only if no other constructor is explicitly declared. In case that any constructor with any number of parameters is declared, none of these two default constructors will exist. So if you want them to be there, you must define your own ones.

Of course, you can also overload the class constructor providing different constructors for when you pass parameters between parenthesis and when you do not (empty):

// overloading class constructors
#include <iostream.h>

class CRectangle {
    int width, height;
  public:
    CRectangle ();
    CRectangle (int,int);
    int area (void) {return (width*height);}
};

CRectangle::CRectangle () {
  width = 5;
  height = 5;
}

CRectangle::CRectangle (int a, int b) {
  width = a;
  height = b;
}

int main () {
  CRectangle rect (3,4);
  CRectangle rectb;
  cout << "rect area: " << rect.area() << endl;
  cout << "rectb area: " << rectb.area() << endl;
}
rect area: 12
rectb area: 25

In this case rectb was declared without parameters, so it has been initialized with the constructor that has no parameters, which declares both width and height with a value of 5.

Notice that if we declare a new object and we do not want to pass parameters to it we do not include parentheses ():

CRectangle rectb;   // right
CRectangle rectb(); // wrong!

Pointers to classes

It is perfectly valid to create pointers pointing to classes, in order to do that we must simply consider that once declared, the class becomes a valid type, so use the class name as the type for the pointer. For example:
CRectangle * prect;
is a pointer to an object of class CRectangle.

As it happens with data structures, to refer directly to a member of an object pointed by a pointer you should use operator ->. Here is an example with some possible combinations:

// pointer to classes example
#include <iostream.h>

class CRectangle {
    int width, height;
  public:
    void set_values (int, int);
    int area (void) {return (width * height);}
};

void CRectangle::set_values (int a, int b) {
  width = a;
  height = b;
}

int main () {
  CRectangle a, *b, *c;
  CRectangle * d = new CRectangle[2];
  b= new CRectangle;
  c= &a;
  a.set_values (1,2);
  b->set_values (3,4);
  d->set_values (5,6);
  d[1].set_values (7,8);
  cout << "a area: " << a.area() << endl;
  cout << "*b area: " << b->area() << endl;
  cout << "*c area: " << c->area() << endl;
  cout << "d[0] area: " << d[0].area() << endl;
  cout << "d[1] area: " << d[1].area() << endl;
  return 0;
}
a area: 2
*b area: 12
*c area: 2
d[0] area: 30
d[1] area: 56

Next you have a summary on how can you read some pointer and class operators (*, &, ., ->, [ ]) that appear in the previous example:

*x     can be read:  pointed by x
&x     can be read:  address of x
x.y    can be read:  member y of object x
(*x).y can be read:  member y of object pointed by x
x->y   can be read:  member y of object pointed by x (equivalent to the previous one)
x[0]   can be read:  first object pointed by x
x[1]   can be read:  second object pointed by x
x[n]   can be read:  (n+1)th object pointed by x
Be sure you understand the logic of all of these before going on. If you have doubts, read again this section and/or consult sections "3.3, Pointers" and "3.5, Structures".

Classes defined with keyword struct

C++ language has extended the C keyword struct to the same functionality of the C++ class keyword except that its members are public by default instead of being private.

Anyway, because both class and struct have almost the same functionality in C++, struct is usually used for data-only structures and class for classes that have procedures and member functions.

© The C++ Resources Network, 2000-2003 - All rights reserved

Previous:
3-6. User defined data types.

index
Next:
4-2. Overloading operators. this. Static members.