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_ptrabove 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:
#define MAX_SIZE 100 class Stack { // 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); // 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: char array[MAX_SIZE]; int counter; };
What's going on:
First, if you look at the lower part of the definition, you will see it is just like the earlier struct, except 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 upper part gives function prototypes for the member functions we will use. We have push, pop, and is_emptyas 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.
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 computer 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 called....
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).
Try this out in objectcenter or your favorite environment/compiler (remember to include <iostream.h> and <assert.h>). Try out simpler examples, or modifying the code, until it starts to feel comfortable.