лекции (2007) (1160825), страница 11
Текст из файла (страница 11)
Язык Delphi - всё очень похоже. Для возбуждения исключительной ситуации используется "raise". Вместо конструкции "try - catch" используется конструкция "try - except", дополнительно введена конструкция "try - finally" (finally-часть выполняется всегда, независимо от того, были ли возбуждены какие-либо исключительные ситуации или нет; совмещать except- и finally-блоки нельзя):
try
...
except
on имя : тип do ...
on тип do ...
else ...
end;
try
...
finally
...
end;
В языке C# - почти то же самое. Используется ключевое слово "throw", конструкция "try - catch - finally". Java - почти как в C#, только к прототипам методов, использующих исключения, в конце должно быть добавлено ключевое слово "throws" со списком типов исключений (в C++ такая спецификация исключений необязательна):
public void f() throws SomeExceptions
{
try
{
...
}
catch (MyException e) {...}
finally {...}
...
}
В Java существует следующая система исключений, которые могут нести разную семантику: класс Throwable является предком классов Error и Exception, от которых, в свою очередь, наследуется большое число различных классов. Среди прочих от Exception наследуют такие классы исключений, как RuntimeException и UserException (соответственно, исключения среды выполнения и пользовательские исключения). Вполне естественно, что обрабатывать исключения среды выполнения, в общем-то, бессмысленно, а вот все пользовательские исключения необходимо обрабатывать.
Часть 2. Объектно-ориентированные ЯП.
Уже достаточно долгое время идут бесплодные споры о том, что же такое "объектно-ориентированное программирование" (ООП). Однако вполне можно говорить о том, что объектно-ориентированное программирование определённо включает в себя три концепции:
1) Инкапсуляция - про неё мы уже говорили.
2) Наследование - в традиционных ЯП объекты не могут "пересекаться" между собой, а вот в объектно-ориентированных языках программирования присутствуют механизмы выведения одного типа из другого
3) Полиморфизм - ООЯП должны обладать полиморфными операциями (операциями, применимыми к различным типам параметров). Можно рассматривать статический, динамический и параметрический полиморфизм.
Глава 1. Сравнение концепций типизаций в традиционных и объектно-ориентированных ЯП.
Уже первые ООЯП (в частности, язык Smalltalk) обладали концепцией типизации - она неразрывно связана с объектно-ориентированным программированием (естественно, в традиционных ЯП концепция типизации была введена задолго до появления ООП). Строгая типизация позволяет делать языки не только эффективными, но и надежными.
Концепция типизации в традиционных ЯП:
1) Концепция уникальности типа - любой объект данных принадлежит одному и только одному типу данных (определение строгой типизации). Фактически, все объекты данных разбиваются на непересекающиеся классы эквивалентности.
2) Концепция именной эквивалентности - типы данных эквивалентны тогда и только тогда, когда их имена совпадают. Необходимо отметить, что не все традиционные ЯП полностью реализуют эту концепцию. Например, язык C:
struct X {double Re, Im;};
struct Y {double Re, Im;};
...
X a; Y b;
Здесь X и Y считаются разными типами, поэтому объекты a и b несовместимы. Тем не менее, если в одном модуле определить функцию, например "void f (struct X x)" и импортировать её в другой модуль как "extern f (struct Y y)", то, объявив в этом втором модуле объект "struct Y b", вполне можно будет вызывать f (b). То есть концепция именной эквивалентности не выдерживается, выдерживается только концепция структурной эквивалентности типов (типы эквивалентны, если их структура совпадает). Кстати, в C++ возникает связанная с этим проблема, ведь там можно перекрывать имена функций; наиболее распространённым выходом, хотя он и не стандартизован, является так называемое кодирование имён ("name mangling") на уровне компилятора. Функциям присваиваются специальные имена, в которых помимо всего прочего закодировано имя функции и типы её параметров; поэтому в такой схеме функции "f (struct X x)" и "f (struct Y y)" получат разные имена. Если вернуться к языку C, то концепция структурной эквивалентности может значительно усложнить жизнь программисту, например, в случае использования ссылочных типов.
Кроме того, во многих ЯП присутствует такое понятие, как синонимия типов. Например, в С тип-синоним можно ввести так: "typedef int my_int"; типы int и my_int считаются эквивалентными.
Также следует отметить, что типы могут быть анонимными, например при объявлении в языке Pascal "A,B: array (1..n) of T;" 1..N - это анонимный тип. В разные языках программирования по-разному подходят к анонимным типам: где-то они считаются эквивалентными, где-то - нет.
Наиболее строгий подход реализован в языке Ada. Здесь полностью выполняется концепция именной эквивалентности, при описании нового типа "type T1 is new T" типы T1 и T считаются неэквивалентными; два объекта любых анонимных типов также считаются несовместимыми.
3) Каждый тип данных характеризуется своим набором операций.
4) Неэквивалентные типы данных несовместимы по операциям.
Каждая операция имеет свой тип операндов, каждый тип данных можно определить по тексту программы; поэтому совместимость операций можно проверять на этапе компиляции.
Все ЯП индустриального программирования для обеспечения надёжности поддерживают строгую типизацию.
Концепция типизации в объектно-ориентированных ЯП:
1) Каждый объект данных принадлежит единственному статическому типу данных. Ссылки и указатели имеют как статический, так и динамический типы данных (динамический тип данных - это тип данных объекта, на который указывает ссылка или указатель).
Между типами данных могут существовать отношения наследования; наследование не является симметричным, но оно транзитивно. Объекты унаследованного (производного) типа данных принадлежат базовому типу в том смысле, что с ними можно работать, как с объектами базового типа.
2) Типы данных эквивалентны тогда и только тогда, когда совпадают их имена (аналогично типизации в традиционных ЯП)
3) Каждый тип данных характеризуется своим набором операций (опять же, как и в традиционных ЯП), при этом производный тип данных наследует операции базового типа.
4) Если типы не являются родственными, то они несовместимы по операциям.
Если типы являются родственными, то можно осуществлять присваивание вида БК := ПК (БК и ПК - базовый и производный классы соответственно). Присваивание в обратную сторону, вообще говоря, неправильно. Если есть функция "void f (X)", у которой в прототипе стоит "void f (БК)", то можно вызывать и "f (ПК)".
То же самое выполняется и для ссылок. Динамический тип данных в ссылках и указателях должен или совпадать со статическим ТД, или быть его производным. операции статического типа данных применимы, когда используется статическое связывание. Динамический тип данных нужен для динамического связывания. Для статических типов данных нет такого понятия, как динамическое связывания.
Глава 2. Наследование.
Можно по-разному воспринимать объектно-ориентированность в ЯП. Например, можно поставить вопрос о том, всё ли является объектом. В языке Smalltalk ответ на этот вопрос будет утвердительным - даже выражение "5 + 3" в этом языке рассматривается в терминах "объекту 5 посылается сообщение с параметром 3". Более того, в Smalltalk вообще отсутствует понятие статического типа данных. Но это всё-таки радикальный вариант; подавляющее большинство современных ООП развивают традиционную концепцию, оснащая ее наследованием и другими механизмами, но при этом оставляя старые понятия. Кроме того, много интересного возникает при рассмотрении различных аспектов, не определяемых правилами языков, и их влиянии на реализацию объектно-ориентированных свойств. Например, можно говорить о различных моделях распределения памяти и их влиянии на реализацию наследования. Скажем, в основных реализациях языков C++, Java и Delphi используется линейная модель памяти (и при наследовании для объектов производных классов новые члены просто добавляются в конец объектов базового класса), а вот реализации языков Oberon и Smalltalk обычно подразумевают цепную модель памяти (и тогда объекты производного класса будут представляться в виде списка, то есть у объекта производного класса помимо новых полей будет ссылка на объект базового класса).
Вернёмся к типам данных и механизму наследования. Необходимо отметить, что в языках Oberon, C++ и Ada присутствует лес типов данных, а в языках Java, C# и Delphi система типов образует дерево.
Теперь последовательно рассмотрим, как выглядит наследование в этих языках. В языке Oberon нет понятия наследования, а есть понятие расширения типов:
TYPE T = RECORD поля END;
T1 = RECORD (T) новые_поля END;
При этом имена полей не должны перекрываться. Вот реализация стека на языке Oberon:
TYPE STACK *= POINTER TO LINK;
TYPE PLINK = STACK;
LINK *= RECORD
NEXT:PLINK;
END;
PROCEDURE PUSH * (VAR S : STACK, X : PLINK);
PROCEDURE POP (VAR S:STACK) : PLINK;
TYPE INT_LINK *= POINTER TO RECORD (LINK)
X : INTEGER;
END;
P : INT_LINK := NEW (INT_LINK);
S : STACK := NEW (STACK)
P.X = 1;
PUSH (S, P);
PP : PLINK;
PP = POP(S):
Язык Delphi - это наследник языков Pascal и Turbo Pascal; поскольку и Oberon, и Pascal разрабатывались Никлаусом Виртом, синтаксис в Delphi напоминает синтаксис Oberon:
type T = class (TObject)
члены_класса
end;
T1 = class(T)
новые_члены
end;
В языке С++ наследование уже более продвинутое и совмещено с управлением доступом:
class X {...};
class Y : модификатор_доступа X {...};
В роли модификаторов доступа выступают "public", "private" и "protected". Всегда наследуются все члены класса, но при разном наследовании права на доступ к ним меняются:
1) При public-наследовании public -> public, protected -> protected, private -> нет доступа;
2) При protected-наследовании public -> protected, protected -> protected, private -> нет доступа;
2) При private-наследовании public -> private, protected -> private, private -> нет доступа;
В языке Java:
class X {...};
class Y extends X {...}
В языке С#:
class X {...}
class Y : X {...}
Наконец, в языке Ada введены так называемые тэгированные типы данных (это ТД, которые могут быть унаследованы):
type T is tagged record
поля
end record;
type t1 is new t with tagged record
новые_поля
end record;
Более того, тэгирование в языке Ada можно легко совместить с инкапсуляцией:
package P is
type T is tagged private;
...
private
type T is tagged record поля end record;
...
type T1 is new T with tagged private;
private
type T1 is tagged null record;
В языке C# для инкапсуляции введен ещё один модификатор доступа ("internal") и такое понятие, как "assembly" (это некий аналог пространств имён, "namespace"; физический модуль, который может объединять несколько файлов). Соответственно, internal-члены класса видны только внутри соответствующей assembly:
class X
{
...
protected int i;
protected internal int j;
...
}
В языке Ada в связи с инкапсуляцией также появилось понятие дочернего модуля, который имеет доступ к приватной части пакета-предка:
package P is
type t is tagged private;
...
end P;
package P.P1 is
type T1 is new T with private;
...
end P.P1
Глава 3. Динамическое связывание (методов)
Итак, каждый объект данных обладает некоторым типом. Уже говорилось о том, что помимо "основного" типа данных (который называется статическим), в объектно-ориентированных ЯП вводится ещё и динамический тип данных; им обладают указатели и ссылки. Выделение динамического ТД связано с тем, что из-за наличия механизма наследования указатель или ссылка может указывать не только на объекты своего типа, но и на объекты, принадлежащие производным классам. Таким образом, изменяя динамический тип указателя или ссылки, программист может значительно увеличить гибкость программы. Но для этого в систему типов и наследование должны входить соответствующие языковые средства. Наиболее распространённое в современных ЯП средство - это механизм виртуальных функций. Например, на языке C++ работа с виртуальными функциями реализована следующим образом:
class X
{