Сравнение Языков Программирования

Материал из eSyr's wiki.

(Различия между версиями)
Перейти к: навигация, поиск
м (Исключения и блоки try {} catch {} finally {}. Семантика возобновления и семантика завершения. - finally не связан со сборкой мусора)
м (Попытка уточнить понятие, Ada)
Строка 18: Строка 18:
; Character: Как я понял, существует несколько разновидностей (зависит от размера) и является особым перечислимым типом (Enumeration)
; Character: Как я понял, существует несколько разновидностей (зависит от размера) и является особым перечислимым типом (Enumeration)
; String: Массив '''Character''' фиксированной длины. Так же есть стандартные пакеты, реализующие строки квазистатической и динамической длины.
; String: Массив '''Character''' фиксированной длины. Так же есть стандартные пакеты, реализующие строки квазистатической и динамической длины.
-
; Floating point: Эти типы обычно определяются вручную в виде конструкции, где Num_Digits указывает минимальную погрешность:
+
; Floating point: Эти типы обычно определяются вручную в виде конструкции, где Num_Digits указывает минимальную (минимально допустимую?) погрешность:
'''digits''' Num_Digits
'''digits''' Num_Digits

Версия 01:39, 14 января 2013

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

ЯП из курса: C, C++, Java, C#, Pascal, Delphi, Оберон-2, Модула-2, Ада (83 и 95 стандарты)

Полезные ссылки:

Сравнение ЯП в википедии

Энциклопедия языков программирования

Немножно рекламное сравнения большинства языков

План (краткий, взят из методички Головина, подробный см. в самой методичке):

Содержание

Базисные типы данных в языках программирования: простые и составные типы данных, операции над ними

ADA

Integer
Размер не фиксирован.
Character
Как я понял, существует несколько разновидностей (зависит от размера) и является особым перечислимым типом (Enumeration)
String
Массив Character фиксированной длины. Так же есть стандартные пакеты, реализующие строки квазистатической и динамической длины.
Floating point
Эти типы обычно определяются вручную в виде конструкции, где Num_Digits указывает минимальную (минимально допустимую?) погрешность:
digits Num_Digits
Fixed Point
Эти типы также обычно определяются вручную в виде конструкции, где Delta означает погрешность:
delta Delta range Low .. High
Boolean
Перечислимый тип с особой семантикой, состоящий из значений true и false
Access
Тип указателя в языке ADA, с некоторой своей особой семантикой. В отличие от других языков (таких как C/C++) тип Access может указывать только на объекты в динамической области памяти. Кроме того этот тип лишен адресной арифметики. Эти два факта якобы позволяют избежать ошибок, связанных с указателями в С/С++. Однако, тип Access в Аде не лишен проблем висячих ссылок и мусора, ситуаций из-за которых возникает подавляющее количество очень неприятных ошибок в языках C/C++. Однако стоит отдать должное языку Ада: большинство ситуаций, в которых можно использовать адрес, зачастую решаются другими способами.
use Smth_Package.Entity;
type Entity_Access is access Entity;
A1, A2 : Entity_Access;
begin
   A1   := new Entity;
   A1   := new Entity; -- Образовался мусор
   A2   := A1;
   Free_Entity (A1); -- A1 теперь null 
                     -- A2 теперь - висячая ссылка 
end

ОБЕРОН и ОБЕРОН-2:

Отличия
В Оберон-2 добавлены
связанные с типом процедуры;
экспорт только для чтения;
открытые массивы в качестве базового типа для указателей;
оператор with с вариантами;
оператор for.
Основные типы
1. BOOLEAN логические значения TRUE и FALSE
2. CHAR символы расширенного набора ASCII (0X .. 0FFX)
3. SHORTINT целые в интервале от MIN(SHORTINT) до MAX(SHORTINT)
4. INTEGER целые в интервале от MIN(INTEGER) до MAX(INTEGER)
5. LONGINT целые в интервале от MIN(LONGINT) до MAX(LONGINT)
6. REAL вещественные числа в интервале от MIN(REAL) до MAX(REAL)
7. LONGREAL вещественные числа от MIN(LONGREAL) до MAX(LONGREAL)
8. SET множество из целых от 0 до MAX(SET)

Типы от 3 до 5 - целые типы, типы 6 и 7 - вещественные типы, а вместе они называются числовыми типами. Эти типы образуют иерархию; больший тип поглощает меньший тип:

LONGREAL >= REAL >= LONGINT >= INTEGER >= SHORTINT

Примеры объявлений переменных
i, j, k: INTEGER
a: ARRAY 100 OF REAL
Операции

+, -, *, /, DIV, MOD

Операции над множествами
+ объединение
- разность (x - y = x * (-y))
x пересечение
/ симметрическая разность множеств (x / y = (x-y) + (y-x))
Отношения

=, #, <, <=, >, >=, IN(принадлежность множеству),IS(проверка типа)

Пример присваивания

i := 0

Тип Запись
   RECORD
       day, month, year: INTEGER
   END

Modula-2

Порядковые: CARDINAL CHAR INTEGER BOOLEAN
Битовое множество BITSET (Величина может зависеть от реализации. Например, 32 бита)
Плавающая точка: REAL LONGREAL (Подчиняются IEEE, но зависят от реализации. К примеру, возможно REAL = LONGREAL = double)
Процедурный тип PROC

C#

Встроенные типы
1. sbyte, byte - 8-битное целое число
2. short, ushort - 16-битное целое число
3. int, uint - 32-битное целое число
4. long, ulong - 64-битное целое число
5. float, double - 32- и 64-битные числа с плавающей запятой
6. bool - 8-битное логическое значение
7. char - 16-битный знак Юникода
8. decimal - 128-битный точный дробный или целочисленный, который может представлять десятичные числа с 29 значащими цифрами

Особняком стоят:

9. string - последовательность знаков
10. object - базовый тип для всех остальных типов
Структуры и классы

В C# структуры являются урезанной версией класса. Память под структуры наравне с простыми типами выделяется на стеке (если конечно они не являются частью объекта - память под объекты всегда выделяется на куче). Ограничения на структуры:

1. Структура не может наследоваться и не может быть унаследована ни от чего, кроме типа object.
2. В структуре нельзя явно определить конструктор умолчания.
Референциальные типы
Классы, массивы, интерфейсы. Располагаются на куче.

Массивы

Длина массива - статический или динамический атрибут.

Си++: длина массива - только статический атрибут.
Оберон, Модула-2: динамический атрибут только для формальных параметров, в остальных случаях - статический.
Ада, Java, C#: может быть и тем и другим.

Замечание из методички:
в языках Оберон и Модула-2 длина формальных параметров — открытых массивов является динамическим атрибутом.
В других случаях длина массива — статический атрибут.
В Аде формальные параметры неограниченных типов-массивов также имеют динамический атрибут-длину (равно как и динамические массивы-локальные переменные).

Пример динамического массива в языке Java (или C#):
void f(int N) {
byte [] dynArray = new byte [N];
// ...обработка ...
}

ОБЕРОН И ОБЕРОН-2

Элементы массива обозначаются индексами, которые являются целыми числами от 0 до длины массива минус 1.

ТипМассив = ARRAY [Длина {"," Длина}] OF Тип.
Длина = КонстантноеВыражение.

Тип вида

   ARRAY L0, L1, ..., Ln OF T

понимается как сокращение

   ARRAY L0 OF
        ARRAY L1 OF
           ...
             ARRAY Ln OF T

Массивы, объявленные без указания длины, называются открытыми массивами. Они могут использоваться только в качестве базового типа указателя, типа элементов открытых массивов и типа формального параметра. Длина формальных параметров — открытых массивов является динамическим атрибутом.

Modula-2

Массивы как в Паскале (только помним про регистрозависимость: все ключевые слова IN CAPITALS).
Пример объявления массива

   VAR
       a: ARRAY [-1..100] OF ARRAY [1..10] OF INTEGER;

Управление памятью

Классы памяти

Статическая Всё что связано со словом «статический» размещается в блоке статической памяти. Время жизни – от начала программы либо момента первого входа в блок, и до конца программы.

Квазистатическая Квазистатическая память связана с понятием блока: переменные связана с этим блоком (от момента объявления, точнее, прохода code flow через объявление, до выхода из блока). Размещаются в стеке. Еще автоматической называют (автоматическая переменная).

Динамическая Время жизни зависит от процедур (ручное управление памятью) или от сборщика мусора.

Указатели

Ада, C, C++, C#, Delphi, Оберон, Модула

Указатели языка Ада 83 ссылаются только на объекты из динамической памяти. Указатель языка Си++ может ссылаться на любой объект данных (динамический, локальный, статический), что может приводить к труднообнаружимым ошибкам.

Также в языке Ада отсутствует адресная арифметика (арифметические операции над указателями), что также уменьшает вероятность появления ошибки работы с памятью.

В языке Оберон(и Оберон-2) указатель может быть объявлен только на массив или запись. ТипУказатель = POINTER TO Тип. Любая переменная-указатель может принимать значение NIL, которое не указывает ни на какую переменную вообще.

В Modula-2 можно объявлять указатель на что угодно. При том как на переменные в динамической памяти, так и в статической. Есть адресная арифметика (ADDADR, SUBADR, DIFADR), преобразование типов указателей (CAST), разыменование (^), функция взятия адреса (ADR), аллокация и деаллокация (NEW-DISPOSE или ALLOCATE-DEALLOCATE - два варианта), размер типа в байтах (TSIZE) и NIL. В случае получения невалидного адреса, разыменования NIL и т.п. выдаётся ошибка.

   TYPE 
       TreePtr = POINTER TO TreeGlue;
       TreeGlue = RECORD
           key     : KeyType;
           left    : TreePtr; (* left child *)
           right   : TreePtr; (* right child *)
       END;

В Java явных указателей нет.

Операторный базис языков программирования. Управление последовательностью вычислений

ADA

Джентльменский набор:

if condition then
 statement;
elseif enother_condition then
 enother_statement;
...
else
 last_one_statement;
end if;
case X is
   when constant1 =>
      stetement1;
   when constant2 | constant3 =>
      statement2_and3;
   ...
   when others =>
      other_statement;
end case;
return; -- for procedures
return Value; -- for functions
goto Label
   Dont_Do_Smth;
<<Label>>

Циклы устроены посложнее:

Endless_Loop :
  loop
     Do_Something;
  end loop Endless_Loop;
While_Loop :
  while X <= 5 loop
     X := Calculate_Something;
  end loop While_Loop;
Until_Loop :
  loop
     X := Calculate_Something;
     exit Until_Loop when X > 5;
  end loop Until_Loop;
Exit_Loop :
  loop
     X := Calculate_Something;
     exit Exit_Loop when X > 5;
     Do_Something (X);
  end loop  Exit_Loop;
For_Loop :
  for I in Integer range 1 .. 10 loop
     Do_Something (I)
  end loop For_Loop;

ОБЕРОН И ОБЕРОН-2

   ОператорIf = 
   IF Выражение THEN ПоследовательностьОператоров 
   {ELSIF Выражение THEN ПоследовательностьОператоров} 
   [ELSE ПоследовательностьОператоров] 
   END.
   CASE ch OF 
   "A" .. "Z": ReadIdentifier 
   | "0" .. "9": ReadNumber 
   | "'", '"' : ReadString 
   ELSE SpecialCharacter 
   END
   WHILE Выражение DO 
   ПоследовательностьОператоров 
   END.
   REPEAT 
   ПоследовательностьОператоров 
   UNTIL Выражение.
   FOR v := beg TO end BY step DO statements END (В Оберон-2)
   LOOP ПоследовательностьОператоров END.
   WITH v: T1 DO S1 | v: T2 DO S2 ELSE S3 END (В Оберон-2)

Процедурные абстракции

Перегрузка операций

Ада 83, Ада 95, Си++, Java, Delphi, C#

Понятие «перегрузка» означает, что одному имени в одной области видимости может соответствовать несколько определений. В современных языках программирования перегружаться могут только имена подпрограмм, но не типов, переменных, модулей. Пример на языке Си++:

   class X {
   public:
       void f();
       void f (int)
   };
               
   X a;
   a.f(); // первая функция
   a.f(0); // вторая функция

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

ADA

В аде есть процедуры и функции. Первые не возвращают значения, вторые - возвращают. Самое интересное здесь - это виды передачи параметров. В аде их 4.

in
Фактический параметр не изменяется и передается как константа read-only (этот модификатор используется по умолчанию)
out
Фактический параметр передается только на изменение. Внутри подпрограммы этот параметр не доступен на чтение.
in out
Фактический параметр передается с read-write семантикой.
access
Фактический параметр является указателем. Это не модификатор, а тип и может использоваться с 3-мя вышеперечисленными модификаторами.

До 2012-го года функции в Аде могли принимать только in и access параметры.

Имеются типы-указатели на подпрограмму, определяются в виде.

type PFunction is access function (X : in Param_Type) return Return_Type;

ОБЕРОН И ОБЕРОН-2

Имеются два вида процедур: собственно процедуры и процедуры- функции. Последние активизируются обозначением функции как часть выражения и возвращают результат, который является операндом выражения. Собственно процедуры активизируются вызовом процедуры. Процедура является процедурой-функцией, если ее формальные параметры задают тип результата. Тело процедуры-функции должно содержать оператор возврата, который определяет результат.

Определение новых типов данных. Логические модули. Классы

ADA

В Аде вообще типы принято определять вручную, пользуясь конструкциями type и subtype. Например:

type Short is range -128 .. +128 
type Index is range 0 .. 256 
type My_String is array ( Index range <> ) of Character -- My_String - это массив, индексируемый типом Index в неизвестных пока границах, состоящий из Character
subtype String_10 is My_String ( 0 .. 10 ); -- Подтип типа My_String, состоящий из 11 элементов.
type Money is delta 1/100 range 0.0 ... 10000.0

Ключевое слово Limited означает, что у новоявленного типа нет стандартных операций до тех пор, пока программист сам их не опишет.

В языке Ada есть мощный механизм пакетов, и этим он, несмотря на то, что основан на Pascal, совсем на него не похож. Пакеты заимствованы из других языков. Они похожи на классы тем, что являются средством абстракции, позволяющем инкапсулировать сложность внутри пакета, предоставив наружу только интерфейс. Ладно, к сути.

Каждый пакет физически разделён на две части.

-- Интерфейс
package Points is
   type Point is record
       x: Integer;
       y: Integer;
   end record;
   zero: constant Point := (0, 0);
   procedure setToZero(point: in out Point);
   function dotProduct(point1: in Point; point2: in Point) return Integer;
end Points;
-- Реализация
package body Points is
   procedure setToZero(point: in out Point) is
       begin
       point := zero;
       end setToZero;
   function dotProduct(point1: in Point; point2: in Point) return Integer is
       begin
       return point1.x * point2.x + point1.y * point2.y;
       end setToZero;
end Points;

Ada имеет средства разграничения доступа. Всё, что следует за спецификатором private, не будет доступно при импорте пакета.

-- Интерфейс
package Points is
   type Point is private; -- Это ключевое слово как бы намекает.
   zero: constant Point; -- Тут тоже неявный private.
   procedure setToZero(point: in out Point);
   function dotProduct(point1: in Point; point2: in Point) return Integer;
private
   type Point is record
       x: Integer;
       y: Integer;
   end record;
   zero: constant Point := (0, 0);
end Points;

Вне пакета можно будет объявить переменные типа «Point», но все действия будут ограничены присваиваниями, сравнениями на равенство и проверками принадлежности (нужно описать подробнее). Поэтому в пакет надо бы добавить «конструктор», если, конечно, предполагается возможная инициализация этих структур вне пакета. Если стандартная реализация этих операций не устраивает, можно использовать limited private, который в остальном ничем от private не отличается, но этих операций не позволяет.

Реализация может также иметь часть-инициализацию. Это всё, что после begin.

package body Points is
    procedure setToZero(point: in out Point) is
        begin
        point := zero;
        end setToZero;
    function dotProduct(point1: in Point; point2: in Point) return Integer is
        begin
        return point1.x * point2.x + point1.y * point2.y;
        end setToZero;
begin
    Ada.Text_IO.Put_Line("Package ready!");
end Points;

Пакеты можно расширять. Взять и определить пакет Points.RandomDistributions, в нём можно пользоваться всем, что объявлено в пакете-родителе. Пакет можно сделать недоступным вне своей иерархии.

private package Points.internalThing is
    <...>

При подключении потомка родитель-пакет подключается автоматически.

О подключениях. Подключить пакет можно с помощью конструкции with.

with Points.RandomDistributions;

После этого можно будет пользоваться ввсем, что в пакете лежит, через точечную нотацию. Если же вызовов слишком много, то можно влить содержимое пакета в текущую область видимости с помощью use (только после подключения!).

use Points.RandomDistributions;

В случае конфликтов имён придётся-таки предварять каждое обращение именем пакета.

ОБЕРОН И ОБЕРОН-2

В Обероне-2 стандартная процедура NEW используется, чтобы распределить блоки данных в свободной памяти. Нет, однако, никакого способа явно освободить распределенный блок. Взамен Оберон-среда использует сборщик мусора чтобы найти блоки, которые больше не используются и сделать их снова доступными для распределения.

Расширение типов

Наиболее важным добавлением(от-но Modula-2) является механизм расширенных типов записи. Он позволяет конструировать новые типы на основе уже существующих и задает определенную степень совместимости между новыми и старыми типами. Предположим, что у нас имеется тип

   T = RECORD x,y: INTEGER END

В дополнение к существующим полям могут быть заданы новые:

   T0 = RECORD (T) z: REAL END
   T1 = RECORD (T) w: LONGREAL END

Здесь определяются типы с полями x,y,z и x,y,w соответственно. Мы говорим, что тип T’

   T’ = RECORD (T) <описание_полей> END

является (прямым) расширением типа T, и наоборот, T называется (прямым) базовым (base) типом для T’.

Modula-2

Перечислимый тип
   TYPE DaysOfWeek = (sunday, monday, tuesday, wednesday, thursday, friday, saturday);
   (* тогда ORD(friday) = 5, MAX(DaysOfWeek) = saturday *)
Тип поддиапазон
   TYPE SubType = [min .. max];

Дополнительно можно указывать хостовой тип

   TYPE
       SubType1 = [0..7]; (* host type is CARDINAL *)
       SubType2 = INTEGER [0..7]; (* host type is INTEGER *)
       WeekDays = [mon .. fri]; (* host type is DaysOfWeek *)

Говорят, что тип Т1 совместим с типом Т0, если либо он описан как Т1 = Т0, либо как диапазон Т0, либо если Т0 является диапазоном Т1, либо если и Т0, и Т1 - оба диапазоны одного и того же (базового) типа.

Записи

Как в Паскале.

   TYPE 
       myRec = RECORD
           val1: type1;
           valn: typen;
       END;

Классы

Конструкторы

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

Деструкторы

Деструкторы в C++ отличаются от деструкторов в Java и C# тем, что в C++ примерно установлено место в программе, в котором будет вызван деструктор (для объектов, выделенных в статической или квазистатической памяти) или он будет вызван вручную. В Java и C# время вызова деструктора не определено из-за наличия сборщика мусора.

Свойства

В C# и Delphi есть поддержка свойств (property).

Свойство - способ доступа к внутреннему состоянию объекта, имитирующий переменную некоторого типа. Обращение к свойству объекта выглядит так же, как и обращение к структурному полю (в структурном программировании), но, в действительности, реализовано через вызов функции. При попытке задать значение данного свойства вызывается один метод, а при попытке получить значение данного свойства — другой.

Свойства повышают гибкость и безопасность программирования, поскольку, являясь частью (открытого) интерфейса, позволяют менять внутреннюю реализацию объекта без изменения его свойств. Свойства предназначены для того, чтобы свести программирование к операциям над свойствами, скрывая вызовы методов.

Свойства в C# — поля с логическим блоком, в котором есть ключевые слова get и set.

class MyClass
{
    private int p_field;
    public int Field
    {
        get
        {
            return p_field;
        }
        private set
        {
            p_field = value;
        }
    }
}

Для описания свойства в Delphi служит слово property.

TMyClass = class
private
  FMyField: Integer;
  procedure SetMyField(const Value: Integer);
  function GetMyField: Integer;
public
  property MyField: Integer read GetMyField write SetMyField;
end;
 
function TMyClass.GetMyField: Integer;
begin
  Result := FMyField;
end;
  
procedure TMyClass.SetMyField(const Value: Integer);
begin
  FMyField := Value;
end;

Инкапсуляция и абстрактные типы данных

ОБЕРОН И ОБЕРОН-2

Позволяют открывать поля структуры

Модульность и раздельная трансляция

Раздельная трансляция

C, C++

Раздельная трансляция означает то, что программа разбивается на части — физические модули или единицы компиляции. Каждая единица может или обязана транслироваться отдельно от остальных. Независимая раздельная трансляция означает то, что транслятор не обладает информацией об уже оттранслированных единицах и поэтому не может проверить корректность межмодульных связей.

Модульность

ОБЕРОН И ОБЕРОН-2

Модуль - совокупность объявлений констант, типов, переменных и процедур вместе с последовательностью операторов, предназначенных для присваивания начальных значений переменным. Модуль представляет собой текст, который является единицей компиляции.
   Модуль 	 = 	MODULE идент ";" [СписокИмпорта] ПоследовательностьОбъявлений
   [BEGIN ПоследовательностьОператоров] END идент ".".
   СписокИмпорта 	 = 	IMPORT Импорт {"," Импорт} ";".
   Импорт 	 = 	[идент ":="] идент.

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

    MODULE Trees; (* экспорт: Tree, Node, Insert, Search, Write, Init *)
    IMPORT Texts, Oberon; (* экспорт только для чтения: Node.name *)
    TYPE
         Tree* = POINTER TO Node;
         Node* = RECORD
              name-: POINTER TO ARRAY OF CHAR;
              left, right: Tree
         END;
    VAR w: Texts.Writer;
    PROCEDURE (t: Tree) Insert* (name: ARRAY OF CHAR);
         VAR p, father: Tree;
    BEGIN p := t;
         REPEAT father := p;
              IF name = p.name^ THEN RETURN END;
              IF name < p.name^ THEN p := p.left ELSE p := p.right END
         UNTIL p = NIL;
         NEW(p); p.left := NIL; p.right := NIL; NEW(p.name, LEN(name)+1); COPY(name, p.name^);
         IF name < father.name^ THEN father.left := p ELSE father.right := p END
    END Insert;
    PROCEDURE (t: Tree) Search* (name: ARRAY OF CHAR): Tree;
         VAR p: Tree;
    BEGIN p := t;
         WHILE (p # NIL) & (name # p.name^) DO
              IF name < p.name^ THEN p := p.left ELSE p := p.right END
         END;
         RETURN p
    END Search;
    PROCEDURE (t: Tree) Write*;
    BEGIN
          IF t.left # NIL THEN t.left.Write END;
          Texts.WriteString(w, t.name^); Texts.WriteLn(w); Texts.Append(Oberon.Log, w.buf);
          IF t.right # NIL THEN t.right.Write END
    END Write;
    PROCEDURE Init* (t: Tree);
    BEGIN NEW(t.name, 1); t.name[0] := 0X; t.left := NIL; t.right := NIL
    END Init;
    BEGIN Texts.OpenWriter(w)
    END Trees.

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

Modula-2

Программа представляет собой набор модулей — самостоятельных единиц компиляции, которые могут компилироваться раздельно. При этом программный модуль может быть (но не обязан) разделён на две части: модуль определений и модуль реализации. Модуль определений — это внешний интерфейс модуля, то есть набор экспортируемых им имён констант, переменных, типов, заголовков процедур и функций, которые доступны внешним модулям. Модуль реализации содержит программный код, в частности, конкретизацию описаний всего, что перечислено в модуле определений. Например, некоторый тип «запись» может быть объявлен в модуле определений с указанием лишь его имени, а в модуле реализации — с полной структурой. В этом случае внешние модули могут создавать значения данного типа, вызывать процедуры и функции, работающие с ним, выполнять присваивание переменных, но не имеют прямого доступа к структуре значений, поскольку эта структура не описана в модуле определений. Если для этого же типа описать в модуле определений структуру, то она станет доступна. Помимо модулей глобального уровня в Модуле-2 допускается создавать локальные модули.
Импорт определений, описанных в прочих модулях, полностью контролируется. Можно импортировать модули определений целиком, но синтаксис позволяет существенно уточнять списки импорта, например, импортировать из модуля конкретные константы, переменные, процедуры и функции, только те, которые необходимы.

   DEFINITION MODULE ModuleName;  (* Модуль определений *)
   {Import}
   {Declaration}
   END ModuleName.
   ========================
   Где Import это:
   [FROM ModuleName] IMPORT
       identifier {,identifier};
   IMPLEMENTATION MODULE ModName;  (* Модуль реализаций *)
   {Import}
   {Declaration}
   [ BEGIN
       ListOfStatements
   [EXCEPT
       ListOfStatements]
   [
    FINALLY
       ListOfStatements
    EXCEPT
       ListOfStatements
   ]
   ]
   END ModName.
   MODULE ModName; (* Должен существовать единственный на весь проект. Это main. *)
   {Import}
   {Declaration}
   BEGIN
       ListOfStatements
   [EXCEPT
       ListOfStatements]
   END ModName.

Исключительные ситуации и обработка ошибок

Исключения и блоки try {} catch {} finally {}. Семантика возобновления и семантика завершения.

Семантика возобновления: после обработки исключения управление может вернуться непосредственно в точку, где возникло исключение (варианты: на следующий оператор или на любой оператор из того же блока). Пример языка c семантикой возобновления: Visual Basic.

Моделирование семантики возобновления на C++:

bool need_restart = true;
while (need_restart) {
    need_restart = false;
    try {
        // Some code here
    } catch (...) {
        // C# - просто catch, без круглых скобок
        // Java - catch (Throwable e)
   
        need_restart = true;
    }
}

Семантика завершения: после возникновения исключения блок, в котором оно возникло, обязательно завершается. Обработка исключения происходит в блоках, вызвавших блок с исключением. Пример языка с семантикой завершения: Си++.

catch (в delphi - except) - то что будет выполнено в случае ошибки в блоке try. finally - то что будет выполнено в любом случае, вне зависимости от того что произошло в блоке try.

Конструкция try ... finally ... есть в C#, Java, Delphi. Декларируется, что в C++ в finally нет необходимости в виду RAII и, как следствие, выполнении деструкторов на выходе из блока.

ADA

Создание исключения:

New_Exception: exception;

Выбрасывание исключения:

raise New_Exception;

Отлов исключения:

begin
   Rise_Exception;
exception
   when New_Exception =>
      Do_Smth;
end;

finally-блока в Аде таки нету. Однако он имитируется.

Отлов исключения так же может иметь такой вид:

when Error: Error_Name =>

Однако, что он означает я до конца не понял, но на экзамене это вряд ли нужно будет. Кому интересно, брал здесь.

Наследование типов и классов

ADA

Наследование в Аде происходит путем создания нового пакета и расширения в нем базовой записи:

package Person is
   type Object is tagged private;
   procedure Put (O : Object);
private
   type Object is
   record
      Name   : String (1 .. 10);
      Gender : Gender_Type;
   end record;
end Person;
with Person;
package Programmer is
   type Object is new Person.Object with private;
   private
      type Object is
      record
         abuility   : Language_List;
      end record;
end Person;

ООП в Аде является вполне полноценным c Динамическим полиморфизмом, RTTI, абстрактными типами и интерфейсами.

Динамический полиморфизм

В оо яп полиморфизм реализуется с помощью наследования классов и виртуальных функций. Класс-потомок наследует сигнатуры методов класса-родителя, а реализация, в результате переопределения метода, этих методов может быть другой, соответствующей специфике класса-потомка. Другие функции могут работать с объектом как с экземпляром класса-родителя, но если при этом объект на самом деле является экземпляром класса-потомка, то во время исполнения будет вызван метод, переопределенный в классе-потомке. Это называется поздним связыванием.

Позднее связывание есть в C++, C#, Java, Delphi, Ада 95, Оберон-2.

Абстрактные типы данных, классы и интерфейсы

Абстрактный тип данных (АТД) — это тип с полностью инкапсулированной структурой. Использовать объекты АТД возможно только при помощи явно определенных в интерфейсе типа операций. Абстрактный класс (АК) — это класс, содержащий хотя бы один абстрактный метод. Он предназначен только для того, чтобы быть базовым классом.

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

Абстрактный класс

В объектно-ориент. яп абстрактный класс реализуются следующими тремя способами
1. Модификатор abstract перед классом. Используется в C# и Java.
2. Класс содержит хотя бы один абстрактный метод. В C# и Java абстрактный метод обозначается модификатором abstract перед объявлением метода. В C++ абстр. методы называются чистыми виртуальными. Например
void Draw() = 0; // тело отсутствует
3. Если в классе, производном от абстрактного класса с абстрактными методами, не замещен хотя бы один абстрактный метод, то класс тоже является абстрактным. В C# и Java незамещенные абстрактные методы должны быть явно объявлены как абстрактные. В языке C# абстрактными могут быть и свойства:
abstract int Length { get;}

Интерфейс

Интерфейс состоит только из абстрактных методов. В нем нет реализации методов (как нет и не виртуальных методов), нет нестатических членов (статические члены, например константы, допустимы).

Пример на C++:

class Set{
public:
    virtual void Incl(T & x) = 0
    virtual void Excl(T & x) = 0
    virtual bool IsIn(T & x) = 0
 
    virtual ~Set () {} // деструктор
};

Множественное наследование

Полностью реализовано только в C++. В Ada, C#, Delphi, Java множественное наследование поддерживается только для интерфейсов.

Динамическая идентификация типа

Динамическая идентификация типа данных (Run-time type identification, RTTI) — механизм, который позволяет определить тип данных переменной или объекта во время выполнения программы.

C++

В C++ для динамической идентификации типов применяются операторы dynamic_cast и typeid (определён в файле typeinfo.h), для использования которых информацию о типах во время выполнения обычно необходимо добавить через опции компилятора при компиляции модуля.

Оператор dynamic_cast пытается выполнить приведение к указанному типу с проверкой. Целевой тип операции должен быть типом указателя, ссылки или void*.

Оператор typeid возвращает ссылку на структуру type_info, которая содержит поля, позволяющие получить информацию о типе.

Delphi

Компилятор Delphi сохраняет в исполняемом файле программы информацию обо всех классах, используемых в ней. При создании любого объекта в памяти перед ним располагается заголовок, в котором есть в том числе ссылка на структуру-описатель класса этого объекта. Встроенные в язык функции работают с этой информацией прозрачно для программиста. Оператор is позволяет проверить, является ли объект или тип наследником определённого типа, а оператор as является аналогом dynamic_cast в C++.

C#

В C# для определения типа объекта во время исполнения используется метод GetType, а также ключевые слова is и as, которые являются аналогами для typeid и dynamic_cast в C++ соответственно.

Оберон-2

В Оберон-2 есть два средства для идентификации типа: операция IS и охрана типа.

Проверка типа

v IS T означает "динамический тип v есть T (или расширение T)" и называется проверкой типа. Проверка типа применима, если

1. v - параметр-переменная типа запись, или v - указатель, и если

2. T - расширение статического типа v

Охрана типа

Операторы with выполняют последовательность операторов в зависимости от результата проверки типа и применяют охрану типа к каждому вхождению проверяемой переменной внутри этой последовательности операторов. Если v - параметр-переменная типа запись или переменная-указатель, и если ее статический тип T0, оператор

WITH v: T1 DO S1 | v: T2 DO S2 ELSE S3 END

имеет следующий смысл: если динамический тип v - T1, то выполняется последовательность операторов S1 в которой v воспринимается так, будто она имеет статический тип T1; иначе, если динамический тип v - T2, выполняется S2, где v воспринимается как имеющая статический тип T2; иначе выполняется S3. T1 и T2 должны быть расширениями T0. Если ни одна проверка типа не удовлетворена, а ELSE отсутствует, программа прерывается.

Java

В Java тип объекта может быть получен при помощи метода getClass(), объявленного в классе java.lang.Object и потому реализуемого каждым классом. Для проверки принадлежности объекта определенному типу используется оператор instanceof, аналогом dynamic_cast из C++ является оператор приведения типа, который в случае несоответствия типов выбрасывает исключение ClassCastException.

Понятие о родовых объектах. Обобщенное программирование

ADA

Note to C++ programmers: generic units are similar to C++ templates. Ada Programming

Объявляем шаблон:

generic
 type Element_T is private;  -- Generic formal type parameter
procedure Swap (X, Y : in out Element_T);

Для его использования необходимо создать объект нужного типа:

procedure Swap_Integers is new Swap (Integer);

Возможна перегрузка:

procedure Instance_Swap is new Swap (Float);
procedure Instance_Swap is new Swap (Day_T);

Можно так же указывать, какие подпрограммы должны быть определены для обобщенного типа:

generic
 type Multiplable_T is private;  -- Generic formal type parameter
 with function "*" ( X, Y: Multiplable_T) return Multiplable_T;

Оператор loop определяет повторное выполнение последовательности операторов.
Операторы with выполняют последовательность операторов в зависимости от результата проверки типа и применяют охрану типа к каждому вхождению проверяемой переменной внутри этой последовательности операторов.

Примеры кода

Примеры кода на Java

package MyPackage; // Всё, что будет написано в этом файле, считается лежащим в этом пакете.
import java.lang.*; // Импорт пакетов.

public class MyClass
{
    private static int m_i = 3;
    private static int m_j;
    static {
        // Демонстрация статического блока в Java. Операторы в этом блоке будут выполнены в самом начале программы.
        m_j = m_i * 4;
    }
    public static void main(String [] argv)
    {
        final int N = 5; // final — аналог констант в языках C/C++
        final int M = 3;
        double a[][] = new double[N][M];
        double [][]b = new double[N][M];
        for (int i = 0; i < a.length; ++i) {
            for (int j = 0; j < a[i].length; ++j) {
                a[i][j] = i * j;
            }
        }
        for (int i = 0; i < b.length; ++i) {
            for (int j = 0; j < b[i].length; ++j) {
                b[i][j] = i + j;
            }
        }
        double res[][] = sumMatrices(a, b);
        for (int i = 0; res != null && i < res.length; ++i) {
            for (int j = 0; res[i] != null && j < res[i].length; ++j) {
                System.out.print(res[i][j] + " ");
            }
            System.out.println();
        }
    }
    public static double[][] sumMatrices(double a[][], double b[][])
    {
        label1: if (a != null && b != null && a.length == b.length) {
            label2: {
                for (double x[] : a) { // аналог for-each
                    for (double y[] : b) {
                        if (x.length != y.length) {
                            break label2; // аналог goto
                        }
                    }
                }
                break label1;
            }
            System.out.println("Bad Arrays...");
            return null;
        } else {
            System.out.println("Bad Arrays...");
            return null;
        }
        double [][]res = new double[a.length][a[0].length];
        for (int i = 0; i < a.length; ++i) {
            for (int j = 0; j < a[i].length; ++j) {
                res[i][j] = a[i][j] + b[i][j];
            }
        }
        return res;
    }
}
package MyPackage;

import java.sql.SQLException;

public class Example
{
    public static void main(String [] argv)
    {
        // демонстрация работы внутренних классов:
        Outer outer = new Outer(10);
        outer.test();
        // демонстрация работы динамической диспетчеризации методов:
        A refA;
        refA = new A(5);
        refA.callMe();
        refA = new B(10, 15);
        refA.callMe();
        // демонстрация работы с абстрактными классами:

        //AbstrA abstra = new AbstrA(5); — нельзя, т.к. AbstrA — абстрактный класс.
        AbstrA refAbstrA;
        refAbstrA = new AbstrB(5, 10);
        refAbstrA.callMe();
        // демонстрация работы с интерфейсами:
        Client client = new Client();
        client.someFunc();
        // демонстрация работы с исключениями:
        try {
            client.badFuncEx();
            // Мы не можем оставить потенциальную посылку функцией исключения без внимания:
            // либо окружим её вызов конструкцией try-catch,
            // либо напишем, что эта функция тоже может бросать исключения (большое отличие от языка С++).
        } catch (Exception ex) { // Ловушка для всех исключений, ибо Exception — это базовый класс.
            //do Smth...
        }
    }
}

class Outer
{
    private int x = 0;
    public Outer(int x)
    {
        this.x = x;
    }
    private void display()
    {
        System.out.println(x);
    }
    public void test()
    {
        Inner in = new Inner();
        in.useOuterFunc();
    }
    class Inner
    {
        public void useOuterFunc()
        {
            // Внутренний класс имеет непосредственный доступ к приватным и публичным полям внешнего класса.
            // Обратное неверно.
            display();
            x = 15;
            display();
        }
    }
}

// Реализация динамической диспетчеризации методов:
class A
{
    private int i;
    A(int i)
    {
        this.i = i;
    }
    public void callMe()
    {
        System.out.println("In A.callMe(). " + i);
    }
}

final class B extends A
// final означает, что никакой класс не сможет отнаследоваться от В.
// final, стоящий перед описанием метода запрещает его переопределение.
{
    private int i; // Поле i класса B перекрывает поле i суперкласса.
    B(int i, int j)
    {
        super(i); // вызывается метод А(int i) суперкласса.
        this.i = j;
    }
    public void callMe()
    // Сигнатура функций должна совпадать. В противном же случае — это обычная перегрузка.
    // Сам метод называется переопределённым (аналог виртуальным методам в С++).
    {
        System.out.println("In B.callMe(). " + i); // Здесь выведется значение поля подкласса.
        // Для доcтупа к полю суперкласа нужно использовать super.i (но здесь поле недоступно, ибо private).
    }
}

// Использование абстрактных классов. Если мы объявляем какой-то метод в классе абстрактным, то
// автоматически должны сделать абстрактным и сам класс. С точки зрения динамической диспетчеризации это означает лишь то,
// что подклассы должны будут явно реализовать все абстрактные методы или тоже быть абстрактными.

abstract class AbstrA
{
    private int i;
    AbstrA(int i)
    {
        this.i = i;
    }
    abstract public void callMe();
}

final class AbstrB extends AbstrA
{
    private int i;
    AbstrB(int i, int j)
    {
        super(i);
        this.i = j;
    }
    public void callMe()
    {
        System.out.println("In AbstrB.callMe(). " + i);
    }
    protected void finalize()
    {
        // Считаю нужным упомянуть о методе finalize. Он должен вызываться при удалении объекта сборщиком мусора.
        // Используется для освобождения занятых ресурсов (закрытия файлов и т.п.).
        // Однако его поведение настолько туманно, что применять его не стоит, а позаботиться об освобождении отдельно. :)
        System.out.println("Wow! You're lucky to see it in your console! :D");
    }
}

// Использование интерфейсов и исключений. :)
// Все методы и члены интерфейсов по умолчанию делаются публичными. Все члены — final static.

interface MyInterface
{
    int pubStatFinalMember = 121;
    // Все переменные должны быть инициализированы.
    void someFunc();
}
/*
Доступ class имя_класса [extends суперкласс]
    [implements интерфейс [, интерфейс...]]
    //тело класса
*/
class Client implements MyInterface
// В классе должны быть определены все функции интерфейса. Спецификатор доступа у них обязан быть public.
// Если в классе определяются не все функции интерфейса, то он должен быть объявлен абстрактным.
// Кроме того интерфейс может быть вложен в класс или же другой интерфейс, а также интерфейсы можно наследовать.
{
    public void someFunc()
    {
        //pubStatFinalMember = 601; ——— Нельзя, т.к. переменная — final.

        System.out.println("In someFunc() from interface.");
    }
    public void badFuncEx() throws SQLException
    {
        //Здесь же и рассмотрим исключения. :)
        try {
            int i = 0;
            i = pubStatFinalMember / i;
            System.out.println("You shouldn't see it.");
        } catch (ArithmeticException ex) { // Тут мы поймаем исключение деления на нуль
            System.out.println(ex);
            throw new SQLException();
            // Пошлём другое исключение выше. >:D Но в таком случае мы должны у функции пометить, что она может
            // разбрасываться исключениями (см. throws список_исключений).
        } finally {
            // Этот блок кода обязательно получит управление после блока try/catch вне зависимости от исключения.
            System.out.println("You will always see it.");
        }
    }
}

Вывод:
10
15
In A.callMe(). 5
In B.callMe(). 15
In AbstrB.callMe(). 10
In someFunc() from interface.
java.lang.ArithmeticException: / by zero
You will always see it.

Итоговая таблица

Если в таблице указан знак вопроса, то либо этого языка не было в списке языков в задании, либо информация отсутствует.

ANSI/ISO C (1989/1990) C++98 C# Java Pascal Delphi Оберон Оберон-2 Modula-2 Ada83 Ada95
есть оператор перехода «goto метка» есть есть есть нет[1] есть есть нет нет нет есть есть
есть try-finally нет[2] нет[3] есть есть нет есть нет нет нет нет нет
есть понятие связывания подпрограмм (методов класса) на этапе выполнения (динамического связывания) нет[4] есть есть есть нет есть нет есть нет нет есть
Тип Запись (struct, record) есть есть[5] есть нет есть есть есть есть есть есть есть
Оператор loop есть есть есть есть есть
Оператор with есть есть нет есть есть нет[6]
Процедурное программирование есть есть есть есть есть есть есть есть есть есть есть
Модульное программирование ч/и ч/и и и и и есть есть есть есть есть
АТД нет есть есть есть есть есть есть
Расширяющее программирование нет и и и и есть есть
ООП нет есть есть есть есть и есть нет есть?
Компонентное программирование и и и и и и и
Композиционное программирование нет нет нет нет нет ч/и ч/и
Обобщенное программирование нет и и нет нет нет нет нет нет есть есть
Параллельное программирование нет нет[7] есть есть нет нет нет ч/и?
Рефлективное программирование нет и есть и и и и
ч/и - частично имитируется
и - имитируется

Примечания

  1. Есть break label, где label ставится с двоеточием перед началом цикла.
  2. Имитируется макросами через goto cleanup;
  3. Декларируется отстутвие необходимости ввиду налиия RAII; есть в качестве расширения в некоторых реализациях (GCC, MSVC)
  4. Обычно реализуется через указатели на функции и указатели на структуры с указателями на функции.
  5. Является классом с публичной областью видимости для полчей; если не использовать виртуальные етоды, близок по использованию к структурам в C
  6. Ключевое слово with означает импортирование классов в текущую область видимости.
  7. В C++10 появился std::thread. Подробнее.
Личные инструменты
Разделы