Примеры
!
Описание типа PERSON
TYPE PERSON
INTEGER AGE
CHARACTER (LEN = 10) NAME
END TYPE PERSON
! Объявление скаляров и массива описанного типа
TYPE (PERSON):: STUDENT, TEACHER, MEMBER (10)
!
Операция над компонентами производного типа
N = TEACHER%AGE - STUDENT%AGE
!
Компонент элемента массива производного типа
MEMBER
(1) % AGE = 50
!
Присваивание объекту производного типа
STUDENT = (20, ' IVANOV’)
! Пример связанного списка
TYPE LINK
REAL VALUE
TYPE
(LINK), POINTER :: PREVIOUS
TYPE (LINK), POINTER :: NEXT
END
TYPE LINK
(Все примеры в работе
приводятся в свободном формате и используются заглавные буквы; фиксированный
формат и строчные буквы также допустимы.).
5. Определяемые операции и присваивания
5.1. Общие сведения
В
языке имеется аппарат, позволяющий программисту распространить для производных
типов встроенные операции (перегрузка операций, см. 7.3.) или описать новые
операции для данных встроенных и производных типов. Кроме того, можно описать
новые операторы присваивания. Эти возможности позволяют программисту определить
абстрактные типы данных (см. 6.), что является одним из элементов
объектно-ориентированного программирования.
Ниже
рассматривается как эти средства реализуются в языке.
5.2. Определяемые
операции
В программе можно
описать и затем использовать в выражениях определяемые унарные и бинарные
операции. Они описываются с помощью функций; если такая функция имеет один
формальный аргумент, она описывает унарную операцию, если два – бинарную; при
этом аргументы должны быть входными, т.е. INTENT(IN). Такая функция может быть помещена в модуль (см. 6.2.).
Знак определяемой
операции либо совпадает со знаком одной из встроенных операций, либо
представляет собой последовательность букв, заключенную в точки.
Примеры знаков определяемых операций:
+ .ADD.
.INVERSE.
.EQ. .PLUS.
Если знак
определяемой операции совпадает со знаком встроенной, то типы аргументов должны
отличаться от типов соответствующей встроенной операции.
В качестве
примера определяемой операции рассмотрим описание операции сложения в
интервальной арифметике. Напомним, что объект в интервальной арифметике,
называемый интервалом, может быть представлен двумя вещественными числами –
границами интервала. Над объектами-интервалами определены операции, подобные
обычным арифметическим операциям. Операции над интервалами описываются через
арифметические операции над границами.
Пример
! Функция для
описания операции сложения
FUNCTION ADD
(A, B) OPERATOR (+)
INTENT (IN) A, B
! Описание типа INTERVAL
TYPE INTERVAL
REAL LOWER, UPPER
END TYPE
TYPE (INTERVAL) ADD, A, B
ADD % LOWER = A % LOWER + B % LOWER
ADD % UPPER = A % UPPER + B % UPPER
END FUNCTION ADD
Если в программной единице в выражении
используется операция, определяемая программистом во внешней функции, то должен
быть включен интерфейсный блок. Такая функция может быть помещена в модуль (см.
6.2.). Ниже приведен фрагмент программы, в котором используется определенная в
предыдущем примере операция сложения.
! Фрагмент вызывающей
программной единицы
! Интерфейсный блок со спецификацией
функции,
! реализующей определяемую операцию
INTERFACE OPERATOR
(+) ! Знак операции “+”
FUNCTION ADD (A, B) OPERATOR (+)
INTENT (IN) A, B
TYPE (INTERVAL) ADD, A, B
END FUNCTION ADD
END INTERFACE
! Использование определяемой операции
TYPE (INTERVAL)
Z, X, Y
Z = X + Y
К функциям,
описывающим определяемую операцию, можно обращаться либо обычным образом с
помощью имени функции (в приведенном примере - ADD),
либо используя знак операции (в примере – “+”).
5.3. Определяемое присваивание
Определяемый оператор
присваивания описывается с помощью подпрограммы. Такая подпрограмма имеет два
аргумента, соответствующие левой и правой частям присваивания; первый аргумент
является переменной с атрибутом OUT
или INOUT, второй – должен иметь атрибут IN. В программной единице, использующей определяемое
присваивание, описанное во внешней процедуре, должен быть задан явный
интерфейс. Такая процедура может быть помещена в модуль (см. 6.2.).
Определяемый оператор
присваивания имеет ту же форму, что и встроенный оператор присваивания, и
отличается от него тем, что для характеристик его левой и правой частей не
выполняются правила, установленные для встроенного оператора присваивания.
Распознавание того,
что оператор вида v=e является
определяемым оператором присваивания, а не встроенным, осуществляется следующим
образом. Такой оператор является определяемым, если имеется интерфейсный блок
подпрограммы присваивания с формальными аргументами x и y, типы которых
соответствуют типам v и e - левой и правой части оператора
присваивания.
Пример описания и
использования определяемого присваивания.
! Описание
присваивания значения логического типа
! переменной символьного типа
SUBROUTINE LOG_TO_SYM (SYMB_VAR, LOG_EXPR)
CHARACTER, INTENT (OUT) ::
SYMB_VAR
LOGICAL, INTENT (IN) :: LOG_EXPR
SYMB_VAR = 'F'
IF (LOG_EXPR == .TRUE.) SYMB_VAR =
'T'
END SUBROUTINE LOG_TO_SYM
! Фрагмент программной
единицы, где используется
! определяемое присваивание
PROGRAM EXAMPLE
CHARACTER A
LOGICAL B
! Интерфейсный блок со спецификацией
подпрограммы,
! описывающей определяемое
присваивание
INTERFACE
ASSIGNMENT (=)
SUBROUTINE LOG_TO_SYM (SYMB_VAR,
LOG_EXPR)
CHARACTER, INTENT (OUT) ::
SYMB_VAR
LOGICAL, INTENT (IN) :: LOG_EXPR
END SUBROUTINE LOG_TO_SYM
END INTERFACE
! Оператор определяемого присваивания
A = B
6. Инкапсуляция и абстракция данных
6.1. Общие сведения
Фундаментальной идеей
ООП является инкапсуляция данных и соответствующих операций. Инкапсуляция в
Фортране 90/95 реализуется с помощью модулей (см. 6.2.) и различных механизмов
динамического размещения объектов (см. 6.3.).
Инкапсуляция объектов
означает возможность скрывать внутренние детали реализации при предоставлении
открытого интерфейса к некоторому объекту. Объекты, обладающие таким свойством,
часто называют абстрактными. Например, для абстрактных данных пользователю
достаточно знать только тип и набор операций над данными такого типа, но он
может не знать, как описан тип и как реализованы эти операции. Если поместить в
модуль описания определяемых пользователем типов и операций над данными такого
типа, это может обеспечить их инкапсуляцию.
Здесь можно провести
аналогию, которая традиционно существует для встроенных операций. Например,
используя операцию .AND. для
логических операндов, программист не интересуется, как представлен логический тип в машине, и как выполняются операции.
Пример, часто
приводимый в литературе, - стек. Тип “стек” может быть реализован (описан),
например, как массив фиксированной длины или как связанный список. Можно
описать операции push (поместить в
стек) и pop (извлечь из стека).
Если описание стека изменено и, естественно, изменено описание операций,
программист все равно может их использовать без необходимости изменять код этих
операций.
Указанная
черта повышает надежность и безопасность программы и, кроме того, делает
программные единицы легче для использования, так как позволяет изменять
внутренние детали, не влияя на пользователя.
6.2. Модули
Модуль – это
новый вид программных единиц в Фортране. Наличие программных единиц-модулей,
производных типов, определяемых пользователем операций и атрибутов PUBLIC и PRIVATE обеспечивают
полную поддержку инкапсуляции и абстракции данных.
Внутри модуля могут
быть описаны производные типы и процедуры, определяющие операции над объектами
этого типа. Их реализация может быть скрыта внутри модуля, и их можно менять в
случае необходимости. Программная единица, использующая модуль, может не знать
внутреннее представление типа, и ей могут быть недоступны отдельные компоненты.
В то же время сами типы и операции могут быть доступны программным единицам,
которые используют модуль, и изменения структуры типов и описаний операций не
влияют на программные единицы, использующие этот модуль (пример см. ниже в
данном разделе).
Модули могут
содержать глобальные объекты, которые могут быть использованы другими
программными единицами без явного объявления их внутри этих программных единиц.
Объекты модуля,
которые используются в других программных единицах, должны иметь атрибут PUBLIC (явно описанный или по умолчанию). Те объекты модуля,
которые предназначены для использования лишь внутри данного модуля, должны быть
объявлены с атрибутом PRIVATE.
В программной единице, использующей
объекты некоторого модуля, должен присутствовать оператор USE c указанием имени модуля.
Аппарат модулей
позволяет создавать пакеты (модули) для различных приложений. Фрагмент
подобного модуля для интервальной арифметики (см. 5.2.) воспроизведен ниже.
Пример
MODULE
INTERVAL_ARITHMETIC
TYPE INTERVAL
PRIVATE
REAL LOWER, UPPER
END TYPE
INTERFACE OPERATOR (+)
MODULE PROCEDURE ADD
END INTERFACE
CONTAINS ! Оператор отделяет модульные процедуры
! Модульная процедура
FUNCTION ADD (A, B) OPERATOR (+)
TYPE (INTERVAL) ADD
TYPE (INTERVAL), INTENT (IN) :: A,
B
ADD % LOWER = A % LOWER + B %
LOWER
ADD % UPPER = A % UPPER + B %
UPPER
END FUNCTION ADD
! Модульные
процедуры для описания других операций
. . .
END MODULE
INTERVAL_ARITHMETIC
Использование модуля интервальной
арифметики показано в следующем примере.
PROGRAM
INT_ARITH_EXAMPLE
USE INTERVAL_ARITHNETIC
TYPE (INTERVAL) X, Y, Z
. . .
Z = X + Y
. . .
END PROGRAM
В приведенном примере главная
программная единица может использовать тип INTERVAL
и операции (сложение и другие описанные в модуле операции), но не имеет доступ
к отдельным компонентам типа INTERVAL, так как описание этого типа содержит оператор PRIVATE.
В объектно-ориентированных языках
программирования используется понятие “класс”. Так, в С++ класс содержит новый
тип и операции, которые могут выполняться этим классом. В такой трактовке в
Фортране 90/95 аналогом класса является модуль, содержащий одно описание
производного типа и описание операций над данными этого типа (однако понятие
модуля в языке значительно шире).
Для читателя, не
знакомого с Фортраном 90/95, приведем некоторую дополнительную информацию о
модулях. Модули полезны не только в связи с ООП. В частности, такие средства описания глобальных объектов
являются более общими и гибкими, чем оператор COMMON, так как предоставляют
пользователю возможность работать не только с общими данными, но также с
другими общими объектами - типами данных, заготовленными описаниями процедур и
определяемых операций и др. Способ работы с глобальными данными, обеспечиваемый
модулями, более гибкий, чем с помощью операторов COMMON: глобальные данные при
этом способе доступны по имени, а не по положению внутри общего блока. Кроме
того, в отличие от описания общих блоков в операторах COMMON, глобальные
переменные надо описывать только в одном месте.
Оператор USE
позволяет ограничить доступ к общедоступным объектам и переименовать доступные
объекты. Поясним на примерах.
Примеры операторов USE.
USE MOD1
USE MOD2 NEWA => A, NEWB => B
USE
MOD3 ONLY: X, Y, NEWZ => Z
Операторы, приведенные в примере,
означают, что в данной программной единице доступны следующие объекты:
1) все объекты с атрибутом PUBLIC из
модуля MOD1 (локальные имена этих объектов совпадают с их именами в модуле MOD1);
2) все объекты с атрибутом PUBLIC из
модуля MOD2, причем объекты c именами A и B получают локальные имена NEWA и
NEWB (остальные объекты сохраняют те же имена, что в модуле MOD2);
3) только объекты с именами X, Y, Z
(они должны иметь атрибут PUBLIC) из модуля MOD3; причем объект Z получает
локальное имя NEWZ (остальные объекты, т.е. X
и Y сохраняют свои имена).
Переименование объектов бывает удобно,
а иногда и необходимо, если в программной единице, использующей объекты модуля,
имеются свои локальные объекты, имена которых совпадают с именами глобальных
объектов модуля.
Еще одно
преимущество модулей заключается в том, что обеспечивается возможность
зависимой (т.е. контролируемой) раздельной компиляции программных единиц. Это
объясняется тем что информация, специфицируемая в модуле, доступна процессору
при компиляции программных единиц, использующих данный модуль. Зависимая и в то
же время раздельная компиляция повышает надежность программ и позволяет
компилятору выполнять более широкий спектр оптимизирующих преобразований.
6.3. Динамические массивы
Инкапсуляция
реализуется не только с помощью модулей. Наличие различных механизмов
динамического размещения объектов также обеспечивает поддержку инкапсуляции и
абстракции данных.
Инкапсуляция
достигается за счет того, что размер массива, создание и освобождение
(уничтожение) производятся в самой процедуре и при вызове об этом можно не
заботиться. Массивы, которые необходимы только в процедуре, скрыты внутри этой
процедуры, и остальная программа не должна их касаться. Такой вид инкапсуляции
имеет те же преимущества: автор процедуры может, например, менять размер
массива без изменения тех программных единиц, которые используют эту процедуру,
что облегчает разделение работы между разработчиками.
В Фортране 77 не
допускались массивы переменной длины. Поскольку программные единицы
транслировались раздельно, длина массива должна была объявляться явно в
вызывающей программной единице и передаваться в процедуру через аргументы или
через COMMON-блоки.
В Фортране 90/95
длины могут быть объявлены только в процедуре, что упрощает вызов, уменьшает
число аргументов. Это объясняется тем, что все массивы, которые нужны только
внутри процедуры, могут быть скрыты в процедуре, и другие программные единицы
могут о них ничего не знать.
В
современных стандартах имеются следующие механизмы динамического размещения
массивов:
·
автоматические массивы, границы которых вычисляются при входе в
процедуру;
·
размещаемые массивы, границы которых вычисляются при выполнении
программы;
·
массивы, создаваемые в процессе выполнения программы, т.е. массивы,
которые не были заранее явно объявлены в программе.
Эти средства позволяют пользователю
организовать работу с массивами, размер которых заранее не известен, а также
оперативно отводить память под массивы, требующиеся на том или ином этапе
выполнения программы и освобождать память по мере необходимости.
6.3.1.
Автоматические массивы
Автоматические массивы
создаются при входе в процедуру, их можно использовать только внутри процедуры,
и они уничтожаются (т.е. освобождается занимаемая ими память) при выходе; таким
образом, автоматические массивы могут быть неизвестны вне процедуры.
Пример
SUBROUTINE SWAP (X, Y)
REAL, DIMENSION (:) :: X, Y
REAL, DIMENSION (SIZE (X)) :: TEMP
! TEMP – автоматический
массив
TEMP = X
X = Y
Y = TEMP
END SUBROUTINE
SWAP
Автоматические
массивы не являются формальными аргументами, их границы и размер вычисляются
при входе в процедуру.
Если в
процедуре появляется временный массив, с переменными границами, который
используется только внутри процедуры, в Фортране 77 он должен быть формальным
аргументом. В Фортране 90/95, если такой массив не нужен на выходе, его можно
исключить из списка аргументов, и вызывающая программа может о нем не знать.
Так в приведенном примере массив TEMP в Фортране 77 должен быть формальным аргументом, и при вызове SWAP его размер должен
быть определен. В современном Фортране информация о массиве TEMP при вызове процедуры
не нужна.
Примечание
В
приведенном примере массивы X и Y
– массивы с передаваемой конфигурацией. Это тоже новая черта Фортрана 90/95;
конфигурация формального аргумента перенимается у фактического.
6.3.2. Размещаемые массивы
Размещаемые массивы отличаются от автоматических тем, что создание и
уничтожение выполняется полностью под контролем программиста. Такие массивы
создаются при выполнении оператора ALLOCATE и уничтожаются при выполнении DEALLOCATE (или при выходе из процедуры, если они
не имеют атрибута SAVE).
Пример
REAL, DIMENSION(:, : ) ALLOCATABLE :: AR1, AR2
. . .
READ (*, *) NX, NY
ALLOCATE AR1 (NX, NY), AR2 (NX,
NY)
Размещаемые
массивы должны быть объявлены с атрибутом ALLOCATABLE; при объявлении
размещаемого массива указывается только его размерность (ранг). При выполнении
оператора размещения ALLOCATE вычисляются границы массива, его размер и
отводится память. Память, занимаемая массивом, освобождается при выполнении
оператора DEALLOCATE.
Как
и в случае автоматических массивов, такие массивы можно не включать в список
аргументов, хотя они не являются массивами с постоянными границами.
6.4. Обсуждение
Подводя итог, еще раз
отметим что инкапсуляция упрощает нескольким авторам разрабатывать программу
независимо друг от друга, облегчает модернизацию программы для другой
архитектуры, или для других требований. Инкапсуляция также упрощает
использование процедур. Это объясняется тем, что детали реализации скрыты от
пользователя: даже если автор процедуры вносит изменения внутри, пользователь
не должен менять свои коды. Использование механизма инкапсуляции особенно
полезно при создании библиотек и пакетов, ориентированных на конкретную
область.
7. Статический полиморфизм
7.1. Общие сведения
Полиморфизм означает
возможность для программиста использовать какой-либо объект более чем одним
способом.
Примером полиморфизма
может служить операция деления в Фортране. Если операнды целого типа -
выполняется целочисленное деление, если хотя бы один операнд вещественного типа
- выполняется деление с плавающей точкой, для комплексных операндов выполняется
другая операция. В таких случаях говорят, что операция перегружена.
При статическом полиморфизме конкретизация способа использования
выполняется на этапе компиляции. Статический полиморфизм поддерживается в
Фортране 90/95 следующим образом:
·
явное и
неявное приведение типов (см. 7.2.);
·
перегрузка
операций (см. 7.3.);
·
перегрузка
процедур (родовые процедуры и процедуры
с необязательными аргументами; см. 7.4 и 7.5.).
Рассмотрим каждую из этих компонент более подробно.
7.2.
Приведение типов
Приведение типов
требуется в тех случаях, когда операнды одной операции (кроме возведения в
степеь) или левая и правая части оператора присваивания имеют разный тип.
Неявное приведение типов выполняется автоматически компилятором (это
традиционно для Фортрана), при этом выполняется автоматическое приведение к
более широкому типу. Более надежным является явное приведение типов с помощью
встроенных функций (REAL, DBLE, CMPLX, INT и др.), что также традиционно для Фортрана.
7.3. Перегрузка операций
Перегрузка операций
означает использование одного и того же имени для нескольких разных операций в
общей области действия. Реализация операции зависит от типов операндов.
В современном
Фортране такие средства предусмотрены как для встроенных операций и
присваиваний (это допускалось и в прежнем Фортране), так и для определяемых
пользователем операций и присваиваний.
Если знак
определяемой операции совпадает со знаком одной из встроенных операций, то типы
операндов определяемой операции должны отличаться от типов операндов данной
встроенной операции. Например, знак “+” можно расширить для сложения объектов
типа INTERVAL (см. пример в 5.2. и 6.2.).
Перегрузка операций
допускается и для операций с определяемыми знаками операций. Типы и параметры
типа операндов определяемой операции должны соответствовать типам и параметрам
типа формальных аргументов функции, описывающей данную операцию. Соответствие
типа операнда и типа формального аргумента производного типа устанавливается по
принципу именной эквивалентности. То же относится и к операторам присваивания.
Примеры были приведены выше (см. 5.2. и 5.3.).
Операции над
скалярами и массивами – еще один пример перегрузки операций. Любая встроенная
операция и многие стандартные функции (называемые поэлементными) могут быть
применимы как к скалярам, так и к массивам (согласованным по конфигурации, т.е.
имеющим одинаковую размерность и размер по каждому измерению). Операции
выполняются на поэлементной основе и вырабатывают результат – массив.
Пример
REAL, DIMENSION (100,
100) :: A, B, X, Z
Z = A + B* SIN
(X)
7.4. Перегрузка процедур
Перегрузка процедур
означает использование одного и того же имени процедуры (обощенного имени) для
разных процедур.
В Фортране 77 такие средства были только для встроенных процедур
(универсальные имена). Например, функция REAL (A) выполняет разные операции в зависимости от типа аргумента, хотя для
этих операций существуют специфические имена FLOAT и SNGL.
В современном Фортране процедуры, определяемые пользователем, также
могут быть настраиваемыми (для использования таких процедур требуется
интерфейсный блок). Разные процедуры объединяются под одним обобщенным
(родовым) именем.
Пример
INTERFACE SWITCH
SUBROUTINE INT_SWITCH (X, Y)
INTEGER X, Y
END SUBROUTINE INT_SWITCH
SUBROUTINE REAL_SWITCH (X, Y)
REAL X, Y
END SUBROUTINE REAL_SWITCH
END INTERFACE
К каждой из процедур
INT_SWITCH и REAL_SWITCH, указанных в этом примере, очевидно, можно обратиться
с помощью оператора CALL, в котором указывается имя процедуры и список
аргументов. Кроме того, интерфейсный блок в такой форме, как он задан в
примере, позволяет обратиться к любой из процедур, с помощью родового имени
SWITCH, например:
CALL SWITCH (P, R)
Если фактические
аргументы P и R имеют целый тип, то данный вызов представляет собой
обращение к процедуре INT_SWITCH, если вещественный, то к процедуре
REAL_SWITCH.
Объединяемые
процедуры могут отличаться не только типом аргументов, как в приведенном
примере. Конкретный вызов должен быть применим не более чем к одной из
специфицированных процедур.
7.5. Процедуры с необязательными аргументами
Необязательные
аргументы также обеспечивают перегрузку процедур. Если формальный аргумент
объявлен как необязательный, т.е. если он указан в операторе OPTIONAL или имеет
атрибут OPTIONAL, то соответствующий фактический аргумент при обращении к
процедуре может быть опущен.
Для вызова процедур с
необязательными аргументами необходим интерфейсный блок.
Пример процедуры с необязательными аргументами:
SUBROUTINE S (F, X, METHOD, PR)
INTEGER, OPTIONAL :: PR, METHOD
...
END SUBROUTINE S
Процедура,
приведенная в примере, может быть вызвана, например, следующим образом:
CALL S (F1, Y, PR = 6)
Использование одной и
той же процедуры с разным набором аргументов – еще один пример перегрузки
процедур.
8. Наследование
Наследование означает, что объект может
перенимать описание типа и операций от соответствующего родительского объекта.
Кроме того, для порожденного объекта могут быть определены дополнительные
свойства. Таким образом, наследование позволяет использовать общие свойства
объекта и подогнать их под специфические потребности.
Наследование в
Фортране 90/95 реализуется частично посредством производного типа; полное
наследование в явном виде не поддерживается, но может быть смоделировано. Ниже
обсуждается как это можно сделать.
Как уже отмечалось
(см. 4.), компоненты производного типа могут иметь ранее описанный производный
тип. Это означает, что производный тип может быть получен из другого
производного типа добавлением компонент так, что новый тип получает копии
значений и операций, которые были описаны (определены) для порождающего
(родительского) типа. Порождающий тип называют также базовым типом. Базовый тип
обладает общими свойствами.
Пример
! Порождающий
(базовый) тип
TYPE PERSON
INTEGER AGE
CHARACTER (LEN
= 15) NAME
INTEGER ID_NUMB
END TYPE PERSON
! Порожденный тип
TYPE STUDENT
TYPE (PERSON) STUD_A
CHARACTER (LEN = 12) FACULITY
INTEGER GROUP
INTEGER YEAR
END TYPE STUDENT
В
приведенном примере тип STUDENT содержит одну компоненту PERSON и три дополнительные компоненты (FACULITY, GROUP и YEAR).
Аналогичным образом
можно создать другой тип из PERSON – тип TEACHER, который содержит новую компоненту SALARY.
TYPE TEACHER
TYPE (PERSON) TEACH_B
INTEGER
SALARY
END TYPE
TEACHER
Кроме того, можно создать типы
студентов и преподавателей конкретного университета и таким способом создать иерархию
типов и семейство типов. Таким образом, производные типы могут создаваться
иерархически.
Так, если для
массивов типа PERSON были описаны
некоторые операции (например, упорядочение по алфавиту или по возрасту
элементов массивов указанного типа), то эти операции могут быть применимы и к
данным, имеющим тип STUDENT или TEACHER. Для порожденных типов можно создать и новые операции и/или
модифицировать (расширить) операции, описанные для порождающего типа. При этом
изменения структуры порождающего типа и описанных для него операций могут не
оказывать влияния на порожденный тип и дополнительные операции для него.
Целесообразность
применения наследования заключается в том, чтобы избежать дублирования кодов
при создании и использовании типов похожих друг на друга, имеющих общие
свойства и принадлежащих одной предметной области.
9. Динамический полиморфизм (динамическое связывание)
Выше (см. раздел 7.) были
описаны средства реализации
статического полиморфизма,
при котором одно и то же имя (операции или процедуры) можно использовать в
разной интерпретации. При этом конкретизация способа использования могла быть
определена на этапе компиляции. При динамическом полиморфизме (иногда его
называют динамическим связыванием) надо написать процедуры, которые могут
выполняться для всех типов наследственной иерархии, и при этом выбор конкретной
процедуры для исполнения, определяется во время выполнения программы, т.е. на
этапе выполнения производится связывание имени с конкретной процедурой.
Динамический полиморфизм, как и
наследование (см. 8.), полезен в тех случаях, когда имеются похожие друг на
друга (но не идентичные) объекты, которые могут быть обработаны обобщенными
процедурами. Например, существует много различных типов студентов и
преподавателей. Задача заключается в том, чтобы избежать написания различных
программ для каждого из них и в то же время иметь возможность обрабатывать
отдельно каждый тип.
В явном виде динамический полиморфизм в
Фортране 90/95 отсутствует, но может быть смоделирован, хотя не всегда это
сделать просто.
Для реализации такого вида полиморфизма
можно использовать аппарат указателей, который имеется в современных стандартах
Фортрана. Надо создать производный тип, который содержит в качестве компонент
указатели на каждый возможный тип в наследственной иерархии. Затем реализовать
обощенную процедуру, которая будет проверять во время выполнения, связан ли в
данный момент указатель с тем или иным объектом (это делается с помощью
стандартной функции ASSOCIATED) и в
зависимости от этого вызывать нужную процедуру. Пример реализации динамического
полиморфизма (как и других концепций ООП) читатель может найти в работе [9].
10. Заключение и
перспективы
При проектировании
объектно-ориентированной программы проблема заключается в том, чтобы выделить объекты
для конкретной прикладной задачи, выделить операции, которые требуется
выполнить, и разбить программу на соответствующие модули. Решение этой проблемы
во многом зависит от конкретной задачи. Рамки препринта не позволяют уделить
достаточное внимание указанной проблеме; эта тема хорошо освещена в литературе.
Наша цель была другая: показать как можно использовать основные идеи ООП при
программировании на современном Фортране.
Мы показали, что
Фортран 90/95 поддерживает многие черты полезные для ООП (производные типы,
определяемые пользователем операции, модули, обеспечивающие инкапсуляцию и
абстракцию объектов, перегрузка операций и процедур, динамические массивы и
др.). Однако в явном виде отсутствуют такие черты как наследование и
динамический полиморфизм. Отсутствующие черты могут быть смоделированы без
дублирования кодов. Это позволяет реализовать все важные концепции ООП, хотя в
некоторых случаях с большими усилиями чем в объектно-ориентированных языках.
Во введении уже
отмечалось, что в настоящее время разрабатывается проект будущего стандрта
языка (рабочее название - Фортран 2000). Путем добавления нескольких небольших
расширений в этом проекте определен полный набор средств поддержки ООП. В
частности, для реализации механизма наследования в полном объеме вводятся
расширяемые (extensible) типы. Вводятся также средства реализации динамического
полиморфизма. В некоторых реализациях новые средства могут появиться и до
официального утверждения нового стандарта.
Литература
1.
ISO/IEC 1539-1: 1997 Information technology - Programming languages -
Fortran - Part 1: Base Language
2.
ISO/IEC 1539: 1991(E) Information technology - Programming languages -
Fortran
3.
Фортран 90.
Международный стандарт. Перевод с англ. Дробышевич С.Г., редактор перевода
Горелик А.М. М.: Финансы и статистика, 1998
4.
Горелик А.М.,
Ушкова В.Л. Фортран сегодня и завтра. М.: Наука, 1990
5.
Меткалф М.,
Рид Дж. Описание языка программирования Фортран 90. Перевод с английского. М.:
Мир, 1995
6.
Горелик А.М.
Современные международные стандарты языка Фортран. //Программирование, 2001, N6
7.
Пол А.
Объектно-ориентированное программирование на С++. М.: ”Невский диалект” – “Изд. Бином”, 1999
8.
Бен-Ари М.
Языки программирования. Перевод с английского. М.: Мир, 2000
9.
Decyk V., Norton C., Szymanski. How to Express C++ concepts in Fortran
90. //Scientific Programming, vol.6, №4, 1997.
10.
Cary J., Shasharina S., Cummings
J., Reinders J., Hinker P. Comparison
of C++ and Fortran 90 for
object-oriented Scientific programming.
//Computer Physics Communications. 105, 1997.