The associated scope rules define this distinction between public (exported) identifiers, that can be seen both inside and outside the ADT, and private (local) identifiers, that can only be seen inside the ADT. Therefore, unlike a classical block structured language, scopes are no longer strictly nested (c.f. 14). This is known as encapsulation.
Example of using ADTs in SML ($CS2111/e*/stack/*
):
type intlist = int list abstype STACK = Stack of intlist with exception stack_is_empty val stack = Stack [] fun push (Stack stk, number) = Stack (number :: stk) fun pop (Stack []) = raise stack_is_empty | pop (Stack (x::stk)) = Stack stk fun top (Stack []) = raise stack_is_empty | top (Stack (x::stk)) = x fun empty (Stack []) = true | empty (Stack stk) = false end val stk = stack val stk = push (stk, 42) val stk = push (stk, 17) val stk = pop (stk) val top_one = top (stk)Here
Stack
is private, and stack_is_empty
, stack
,
push
, pop
, top
, and empty
are public. The
local
keyword can be used to make identifiers private.
LAST_RANDOM: FLOAT := 0.5 + FLOAT'EPSILON; procedure RANDOM(RR: FLOAT out) is declare A: constant FLOAT := LAST_RANDOM*125.0; begin LAST _RANDOM := A_TRUNC(A); RR := LAST_RANDOM; end;Users of
RANDOM
should not be allowed to get at the variable
LAST_RANDOM
. This can be prevented by wrapping these declarations up
in a module (package) from which LAST_RANDOM
will not be exported.
Note that the declaration of the package
is separate from its implementation (body).
This is a feature found in many, but not all, languages providing ADTs,
modules or classes.
Example of using Modules in Ada:
package RANDOM_PACKAGE is procedure RANDOM(RR: FLOAT out); -- only identifiers declared here will be exported end RANDOM_PACKAGE; package body RANDOM_PACKAGE is -- insert LAST_RANDOM and RANDOM as above end RANDOM_PACKAGE; declare use RANDOM_PACKAGE; begin -- here we can see RANDOM but not LAST_RANDOM endThe scope of
RANDOM
is the inside of the package body plus the inside
of the block where RANDOM_PACKAGE
is used. The scope of
LAST_RANDOM
, however, is the inside of the package body only.
Binding: inheritance and polymorphism make it harder to decide the exact type of every object and thus which version of an overloaded operation to use. (Does the object belong to the class where the operation was defined, or did it inherit it, or could it have inherited it but is in a class where the operation was redefined?) Therefore, whereas in many programming languages we can decide this at compile time (static or eager binding), in many but not all OO languages we can only decide this at run time (dynamic or lazy binding)
new
and delete
,
rather than malloc
and free
, to create heap instances of
classes, and can have static and auto instances, just like C variables.
A C++ class can have private
and public
components, with the
standard meaning. It can also include special constructor and
destructor functions, which are automatically used to create and
destroy instances of the class, usually because entities of the class need
initialising - they can also be used to new
and delete
dynamic entities within the class. A constructor function has the same name
as the class, a destructor function has the same name preceded by a
~
. Overloaded functions can be created using the virtual
keyword.
There are various facilities to help create related classes and sub-classes,
including protected
entities and friend
classes. A hierarchy
of classes is simply created by using entities of one class type inside
another.
Example of using Objects/Classes in C++:
#include <iostream.h> class stack { private: int *stack_ptr, max_len, top_ptr; public: stack (int size) { stack_ptr= new int [size]; max_len= size - 1; top_ptr= -1; }; ~stack (void) {delete stack_ptr;}; void push (int number) { if (top_ptr == max_len) cout << "Error in push: stack is full\n"; else stack_ptr[++top_ptr]= number; } void pop (void) { if (top_ptr == -1) cout << "Error in pop: stack is empty\n"; else top_ptr--; } int top (void) { if (top_ptr == -1) cout << "Error in top: stack is empty\n"; else return (stack_ptr[top_ptr]); } int empty (void) {return (top_ptr == -1);} } int main (void) { int top_one; stack stk (100); stk.push (42); stk.push (17); stk.pop (); top_one= stk.top (); return 0; }