C++ provides the notion of a class which is very useful for implementing Abstract Data Types. A class in C++ is much like a struct in C, but with two important differences:
Let's consider an example of implementing a stack of characters by using an array. To simplify matters, we assume that we will never need to have more than 100 characters in the stack at any one time. First, in the standard way using structs, we might do it like this (to be clear, we are using ``stack_ptr'' instead of ``Stack'' to mean a pointer to a stack_struct):
/*******************************************************************************/
#define MAX_SIZE 100
/* Stack of CHARs. A stack_ptr is a pointer to a stack_struct. */
/* array[0] is bottom of stack and array[counter-1] is top of stack. */
struct stack_struct {
char array[MAX_SIZE];
int counter;
};
typedef struct stack_struct *stack_ptr;
stack_ptr init(void) /* initialize */
{
stack_ptr s = new stack_struct;
s->counter = 0;
return s;
}
void push(stack_ptr s, char c) /* Push. Exit if full */
{
assert(s->counter < MAX_SIZE); /* Quit if count >= MAX_SIZE (needs assert.h) */
s->array[s->counter] = c;
(s->counter)++;
}
char pop(stack_ptr s) /* Pop. Return '\0' if empty */
{
if(s->counter == 0) return '\0'; /* special if empty. */
(s->counter)--;
return s->array[s->counter];
}
int is_empty(stack_ptr s) /* returns 1 if empty and zero otherwise */
{
return (s->counter == 0);
}
/*******************************************************************************/
Now, let's do this with classes. To follow C++ convention, instead of our basic quantity being a pointer as above, a Stack will be an actual object (a class, like a struct). The reason that we used stack_ptr above is that C is call-by-value. When we write push(s,'a') we don't want the character 'a' to be pushed onto a copy of s (which would then get thrown away when push returned), we want it pushed onto the stack s itself.
First, here is what the class definition looks like:
class Stack {
// The next part is just like in the struct, but it's "private", which
// means only the member functions are allowed to access this data.
private:
static const int MAX_SIZE=100; // define maximum depth of stack
char array[MAX_SIZE];
int counter;
// First, the member functions. These are the interface. The keyword
// "public" means that they can be called by other (non-member) functions.
public:
Stack(); // constructor: like "init"
void push(char c);
char pop(void);
int is_empty(void);
};
What's going on:
First, if you look at the upper part of the definition, you will see it is almost like the earlier struct, except for the definition of MAX_SIZE and that the fields are labeled as private. This means that they can only be accessed through the member functions. The purpose for doing this is to enforce our interface. We can be sure no other function we write will mistakenly access or modify the data directly.
The lower part gives function prototypes for the member functions we will use. We have push, pop, and is_empty as before. You will notice that these don't take a stack as an argument any more. Also, the initializer, which is now called a constructor, has been given the name ``Stack'' and does not have a return type. We will get back to why this is in a moment.
The class definition itself can be placed in a header (.h) file, just like a struct definition.
Let's now write some member functions. These go into a .C file. The C++ notation takes a little getting used to. The main things to notice are (1) the names all begin with ``Stack::'', (2) they don't take a stack as an argument, and (3) inside a member function you are ``inside the Stack'', and the fields ``array'' and ``counter'' are just like regular variables. The ``::'' is called the scope operator. Let's begin with push, pop, and is_empty.
void Stack::push(char c)
{
assert(counter < MAX_SIZE); // Quit if count >= MAX_SIZE (needs assert.h)
array[counter] = c;
++counter;
}
char Stack::pop(void)
{
if (counter == 0) return '\0';
--counter;
return array[counter];
}
int Stack::is_empty(void)
{
return (counter == 0);
}
These functions are just like the ones we had before, except we can access the fields (array, counter) just like they were variables. The prefix ``Stack::'' tells the compiler that these functions are member functions of the Stack class. Let's now write the constructor.
Stack::Stack(void)
{
counter = 0;
}
Notice two things. First, the constructor has no return type, and
second, it doesn't allocate any space. The reason has to do with the
meaning of a constructor, which we get to in the next section.
Here is how we might use the member functions written above. First, to create two stacks called ``s'' and ``t'', we just say:
Stack s, t;
What happens when we do this is that two classes get created. Then, for each one, the constructor is called, whose job is to initialize the values as we want. For instance, if we changed the constructor to set counter = 1 instead of setting counter to 0, then s and t would have their counter fields set to 1. The reason the constructor doesn't have to allocate a new stack and doesn't have a return type is that its job is just to initialize the values.
Now, say we want to push the characters 'a' and 'b' in order onto s, and 'c' and 'd' in order onto t. To do that we just write:
s.push('a');
s.push('b');
t.push('c');
t.push('d');
The member function is just like a field in the structure.
If you want to think in an ``object-oriented'' way, you can think of
writing s.push('a') as asking the Stack s to push the character
'a' onto itself. If we now write:
cout << t.pop() << endl;we will get the character 'd' printed out.
If we write:
cout << s.pop() << endl;we will get the character 'b' printed out.
We could also write a loop, like
while(! s.is_empty()) { blah, blah, blah; }
But, if we try to directly access one of the private fields from a
non-member function, like
void foo(void)
{
Stack s;
s.counter = 3;
}
then we will get an error, since counter is private.
We could also define a stack_ptr to be a pointer to a Stack and then use the ``->'' instead of the ``.'' notation. For example,
typedef Stack *stack_ptr;
int main(void)
{
stack_ptr p;
p = new Stack; // Allocates space, then uses constructor to initialize.
p->push('a');
cout << p->pop() << endl;
return 0;
}
will print out the letter 'a'. In fact, writing p->push('a') is
a lot like writing push(p,'a') in our definition using structs.
The reason we defined ``Stack'' to be a class rather than a pointer to a class is that we did not need to worry about passing it into the member functions. If you need to pass a Stack into another function, however, then you should be careful and you probably want to pass a pointer (or a reference).
The definition of MAX_SIZE as a static const means that any object of type Stack can access the constant MAX_SIZE. If MAX_SIZE had been declared in the public portion of class, then it could be accessed anywhere by using the scope operator, i.e., Stack::MAX_SIZE.
Try this out in favorite environment/compiler (remember to include <iostream.h> and <assert.h>). Try out simpler examples, or modify the code, until you feel comfortable with classes.