PROGLANG (1129115), страница 3
Текст из файла (страница 3)
заголовок
объявления
begin
операторы when ex1|ex2|...|exn
exception операторы_обработки
набор_ловушек when others
e nd операторы_обработки
Этот принцип называется динамической ловушкой. В теле ловушки можно просто указать оператор raise, если обработчик до конца не справился с исключением, то, указав raise, мы распространим его не уровень выше.
В языке С++ исключения сопоставлены типам, а в Java все исключения выходят из класса Troughable. Для снижения накладных расходов с С++ исключения могут возбуждаться только внутри блока try {...} набор_ловушек;
try
{ операторы; }
catch (тип) catch (тип имя) присутствует информация об объекте
{ блок_реакции } или { блок_реакции }
Блок
catch (...) {}
ловит все исключения. Оператор
throw выражение;
является эквивалентом оператора raise. Тип выражения определяет возбуждаемое исключение.
throw 1; // исключение типа int
throw «message»;// исключения типа char*
Ловушки должны описываться только в блоке try, а не где-нибудь в другом месте. Если нет блока try и вызван throw, то это эквивалентно abort. Если нет соответствующей ловушки дл я данного исключения, то исключение распространяется выше, иначе считается, что исключения обработано, и выполнение продолжается с точки, находящейся сразу за блоком try. если ловушка-обработчик сама не может справится с исключением, то нужно указать throw без параметров, и исключение распространится выше.
При возникновении исключения происходит свертка стека: стек не освобождается корректным образом от сломавшихся объектов, а перед этим вызываются их деструкторы. Можно использовать функцию set_terminate
typedef void(* PVF) (void);
PVF set_terminate (PVF);
для выдачи посмертного дампа. Можно указать шаблон ожидаемых исключений
прототип блок;
прототип throw (список_имен_типов);
блок
Если произойдет неожиданное исключение, то программу не имеет смысла продолжать, но можно что-то напоследок в назидание выдать
PVF set_unexpected (PVF);
В
Java, если функция возбуждает исключение, она должна предупредить об этом компилятор
class C
{ ...
void f() throws (MyExc)
{ ... trow MyEsc(); ...}
}
Часть II Объектно-ориентированные ЯП
Введение
Понятие типа
Традиционные ЯП

Модульность, раздельная трансляция
Объект
состояние, атрибуты объекта
поведение объекта
Объекты объединяются в классы по однотипным характеристикам поведения. Классовое поведение не зависит от конкретного объекта. Объектные языки (Ada) обладают понятием объект (состояние, поведение). Язык С в грубом приближении тоже можно назвать объектным. Объектным языкам не хватает принципа уникальности типа объекта, что есть ООЯП, а также нет полиморфизма, когда объект реагирует по разному на одно и то же сообщение в зависимости от текущего типа объекта. ООЯП реализует все концепции традиционных языков, а к тому же концепцию разнотипных объектов (принадлежащих разным классам), полиморфизма, наследования, динамического связывания методов (виртуальные функции).
Глава 1 Наследование в ОЯП
При наследовании наследуются свойства класса и могут добавляться новые.
Расширение типа – наследование, динамическое связывание типов появилось только в Оберон-2. В Обероне расширяемым является только тип запись.
TYPE T = RECORD TYPE T1 = RECORD(T)
X : INTEGER; Z : INTEGER;
Y : REAL; END;
END;
производный тип
Присваивание базовому типу производного корректно, обратное присваивание не позволяет определить поле Z. Аналогично присваивание указателей и ссылок, а следовательно, и параметры-переменные, проверяется на корректность.
Попробуем реализовать неоднородный контейнер, напишем разнородный стек.
Модуль определений, сгенерированный Обероном:
DEFINITION Stacks
TYPE
Stack = RECORD END;
PROCEDURE Open (VAR S : STACK);
PROCEDURE Push (VAR S : Stack; P : Node);
PROCEDURE Pop (VAR S : Stack; VAR P : Node);
...
END Stacks;
Модуль реализаций:
MODULE Stacks
TYPE
Node* = POINTER TO Node_Desc;
Stack* = RECORD N : Node END;
Node_Desc* = RECORD Next : Node END;
...
END Stacks;
Клиентский модуль
MODULE M;
IMPORT Stacks;
TYPE
T = RECORD (Stacks : Node_desc)
i : INTEGER;
END;
PT : POINTER TO T;
VAR
S : Stacks.Stack;
p : PT;
Stacks.Open (S);
p := NEW (PT);
p.i := 3;
Stacks.Push (S,P);
...
Но у Pop будут проблемы с идентификацией типа. Pop-ом надо доставать только базовый тип, а потом разбираться, что мы достали. Динамическая проверка типа производится с помощью стражей типа
P is PT
Результат логического типа, поэтому можно использовать его в условных конструкциях. Поэтому, если
W = RECORD (Stacks.Node_desc)
Z : REAL;
END
Тогда можно написать
IF P1 is PT THEN
P1(PT).i = ...
ELSIF P1 is PW THEN
P1(PW).z := ...
END;
Запись P1( T’) означает, что надо трактовать тип P1 как T’. Можно провести групповую проверку типа, но проблема модификации кода из-за большого количества переключателей остается.
WITH P1 : PT DO
P1.i := ...
...
END;
При анализе возможностей языка удобно разобрать модель графического редактора.
TYPE
Figure = POINTER TO Figure_Desc;
Figure_desc = RECORD
Next : Figure;
X, Y : INTEGER;
END;
...
В другом модуле можно написать
TYPE
Line = PONTER TO Line_Desc;
Line_Desc = RECORD (Figure_Desc)
X2, Y2 : INTEGER;
END;
Rect = POINTER TO RectDesc;
Rect_Desc = RECORD (Figure_Desc)
H, W : INTEGER;
END;
...
Процедура отрисовки
PROCEDURE DrawLine (R : Line);
PROCEDURE DrawRect (R : Rect);
PROCEDURE Draw... ( ... );
...
Процедура отрисовки всех объектов
PROCEDURE DrawAll;
VAR P : Figure;
BEGIN
P := List;
WHILE P#NIL DO
IF P is Line THEN
DrawLine (P(Line))
ELSEIF P is Rect THEN
DrawRect (P(Rect))
ELSEIF
...
END;
P := P.Next
END
END;
Наследование дает возможность выводить новые фигуры и с небольшими изменениями кода выполнять их отрисовку. Но нет перекрытия имен и из-за сложной модифицируемости программы (куча переключателей) все преимущества теряются. Оберон кроме защищенности не дает ничего нового по сравнению с С. В отчаянной попытке исправить положение Н. Вирт предложил концепцию обработчиков.
TYPE
DrawProc = PROCEDURE (P.Figure);
MoveProc = PROCEDURE (P.Figure : Figure; DX, DY : INTEGER);
...
Figure_Desc = RECORD
Next : Figure;
X, Y : INTEGER;
Draw : DrawProc;
Move : MoveProc;
END;
PROCEDURE DrawLine (P : FIGURE)
...
BEGIN
WITH P.Line DO аналогично и в DrawRect
...
END
END DrawLine ;
Тогда отрисовка всех фигур станет приятней
PROCEDURE DrawAll;
BEGIN
P := List;
WHILE P#NIL DO
P.Draw (P);
P := P.Next;
END
END DrawAll;
В данном случае DrawAll не надо переписывать при добавлении новой фигуры. Таким образом вручную моделируется полиморфизм, что опять же небезопасно – тот ли указатель передаем, правильно ли проинициализирован обработчик?
Если класс X наследует класс Y, память будет распределена линейным образом
X
Y
Присваивание сыну отца запрещено в целях надежности и логичности, причем проверяется корректность и для указателей и ссылок. Конструкторы и деструкторы не наследуются автоматически по концепции языка (а члены-функции наследуются). При наследовании, в отличие от Оберона, можно переопределять поля (данные и методы), при этом с помщью квалификации можно обращаться к исходным полям в классе предке. В С++ применимо множественное наследование
class T : public T1; public T2
{ ... };
При этом классы Т1 и Т2 должны быть не родственными, проблемы возникают, когда необходим доступ к полям предков.
ios T1 = T1
T2 T3
istream ostream
T
iostream
Чтобы объект Т1 не дублировался в памяти, нужно использовать ключевое слово virtual.
class T2 : virtual public T1;
class T3 : virtual public T1;
class T : public T2, public T3;
В стандарте С++ ничего не упоминается, какую модель памяти должен использовать компилятор. Но линейная схема, по словам Страуструпа, не так уж плоха для С++ и вполне эффективна, только пришлось немного уточнить некоторые моменты.
Проблема возникает при множественном наследовании. Метод языка SmallTalk, использующий цепную модель, не совсем эффективен по скорости, но эффективно использует память. Пример:
a) X Y
X& x=y; // тут всё понятно
б) X,Y Z
Y& y=z; // а тут начинается мухлёж
Компилятор должен преобразовать адреса, в данном случае это произойдёт статически на этапе компиляции. Но если модель наследования более сложна:
X
X X – память используется не эффективно
Y
Y
Z X
Y
T
T
Поэтому для предотвращения дублирования объектов в памяти используют виртуальное наследование:
class Y : public virtual X{..};