Производные типы данных#

Как уже говорилось в разделе Переменные, в языке Fortran существуют пять встроенных типов данных. Производный тип данных – особая форма типа данных, которая может включать в себя другие встроенные типы данных,а также другие производные типы данных. Его можно считать эквивалентом структуры в языках программирования C и C++.

Кратко о производных типах данных#

Пример базового производного типа данных:

type :: t_pair
  integer :: i
  real :: x
end type

Синтаксис для создания переменной типа t_pair и доступа к её компонентам следующий:

! Declare
type(t_pair) :: pair
! Initialize
pair%i = 1
pair%x = 0.5

Символ процента % используется для доступа к компоненту производного типа данных.

В приведённом выше фрагменте мы объявили экземпляр производного типа данных и инициализировали его компоненты в явном виде. Вы также можете инициализировать компоненты производного типа данных, вызвав конструктор производного типа данных.

Пример использования конструктора производного типа данных:

pair = t_pair(1, 0.5)      ! Initialize with positional arguments
pair = t_pair(i=1, x=0.5)  ! Initialize with keyword arguments
pair = t_pair(x=0.5, i=1)  ! Keyword arguments can go in any order

Пример с инициализацией по умолчанию:

type :: t_pair
  integer :: i = 1
  real :: x = 0.5
end type

type(t_pair) :: pair
pair = t_pair()       ! pair%i is 1, pair%x is 0.5
pair = t_pair(i=2)    ! pair%i is 2, pair%x is 0.5
pair = t_pair(x=2.7)  ! pair%i is 1, pair%x is 2.7

Производные типы данных в деталях#

Полный синтаксис производного типа данных со всеми необязательными свойствами представлен ниже:

type [,attribute-list] :: name [(parameterized-declaration-list)]
  [parameterized-definition-statements]
  [private statement or sequence statement]
  [member-variables]
contains
  [type-bound-procedures]
end type

Параметры объявления производного типа данных#

список атрибутов может относиться к следующему:

  • тип доступа, который может быть public (разрешение доступа для использования снаружи) или private (доступ для использования только внутри производного типа данных)

  • bind(c) обеспечивает взаимодействие с языком программирования C

  • extends(parent), где parent – имя ранее объявленного производного типа данных, от которого текущий производный тип данных будет наследовать все свои компоненты и функциональность

  • abstract – объектно-ориентированная функция, которая рассматривается в учебнике по продвинутому программированию

Если используется атрибут bind(c) или оператор sequence, то производный тип данных не может иметь атрибут extends и наоборот.

Атрибут sequence может использоваться только для объявления того, что доступ к следующим компонентам должен осуществляться в том же порядке, в котором они определены в производном типе данных.

Пример с атрибутом sequence:

type :: t_pair
  sequence
  integer :: i
  real :: x
end type
! Initialize
type(t_pair) :: pair
pair = t_pair(1, 0.5)

Использование атрибута sequence предполагает, что типы данных, определённые ниже, не относятся ни к распределённому, ни у указательному типу. Кроме того, это не означает, что эти типы данных будут храниться в памяти в какой-либо определённой форме, т.е. нет никакой связи с атрибутом contiguous.

Атрибуты типа доступа public и private, если они используются, объявляют, что всем переменным-компонентам, объявленным ниже, будет автоматически присвоен соответствующий атрибут.

Атрибут bind(c) используется для достижения совместимости между производным типом данных языка Fotran и структурой языка C.

Пример с атрибутом bind(c):

module f_to_c
  use iso_c_bindings, only: c_int
  implicit none

  type, bind(c) :: f_type
    integer(c_int) :: i
  end type

end module f_to_c

что соответствует следующему виду структуры языка C:

struct c_struct {
  int i;
};

Производный тип данных языка Fortran с атрибутом bind(c) не может иметь атрибуты sequence и extends. Кроме того, он не может содержать pointer (указатель) языка Fortran или allocatable (динамически выделяемый в памяти) тип данных.

parameterized-declaration-list – необязательный параметр. Если он используется, то параметры должны быть перечислены вместо [parameterized-definition-statements] и они должны быть либо параметрами len, либо kind, либо и тем и другим.

Пример производного типа данных со списком parameterized-declaration-list и с атрибутом public:

module m_matrix
 implicit none
 private

 type, public :: t_matrix(rows, cols, k)
   integer, len :: rows, cols
   integer, kind :: k = kind(0.0)
   real(kind=k), dimension(rows, cols) :: values
 end type

end module m_matrix

program test_matrix
 use m_matrix
 implicit none

 type(t_matrix(rows=5, cols=5)) :: m

end program test_matrix

В этом примере параметру k уже присвоено значение по умолчанию – kind(0.0) (число с плавающей точкой одинарной точности). Поэтому его можно опустить, как это сделано в данном случае при объявлении внутри основной программы.

По умолчанию производные типы данных и их компоненты являются общедоступными. Однако в данном примере в начале модуля используется атрибут private. Поэтому всё внутри модуля будет по умолчанию иметь атрибут private, пока явно не будет объявлен атрибут public. Если в приведённом примере типу данных t_matrix не был бы присвоен атрибут public , то компилятор выдал бы ошибку внутри программы program test.

Атрибут extends был добавлен в стандарте F2003 и вводит важную особенность объектно-ориентированной парадигмы (ООП), а именно наследование. Он делает возможным повторно использовать код, позволяя дочерним типам данных происходить от расширяемых родительских типов данных: type, extends(parent) :: child. Здесь, child наследует все компоненты и функциональность из типа данных type :: parent.

Пример с атрибутом extends:

module m_employee
  implicit none
  private
  public t_date, t_address, t_person, t_employee
  ! Note another way of using the public attribute:
  ! gathering all public data types in one place.

  type :: t_date
    integer :: year, month, day
  end type

  type :: t_address
    character(len=:), allocatable :: city, road_name
    integer :: house_number
  end type

  type, extends(t_address) :: t_person
    character(len=:), allocatable :: first_name, last_name, e_mail
  end type

  type, extends(t_person)  :: t_employee
    type(t_date) :: hired_date
    character(len=:), allocatable :: position
    real :: monthly_salary
  end type

end module m_employee

program test_employee
  use m_employee
  implicit none
  type(t_employee) :: employee

  ! Initialization

  ! t_employee has access to type(t_date) members not because of extends
  ! but because a type(t_date) was declared within t_employee.
  employee%hired_date%year  = 2020
  employee%hired_date%month = 1
  employee%hired_date%day   = 20

  ! t_employee has access to t_person, and inherits its members due to extends.
  employee%first_name = 'John'
  employee%last_name  = 'Doe'

  ! t_employee has access to t_address, because it inherits from t_person,
  ! which in return inherits from t_address.
  employee%city         = 'London'
  employee%road_name    = 'BigBen'
  employee%house_number = 1

  ! t_employee has access to its defined members.
  employee%position       = 'Intern'
  employee%monthly_salary = 0.0

end program test_employee

Опции объявления компонентов производного типа данных#

[member-variables] относится к объявлению всех компонентов типов данных. Эти типы данных могут быть любыми встроенными типами данных и/или производными типами данных, как уже было показано в примерах выше. Однако переменные-компоненты могут иметь свой собственный расширенный синтаксис в виде: type [,member-attributes] :: name[attr-dependent-spec][init]

type: любой встроенный тип данных или другой производный тип данных

member-attributes (необязательные атрибуты):

  • public или private атрибуты доступа

  • allocatable (выделяемый) с атрибутом или без атрибута dimension для задания динамического массива

  • pointer, codimension, contiguous, volatile, asynchronous

Примеры общих случаев:

type :: t_example
  ! 1st case: simple built-in type with access attribute and [init]
  integer, private :: i = 0
  ! private hides it from use outside of the t_example's scope.
  ! The default initialization [=0] is the [init] part.

  ! 2nd case: dynamic 1-D array
  real, allocatable, dimension(:) :: x
  ! the same as
  real, allocatable :: x(:)
  ! This parentheses' usage implies dimension(:) and is one of the possible [attr-dependent-spec].
end type

Следующие атрибуты: pointer, codimension, contiguous, volatile, asynchronous – расширенные возможности, которые не будут рассматриваться в данном Кратком руководстве. Однако они приводятся здесь для того, чтобы читатели знали, что такие возможности существуют, и могли их распознать. Эти возможности будут подробно рассмотрены в предстоящем руководстве по Продвинутому программированию.

Подпрограммы связанные с производным типом данных#

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

Невозможно полностью описать подпрограммы связанные с производным типом данных, не углубляясь в особенности ООП современного Fotran. Сейчас мы остановимся на простом примере, чтобы показать их базовое использование.

Пример производного типа данных со связанной с ним подпрограммой:

module m_shapes
  implicit none
  private
  public t_square

  type :: t_square
  real :: side
  contains
    procedure :: area  ! procedure declaration
  end type

contains

  ! Procedure definition
  real function area(self) result(res)
    class(t_square), intent(in) :: self
    res = self%side**2
  end function

end module m_shapes

program main
  use m_shapes
  implicit none

  ! Variables' declaration
  type(t_square) :: sq
  real :: x, side

  ! Variables' initialization
  side = 0.5
  sq%side = side

  x = sq%area()
  ! self does not appear here, it has been passed implicitly

  ! Do stuff with x...

end program main

Что нового?:

  • self – произвольное имя, которое мы выбрали для представления экземпляра производного типа данных t_square внутри связанной с ним функции. Это позволяет нам получить доступ к его компонентам и автоматически передавать его в качестве аргумента при вызове подпрограммы связанной с производным типом данных.

  • Теперь мы используем class(t_square) вместо type(t_square) в интерфейсе функции area. Это позволяет нам вызывать функцию area для любого производного типа, который расширяет производный тип t_square. Ключевое слово class вводит свойство ООП – полиморфизм.

В приведённом выше примере подпрограмма area связанная с производным типом данных определена как функция и может быть вызвана только в выражении, например, x = sq%area() или print *, sq%area(). Если вместо этого определить её как процедуру, то её можно вызывать с помощью оператора call:

! Change within module
contains
  subroutine area(self, x)
    class(t_square), intent(in) :: self
    real, intent(out) :: x
    x = self%side**2
  end subroutine

! ...

! Change within main program
call sq%area(x)

! Do stuff with x...

В отличии от примера с функцией связанной с производным типом данных, теперь у нас два аргумента:

  • class(t_square), intent(in) :: self – сам экземпляр производного типа

  • real, intent(out) :: x – переменная, которая используется для хранения вычисленной площади и возврата значения