Implementing a stack as two discrete variables (the data array and a stack pointer) has two significant disadvantages. First, a programmer can inadvertently alter one of the variables. For example, if the value stored in the stack pointer (sp) is changed, the next push or pop operation will occur at the wrong location in the stack array. Second, discrete variables limit the number of stacks that we can create within a given scope. For example, it's impossible to have two variables named "sp" defined in the same scope. While we can give the variables unique names like "sp1" and "sp2," doing so makes it difficult to create reusable library code. Structures solve the second problem, but we won't see a good solution for the first problem until we study classes in chapter 9.
Using a structure to implement a stack has one significant drawback: needing to pass a pointer to the stack in the stack functions. We also eliminate this clumsy requirement when studying classes in chapter 9. (We can also use pass-by-reference, but using pointers will better set the stage for our study of classes.)
stack.cpp (automatic structure version).
The example organizes the six stack functions into three distinct groups:
make_stack and init_stack
Initializing the stack pointer to 0 is the key task of both functions; without this step, the remaining stack functions will not behave correctly. A "real" stack implementation will only include one of these functions.
push and pop
These are the primary stack functions for storing and retrieving data from a stack. Note:push increments the stack pointer after storing the data, and pop decrements the stack pointer before removing the data. The if-statements prevent inserting data into a full stack and removing data from an empty stack. The throw keyword throws or launches an exception, relaying an error condition to the calling function. A subsequent chapter explains how to handle the exceptions.
size and peek
The last two functions are optional but common. The first function returns the stack's size - the number of elements currently saved on the stack. In this implementation, the stack pointer, sp, effectively represents the stack's size because it begins at index 0, and each push operation increments it by 1. The second function returns the top element on the stack without removing it.
Making And Using A stack
stack s = make_stack();
stack s;
init_stack(&s);
(a)
(b)
#include <iostream>
#include "stack.h"
using namespace std;
int main()
{
//stack s = make_stack(); // choose one
stack s;
init_stack(&s);
push(&s, 'x');
push(&s, 'y');
push(&s, 'z');
char c = peek(&s);
cout << c << endl;
c = pop(&s);
cout << c << endl;
while (size(&s) > 0)
cout << pop(&s) << endl;
return 0;
}
(c)
test.cpp (automatic structure version).
An example of how to use a stack implemented as a structure. The current implementation supports two ways of making and initializing a stack object.
The first option for creating a stack: a function creates and initializes a stack object, which the assignment operator copies into variable "s" (this option follows the pattern used by the Time structure)
The second option for creating a stack: The program creates a stack object as a local variable and passes it by-pointer to a stack function that initializes it (this option is closer to a class implementation of a stack)
Regardless of how the stack is created and initialized - (a) or (b) - the remaining functions all behave in the same way. Notice the use of the address of operator