Опубликован: 05.11.2013 | Доступ: свободный | Студентов: 542 / 46 | Длительность: 11:51:00
Лекция 6:

Абстрактный тип данных

По итогам наших рассуждений h-файл для модуля Char_Stack (char_stack.h) выглядит так:

----- файл char_stack.h -----
void Push_Stack(char Item);
/*====================================================
Операция записи символа в стек.
Предусловие Is_Stack_Full() == 0
Если предусловие нарушено,
состояние стека не изменяется.
=====================================================*/
void Pop_Stack(char *Item);
/*====================================================
Операция извлечения символа из стека.
Предусловие Is_Stack_Emty() == 0
Если предусловие нарушено,
состояние параметра не изменяется.
=====================================================*/
char Check_Top();
/*====================================================
Операция проверки символа, находящегося в вершине стека.
Предусловие Is_Stack_Emty() == 0
Если предусловие нарушено,
возвращается символ с кодом '/0'.
=====================================================*/
int Is_Stack_Empty();
/*====================================================
Операция проверки пустого стека.
Is_Stack_Emty() == 1, если в стеке нет ни одного элемента.
=====================================================*/
int Is_Stack_Full();
/*====================================================
Операция проверки пустого стека.
Is_Stack_Full() == 1, если стек заполнен полностью.
=====================================================*/  
    

Программный код самого модуля char_stack.c может выглядеть следующим образом:

----- файл char_stack.c -----
#include "char_stack.h"
define MAX_ITEM_NUMBER 20
char Stack[MAX_ITEM_NUMBER];
int Top = 0;     /* Изначально стек пуст */
void Push_Stack(char Item);
/*====================================================
Операция записи символа в стек.
=====================================================*/
{
  if (Is_Stack_Full() == 0) /* Если стек не полон */
  {       /* Включить элемент в стек */
    Stack[Top++] = Item;
  }
}
void Pop_Stack(char *Item);
/*====================================================
Операция извлечения символа из стека.
=====================================================*/
{
  if (Is_Stack_Emty() == 0) /* Если стек не пуст */
  { /* Извлечь элемент из стека */
    Item* = Stack[--Top];
  }
}
char Check_Top();
/*====================================================
Операция копирования символа из вершины стека.
=====================================================*/
{
  if (Is_Stack_Emty() == 0) /* Если стек не пуст */
  {       /* Копировать вершину стека */
    return( Stack[Top - 1]);
  }
  else     /* Если стек пуст */
  { /* Вернуть символ с нулевым кодом */
    return( '\0' );
  }
}
int   Is_Stack_Empty();
/*====================================================
Операция проверки пустого стека.
=====================================================*/
{
  return ( Top == 0 );
}
  int Is_Stack_Full();
/*====================================================
Операция проверки пустого стека.
=====================================================*/
{
  return ( Top == MAX_ITEM_NUMBER );
}  
    

Приведенный пример кода выглядит более понятным, нежели реализация стека прямо по ходу программы. Обратим внимание на ряд деталей.

Наличие определений функций предусловий в заголовочном файле, который мы включили в модуль, обеспечивает не только согласованность типов параметров, но и возможность обращения к функциям проверки предусловий Is_Stack_Emty() и Is_Stack_Full () до их объявления в модуле.

Определение размера стека через константу MAX_ITEM_NUMBER позволяет упростить при необходимости изменение его размера. А применение операций Top++ при записи в стек и --Top при извлечении верхнего элемента обеспечивает компактность записи без потери наглядности.

Локализация в модуле, поддерживающем абстрактный тип всех тонкостей его реализации, освобождает пользователя, импортирующего АТД, от ненужных ему подробностей. Однако вводимые пользователем типы имеют определенные отличия от встроенных в язык типов.

Рассмотренный выше тип "стек" представлял собой статический тип данных. Он был единственным и представлялся самим модулем Char_Stack. Мы не могли с его помощью создать еще один стек, например для хранения операндов выражения. Способом реализации типа "стек" с более гибкой структурой может быть стек, реализованный на списке с указателями.

Создание стека - установка указателя на его головной элемент. Пустому стеку соответствует значение указателя NILL. Включение нового элемента - создание структуры: значение, указатель на следующий; копирование включаемого значения в созданный элемент и подключение нового элемента в качестве вершины стека.

Что является отличительной чертой такого способа реализации АТД?

  1. Тип объявляется в модуле-импортере как указатель (как правило, на структуру).
  2. Память переменной АТД (указателя) распределена в модуле- импортере.
  3. Начальное состояние переменной АТД никак не определено. Обязательно нужен начальный конструктор. До его применения невозможно определение предусловий.
  4. Как правило, размещение новых значений в подобном АТД связано с обращением к модулю управления динамической памятью, что, с одной стороны, увеличивает гибкость, снимая статические ограничения на размер структуры, но, с другой стороны, создает опасность динамического выхода за границы допустимой памяти.

Заметим, конкретно для стека возможна и смешанная стратегия реализации. Суть ее в том, что при создании стека отводится память сразу подо все его будущие элементы, так сказать, по максимуму. Это приводит к его первичной инициализации (состояние "пустой") и дает возможность легко следить за переполнением (мы знаем и храним максимально возможное количество элементов).

Для структур такого типа самая опасная ошибка - пропуск операции создания переменной АТД. Дело в том, что модуль реализации абстрактного типа в большинстве случаев может сам определять механизмы для создания переменных своего типа. Для каждого нового типа будет своя новая, отличная от других, операция создания. При этом компилятор не может следить за правильностью ее использования.

Объявив в коде две переменных Stack_1 и Stack_2 типа "стек", пользователь может забыть создать переменную Stack_2. При компиляции эта ошибка не будет обнаружена. Аналогичная ситуация складывается для операций удаления переменных абстрактного типа потому, что если абстрактный тип использует динамическую память, то автоматический "сборщик мусора" не сможет освободить выделенную память за пользователя, который сам не сделал это явно (например, забыл удалить Stack_1).

Кроме того, абстрактные типы не удовлетворяют требованиям, предъявляемым встроенным переменным. Например, при передаче в качестве параметра по значению абстрактного типа, который на самом деле является указателем, имеется возможность изменить значение переданной переменной, что явно не видно из кода.

Еще одним неудобством становится необходимость практически всегда реализовывать операции сравнения и копирования для АТД и в пользовательской программе использовать только их. При этом наглядность таких действий несколько снижается. Строка

А=В;  
    

выглядит понятнее, чем

fraction_copy(A,B);  
    

Рассмотрим еще один пример реализации и использования абстрактного типа данных - множество. В некоторых языках программирования, например Modula-2, множество является стандартным типом данных, но в Си такой тип отсутствует. Для примера рассмотрим множество символов расширенной таблицы ASCII, т.е. элемен-тами множества могут быть любые символы char. Для хранения такого множества можно использовать массив с длиной, равной количеству всех значений char, т.е. 256 элементов. Для символов, принадлежащих конкретному множеству, значение элемента с номером, соответствующим ASCII-коду символа, устанавливается в единицу. Остальные элементы массива равны нулю.

Перед использованием множества следует обнулить массив, чтобы получить пустое множество. Далее путем добавления нужных элементов можно получить требуемое множество.

В некоторых случаях удобно сразу создать множество на основе строки символов, поместив все символы строки в результирующее множество.