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

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

Для сокращения дроби в модуле реализована отдельная функция shorten, которая не указана в заголовочном файле fraction.h и является недоступной импортеру. Она используется только внутри самого модуля.

При выполнении арифметических операций возможна ситуация, что и числитель, и знаменатель не "уместятся" в тип int и произойдет переполнение. При этом переполнение может произойти в ходе промежуточных действий, а сам результат функции может "уместиться" в имеющийся тип. Например, умножение

(MAXINT / 2 ) * (2 / MAXINT)  
    

дает результат 1/ 1, что не выходит за границы int, но в промежуточных вычислениях вполне возможно получение значения 2*MAXINT, не укладывающееся в int (здесь MAXINT - максимальное значение, которое может храниться в int).

Можно было бы придумать такой алгоритм, в котором сначала находится общий делитель знаменателя и числителя и перед умножением на него выполняется деление. Однако этот подход не сработает в случае сложения, например для случая

(MAXINT / 3) + ((MAXINT + 1) / 3) = (2*MAXINT + 1) / 3  
    

если int занимает 4 байта, MAXINT = 2147483647. Это число не делится на 3, и дробь (MAXINT / 3) является сокращенной. Число 2147483648 (2147483647 + 1) тоже не делится на 3, и дробь ((MAXINT + 1) / 3) также является сокращенной. Однако дробь (2*MAXINT + 1) / 3 может быть сокращена, так как (2*MAXINT + 1) = 4294967295 делится на 3. При этом результат деления умещается в int.

Пример реализации, лишенной этой проблемы, может быть следующим:

Fraction addF2(Fraction f1, Fraction f2)
{
  Fraction res;
  long num, resNum;
  long denum, resDenum;
  num = (f1.numerator*f2.denominator) + (f2.numerator*f1.denominator);
  denum = f1.denominator*f2.denominator;
  shortenLong(num, denum, &resNum, &resDenum);
  res.numerator = (int) resNum;
  res.denominator = (int) resDenum;
  if ((res.numerator == resNum) && (res.denominator == resDenum))
  {
  wasError = 0;
  } else
  {
  wasError = 1;
  res.numerator = 0;
  res.denominator = 1;
  }
  return res;
}
void shortenLong (long num, long denum, long *resNum,
long *resDenum)
{
  long div;
  div = gcd(num, denum);
  *resNum = num / div;
  *resDenum = denum / div;
}  
    

Для сокращения значений увеличенной точности реализована отдельная функция shortenLong.

В модуле определяется переменная wasError, которая после каждой операции выставляется в 0, если операция выполнена корректно (т.е. получен ожидаемый результат), и wasError = 1, если операция не привела к ожидаемому результату или в ходе ее выполнения произошла ошибка. Например, при попытке деления дроби на нулевую дробь wasError выставляется в 1, а результирующая дробь равна 0/1.

5.4. Проблемы абстрактных типов данных

Модульное проектирование в совокупности с выделением абстрактных типов данных приводит к централизации управления типом в модуле и, как следствие, к более структурированному коду основной программы. Например, в программной системе требуется реализовать операции со стеком, т.е. с абстрактной структурой типа "список" с ограничением доступа (включение и извлечение) только на одном конце. Иногда такую процедуру обслуживания называют LIFO (Last In First Out).

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

Две основные операции PUSH и POP (включить и извлечь), очевидно, могут быть определены как void Push_Stack(char Item) и void Pop_Stack(char *ltem). Такое определение подчеркивает для программиста тот факт, что обе процедуры изменяют состояние стека.

Если процедуру извлечения определить как функцию char Pop_Stack (), то изменение стека (удаление из него верхнего элемента) скорее придется рассматривать как побочный эффект. Т.е., кроме прямого результата - вычисления значения, функция производит еще что- то, возможно, влияющее на ее глобальное окружение. С другой стороны, для вспомогательной функции просмотра элемента вершины стека форма char Check_Top () достаточно очевидна, так как измене-ние состояния стека при ее выполнении не происходит.

Полезно определить пару функций проверки (вычисления) предусловий для операций со стеком. int Is_Stack_Empty() - функция проверки пустого стека, int Is_Stack_Full() - функция проверки полностью заполненного стека.

Is_Stack_Empty () будет являться средством проверки возможности извлечь элемент из стека или обратиться к его верхнему элементу. Истинность этой функции запрещает применение операций Check_Top и Pop_Stack. В свою очередь, истинность функции Is_Stack_Full не разрешает применять операцию включения Push_Stack.

Проводя анализ введенных пяти операций рассматриваемого абстрактного типа, можно сделать вывод, что мы имеем операции-конструкторы: Push_Stack, Pop_Stack, которые можно рассматривать одновременно и как селекторы. "Чистый" селектор - Check_Top. Но и его можно еще рассматривать как преобразователь типа "стек" в "символ", что будет не совсем верно, так как сам стек не является в явном виде параметром операции. Кроме того, имеются типичные операции проверки предусловий Is_Stack_Empty и Is_Stack_Full.

Чего же у нас нет? Нет операций ввода/вывода - они реализуются с элементами стека стандартными средствами обработки символьных значений. И главное - нет начального конструктора, который задал бы исходное состояние пустого стека. Нет и операций копирования.

Решение об отсутствии всех этих типов операций принято сознательно. Наш стек уникален (можно использовать только один-един- ственный стек, поддерживаемый модулем), и модуль отвечает за его исходное состояние. При этом есть большой соблазн ввести операцию по очистке стека (после нее функция Is_Stack_Empty становится истинной). Ее можно рассматривать как начальный конструктор, так как ей безразлично исходное состояние списка. Но воздержимся от такого решения. Всегда можно воспользоваться операцией извлечения элемента, применяя ее до тех пор, пока стек не опустеет.