In this lecture, we cover how you would implement a general-purpose stack in both C and C++. Part of the goal is to get you to start thinking in an object-oriented manner.
#ifndef _JSTACK_ #define _JSTACK_ #include "jval.h" typedef void *Stack; /* Allocate and deallocate a stack. New_stack() does call malloc. */ extern Stack new_stack(); extern free_stack(Stack); /* Test to see if the stack is empty -- or how many elements it as */ extern int stack_empty(Stack); extern int stack_size(Stack); /* Put something on the stack */ extern stack_push(Stack, Jval); /* Take something off the stack */ extern Jval stack_pop(Stack); /* Look at the top element, but don't take it off */ extern Jval stack_top(Stack); #endif |
Note a few things about this header file:
What these lines do is allow you to include the same header file multiple times, and it makes sure that the main body of the header file is only included once. Remember this trick when you start writing your own header files.
#include "c_stack.h"
main()
{
Stack s;
int i;
s = new_stack();
while(scanf("%d", &i) == 1) {
stack_push(s, new_jval_i(i));
}
while(!stack_empty(s)) {
printf("%d\n", jval_i(stack_pop(s)));
}
free_stack(s);
}
|
Try it out: (note, when compiling, you have to include c_stack.o, which is where the stack functions are implemented -- we'll go over this in a bit. Also you have to include libfdr.a because that is where the Jval routines are implemented).
UNIX> make c_numrev gcc -I/blugreen/homes/plank/cs302/include -c c_numrev.c gcc -I/blugreen/homes/plank/cs302/include -c c_stack.c gcc -I/blugreen/homes/plank/cs302/include -o c_numrev c_numrev.o c_stack.o /blugreen/homes/plank/cs302/cobjs/libfdr.a UNIX> c_numrev 1 2 3 4 5 6 7 8 9 10 < CNTL-D > 10 9 8 7 6 5 4 3 2 1 UNIX>Also, note that you didn't need to call free_stack(). I just did it so that I can correlate it to the C++ example.
The routines defined in c_stack.h are implemented in c_stack.c. I won't go over it -- it is straightforward, and if you would like some description, see the stack implementation lecture from CS140.
The key things to note about this is the following:
As in last lecture, our member variables are protected. The only public members of the Stack class are its methods. The member variable is of type (class StackNode *) -- we don't know what that is, but that's ok; we don't need to know what it is to use the Stack class. The C++ compiler knows that top_node will be a pointer, even if it does not know what it will point to.
We can write the C++ version of numrev using the Stack class. Here's the code (from numrev.cpp):
#include "stack.h"
#include < stdio.h >
main()
{
Stack *s;
int i;
s = new Stack;
while(scanf("%d", &i) == 1) {
s->push(new_jval_i(i));
}
while(!s->isEmpty()) {
printf("%d\n", jval_i(s->pop()));
}
delete s;
}
A few things to note. Unlike the Golfer class from the
last lecture (the puttproc lecture), we
don't need to pass any parameters when we create an instance of
the Stack class. Instead, we just call ``new Stack.''
Everything else is pretty straightforward. Note, I don't have to
call ``delete s'' at the end, but I do just to show you a
destructor method being called.
Try it out -- you'll see it works just like c_numrev.
Now, the implementation of stacks is in stack.cpp. Note that this is where the StackNode class is defined and implemented. We do both in stack.cpp because we don't want to allow others to see or make use of the StackNode class.
Here is the StackNode definition and implementation:
class StackNode {
public:
StackNode(Jval, StackNode *); // Create a new node with given val and next
~StackNode();
Jval val;
StackNode *next;
};
StackNode::StackNode(Jval v, StackNode *n)
{
val = v;
next = n;
}
StackNode::~StackNode()
{
if (next != NULL) delete next;
}
Everything is pretty straightforward. Note that the destrictor is actually
recursive -- it will delete every StackNode on the stack. Moreover,
note that the member variables val and next are public rather
than protected. This is because StackNode is only used in this
file, so we don't worry about protecting it. In fact, we don't want it
protected because we want the Stack methods to be able to access
these variables directly.
Now, here are the constructor/destructors for the Stack class:
Stack::Stack() // Create an empty stack
{
top_node = NULL;
}
Stack::~Stack() // Destroy a stack
{
if (top_node != NULL) delete top_node;
}
Once again, these are all straightforward, as are the rest of the
methods:
void Stack::push(Jval v)
{
top_node = new StackNode(v, top_node);
}
Jval Stack::pop()
{
StackNode *tmp;
Jval v;
if (top_node == NULL) {
fprintf(stderr, "ERROR: Trying to pop empty stack\n");
exit(1);
}
tmp = top_node;
v = tmp->val;
/* Unhook the top node */
top_node = tmp->next;
/* Delete the top node */
tmp->next = NULL;
delete tmp;
return v;
}
Jval Stack::top()
{
if (top_node == NULL) {
fprintf(stderr, "ERROR: Trying to get the top of an empty stack\n");
exit(1);
}
return top_node->val;
}
bool Stack::isEmpty()
{
return (top_node == NULL);
}
Now, compile everything and give it a try. You might even try using
stack.h and stack.cpp to do a different task, like
reversing the lines of standard input.
#include "stack.h"
#include < stdio.h >
main()
{
Stack s;
int i;
while(scanf("%d", &i) == 1) {
s.push(new_jval_i(i));
}
while(!s.isEmpty()) {
printf("%d\n", jval_i(s.pop()));
}
}
Note, we don't call new or delete -- these are called
automatically (If you don't believe me, put print statements in
Stack::Stack() and Stack::~Stack() ).
This is nice because you don't have to keep track
of memory quite as much.