Производные типы данных#
Как уже говорилось в разделе Переменные, в языке 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)
обеспечивает взаимодействие с языком программирования Cextends(
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
– переменная, которая используется для хранения вычисленной площади и возврата значения