Ordered-Access Collections
Stack ADT
- Ordered collection of homogenous elements
- Can be constructed and destroyed
- Can insert and remove elements (last in is first out)
- Push: insert onto top; pop: remove from top
- Top: peek at top element, without removing it
- Can be cleared (emptied)
- Can be copied and assigned
- Has a number of elements (which can be accessed)
Implementation concerns
- Implement as template class
behavior, implementation independent of type of data stored- Implement underneath as an array
can implement as a list, but inefficient- Needs dynamic expansion / contraction
similar to realloc( )- We will double amount of memory when array becomes full
- We will halve amount of memory when array is less than 1/4 full
- Bottom element is kept in first slot of underlying array
Implementation as stack template class
- Array holds data elements
- Push: modify one-past-last element, increment size
- Pop: return last element, decrement size
- No random-access ( no [ ], no at( ) function )
would allow breaking of ordered insertion/removal- No iterators
also would break access rules
template < class T >
class Stack
{
public:
Stack ( ); // constructs an empty stack
Stack ( const Stack < T > & ) ; // copy constructor
Stack& operator= ( const stack < T > & ); // assignment
~Stack ( ); // deallocates associated memory
void Clear ( ); // removes all existing elements from stack
void Push ( const T& );
T Pop ( void );
T& Top ( void ); // Allows us to change Top; could have popped and re-pushed
int NumElements ( void );
bool IsEmpty ( void );
int Capacity ( void ); // User may want to know how long before reallocation
void Print ( ostream& o = cout ); // Doesn't break access rules
// We could Pop, print, Push onto another stack, then undo onto original stack
private:
int num_elements; // The number of T objects currently being stored
T* array;
int capacity; // The number of T objects we currently have room for
};
template < class T >
Stack < T > :: Stack ( ) : num_elements ( 0 ) , capacity ( 1 )// constructor
{
array = new T [ 1 ] ; // Since 2 * 0 is still 0, we want to start with capacity 1
// new [ ] syntax allows us to always use delete [ ] later
}
template < class T >
Stack < T > :: Stack ( const Stack < T > & rhs ) // copy constructor
{
num_elements = rhs . num_elements;
capacity = rhs . capacity;
array = new T [ capacity ];
for ( int i = 0; i < num_elements ; i++ )
array [ i ] = rhs . array [ i ];
}
template < class T >
Stack < T > & Stack < T > :: operator = ( const Stack < T > & ) // assignment operator
{
if ( this != & rhs )
{
delete [ ] array; // Empty out the left-hand side
num_elements = rhs . num_elements;
capacity = rhs . capacity;
array = new T [ capacity ];
for ( int i = 0; i < capacity ; i ++ )
array [ i ] = rhs . array [ i ] ;
}
return *this;
}
template < class T >
T& Stack < T > :: Top ( void ) // Peek at top element
{
if ( num_elements )
return array [ num_elements - 1 ];
// otherwise need to throw an exception
}
template < class T >
T Stack < T > :: Pop ( void ) // Remove and return top element
{
if ( num_elements == 0 )
// need to throw some exception
T return_value = array [ num_elements - 1 ];
num_elements -- ;
// Now we may need to contract array
if ( num_elements < capacity / 4 )
{
T* new_array = new T [ max ( capacity / 2, 1 ) ] ; // Don't go below 0!
for ( int i = 0; i < num_elements; i ++ )
new_array [ i ] = array [ i ]; // Only copy over as many elements as are being stored
delete [ ] array;
array = new_array;
capacity = max ( capacity / 2, 1 );
}
return return_value;
}
template < class T >
void Stack < T > :: Push ( const T& value )
{
// If we are already at capacity, we need to expand the array first.
if ( num_elements == capacity )
{
T* new_array = new T [ capacity * 2 ];
for ( int i = 0; i < num_elements; i ++ )
new_array [ i ] = array [ i ];
delete [ ] array;
array = new_array;
capacity *= 2;
}
array [ num_elements ] = value;
num_elements ++;
}
template < class T >
void Stack < T > :: Clear ( void ) // Empty out the stack
{
delete [ ] array;
num_elements = 0;
capacity = 1;
array = new T [ 1 ];
}
template < class T >
Stack < T> :: ~Stack ( )
{
delete [ ] array;
}
template < class T >
int Stack < T > :: NumElements ( void )
{
return num_elements;
}
template < class T >
bool Stack < T > :: IsEmpty ( void )
{
return ( num_elements == 0 )
}
template < class T >
int Stack < T > :: Capacity ( void ) // Returns total amount of space allocated
{
return capacity;
}
template < class T >
void Stack < T > :: Print ( ostream& o )
{
o << "Stack has " << num_elements << " elements." << endl;
for ( int i = num_elements - 1 ; i >= 0; i -- ) // Print top element first
o << array [ i ] << " "; // Assumes T class implements << operator
o << endl;
}
Stack Usage Examples
Stack < VarInt > VarIntStack ;
Stack < double > DoubleStack;
for ( int i = 0; i < 50; i ++ )
DoubleStack . Push ( i ) ; // Pushes doubles 0 - 49 onto stack
for ( int i = 0; i < 10; i ++ )
VarIntStack . Push ( itoa ( i ) ); // Pushes hex numbers 0 - 9 onto stack
DoubleStack . Top ( ) = 2.5; // DoubleStack is now 0.0 - 48.0, 2.5
for ( int i = 0 ; i < 5; i ++ )
VarIntStack . Pop ( ); // VarIntStack now holds hex numbers 0 - 4
VarIntStack . Print ( ) ; // Will use VarInt's << operator