Стек. От абстрактного, универсального класса к конкретным версиям
Возьмем классическую задачу определения стека. Следуя схеме, определим абстрактный универсальный класс, описывающий всевозможные представления стеков:
/// <summary>
/// Абстрактный класс GenStack<T> задает контейнер с
/// доступом LIFO:
/// Функции:
/// конструктор new: -> GenStack<T>
/// запросы:
/// item: GenStack -> T
/// empty: GenStack -> Boolean
/// процедуры:
/// put: GenStack*T -> GenStack
/// remove: GenStack -> GenStack
/// Аксиомы:
/// remove(put(s,x)) = s
/// item(put(s,x)) = x
/// empty(new)= true
/// empty(put(s,x)) = false
/// </summary>
abstract public class GenStack<T>
{
/// <summary>
/// require: not empty();
/// </summary>
/// <returns>элемент вершины(последний пришедший)</returns>
abstract public T item();
/// <summary>
/// require: not empty();
/// ensure: удален элемент вершины(последний пришедший)
/// </summary>
abstract public void remove();
/// <summary>
/// require: true; ensure: elem находится в вершине стека
/// </summary>
/// <param name="elem"></param>
abstract public void put(T t);
/// <summary>
/// require: true;
/// </summary>
/// <returns>true если стек пуст, иначе false </returns>
abstract public bool empty();
}// class GenStack
В приведенном примере программного текста чуть-чуть. Это объявление абстрактного универсального класса:
abstract public class GenStack<T>
и четыре строки с объявлением сигнатуры его методов. Основной текст задает описание спецификации класса и его методов. Заметьте, здесь спецификации заданы достаточно формально с использованием аксиом, характеризующих смысл операций, которые выполняются над стеком.
Не хочется вдаваться в математические подробности, отмечу лишь, что, если задать последовательность операций над стеком, то аксиомы позволяют точно определить состояние стека в результате выполнения этих операций. Как неоднократно отмечалось с первых лекций курса, XML-отчет, построенный по этому проекту, будет содержать в читаемой форме все спецификации нашего класса. Отмечу еще, что все потомки класса должны удовлетворять этим спецификациям, хотя могут добавлять и собственные ограничения.
Наш класс является универсальным - стек может хранить элементы любого типа, и конкретизация типа будет производиться в момент создания экземпляра стека.
Наш класс является абстрактным - не задана ни реализация методов, ни то, как стек будет представлен. Эти вопросы будут решать потомки класса.
Перейдем теперь ко второму этапу и построим потомков класса, каждый из которых задает некоторое представление стека и соответствующую этому представлению реализацию методов. Из всех возможных представлений ограничимся двумя. В первом из них стек будет представлен линейной односвязной списковой структурой. Во втором - он строится на массиве фиксированного размера, задавая стек ограниченной емкости. Вот как выглядит первый потомок абстрактного класса:
/// <summary>
/// Стек, построенный на односвязных элементах списка GenLinkable<T>
/// </summary>
public class OneLinkStack<T> : GenStack<T>
{
public OneLinkStack()
{
last = null;
}
GenLinkable<T> last; //ссылка на стек (вершину стека)
public override T item()
{
return (last.Item);
}//item
public override bool empty()
{
return (last == null);
}//empty
public override void put(T elem)
{
GenLinkable<T> newitem = new GenLinkable<T>();
newitem.Item = elem; newitem.Next = last;
last = newitem;
}//put
public override void remove()
{
last = last.Next;
}//remove
}//class OneLinkStack
Посмотрите, что происходит при наследовании от универсального класса. Во-первых, сам потомок также является универсальным классом:
public class OneLinkStack<T> : GenStack<T>
Во-вторых, если потомок является клиентом некоторого класса, то и этот класс, возможно, также должен быть универсальным, как в нашем случае происходит с классом GenLinkable<T>:
GenLinkable<T> last; //ссылка на стек (элемент стека)
В-третьих, тип T встречается в тексте потомка всюду, где речь идет о типе элементов, добавляемых в стек, как, например:
public override void put(T elem)
По ходу дела нам понадобился класс, задающий представление элементов стека в списковом представлении. Объявим его:
public class GenLinkable<T>
{
public T Item;
public GenLinkable<T> Next;
public GenLinkable()
{ Item = default(T); Next = null; }
}
Класс устроен достаточно просто, у него два поля: одно для хранения элементов, помещаемых в стек и имеющее тип T, другое - указатель на следующий элемент. Обратите внимание на конструктор класса, в котором для инициализации элемента используется новая конструкция default(T), которая возвращает значение, устанавливаемое по умолчанию для типа T.
Второй потомок абстрактного класса реализует стек по-другому, используя представление в виде массива. Потомок задает стек ограниченной емкости. Емкостью стека можно управлять в момент его создания. В ряде ситуаций использование такого стека предпочтительнее по соображениям эффективности, поскольку не требует динамического создания элементов. Приведу текст этого класса уже без дополнительных комментариев:
public class ArrayUpStack<T> : GenStack<T>
{
int SizeOfStack;
T[] stack;
int top;
/// <summary>
/// конструктор
/// </summary>
/// <param name="size">размер стека</param>
public ArrayUpStack(int size)
{ SizeOfStack = size; stack = new T[SizeOfStack]; top = 0; }
/// <summary>
/// require: (top < SizeOfStack)
/// </summary>
/// <param name="x"> элемент, помещаемый в стек</param>
public override void put(T x)
{ stack[top] = x; top++; }
public override void remove()
{ top--; }
public override T item()
{ return (stack[top-1]); }
public override bool empty()
{ return (top == 0); }
}//class ArrayUpStack
Созданные в результате наследования классы-потомки перестали быть абстрактными, но все еще остаются универсальными. На третьем этапе порождаются конкретные экземпляры потомков - универсальных классов, в этот момент и происходит конкретизация типов, и два экземпляра одного универсального класса могут работать с данными различных типов. Этот процесс создания экземпляров с подстановкой конкретных типов называют родовым порождением экземпляров. Вот как в тестирующей процедуре создаются экземпляры созданных нами классов:
public void TestStacks()
{
OneLinkStack<int> stack1 = new OneLinkStack<int>();
OneLinkStack<string> stack2 = new OneLinkStack<string>();
ArrayUpStack<double> stack3 = new ArrayUpStack
<double>(10);
stack1.put(11); stack1.put(22);
int x1 = stack1.item(), x2 = stack1.item();
if ((x1 == x2) && (x1 == 22)) Console.WriteLine("OK!");
stack1.remove(); x2 = stack1.item();
if ((x1 != x2) && (x2 == 11)) Console.WriteLine("OK!");
stack1.remove(); x2 = (stack1.empty())? 77 : stack1.item();
if ((x1 != x2) && (x2 == 77)) Console.WriteLine("OK!");
stack2.put("first"); stack2.put("second");
stack2.remove(); string s = stack2.item();
if (!stack2.empty()) Console.WriteLine(s);
stack3.put(3.33); stack3.put(Math.Sqrt(Math.PI));
double res = stack3.item();
stack3.remove(); res += stack3.item();
Console.WriteLine("res= {0}", res);
}
В трех первых строках этой процедуры порождаются три экземпляра стеков. Все они имеют общего родителя - абстрактный универсальный класс GenStack, но каждый из них работает с данными своего типа и по-разному реализует методы родителя. На рис. 22.3 показаны результаты работы этой процедуры.
Рис. 22.3. Три разных стека, порожденных абстрактным универсальным классом
Дополним наше рассмотрение еще одним примером работы с вариацией стеков, в том числе хранящим объекты класса Person:
public void TestPerson()
{
OneLinkStack<int> stack1 = new OneLinkStack<int>();
OneLinkStack<string> stack2 = new OneLinkStack<string>();
ArrayUpStack<double> stack3 = new ArrayUpStack
<double>(10);
ArrayUpStack<Person> stack4 = new ArrayUpStack<Person>(7);
stack2.put("Петров"); stack2.put("Васильев");
stack2.put("Шустов");
stack1.put(27); stack1.put(45); stack1.put(53);
stack3.put(21550.5); stack3.put(12345.7);
stack3.put(32458.8);
stack4.put(new Person(stack2.item(), stack1.item(),
stack3.item()));
stack1.remove(); stack2.remove(); stack3.remove();
stack4.put(new Person(stack2.item(), stack1.item(),
stack3.item()));
stack1.remove(); stack2.remove(); stack3.remove();
stack4.put(new Person(stack2.item(), stack1.item(),
stack3.item()));
Person pers = stack4.item(); pers.PrintPerson();
stack4.remove(); pers = stack4.item(); pers.PrintPerson();
stack4.remove(); pers = stack4.item(); pers.PrintPerson();
stack4.remove(); if (stack4.empty()) Console.WriteLine("OK!");
}
Результаты работы этой процедуры приведены на рис. 22.4.
Рис. 22.4. Работа со стеками
Yandex.RTB R-A-252273-3- 1. Лекция: Visual Studio .Net, Framework .Net
- Открытость
- Модульность
- Виртуальная машина
- Дизассемблер и ассемблер
- Метаданные
- Сборщик мусора - Garbage Collector - и управление памятью
- Исключительные ситуации
- События
- Общие спецификации и совместимые модули
- 2. Лекция: Язык c# и первые проекты
- Создание c#
- Виды проектов
- Консольный проект
- Windows-проект
- Начало начал - точка "большого взрыва"
- Выполнение проекта по умолчанию после "большого взрыва"
- Проект WindowsHello
- Общий взгляд
- Система типов
- Типы или классы? и типы, и классы
- Семантика присваивания
- Преобразование к типу object
- Примеры преобразований
- Семантика присваивания. Преобразования между ссылочными и значимыми типами
- Операции "упаковать" и "распаковать" (boxing и unboxing).
- 4. Лекция: Преобразования типов
- Где, как и когда выполняются преобразования типов?
- Преобразования ссылочных типов
- Преобразования типов в выражениях
- Преобразования внутри арифметического типа
- Преобразования и класс Convert
- Проверяемые преобразования
- Исключения и охраняемые блоки. Первое знакомство
- Опасные вычисления в охраняемых проверяемых блоках
- Опасные вычисления в охраняемых непроверяемых блоках
- Опасные преобразования и методы класса Convert
- 5. Лекция: Переменные и выражения
- Объявление переменных
- Время жизни и область видимости переменных
- Глобальные переменные уровня модуля. Существуют ли они в c#?
- Int X,y; //координаты точки
- Локальные переменные
- Глобальные переменные уровня процедуры. Существуют ли?
- Константы
- Выражения
- Приоритет и порядок выполнения операций
- Перегрузка операций
- Операции sizeof и typeof
- Как получить подробную информацию о классе?
- Статические поля и методы арифметических классов
- Логические операции
- Условное выражение
- Операция приведения к типу
- Присваивание
- Специальные случаи присваивания
- Определенное присваивание
- Еще раз о семантике присваивания
- Рассмотрим объявления:
- Класс Math и его функции
- Класс Random и его функции
- Блок или составной оператор
- If(выражение_1) оператор_1
- If(выражение1) if(выражение2) if(выражение3) ...
- Оператор switch
- Операторы break и continue
- Циклы While
- Цикл foreach
- Процедуры и функции - функциональные модули
- Процедуры и функции - методы класса
- Процедуры и функции. Отличия
- Описание методов (процедур и функций). Синтаксис
- Список формальных аргументов
- Тело метода
- Вызов метода. Синтаксис
- О соответствии списков формальных и фактических аргументов
- Вызов метода. Семантика
- Поля класса или функции без аргументов?
- Пример: две версии класса Account
- Функции с побочным эффектом
- Методы. Перегрузка
- 10. Лекция: Корректность методов. Рекурсия
- Корректность методов
- Инварианты и варианты цикла
- Рекурсия
- Рекурсивное решение задачи "Ханойские башни"
- Быстрая сортировка Хоара
- 11. Лекция: Массивы языка c#
- Общий взгляд
- Динамические массивы
- Многомерные массивы
- Массивы массивов
- Процедуры и массивы
- Класс Array
- Массивы как коллекции
- Сортировка и поиск. Статические методы класса Array
- Сводка свойств и методов класса Array
- Класс Object и массивы
- Массивы объектов
- Массивы. Семантика присваивания
- Общий взгляд
- Класс char[] - массив символов
- Операции над строками
- Строковые константы
- Неизменяемый класс string
- Статические свойства и методы класса String
- Метод Format
- Методы Join и Split
- Динамические методы класса String
- Операции над строками
- Основные методы
- Емкость буфера
- Пространство имен RegularExpression и классы регулярных выражений
- Немного теории
- Синтаксис регулярных выражений
- Классы Match и MatchCollection
- Классы Group и GroupCollection
- Пример "чет и нечет"
- Пример "око и рококо"
- Пример "кок и кук"
- Пример "обратные ссылки"
- Пример "Дом Джека"
- Пример "Атрибуты"
- 16. Лекция: Классы
- Синтаксис класса
- Поля класса
- Доступ к полям
- Методы-свойства
- Индексаторы
- Константы
- Конструкторы класса
- Деструкторы класса
- Проектирование класса Rational
- Методы класса Rational
- Закрытый метод нод
- Операции над рациональными числами
- Константы класса Rational
- Развернутые и ссылочные типы
- Классы и структуры
- Класс Rational или структура Rational
- Встроенные структуры
- Еще раз о двух семантиках присваивания
- Перечисления
- Персоны и профессии
- 18. Лекция: Отношения между классами. Клиенты и наследники
- Отношения между классами
- Отношения "является" и "имеет"
- Отношение вложенности
- Расширение определения клиента класса
- Отношения между клиентами и поставщиками
- Сам себе клиент
- Наследование
- Добавление полей потомком
- Конструкторы родителей и потомков
- Добавление методов и изменение методов родителя
- Статический контроль типов и динамическое связывание
- Три механизма, обеспечивающие полиморфизм
- Пример работы с полиморфным семейством классов
- Абстрактные классы
- Классы без потомков
- Преобразование к классу интерфейса
- Наследование от общего предка
- Клонирование и интерфейс iCloneable
- Сериализация объектов
- Класс с атрибутом сериализации
- Интерфейс iSerializable
- 20. Лекция: Функциональный тип в c#. Делегаты
- Как определяется функциональный тип и как появляются его экземпляры
- Функции высших порядков
- Вычисление интеграла
- Построение программных систем методом "раскрутки". Функции обратного вызова
- Наследование и полиморфизм - альтернатива обратному вызову
- Делегаты как свойства
- Операции над делегатами. Класс Delegate
- Пример "Плохая служба"
- 21. Лекция: События
- Классы с событиями
- Класс sender. Как объявляются события?
- Делегаты и события
- Как зажигаются события
- Классы receiver. Как обрабатываются события
- Классы с событиями, допустимые в каркасе .Net Framework
- Пример "Списки с событиями"
- Класс sender
- Классы receiver
- Переопределение значений аргументов события
- Классы с большим числом событий
- Проект "Город и его службы"
- 22. Лекция: Универсальность. Классы с родовыми параметрами
- Наследование и универсальность
- Синтаксис универсального класса
- Класс с универсальными методами
- Два основных механизма объектной технологии
- Стек. От абстрактного, универсального класса к конкретным версиям
- Ограниченная универсальность
- Синтаксис ограничений
- Список с возможностью поиска элементов по ключу
- Как справиться с арифметикой
- Родовое порождение класса. Предложение using
- Универсальные делегаты
- Framework .Net и универсальность
- 23. Лекция: Отладка и обработка исключительных ситуаций
- Корректность и устойчивость программных систем
- Жизненный цикл программной системы
- Искусство отладки
- Отладочная печать и условная компиляция
- Классы Debug и Trace
- Метод Флойда и утверждения Assert
- Выбрасывание исключений. Создание объектов Exception
- If !MyMethod(){// обработка ошибки}
- Параллельная работа обработчиков исключений
- Блок finally
- Класс Exception
- Организация интерфейса
- Форма и элементы управления
- Взаимодействие форм
- Модальные и немодальные формы
- Передача информации между формами
- Шаблон формы для работы с классом
- Наследование форм
- Два наследника формы TwoLists
- Огранизация меню в формах
- Создание меню в режиме проектирования
- Классы меню
- Создание инструментальной панели с командными кнопками
- Методы класса Graphics
- Класс Pen
- Класс Brush
- Событие Paint
- Кисти и краски
- 25. Лекция: Финальный проект
- Абстрактный класс Figure
- Класс Circle
- Список с курсором. Динамические структуры данных
- Классы элементов списка
- Организация интерфейса