7.12.1. Automatic Stack Implementation

Time: 00:10:46 | Download: Large Small | Streaming
Review

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.)

Header File

//#define SIZE 100		// 3 ways of making a constant
//enum { SIZE = 100; }
const int SIZE = 100;

struct stack
{
	char	st[SIZE];
	int	sp;
};

stack	make_stack();
void	init_stack(stack* s);
void	push(stack* s, char data);
char	pop(stack* s);
int	size(stack* s);
char	peek(stack* s);
stack.h (automatic structure version). It is a common practice to place descriptive information about a data structure into a header file. The descriptive information includes a symbolic constant for the maximum size of the array. The example illustrates three ways to create the constant, but only one is needed. Perhaps STACK_SIZE would be a better name for the constant to prevent a collision with sizes used in other contexts. The description also includes the structure specification and the function prototypes. Following the implementation pattern introduced in Chapter 5, we name the header and source code files after the structure.

The stack example includes two functions for creating a new stack: make_stack and init_stack. Although only one function is needed, the example includes the first to provide continuity with previous examples and the second to segue to the constructor functions in Chapter 9.

Stack Functions

Construction Typical Operations Optional Operations
stack make_stack()
{
    stack  temp;
    temp.sp = 0;
    return temp;
}

void init_stack(stack* s)
{
   s->sp = 0;
}
 
 
 
 
void push(stack* s, char data)
{
    if (s->sp < SIZE)
        s->st[s->sp++] = data;
    else
        throw "Stack Overflow";
}

char pop(stack* s)
{
    if (s->sp > 0)
        return s->st[--(s->sp)];
    else
        throw "Stack Underflow";
}
int size(stack* s)
{
    return s->sp;
}

char peek(stack* s)
{
    return s->st[s->sp - 1];
}
 
 
 
 
 
 
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.
  1. 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)
  2. 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)
  3. 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

Downloadable Code

Formatted with tab stops set at 8 spaces.

ViewDownload
stack.h stack.h
stack.cpp stack.cpp
test.cpp test.cpp