What is a class?

The term class is one of the parts of C++ that was not part of C. In C, we have the idea of a struct, which may contain several data fields, such as:

        struct Account
         {
          char * Password;
          float Balance;
         };

A class is similar, but it may contain functions as well as data fields. Actually, in C++, a struct and a class are nearly the same.

The data fields in a struct or class are called data members, and the functions are called methods or member functions. Each of these can be either private (in which case the methods have access to them, but anything else does not usually have access), or public (in which case other parts of the code can use them).


Extremely simple example of a class

        class Simple  // This is actually too simple to be useful.
         {
          public: 
           Simple();           // constructor
           void SetValue(int Y);
           int  GetValue();
          private:
           int X;
         };
       
        Simple::Simple()
         {
          X = 1;
         };

        void Simple::SetValue(int Y)
         {
          X = Y;
         };

        int Simple::GetValue()
         {
          return(X);
         };

Let's look at Simple a piece at a time:

We have the key word class and the name of the class. As with a structure, the members are listed inside a set of braces { }. We have a list of public members and a list of private members. We could mix these up, if we like:

        class Simple
         {
          public:
           void Simple();           // constructor
          private
           int X;
          public:
           void SetValue(int);
           int  GetValue();
         };

In a class, the default is private. In a struct, the default is public. That's the principal difference between them.

The data being stored here is the value of X. It is private, but we have provided methods to get the value of X and to change the value of X. These functions are public, so a program using this class could call them. It is a common practice to have access methods of this sort ("get" and "set" methods).

Look at the definition of SetValue:

      void Simple::SetValue(int Y)
        {
         X = Y;
        };

Why do we have Simple:: before SetValue? Here the double colon, "::", is the scope resolution operator. Why do we need it? We are defining a function, and we need to tie it to the Simple class. There could be other functions defined somewhere also called "SetValue", and this notation makes it clear which one we are defining. It is occasionally necessary to use this notation in calling a member function or referring to a data member.

In SetValue, we can refer to X directly because, like SetValue, it is a member of the Simple class.

Next look at the method called "Simple". This is what is called a constructor function. Notice it does not have a type. It is called automatically when we declare a variable of this type:

       Simple M;

This creates an instance of the Simple class, that is, a variable of this type. Each instance stores its own data members, but the code for the member functions is stored just once. The constructor function is called automatically.

We can also have pointers to classes:

       Simple * P;

This is just a pointer, so the constructor has not been called yet. Suppose we use the new operator:

       P = new Simple;

As we are creating an instance of the Simple class, the constructor is called automatically.

A constructor function always has the same name as the class. If we do not define a constructor function, one is provided for us by default, and there is no guarantee that it will do everything we want. (For instance, the default constructor might have initialized X to 0 instead or left it uninitialized.)

There is also a destructor function which does the opposite: it gets rid of the instance of the class. The Simple class does not include a destructor, so we will have the default destructor. All it will do is give the memory involved (in this case, space for one integer) back to the system. If we were going to define a destructor, its name would be "~Simple". The name of a destructor is always of the form "~name", where "name" is the name of the class. A destructor also does not have a type.

When is the destructor function used? It is called when the variable disappears, that is, when it passes out of scope. Suppose we use the free operator:

       free P;  // Here P is a pointer to Simple as seen above.

Now the destructor will be called automatically.

We do not normally have any reason to call a destructor function explicitly. We will have reasons to write our own destructor functions sometimes and not simply use the default.


Slightly fancier example of a class

        class Account
         {
          public:
           Account();              // constructor
           ~Account();             // destructor
           void GetPassword();
           void SetPassword();
           float GetBalance();
           void SetBalance(float NewBalance);
           void NewUser();
           void EndSession();
          private:
           char * Password;
           float Balance;
           bool LoggedOn;
         }

Here the constructor function Account will initialize Password to an empty string, Balance to 0.0, and LoggedOn to false.

GetPassword will ask the user to supply a password, compare it to the value of Password, and set LoggedOn to true if they match.

Presumably GetBalance and SetBalance check the value of LoggedOn before doing anything.

NewUser will ask the user to provide the initial value of Password.

EndSession will set LoggedOn back to false.

Notice there are no methods for setting or getting the value of LoggedOn; it is only used internally.

In this case, we need to define a destructor function as well, to make sure the string pointed to by Password is properly deleted and the memory involved is given back to the system. If we do not do so, the danger is the default destructor may simply get rid of Password (a pointer) and not free the memory used by the string.


Assignment statements and classes

Suppose we have two instances of the Account class:

        Account A, B;

and now we do some work with A and B, supplying values for the fields.

Next we use an assignment statement:

        B = A;

What will happen? With a struct or a class, the assignment statement will (by default) simply make a copy of all the data members in A, assigning those values to B. The new values for the fields in B will replace whatever values those fields previously had. (This is sometimes called a bitwise copy.) In other words, an assignment statement actually does some work, so it can be regarded as a function call. The function involved is called operator =.

Often this is exactly what we want. It will work perfectly for the Simple class. For the Account class, though, it may cause some problems. Specifically, think about the Password field. It was declared as:

        char * Password;

Remember that an array can be regarded as a pointer. When we copy the value of A.Password into B.Password, the value that is copied is the pointer and not the whole string.

Suppose A.Password = "ABCDEFGH" and B.Password = "IJKLMNOP". That means A.Password is really a pointer to a 9-byte section of memory somewhere which contains "ABCDEFGH" (plus a '\0'), and B.Password is likewise a pointer to a 9-byte section of memory containing "IJKLMNOP" (plus a '\0').

After the assignment statement, both A.Password and B.Password are pointing to the 9 bytes containing "ABCDEFGH". What happened to the other 9 bytes? As nothing is pointing to them, they are simply lost--a memory leak--until the program ends.

For reasons like this, we will eventually discuss how we can redefine the assignment = operator.

A similar problem arises when we need to make a copy of an instance of a class. For example, when we pass a parameter (by value) to a function, the function actually receives a copy of the parameter. If we had a function such as:

        int SquareSimple(Simple G);  // This is the header.

and we called it:

        M = SquareSimple(Q);    

where M is an int and Q is a Simple, then the SquareSimple function is actually receiving a copy of Q, not Q itself.

This involves what is called the copy constructor. If we do not define a copy constructor for ourselves, we get the default copy constructor, which makes a bitwise copy. Again, for the Simple class, this is not a problem, but for the Account class (or any class containing pointers) we would encounter problems similar to the problems with the assignment = operator.

We will eventually discuss what is required in a good copy constructor.

A similar problem occurs with the default destructor, and we will eventually discuss what is required in a good destructor.