Abstract Data Types and C++ Implementation

 

Reading: Dietel 6.1-6.14

Abstract Data Types (ADTs)

Programmers implement the ADT; can see internal variables, function definitions, etc.

Users instantiate and work with the ADT; can see only the external operations / behavior

Example: Stack

Data Abstraction

 

Methods of Abstraction

Procedural Abstraction

    - repeated tasks coded once into a procedure, then called repeatedly
    - procedure written by one programmer
    - other programmers call procedure; need only interface (prototype), not
        implementation (definition)
    - implementation information is hidden
    - implementation and usage may be in same file or separate files
    - more than one data set may use the same procedure
    - problem: all data is "public", and may be accessed/changed by user of procedure

Example Stack Implementation -- Procedural

    typedef struct

    {    

          int cur_pos ;

          int array [ 100 ] ;      // Can have at most 100 elements in stack

    } stack;

    void Init ( stack* s )

    { 

        s -> cur_pos = 0;

    }

    void Push ( stack* s, int x )

    {

        if ( s -> cur_pos == 100 )

           cerr << "Stack full!";

        else

            s -> array [ s -> cur_pos ++ ] = x;

    }

    int Pop (stack* s)

    {

        if ( s -> cur_pos == 0 )

            { cerr << "Stack empty!"; return -1; }

        else

            return s -> array [ -- s -> cur_pos ];

    }

 

    void main()

    {

        stack s1, s2;    int x,y;

       Init ( &s1 );   Init( &s2 ); 

       Push ( &s1, 7 );  Push ( &s2,  9 );            // Good...

        x = Pop ( &s2 );   y = Pop ( &s1 );

        x = s1.array[187];      s2.array[503] = 6;  // ... and bad

        s2.cur_pos = -3;                                     

    }

Modular (File) Abstraction

    - two-part collection of related data and procedures
    - "private" part accessible within module only
    - "public" part accessible both within and outside module
    - separate parts according to what implementer and user must know (internal data,
         external operations)
    - problem: only one instantiation of data type possible at one time

Example Stack Implementation -- Modular

    File 1 (precompiled):

                < typedef from above >

                static stack s;

               void Init ( void )

               {

                    s.cur_pos = 0;

                }

                void Push ( int x )

                {

                    if ( s.cur_pos == 100 )

                        cerr << "Can't push onto a full stack!";

                    else

                        s.array [ s.cur_pos ++ ] = x;

                }

                int Pop ( void )

                {

                    if ( s.cur_pos == 0 )

                        { cerr << "Can't pop from an empty stack!"; return -1; }

                    else

                        return s.array [ -- s.cur_pos ];

                }

    File 2:

                 void main ( )

                {

                    Init ( );

                    Push ( 8 );        // We can't access the data directly without knowing the typedef

                    Push ( 10 );

                    int x = Pop ( );                    

                   }

Object-Oriented Programming

    - both data and operations collected within an object
    - object definition specifies which data, operations are public / private
    - multiple instantiations of each object possible; each instantiation has its own set
        of data values
    - all data access / modification performed by the object's operation set
    - control data access 
    - allow new/better object implementations without changing prior programs using that object
    - change of emphasis: procedural programming would call "push" with a stack and a
         value; object-oriented programming would tell a stack to push a value onto itself
         via that particular stack's push operation

Example Stack Implementation -- Object-Oriented

class stack

{

    private:                                        // Only stack member functions can access this data

                                                       //  Can also have private member functions

                int array[100];

                int cur_pos ;

 

    public:                                             // Data and functions accessible by any part of code

            void Push ( int x )                    // No need to pass in stack parameter to stack functions

            {

                if ( Is_Full() )                        // Can call another function on this object if necessary

                    cerr << "Can't push on a full stack!";

                else

                    array [ cur_pos ++ ] = x;  // No need to use . to access fields

            }

            int Pop ( void )

            {

                if ( Is_Empty() )                        

                    { cerr << "Can't pop from an empty stack!"; return -1; }

                else

                    return array [-- cur_pos  ] ; 

            }

            int Is_Empty ( void ) const   // Function does not change stack on which it is called

            {

                return ( cur_pos ==  0 );

            }

            int Is_Full ( void) const       // Could make Is_Full return false for a growing stack

            {

                return ( cur_pos ==  100 );

            }

            stack ( )     // Constructor function: initialize object as necessary

            {

                cur_pos = 0;

             }

    }

void main()

{

    stack s1, s2;                           // Constructor function called implicitly for each

    s1.Push(8);   s2.Push(27);      // Note: we cannot access the stack's array or cur_pos directly

                                                  // Compiler prevents erroneous stack usage

}

Inheritance