Основы и структура программы
Структура программы и Hello World
Каждая программа Fortran начинается с 'program NAME' и заканчивается 'end program NAME'. 'implicit none' ОБЯЗАТЕЛЕН в современном Fortran — он форсирует явное объявление всех переменных (без него Fortran использует неявную типизацию, где переменные, начинающиеся с i-n, целые, остальные вещественные, что является главным источником багов). Блок 'contains' разделяет исполняемый код и внутренние процедуры (подпрограммы/функции, определённые внутри программы). Комментарии начинаются с '!'. Свободная форма исходного кода (Fortran 90+) использует расширение .f90; столбцы не имеют значения. Компилируйте с gfortran/ifort.
program hello
! A complete Fortran program structure
implicit none
! declarations go here
integer :: status = 0
print *, "Hello, World!" ! list-directed output to stdout
! executable statements
call do_work(status)
print *, "Exit status: ", status
contains
subroutine do_work(st)
integer, intent(out) :: st
st = 0
print *, "Working..."
end subroutine do_work
end program hello
! Compile: gfortran hello.f90 -o hello
! Run: ./helloПеременные и встроенные типы
В Fortran 5 встроенных типов: integer, real, complex, character, logical. 'kind' выбирает точность/размер — используйте kind=8 для 64-битных (или лучше selected_real_kind/iso_fortran_env для переносимости). Вещественным литералам нужен суффикс kind: 3.14_8 (не просто 3.14). Double precision — устаревший синтаксис для real(kind=8). Комплексные литералы используют форму (real, imag). Логические значения .true. / .false. (с точками). Симв ольные строки имеют фиксированный 'len', если не объявлены с len=: и allocatable (отложенная длина, Fortran 2003+). Всегда инициализируйте с соответствующим суффиксом kind, чтобы избежать скрытой потери точности.
program variables
implicit none
! Integer types
integer :: count = 0
integer(kind=8) :: big = 9223372036854775807_8 ! 64-bit
! Real types
real :: x = 3.14 ! default (often 32-bit)
real(kind=8) :: y = 2.718281828459045_8 ! double precision
double precision :: z = 1.0d0
! Complex
complex :: c = (1.0, 2.0) ! 1 + 2i
complex(kind=8) :: cw = (1.0_8, 2.0_8)
! Character
character(len=20) :: name = "Alice"
character(len=:), allocatable :: flexible ! deferred length
! Logical
logical :: flag = .true.
! Print all
print *, count, big
print *, x, y, z
print *, c, cw
print *, name, flag
end program variablesКонстанты и параметры
Константы используют атрибут 'parameter' и должны быть инициализированы при объявлении. Их нельзя изменять — компилятор может оптимизировать и встраивать их. Используйте SCREAMING_SNAKE_CASE по соглашению. character(*) означает 'взять длину из инициализатора' (удобно для строковых констант). Параметры обычно используются для размеров массивов, физических констант и enum-подобных целочисленных кодов. В Fortran 2003+ также есть настоящие типы ENUM, но целочисленные параметры остаются идиоматичным выбором. Параметры могут использоваться в объявлениях размерностей массивов и других контекстах константных выражений.
program constants
implicit none
! Named constants via 'parameter' attribute
integer, parameter :: MAX_SIZE = 100
real, parameter :: PI = 3.14159265
real, parameter :: E = 2.718281828
character(*), parameter :: APP_NAME = "MyApp"
! Using parameters
real :: arr(MAX_SIZE)
arr = 0.0
print *, APP_NAME, " size=", MAX_SIZE
print *, "Circumference: ", 2.0 * PI * 5.0
! Enum-like via parameter
integer, parameter :: SUNDAY = 1, MONDAY = 2, TUESDAY = 3
integer :: day = MONDAY
print *, "Day code: ", day
end program constantsОператоры и выражения
Операторы Fortran: арифметические (+ - * / **), с ** для возведения в степень (уникально для Fortran). Целочисленное деление усекается к нулю — используйте real(a)/b для истинного деления. Два синтаксиса отношений: современный (< > == /= <= >=) и устаревший (.lt. .gt. .eq. .ne. .le. .ge.). Логические: .and. .or. .not. .eqv. (эквивалентность) .neqv. (исключающее ИЛИ). Конкатенация строк использует //; trim() удаляет завершающие пробелы (Fortran дополняет строки фиксированной длины пробелами). mod vs modulo: mod следует знаку усечённого деления, modulo — знаку округлённого вниз — они различаются для отрицательных операндов.
program operators
implicit none
integer :: a = 17, b = 5
real :: x = 2.0
! Arithmetic
print *, a + b, a - b, a * b ! 22 12 85
print *, a / b ! 3 (integer division!)
print *, real(a) / b ! 3.4 (cast to real)
print *, a ** 2 ! 289 (exponentiation)
print *, mod(a, b) ! 2 (modulo)
print *, modulo(a, b) ! 2 (differs for negatives)
! Relational (both forms work)
print *, a > b, a < b ! T F
print *, a .gt. b, a .lt. b ! T F (old form)
print *, a == b, a /= b ! F T
! Logical
print *, (a > 0) .and. (b > 0) ! T
print *, (a > 0) .or. (b < 0) ! T
print *, .not. (a > 0) ! F
print *, (a > 0) .eqv. (b > 0) ! T (equivalence)
! String concatenation
character(10) :: s1 = "Hello", s2 = "World"
print *, trim(s1) // " " // trim(s2) ! Hello World
end program operatorsВстроенные функции и математика
В Fortran богатый набор встроенных (intrinsic) функций. Математика: abs, sqrt, exp, log (натуральный), log10, sin/cos/tan/asin/acos/atan/atan2, sinh/cosh/tanh. Округление: int (усечение), nint (ближайшее), floor, ceiling. Преобразование: real(), int(), cmplx(). Запрос: size, shape, huge (макс. значение), tiny (мин. положительное), kind. Все тригонометрические функции принимают радианы. atan2(y, x) возвращает угол в правильном квадранте (в отличие от atan). Используйте huge/tiny для проверки пределов диапазона. Встроенные функции элементарны — они работают с массивами поэлементно автоматически.
program intrinsics
implicit none
real :: x = -3.7, y = 2.5
! Math functions
print *, abs(x) ! 3.7
print *, sqrt(2.0) ! 1.414...
print *, exp(1.0) ! 2.718... (e^x)
print *, log(2.0) ! 0.693... (natural log)
print *, log10(1000.0) ! 3.0
print *, sin(3.14159/2) ! 1.0
print *, cos(0.0) ! 1.0
print *, atan2(1.0,1.0) ! 0.785... (pi/4)
! Rounding
print *, int(x) ! -3 (truncate toward zero)
print *, nint(x) ! -4 (nearest integer)
print *, floor(x) ! -4 (toward -inf)
print *, ceiling(x) ! -3 (toward +inf)
print *, abs(x), max(x, y), min(x, y) ! 3.7 2.5 -3.7
! Type conversion
print *, real(5) ! 5.0
print *, int(3.9) ! 3
! Inquiry
real :: arr(10)
print *, size(arr) ! 10
print *, huge(1) ! 2147483647
print *, tiny(1.0) ! smallest positive real
end program intrinsicsПоток управления
If...Then...Else
Блочный IF: 'if (cond) then ... else if (cond) then ... else ... end if'. Каждая ветвь требует 'then' (кроме финального else). Логический IF — однострочник: 'if (cond) statement' (без 'then'/'end if'). Условия используют операторы отношения (< > == /= <= >= или .lt. .gt. .eq. .ne. .le. .ge.) комбинированные с .and. .or. .not. 'stop' завершает программу (опционально с сообщением/кодом). Арифметический IF (if (x) label1, label2, label3) удалён в Fortran 2018 — никогда не используйте его. Всегда используйте 'implicit none', чтобы необъявленные переменные перехватывались.
program if_demo
implicit none
integer :: score = 85
character(1) :: grade
! Multi-branch if/else if/else
if (score >= 90) then
grade = 'A'
else if (score >= 80) then
grade = 'B'
else if (score >= 70) then
grade = 'C'
else if (score >= 60) then
grade = 'D'
else
grade = 'F'
end if
print *, "Score ", score, " -> Grade ", grade
! Logical if (single statement, no 'then')
if (score < 0 .or. score > 100) stop "Invalid score"
! Arithmetic if (OBSOLETE - avoid)
! if (x) 10, 20, 30 ! jump to label based on sign
end program if_demoSelect Case (переключатель)
select case — оператор переключения Fortran. Ветви могут быть одиночными значениями (case (3)), списками (case (1, 3, 5)) или диапазонами (case (4:5) означает от 4 до 5 включительно). case default — запасной вариант. В отличие от C, НЕТ проваливания — каждая ветвь независима и выполняется только одна. Работает с целыми, символьными и логическими типами (НЕ real). Символьные диапазоны используют ASCII-упорядочение ('A':'Z'). Для сравнения с плавающей точкой используйте if/else. select case эффективнее длинных цепочек if/else if для целочисленного/символьного выбора (компилятор может использовать таблицы переходов).
program case_demo
implicit none
integer :: day = 3
character(1) :: ch = 'A'
character(10) :: day_name
! Integer select case
select case (day)
case (1)
day_name = "Monday"
case (2)
day_name = "Tuesday"
case (3)
day_name = "Wednesday"
case (4:5)
day_name = "Thu/Fri"
case (6:7)
day_name = "Weekend"
case default
day_name = "Invalid"
end select
print *, day_name
! Character select case (case-insensitive via pre-upper)
select case (ch)
case ('A':'Z')
print *, "Uppercase letter"
case ('a':'z')
print *, "Lowercase letter"
case ('0':'9')
print *, "Digit"
case default
print *, "Other"
end select
! Logical select case
select case (day > 5)
case (.true.)
print *, "Weekend!"
case (.false.)
print *, "Weekday"
end select
end program case_demoЦиклы Do (со счётчиком)
Цикл DO со счётчиком: 'do var = start, end, step' (step по умолчанию 1). Цикл выполняется, пока var <= end (для положительного step) или var >= end (для отрицательного step). var увеличивается ПОСЛЕ каждой итерации. Конструкции implied-do [(expr, var=start,end)] мощны для инициализации массивов и списков ввода-вывода. Именованные циклы (outer: do ... end do outer) позволяют направлять cycle/exit на специфический уровень вложенности. Переменная цикла автоматически определена; в Fortran она сохраняет своё финальное значение после цикла. Избегайте изменения переменной цикла внутри тела цикла.
program do_loops
implicit none
integer :: i, j, total
! Basic counted loop: do var = start, end [, step]
do i = 1, 5
print *, i ! 1 2 3 4 5
end do
! With step
do i = 10, 1, -1 ! countdown
print *, i
end do
do i = 0, 100, 25 ! 0 25 50 75 100
print *, i
end do
! Implied-do (inline, for array init / I/O)
integer :: arr(5) = [(i**2, i=1,5)] ! 1 4 9 16 25
print *, arr
print *, (i, i=1,3) ! 1 2 3
! Nested loops with labels (for cycle/exit targeting)
total = 0
outer: do i = 1, 3
inner: do j = 1, 3
total = total + i*j
end do inner
end do outer
print *, "Total: ", total
end program do_loopsDo While и бесконечные циклы
do while (cond) ... end do — цикл с предусловием (проверяет условие перед каждой итерацией; может выполниться ноль раз). Для постусловия используйте do ... if (cond) exit ... end do. Голый 'do ... end do' — бесконечный цикл — вы ДОЛЖНЫ иметь оператор exit (иначе бесконечность). 'exit' покидает внутренний цикл (или именованный цикл). Именованные циклы (factorial_loop:) позволяют exit направлять на внешний цикл. Используйте do while, когда количество итераций неизвестно и зависит от условия; используйте цикл do со счётчиком, когда количество известно заранее.
program while_demo
implicit none
integer :: n, count
real :: x, sum
! do while: pre-test loop
n = 1024
count = 0
do while (n > 1)
n = n / 2
count = count + 1
end do
print *, "log2(1024) = ", count ! 10
! Infinite loop with exit
sum = 0.0
do
read(*, *) x
if (x < 0) exit ! leave loop
sum = sum + x
end do
print *, "Sum: ", sum
! do ... end do with conditional exit
n = 1
factorial_loop: do
if (n > 10) exit factorial_loop
print *, n, factorial(n)
n = n + 1
end do factorial_loop
contains
recursive function factorial(n) result(f)
integer, intent(in) :: n
integer :: f
if (n <= 1) then
f = 1
else
f = n * factorial(n-1)
end if
end function factorial
end program while_demoCycle, Exit и управление циклом
cycle пропускает остаток текущей итерации и переходит к следующей (как 'continue' в C/Python). exit полностью выходит из цикла (как 'break'). Оба по умолчанию направлены на внутренний цикл, но с именованными циклами (search: do ... end do search) можно направить на внешний цикл: 'exit search' или 'cycle search'. Это необходимо для чистого выхода из вложенных циклов. Используйте cycle для фильтрации (пропуск нежелательных итераций) и exit для раннего завершения (поиск найден, обнаружена ошибка). Именованные циклы делают вложенный поток управления явным и читаемым.
program loop_control
implicit none
integer :: i, j
! cycle: skip to next iteration (like 'continue' in C)
do i = 1, 10
if (mod(i, 2) == 0) cycle ! skip even numbers
print *, i ! 1 3 5 7 9
end do
! exit: break out of loop (like 'break' in C)
do i = 1, 100
if (i * i > 50) then
print *, "Stopped at i=", i
exit
end if
end do
! Named loops: cycle/exit can target outer loops
search: do i = 1, 5
do j = 1, 5
if (i + j == 7) then
print *, "Found: ", i, "+", j, "= 7"
exit search ! break out of OUTER loop
end if
end do
end do search
! Early exit from a search
integer :: arr(10) = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
do i = 1, size(arr)
if (arr(i) == 9) then
print *, "Found 9 at index ", i
exit
end if
end do
end program loop_controlМассивы и векторные операции
Объявление и инициализация массивов
Массивы Fortran по умолчанию индексируются с 1 (нижняя граница = 1), но можно указать пользовательскую нижнюю границу: a(0:4) имеет индексы 0..4. Многомерные массивы используют порядок (rows, cols) — column-major storage (первый индекс изменяется быстрее всего в памяти). Инициализируйте конструкторами массивов [1,2,3] или implied-do [(expr, i=start,end)]. reshape заполняет многомерный массив из 1D-списка. size() возвращает общее количество элементов; lbound/ubound возвращают нижние/верхние границы. shape() возвращает форму как 1D-массив. Массивы 'whole-array' — можно присваивать и оперировать ими без явных циклов.
program array_decl
implicit none
! Declaration with dimension
integer :: a(5) ! 1D, indices 1..5
integer :: b(0:4) ! 1D, indices 0..4 (custom lower bound)
real :: c(3, 4) ! 2D, 3 rows x 4 cols
real, dimension(10) :: d ! using dimension attribute
! Initialization at declaration
integer :: x(5) = [1, 2, 3, 4, 5]
integer :: y(5) = [(i*2, i=1,5)] ! implied-do: 2 4 6 8 10
integer :: z(5) = 0 ! all zeros
real :: m(2,2) = reshape([1,2,3,4], [2,2])
! Allocation later
print *, size(x), lbound(x), ubound(x) ! 5 1 5
print *, size(c, dim=1) ! 3 (rows)
print *, size(c, dim=2) ! 4 (cols)
! Array of characters
character(10) :: names(3) = ["Alice", "Bob", "Carol"]
print *, names(2) ! Bob
end program array_declСечения массивов и векторные индексы
Сечения массивов (slicing) используют синтаксис a(start:end:stride) — все части необязательны. Stride может быть отрицательным (реверс). Векторные индексы позволяют собирать/рассеивать через индексный массив: a(idx) возвращает [a(idx(1)), a(idx(2)), ...]. Сечениям можно присваивать: a(2:4) = [99,98,97]. Конструкция 'where' — условное присваивание на уровне массива (как numpy where). Операции с массивами Fortran векторизованы — не нужны явные циклы для поэлементных операций. Это киллер-фича Fortran для численного кода: чистый, математически подобный синтаксис, который компиляторы авто-векторизуют.
program array_sections
implicit none
integer :: a(10) = [(i, i=1,10)]
integer :: b(5)
integer :: idx(3) = [2, 5, 7]
! Array sections (slicing): a(start:end[:stride])
print *, a(3:7) ! 3 4 5 6 7
print *, a(1:10:2) ! 1 3 5 7 9 (stride 2)
print *, a(:5) ! 1 2 3 4 5 (start defaults to 1)
print *, a(6:) ! 6 7 8 9 10 (end defaults to ubound)
print *, a(::3) ! 1 4 7 10
print *, a(10:1:-1) ! 10 9 8 ... 1 (reverse)
! Vector subscript (gather)
b = a(idx) ! b = [a(2), a(5), a(7)] = [2, 5, 7]
print *, b
! Assigning to a section
a(2:4) = [99, 98, 97]
print *, a(1:5) ! 1 99 98 97 5
! 2D sections
real :: m(4,4) = 0.0
m(2:3, 2:3) = 1.0 ! set 2x2 sub-block
print *, m(2,2), m(3,3) ! 1.0 1.0
! Where construct (masked array assignment)
where (a > 50) a = 0 ! set elements > 50 to 0
end program array_sectionsМногомерные массивы и матрицы
Fortran хранит массивы column-major (первый индекс изменяется быстрее всего в памяти) — противоположно C. reshape заполняет в порядке column-major, поэтому reshape([1,2,3,4],[2,2]) даёт [[1,3],[2,4]]. matmul(A,B) — истинное матричное умножение (линейная алгебра); A*B — поэлементное (адамарово) — это НЕ одно и то же! transpose(A) возвращает транспонированную. Редукции: sum, product, maxval, minval, maxloc, minloc, count — все поддерживают dim= для редукции по одной оси. Для высокой производительности пишите циклы в порядке column-major (внутренний цикл по первому индексу) для cache-friendliness.
program matrices
implicit none
real :: A(3,3), B(3,3), C(3,3)
integer :: i, j
! Initialize with implied-do
A = reshape([(real(i), i=1,9)], [3,3])
! A = 1 4 7
! 2 5 8
! 3 6 9 (column-major fill!)
! Identity matrix
B = 0.0
do i = 1, 3
B(i,i) = 1.0
end do
! Matrix multiplication (intrinsic)
C = matmul(A, B) ! A * I = A
print *, C(1,1), C(2,2) ! 1.0 5.0
! Transpose
print *, transpose(A)(1,:) ! 1 2 3 (first row of A^T)
! Element-wise operations
C = A + B ! element-wise add
C = A * 2.0 ! scalar multiply
C = A * B ! element-wise (NOT matmul!)
! Array reduction along a dimension
print *, sum(A, dim=1) ! column sums: 6 15 24
print *, sum(A, dim=2) ! row sums: 12 15 18
print *, maxval(A) ! 9.0
print *, maxloc(A) ! 3 3 (location of max)
! Reshape
integer :: flat(6) = [1,2,3,4,5,6]
integer :: mat(2,3)
mat = reshape(flat, [2,3])
end program matricesAllocatable-массивы (динамические)
allocatable-массивы — современный способ динамической памяти в Fortran — безопаснее указателей (без утечек памяти, автоматическое освобождение при выходе из области). Объявляйте с атрибутом allocatable и отложенной формой (:, (:,:) и т. д.). allocate() с stat= перехватывает ошибки (всегда проверяйте!). deallocate() освобождает явно. Fortran 2003+ поддерживает автоматическое перераспределение при присваивании: flex = [flex, 4] увеличивает массив. allocated() проверяет, выделен ли в данный момент. Allocatable-массивы предпочтительнее указателей для динамических массивов, потому что компилятор отслеживает и освобождает их автоматически — без утечек, без висячих указателей.
program alloc_demo
implicit none
integer, allocatable :: arr(:), matrix(:,:)
integer :: n, m, i, stat
! Get size from user
print *, "Enter size:"
read(*, *) n
m = n * 2
! Allocate
allocate(arr(n), matrix(n, m), stat=stat)
if (stat /= 0) then
print *, "Allocation failed!"
stop 1
end if
! Use the arrays
arr = [(i, i=1, n)]
matrix = 0.0
do i = 1, n
matrix(i, :) = i
end do
print *, size(arr), size(matrix, dim=2)
print *, allocated(arr) ! T
! Deallocate (or let it auto-deallocate at scope exit)
deallocate(arr, matrix)
print *, allocated(arr) ! F
! Automatic reallocation on assignment (Fortran 2003)
integer, allocatable :: flex(:)
flex = [1, 2, 3] ! auto-allocates to size 3
flex = [flex, 4, 5] ! reallocates to size 5: 1 2 3 4 5
print *, flex
deallocate(flex)
end program alloc_demoВстроенные функции массивов
Встроенные функции массивов Fortran — её суперсила. Редукции: sum, product, maxval, minval, maxloc (индекс максимума), minloc, count (количество true), any (существует), all (каждый). Все поддерживают mask= для условной редукции. pack() собирает элементы, где mask истинно (как numpy compress); unpack() рассеивает. cshift/eoshift вращают массивы (циркулярно vs end-off). merge(a, b, mask) выполняет поэлементный выбор. Примечание: в Fortran НЕТ встроенной сортировки — вы должны написать её (или использовать библиотеку). Эти встроенные функции элементарны и векторизуемы, делая код Fortran и чистым, и быстрым.
program array_funcs
implicit none
integer :: a(5) = [3, 1, 4, 1, 5, 9, 2, 6]
! Wait, let's fix size
integer :: b(8) = [3, 1, 4, 1, 5, 9, 2, 6]
integer :: c(5) = [10, 20, 30, 40, 50]
logical :: mask(5) = [.true., .false., .true., .false., .true.]
! Inquiry
print *, size(b) ! 8
print *, shape(b) ! 8
print *, lbound(b), ubound(b) ! 1 8
! Reductions
print *, sum(b) ! 31
print *, product(c) ! 120000000
print *, maxval(b), minval(b) ! 9 1
print *, maxloc(b) ! 6 (index of max)
print *, minloc(b) ! 2 (index of min, first occurrence)
print *, count(b > 3) ! 5 (number of true elements)
print *, any(b > 8) ! T (at least one)
print *, all(b > 0) ! T (all of them)
! With mask
print *, sum(b, mask=b > 3) ! sum of elements > 3
print *, pack(b, b > 3) ! compact array of elements > 3
print *, unpack([1,2], mask, 0) ! spread values per mask
! Manipulation
print *, cshift(b, 2) ! circular shift left by 2
print *, eoshift(b, 2) ! end-off shift left by 2 (fills 0)
print *, merge(b, c, mask) ! element-wise: b where mask true, else c
! Sorting (Fortran 2003+)
integer :: sorted(8)
sorted = b
call sort_array(sorted) ! custom sort (no built-in sort)
end program array_funcsСтроки и обработка символов
Объявление character и длина
Строки Fortran по умолчанию ФИКСИРОВАННОЙ ДЛИНЫ — более короткие строки дополняются пробелами до объявленной длины. character(N) или character(len=N) объявляет длину N. character(*) берёт длину из контекста (инициализатор параметра или фиктивный аргумент). character(:), allocatable включает динамические строки отложенной длины (Fortran 2003+) — строка перераспределяется при присваивании. len() возвращает объявленную длину; len_trim() возвращает длину без завершающих пробелов. trim() возвращает строку без завершающих пробелов (но результат всё ещё фиксированной длины в контексте). Для обработки текста переменной длины используйте allocatable строки отложенной длины.
program char_decl
implicit none
! Fixed-length strings
character(10) :: s1 = "Hello"
character(len=20) :: s2 = "World"
character(20) :: s3 ! len= keyword optional
! Deferred-length (allocatable) - Fortran 2003+
character(:), allocatable :: flex
flex = "Dynamic" ! len=7
flex = "Now longer string" ! reallocates to len=18
print *, len(flex) ! 18
! Array of strings
character(15) :: names(3) = ["Alice", "Bob", "Carol"]
! Single character
character(1) :: ch = 'A'
character :: ch2 = 'B' ! len=1 default
print *, s1 ! "Hello " (padded to 10)
print *, trim(s1) ! "Hello" (no padding)
print *, len(s1), len_trim(s1) ! 10 5
print *, names(2) ! "Bob" (padded)
end program char_declКонкатенация строк и операции
Конкатенация строк использует оператор //. repeat(s, n) повторяет строку n раз. Подстроки используют s(start:end) — индексация с 1, ВКЛЮЧАТЕЛЬНО с обеих сторон (в отличие от Python). s(8:) означает от позиции 8 до конца; s(:5) означает от начала до позиции 5. index(s, sub) возвращает позицию первого вхождения sub (0, если не найдено, с учётом регистра). scan(s, set) возвращает позицию первого символа В наборе; verify(s, set) возвращает первый символ НЕ в наборе. adjustl/adjustr сдвигают начальные/завершающие пробелы. Строки Fortran НЕ null-terminated как C — длина отслеживается отдельно.
program string_ops
implicit none
character(20) :: first = "John", last = "Doe"
character(50) :: full
! Concatenation with //
full = first // " " // last ! "John Doe"
print *, trim(full)
! Repeat
print *, repeat("-", 30) ! 30 dashes
! Substring (1-indexed, inclusive)
character(20) :: s = "Hello, World!"
print *, s(1:5) ! "Hello"
print *, s(8:12) ! "World"
print *, s(8:) ! "World!" (to end)
print *, s(:5) ! "Hello" (from start)
! Index (find substring)
print *, index(s, "World") ! 8 (position, 0 if not found)
print *, index(s, "world") ! 0 (case-sensitive)
print *, scan(s, "aeiou") ! 2 (first vowel position)
print *, verify(s, "abcdefg") ! 1 (first char NOT in set)
! Length
print *, len_trim(s) ! 13
print *, adjustl(s) ! left-justify
print *, adjustr(s) ! right-justify
end program string_opsВстроенные строковые функции
iachar(c) возвращает ASCII-код символа; achar(i) — обратное. (ichar/char зависят от процессора — предпочитайте iachar/achar для переносимости.) В Fortran НЕТ встроенного преобразования регистра — используйте iachar/achar вручную (A-Z — 65-90, a-z — 97-122, разница 32). Лексикографическое сравнение: lge/lgt/lle/llt (лексически больше/меньше чем) обрабатывают строки разной длины корректно. Внутренний ввод-вывод (read/write в строку вместо файла) — идиоматичный способ преобразования между строками и числами: read(str, *) num и write(str, fmt) num. Формат I0 даёт целые минимальной ширины (без дополнения).
program string_funcs
implicit none
character(20) :: s = "Hello World"
integer :: i
character(1) :: ch
! Character code conversion
print *, iachar('A') ! 65 (ASCII code)
print *, achar(66) ! 'B' (from ASCII code)
print *, ichar('A') ! processor-dependent (use iachar for ASCII)
! Case conversion (manual - no built-in)
do i = 1, len_trim(s)
ch = s(i:i)
if (ch >= 'A' .and. ch <= 'Z') then
s(i:i) = achar(iachar(ch) + 32) ! to lowercase
end if
end do
print *, s ! "hello world"
! Comparison
print *, lge("apple", "banana") ! F (lexicographic >=)
print *, lgt("zebra", "apple") ! T
print *, lle("abc", "abcd") ! T (<=)
print *, llt("abc", "abd") ! T (<)
! String to number conversion (internal read)
character(20) :: num_str = "3.14159"
real :: pi_val
read(num_str, *) pi_val
print *, pi_val * 2 ! 6.28318
! Number to string (internal write)
character(20) :: out_str
integer :: n = 42
write(out_str, '(I0)') n ! I0 = minimal-width integer
print *, "Number: " // trim(out_str)
end program string_funcsФорматирование строк
Спецификации формата живут в строке: '(I5, F8.2, A)'. I=целое, F=вещественное с фиксированной точкой, E=экспоненциальное, ES=научное (мантисса 1-10), A=символьное, X=пробел, /=перевод строки. Сначала ширина (I5 = ширина 5), затем необязательное .m для мин. цифр (I5.3). I0 означает минимальную ширину (без дополнения). ES даёт правильную научную нотацию (1.23E+6) против E (0.12E+7). Повторения: 3I4 = три целых каждое шириной 4. Строковые литералы в формате: '("text")'. Формат может быть строковой переменной, * (list-directed, компилятор выбирает) или меткой (номер оператора). Для чистого вывода предпочитайте I0 для целых и F или ES для вещественных.
program format_demo
implicit none
integer :: n = 42
real :: pi = 3.14159265
real :: big = 1234567.89
character(20) :: name = "Alice"
! Format specifiers
! Iw - integer, width w
! Iw.m - integer, width w, at least m digits
! Fw.d - fixed-point real, width w, d decimals
! Ew.d - exponential, width w, d decimals
! Aw - character, width w
! A - character, default width
! nX - n spaces
! / - newline
write(*, '(I5)') n ! " 42"
write(*, '(I5.3)') n ! " 042"
write(*, '(I0)') n ! "42" (minimal)
write(*, '(F10.4)') pi ! " 3.1416"
write(*, '(E12.4)') big ! " 0.1235E+07"
write(*, '(ES12.4)') big ! " 1.2346E+06" (scientific)
write(*, '(A10)') name ! " Alice"
write(*, '(A, I3)') "n=", n ! "n= 42"
! Multiple items
write(*, '(A, I3, A, F8.4)') "n=", n, " pi=", pi
! Repeated format: 3I4 = three integers width 4
write(*, '(3I4)') 1, 2, 3 ! " 1 2 3"
! Newline and spacing
write(*, '("Name: ", A, /, "Age: ", I3)') name, n
! List-directed (default format)
print *, name, n, pi
end program format_demoРазбор и токенизация
В Fortran НЕТ встроенного split/tokenize — вы должны написать это вручную, используя index() и подстроки. Шаблон: найдите разделитель через index, извлеките токен через подстроку, продвиньтесь мимо разделителя, повторите. Для пар ключ=значение найдите '=' через index и разделите на ключ (до) и значение (после). trim() удаляет завершающие пробелы; adjustl() удаляет начальные пробелы. Для надёжного разбора также обрабатывайте пустые токены и пробелы. Альтернативно используйте внутренние чтения со спецификаторами формата для структурированных данных или читайте из строки, как будто это файл. Библиотеки вроде split() существуют в некоторых фреймворках Fortran, но не стандартны.
program parse_demo
implicit none
character(100) :: line = "name=Bob,age=30,city=NYC"
character(50) :: token
integer :: pos, start, end
! Split by comma
start = 1
do
end = index(line(start:), ",")
if (end == 0) then
! last token
token = line(start:)
call process(trim(token))
exit
end if
token = line(start:start+end-2)
call process(trim(token))
start = start + end
end do
contains
subroutine process(t)
character(*), intent(in) :: t
integer :: eq_pos
eq_pos = index(t, "=")
if (eq_pos > 0) then
print *, "Key: ", trim(t(:eq_pos-1)), &
" Value: ", trim(t(eq_pos+1:))
end if
end subroutine process
end program parse_demo
! Output:
! Key: name Value: Bob
! Key: age Value: 30
! Key: city Value: NYCПроцедуры: функции и подпрограммы
Функции
Функции возвращают значение и используются в выражениях (как математические функции). Современный синтаксис: 'function name(args) result(var)' — переменная result — то, что возвращается. intent(in) помечает аргументы только для чтения (компилятор это обеспечивает). Функции могут возвращать скаляры ИЛИ массивы (используйте size() входа для размера выхода). Функции должны быть PURE (без побочных эффектов) — не изменяйте глобальное состояние и не делайте ввод-вывод в функции. Внутренние процедуры (в блоке 'contains') имеют доступ к переменным хоста (host association). Для внешних процедур используйте блок interface для указания сигнатуры.
program func_demo
implicit none
! Function declared in interface or as external
print *, add(3, 4) ! 7
print *, square(5) ! 25
print *, distance(0.0, 0.0, 3.0, 4.0) ! 5.0
contains
! Basic function with result clause
function add(a, b) result(c)
integer, intent(in) :: a, b
integer :: c
c = a + b
end function add
! Function with result named same as function (legacy)
function square(x) result(y)
integer, intent(in) :: x
integer :: y
y = x * x
end function square
! Real function
function distance(x1, y1, x2, y2) result(d)
real, intent(in) :: x1, y1, x2, y2
real :: d
d = sqrt((x2-x1)**2 + (y2-y1)**2)
end function distance
! Array-valued function
function reverse(arr) result(rev)
integer, intent(in) :: arr(:)
integer :: rev(size(arr))
integer :: i, n
n = size(arr)
do i = 1, n
rev(i) = arr(n-i+1)
end do
end function reverse
end program func_demoПодпрограммы и Intent
Подпрограммы вызываются с 'call' и не возвращают значение — они изменяют аргументы на месте. Используйте подпрограммы, когда: (1) нужно изменить несколько аргументов, (2) операция — 'команда', а не 'вычисление', (3) возврат массива-результата неудобен. Атрибуты intent документируют и обеспечивают направление аргумента: intent(in) = только чтение (ошибка компиляции при присваивании), intent(out) = только запись (неопределён при входе, должен быть установлен перед возвратом), intent(inout) = чтение-запись. Всегда указывайте intent — он ловит баги и включает оптимизацию. Подпрограммы могут изменять переданные им массивы (без копии, если они contiguous).
program sub_demo
implicit none
integer :: x = 10, y = 20
real :: arr(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
! Subroutines are called with 'call'
call swap(x, y)
print *, x, y ! 20 10
call scale_array(arr, 2.0)
print *, arr ! 2 4 6 8 10
call fill_zero(arr)
print *, arr ! 0 0 0 0 0
contains
subroutine swap(a, b)
integer, intent(inout) :: a, b ! read AND write
integer :: tmp
tmp = a
a = b
b = tmp
end subroutine swap
subroutine scale_array(a, factor)
real, intent(inout) :: a(:)
real, intent(in) :: factor
a = a * factor ! whole-array operation
end subroutine scale_array
subroutine fill_zero(a)
real, intent(out) :: a(:) ! write-only (output)
a = 0.0
end subroutine fill_zero
end program sub_demoPure и Elemental функции
pure функции НЕ имеют побочных эффектов: без ввода-вывода, без изменения глобальных переменных, без stop, могут вызывать только другие pure процедуры. Они обеспечивают оптимизацию компилятором (параллелизация, устранение общих подвыражений) и требуются в некоторых контекстах (например, DO CONCURRENT). elemental функции пишутся для СКАЛЯРОВ, но автоматически работают с массивами поэлементно — напишите один раз, используйте для обоих. pure elemental объединяет оба. Используйте pure для любой функции, которая действительно математическая (без побочных эффектов). Используйте elemental, когда операция естественно применяется поэлементно к массивам (математические функции, преобразования). Компилятор может авто-векторизовать elemental-вызовы на массивах.
program pure_demo
implicit none
real :: a(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
real :: b(5)
integer :: i
! Pure function: no side effects, no I/O
b = square_arr(a)
print *, b ! 1 4 9 16 25
! Elemental function: works on scalars AND arrays automatically
b = cube(a) ! applies cube() element-wise
print *, b ! 1 8 27 64 125
print *, cube(2.0) ! also works on scalar: 8.0
contains
! Pure: no side effects, no I/O, no stop, only pure calls
pure function square_arr(x) result(y)
real, intent(in) :: x(:)
real :: y(size(x))
y = x * x
end function square_arr
! Elemental: scalar signature, but works on arrays too
elemental function cube(x) result(y)
real, intent(in) :: x
real :: y
y = x * x * x
end function cube
! Pure elemental: both
pure elemental double precision function sq(x) result(y)
double precision, intent(in) :: x
double precision :: y
y = x * x
end function sq
end program pure_demoOptional и именованные аргументы
optional-аргументы позволяют вызывающим их опускать. Используйте present(arg) внутри процедуры, чтобы проверить, был ли передан аргумент — обращение к отсутствующему optional — неопределённое поведение. Именованные аргументы (name="value") позволяют передавать аргументы в любом порядке и делают вызовы самодокументируемыми. После использования ключевого слова все последующие аргументы также должны использовать ключевые слова. Optional-аргументы должны идти после всех обязательных в сигнатуре. Значения по умолчанию реализуются через проверки present() (в Fortran нет встроенного синтаксиса по умолчанию). Именованные + optional вместе обеспечивают гибкие API: вызывающие указывают только то, что им нужно.
program optional_demo
implicit none
! All arguments after the first optional must also be optional
print *, greet("Alice") ! Hello, Alice!
print *, greet("Bob", "Hi") ! Hi, Bob!
print *, greet("Carol", greeting="Hey") ! keyword argument
print *, greet(greeting="Welcome", name="Dave") ! all keywords
! With present() check
call log_msg("Starting up")
call log_msg("Error!", level=2)
call log_msg("Debug info", level=0, file="debug.log")
contains
function greet(name, greeting) result(msg)
character(*), intent(in) :: name
character(*), intent(in), optional :: greeting
character(50) :: msg
character(20) :: g
if (present(greeting)) then
g = greeting
else
g = "Hello"
end if
msg = trim(g) // ", " // name // "!"
end function greet
subroutine log_msg(message, level, file)
character(*), intent(in) :: message
integer, intent(in), optional :: level
character(*), intent(in), optional :: file
integer :: lvl
lvl = 1
if (present(level)) lvl = level
print *, "[L", lvl, "] ", trim(message)
end subroutine log_msg
end program optional_demoВнутренние и рекурсивные процедуры
Внутренние процедуры (внутри 'contains') имеют host association — они могут читать И изменять переменные хост-программы (как замыкания). Используйте их для помощников, которым нужно состояние хоста. recursive-процедуры должны объявляться с префиксом 'recursive' (Fortran 90/2003); Fortran 2018 делает рекурсию по умолчанию. Для взаимной рекурсии используйте блок interface для объявления предварительной ссылки. Рекурсия элегантна, но может быть медленной (накладные расходы вызова функции) и рискованной (переполнение стека при глубокой рекурсии). Для факториала/фибоначчи итеративные версии быстрее и безопаснее. Используйте рекурсию для естественно рекурсивных проблем (обход дерева, разделяй и властвуй) с ограниченной глубиной.
program nested_demo
implicit none
integer :: counter = 0 ! host variable
! Internal procedures (in contains) access host variables
call increment
call increment
print *, counter ! 2
! Recursive function
print *, factorial(5) ! 120
print *, fib(10) ! 55
! Mutual recursion (needs forward declaration)
print *, is_even(4) ! T
contains
subroutine increment
counter = counter + 1 ! modifies host's counter
end subroutine increment
recursive function factorial(n) result(f)
integer, intent(in) :: n
integer :: f
if (n <= 1) then
f = 1
else
f = n * factorial(n-1)
end if
end function factorial
recursive function fib(n) result(f)
integer, intent(in) :: n
integer :: f
if (n < 2) then
f = n
else
f = fib(n-1) + fib(n-2)
end if
end function fib
! Mutual recursion with interface
recursive function is_even(n) result(r)
integer, intent(in) :: n
logical :: r
interface
recursive function is_odd(m) result(ro)
integer, intent(in) :: m
logical :: ro
end function is_odd
end interface
if (n == 0) then
r = .true.
else
r = is_odd(n-1)
end if
end function is_even
end program nested_demoМодули и инкапсуляция
Основы модулей и Use
Модули — основной механизм инкапсуляции Fortran (заменяющий common blocks и внешние процедуры). Файл модуля содержит: (1) объявления (константы, переменные, производные типы), (2) блок 'contains' с процедурами. Используйте 'use module_name' для импорта; 'use module_name, only: x, y' импортирует только специфичные сущности (рекомендуется — избегает загрязнения пространства имён). Переменные модуля персистентны (статические) и разделяются всеми процедурами, использующими модуль. Модули обеспечивают явные интерфейсы (компилятор проверяет типы аргументов), в отличие от внешних процедур. Всегда компилируйте файлы модулей перед файлами, которые их используют. 'implicit none' в модуле распространяется на все его процедуры.
! geometry.f90 - module file
module geometry
implicit none
private ! default: everything private
public :: circle_area, circle_perimeter, PI
! Module-level constants (persistent)
real, parameter :: PI = 3.14159265
contains
function circle_area(r) result(a)
real, intent(in) :: r
real :: a
a = PI * r * r
end function circle_area
function circle_perimeter(r) result(p)
real, intent(in) :: r
real :: p
p = 2.0 * PI * r
end function circle_perimeter
end module geometry
! main.f90 - using the module
program use_module
use geometry, only: circle_area, circle_perimeter, PI
implicit none
real :: r = 5.0
print *, "Area: ", circle_area(r) ! 78.5398
print *, "Perimeter: ", circle_perimeter(r) ! 31.4159
print *, "PI: ", PI
end program use_module
! Compile: gfortran geometry.f90 main.f90 -o mainУправление доступом (Public/Private)
Управление доступом: 'private' делает сущности внутренними для модуля; 'public' экспортирует их. По умолчанию можно установить на уровне модуля ('private' затем выборочно 'public :: ...') — это лучшая практика (явный интерфейс). Компоненты производного типа могут быть private, даже если сам тип public — вызывающие могут использовать тип, но не обращаться к внутренностям напрямую; они должны идти через процедуры. 'save' делает переменные модуля персистентными (они сохраняют значения между вызовами) — переменные модуля сохраняются по умолчанию. 'final' определяет деструктор (вызывается при выходе объекта из области видимости). Эта инкапсуляция обеспечивает настоящее ООП с инвариантами, обеспеченными через процедуры.
module bank_account
implicit none
private ! default: everything private
! Explicitly export:
public :: account_t, deposit, withdraw, get_balance
! Derived type - can expose type but hide internals
type :: account_t
private ! components are private
real :: balance = 0.0
integer :: id = 0
contains
procedure :: balance => get_bal ! type-bound procedure
final :: cleanup ! destructor
end type account_t
! Module-level counter (private, not exported)
integer, save :: next_id = 1000
contains
! Constructor (factory function)
function create_account(initial) result(acc)
type(account_t) :: acc
real, intent(in) :: initial
acc%balance = initial
acc%id = next_id
next_id = next_id + 1
end function create_account
subroutine deposit(acc, amount)
type(account_t), intent(inout) :: acc
real, intent(in) :: amount
acc%balance = acc%balance + amount
end subroutine deposit
function get_bal(acc) result(b)
class(account_t), intent(in) :: acc
real :: b
b = acc%balance
end function get_bal
subroutine cleanup(acc)
type(account_t) :: acc
! cleanup code (e.g., log closure)
end subroutine cleanup
end module bank_accountПроизводные типы в модулях
Производные типы, определённые в модулях, могут иметь: type-bound процедуры (procedure :: name => impl), конструкторы (через перегруженный interface с именем типа) и allocatable-компоненты. 'class(keyword)' в type-bound процедурах обеспечивает полиморфизм (фактический тип может быть подклассом). Type-bound процедуры вызываются как obj%method(args) — синтаксис ООП. Перегрузка имени типа как интерфейса позволяет иметь несколько конструкторов (vector_from_array, vector_from_size). Allocatable-компоненты автоматически выделяются/освобождаются. Это современное ООП Fortran: инкапсуляция, методы, конструкторы и полиморфизм, всё в рамках модульной системы.
module vector_mod
implicit none
private
public :: vector_t, vector_add, vector_scale
! Derived type with type parameters (Fortran 2003)
type :: vector_t
real, allocatable :: data(:)
integer :: length = 0
contains
procedure :: norm => vector_norm
procedure :: print => vector_print
end type vector_t
! Constructor interface (overloaded)
interface vector_t
procedure vector_from_array
procedure vector_from_size
end interface vector_t
contains
function vector_from_array(arr) result(v)
real, intent(in) :: arr(:)
type(vector_t) :: v
v%data = arr
v%length = size(arr)
end function
function vector_from_size(n, fill) result(v)
integer, intent(in) :: n
real, intent(in) :: fill
type(vector_t) :: v
allocate(v%data(n))
v%data = fill
v%length = n
end function
function vector_norm(self) result(n)
class(vector_t), intent(in) :: self
real :: n
n = sqrt(sum(self%data**2))
end function
subroutine vector_print(self)
class(vector_t), intent(in) :: self
print *, "Vector(len=", self%length, "): ", self%data
end subroutine
function vector_add(a, b) result(c)
type(vector_t), intent(in) :: a, b
type(vector_t) :: c
c%data = a%data + b%data
c%length = a%length
end function
function vector_scale(a, s) result(c)
type(vector_t), intent(in) :: a
real, intent(in) :: s
type(vector_t) :: c
c%data = a%data * s
c%length = a%length
end function
end module vector_modДженерические процедуры и перегрузка
Дженерические интерфейсы обеспечивают ad-hoc полиморфизм (перегрузку): одно имя направляет к разным специфичным процедурам на основе типов аргументов. Блок 'interface name / module procedure proc1, proc2 / end interface' перечисляет все специфичные процедуры. Компилятор выбирает подходящую по типу/рангу аргумента во время компиляции. Все специфичные процедуры в дженерике должны иметь РАЗЛИЧНЫЕ сигнатуры (различимые по типам аргументов) — иначе неоднозначность. Так Fortran делает перегрузку операторов/функций без шаблонов. Дженерическая диспетчеризация разрешается во время компиляции (без накладных расходов во время выполнения). Используйте дженерики для обеспечения унифицированного API между типами.
module generics_mod
implicit none
private
public :: print_value, add
! Generic interface: one name, multiple specific procedures
interface print_value
module procedure print_int
module procedure print_real
module procedure print_str
module procedure print_int_array
end interface print_value
interface add
module procedure add_int
module procedure add_real
module procedure add_arrays
end interface add
contains
subroutine print_int(x)
integer, intent(in) :: x
print *, "Integer: ", x
end subroutine
subroutine print_real(x)
real, intent(in) :: x
print *, "Real: ", x
end subroutine
subroutine print_str(s)
character(*), intent(in) :: s
print *, "String: ", trim(s)
end subroutine
subroutine print_int_array(arr)
integer, intent(in) :: arr(:)
print *, "Array: ", arr
end subroutine
function add_int(a, b) result(c)
integer, intent(in) :: a, b
integer :: c
c = a + b
end function
function add_real(a, b) result(c)
real, intent(in) :: a, b
real :: c
c = a + b
end function
function add_arrays(a, b) result(c)
real, intent(in) :: a(:), b(:)
real :: c(size(a))
c = a + b
end function
end module generics_mod
program use_generics
use generics_mod
implicit none
call print_value(42) ! Integer: 42
call print_value(3.14) ! Real: 3.14
call print_value("Hello") ! String: Hello
call print_value([1,2,3]) ! Array: 1 2 3
print *, add(2, 3) ! 5
print *, add(2.5, 3.5) ! 6.0
end program use_genericsПерегрузка операторов
Перегрузка операторов позволяет определить, как +, -, *, /, == и т. д. работают с вашими производными типами. interface operator(+) / module procedure vec_add / end interface связывает оператор + с функцией. Для бинарных операторов можно перегрузить оба порядка (vec*scalar и scalar*vec) отдельными процедурами. assignment(=) перегружает оператор присваивания (процедура — подпрограмма с intent(out) LHS и intent(in) RHS). Это обеспечивает математически-подобный синтаксис: c = a + b вместо c = vec_add(a, b). Используйте перегрузку операторов для математических типов (векторы, матрицы, комплексные числа), где она улучшает читаемость. Избегайте перегрузки для неочевидной семантики. Конструктор структуры vec3(x,y,z) встроен для производных типов.
module vec_ops
implicit none
private
public :: vec3, operator(+), operator(*), assignment(=)
type :: vec3
real :: x, y, z
end type vec3
! Overload the + operator for vec3 + vec3
interface operator(+)
module procedure vec_add
end interface
! Overload * for vec3 * scalar and scalar * vec3
interface operator(*)
module procedure vec_scale_r
module procedure vec_scale_l
module procedure vec_dot
end interface
! Overload = for array-to-vec assignment
interface assignment(=)
module procedure arr_to_vec
end interface
contains
function vec_add(a, b) result(c)
type(vec3), intent(in) :: a, b
type(vec3) :: c
c = vec3(a%x+b%x, a%y+b%y, a%z+b%z)
end function
function vec_scale_r(v, s) result(r)
type(vec3), intent(in) :: v
real, intent(in) :: s
type(vec3) :: r
r = vec3(v%x*s, v%y*s, v%z*s)
end function
function vec_scale_l(s, v) result(r)
real, intent(in) :: s
type(vec3), intent(in) :: v
type(vec3) :: r
r = vec3(v%x*s, v%y*s, v%z*s)
end function
! vec3 * vec3 = dot product (scalar)
function vec_dot(a, b) result(d)
type(vec3), intent(in) :: a, b
real :: d
d = a%x*b%x + a%y*b%y + a%z*b%z
end function
subroutine arr_to_vec(v, arr)
type(vec3), intent(out) :: v
real, intent(in) :: arr(3)
v = vec3(arr(1), arr(2), arr(3))
end subroutine
end module vec_ops
program use_ops
use vec_ops
implicit none
type(vec3) :: a, b, c
a = vec3(1.0, 2.0, 3.0)
b = vec3(4.0, 5.0, 6.0)
c = a + b ! vec3 addition
print *, c%x, c%y, c%z ! 5 7 9
print *, a * 2.0 ! scale: 2 4 6
print *, 3.0 * b ! scale: 12 15 18
print *, a * b ! dot product: 32
end program use_opsПроизводные типы (структуры) и ООП
Определение производных типов
Производные типы — структуры Fortran (пользовательские составные типы). Определяйте с 'type :: Name ... end type Name'. Компоненты доступны через % (НЕ . — это для комплексных чисел). Конструктор структуры: Name(val1, val2) создаёт экземпляр. Компоненты могут иметь значения по умолчанию (= value в объявлении). Присваивание целого типа копирует все компоненты (глубокая копия для allocatable-компонентов). Массивы производных типов поддерживаются. Производные типы — основа для ООП в Fortran (с type-bound процедурами, наследованием, полиморфизмом). Используйте % для доступа к компонентам: obj%field, obj%method(). Синтаксис конструктора Name(args) автоматический, если вы не переопределили его интерфейсом.
program derived_types
implicit none
! Basic derived type (struct)
type :: Point
real :: x, y
end type Point
! Type with default initialization
type :: Person
character(20) :: name = "Unknown"
integer :: age = 0
logical :: active = .true.
end type Person
! Declare and construct
type(Point) :: p1, p2
type(Person) :: alice, bob
! Structure constructor
p1 = Point(3.0, 4.0)
p2 = Point(0.0, 0.0)
alice = Person("Alice", 30, .true.)
bob = Person("Bob", 25) ! uses default for 'active'
! Access components with %
print *, p1%x, p1%y ! 3.0 4.0
print *, alice%name, alice%age ! Alice 30
print *, bob%active ! T (default)
! Modify components
p1%x = p1%x + 1.0
alice%age = 31
! Whole-type assignment (component-wise copy)
p2 = p1
print *, p2%x ! 4.0
! Array of derived types
type(Point) :: points(3)
points(1) = Point(1.0, 1.0)
points(2) = Point(2.0, 2.0)
points(3) = Point(3.0, 3.0)
print *, points(2)%y ! 2.0
end program derived_typesКомпоненты типов и конструкторы
Производные типы могут иметь: allocatable-компоненты (авто-управляемая память), компоненты с инициализацией по умолчанию, type-bound процедуры (методы) и финализаторы (деструкторы). 'interface TypeName / module procedure custom_init / end interface' перегружает конструктор структуры пользовательской фабричной функцией. 'class(ClassName)' (vs 'type(ClassName)') в type-bound процедурах обеспечивает полиморфизм (фактический тип может быть подклассом). 'final' процедуры выполняются при выходе объекта из области видимости (деструктор) — используйте их для освобождения ресурсов. Allocatable-компоненты автоматически освобождаются при финализации, но явные финализаторы яснее для сложной очистки. Конструкции block позволяют объявлять переменные в середине кода (Fortran 2008).
program type_components
implicit none
! Type with various component kinds
type :: Student
integer :: id
character(20) :: name
real, allocatable :: grades(:) ! allocatable component
integer :: num_grades = 0
contains
procedure :: add_grade
procedure :: average => student_avg
final :: student_finalize
end type Student
! Overloaded constructor
interface Student
module procedure student_init
end interface Student
type(Student) :: s
! Use custom constructor
s = Student(101, "Alice")
call s%add_grade(85.0)
call s%add_grade(92.0)
call s%add_grade(78.0)
print *, s%average() ! 85.0
contains
function student_init(id, name) result(s)
integer, intent(in) :: id
character(*), intent(in) :: name
type(Student) :: s
s%id = id
s%name = name
s%num_grades = 0
end function
subroutine add_grade(self, g)
class(Student), intent(inout) :: self
real, intent(in) :: g
integer :: n
n = self%num_grades
if (n == 0) then
allocate(self%grades(1))
else
! grow array
block
real, allocatable :: tmp(:)
tmp = self%grades
deallocate(self%grades)
allocate(self%grades(n+1))
self%grades(1:n) = tmp
end block
end if
self%num_grades = n + 1
self%grades(n+1) = g
end subroutine
function student_avg(self) result(avg)
class(Student), intent(in) :: self
real :: avg
if (self%num_grades > 0) then
avg = sum(self%grades) / self%num_grades
else
avg = 0.0
end if
end function
subroutine student_finalize(self)
type(Student) :: self
if (allocated(self%grades)) deallocate(self%grades)
end subroutine
end program type_componentsType-bound процедуры (методы)
Type-bound процедуры — методы Fortran: 'procedure :: method_name => implementation'. Вызывайте их как obj%method(args) — синтаксис ООП. Первый аргумент — 'self' (объект), объявленный как 'class(TypeName)' (полиморфный) или 'type(TypeName)' (конкретный). 'class' разрешает наследование/полиморфизм; 'type' — для нерасширяемых типов. '=> implementation' отображает имя метода на специфичную процедуру (позволяет переименование). final — деструктор. Этот пример реализует динамический стек с авто-растущим массивом. Type-bound процедуры дают настоящее ООП: инкапсуляцию (данные + методы вместе), синтаксис передачи сообщений (obj%method) и полиморфизм (через class). Всегда используйте class() для type-bound процедур, чтобы обеспечить будущее наследование.
module stack_mod
implicit none
private
public :: stack_t
type :: stack_t
integer, allocatable :: data(:)
integer :: top = 0
integer :: capacity = 0
contains
procedure :: push => stack_push
procedure :: pop => stack_pop
procedure :: peek => stack_peek
procedure :: is_empty => stack_is_empty
procedure :: size => stack_size
procedure :: clear => stack_clear
final :: stack_finalize
end type stack_t
interface stack_t
module procedure stack_init
end interface
contains
function stack_init(initial_cap) result(s)
integer, intent(in), optional :: initial_cap
type(stack_t) :: s
integer :: cap
cap = 16
if (present(initial_cap)) cap = initial_cap
allocate(s%data(cap))
s%capacity = cap
s%top = 0
end function
subroutine stack_push(self, val)
class(stack_t), intent(inout) :: self
integer, intent(in) :: val
if (self%top >= self%capacity) then
! grow
block
integer, allocatable :: tmp(:)
tmp = self%data
deallocate(self%data)
allocate(self%data(self%capacity * 2))
self%data(1:self%capacity) = tmp
self%capacity = self%capacity * 2
end block
end if
self%top = self%top + 1
self%data(self%top) = val
end subroutine
function stack_pop(self) result(val)
class(stack_t), intent(inout) :: self
integer :: val
if (self%top == 0) stop "Stack underflow"
val = self%data(self%top)
self%top = self%top - 1
end function
function stack_peek(self) result(val)
class(stack_t), intent(in) :: self
integer :: val
val = self%data(self%top)
end function
logical function stack_is_empty(self) result(r)
class(stack_t), intent(in) :: self
r = (self%top == 0)
end function
integer function stack_size(self) result(n)
class(stack_t), intent(in) :: self
n = self%top
end function
subroutine stack_clear(self)
class(stack_t), intent(inout) :: self
self%top = 0
end subroutine
subroutine stack_finalize(self)
type(stack_t) :: self
if (allocated(self%data)) deallocate(self%data)
end subroutine
end module stack_mod
program use_stack
use stack_mod
implicit none
type(stack_t) :: s
s = stack_t(8) ! initial capacity 8
call s%push(10)
call s%push(20)
call s%push(30)
print *, s%size() ! 3
print *, s%pop() ! 30
print *, s%pop() ! 20
print *, s%is_empty() ! F
end program use_stackНаследование и полиморфизм
ООП Fortran: 'type, extends(Parent) :: Child' создаёт подкласс (наследование). 'type, abstract :: Name' с 'procedure(...), deferred :: method' определяет абстрактную базу (как абстрактный класс Java / чисто виртуальный C++). 'class(Base)' полиморфен — может содержать любой подкласс. Полиморфная диспетчеризация: вызов obj%method() вызывает переопределение подкласса. 'select type (var => expr) / type is (ConcreteType) / end select' выполняет проверку типа во время выполнения (downcasting). Выделяйте с 'allocate(TypeName::var)' для создания полиморфного объекта специфичного конкретного типа. Это полноценное ООП: наследование, полиморфизм, абстрактные типы и диспетчеризация во время выполнения — сопоставимо с Java/C++.
module shapes
implicit none
private
public :: shape, circle, rectangle, shape_ptr
! Base type (abstract)
type, abstract :: shape
character(20) :: name
contains
procedure(area_if), deferred :: area
procedure(describe_if), deferred :: describe
procedure :: get_name => shape_get_name
end type shape
! Abstract interface (must be implemented by subclasses)
abstract interface
function area_if(self) result(a)
import :: shape
class(shape), intent(in) :: self
real :: a
end function area_if
subroutine describe_if(self)
import :: shape
class(shape), intent(in) :: self
end subroutine describe_if
end interface
! Derived type: circle extends shape
type, extends(shape) :: circle
real :: radius
contains
procedure :: area => circle_area
procedure :: describe => circle_describe
end type circle
! Derived type: rectangle extends shape
type, extends(shape) :: rectangle
real :: width, height
contains
procedure :: area => rect_area
procedure :: describe => rect_describe
end type rectangle
! Class pointer for polymorphism
type :: shape_ptr
class(shape), allocatable :: ptr
end type shape_ptr
contains
function shape_get_name(self) result(n)
class(shape), intent(in) :: self
character(20) :: n
n = self%name
end function
function circle_area(self) result(a)
class(circle), intent(in) :: self
real :: a
a = 3.14159265 * self%radius**2
end function
subroutine circle_describe(self)
class(circle), intent(in) :: self
print *, "Circle '", trim(self%name), "' r=", self%radius
end subroutine
function rect_area(self) result(a)
class(rectangle), intent(in) :: self
real :: a
a = self%width * self%height
end function
function rect_describe(self) result(s)
class(rectangle), intent(in) :: self
character(50) :: s
write(s, '("Rectangle ", F6.2, "x", F6.2)') self%width, self%height
end function
end module shapes
program use_polymorphism
use shapes
implicit none
type(shape_ptr) :: shapes_arr(2)
! Allocate concrete types into polymorphic container
allocate(circle::shapes_arr(1)%ptr)
select type(s => shapes_arr(1)%ptr)
type is (circle)
s%radius = 5.0
s%name = "C1"
end select
allocate(rectangle::shapes_arr(2)%ptr)
select type(s => shapes_arr(2)%ptr)
type is (rectangle)
s%width = 3.0
s%height = 4.0
s%name = "R1"
end select
! Polymorphic dispatch
block
integer :: i
do i = 1, 2
call shapes_arr(i)%ptr%describe
print *, "Area: ", shapes_arr(i)%ptr%area()
end do
end block
end program use_polymorphismВложенные типы и массивы типов
Производные типы могут быть вложенными (композиция): тип может иметь компоненты других производных типов. Доступ к вложенным компонентам осуществляется цепочкой %: emp%home%city. Конструкторы структур вкладываются естественным образом: Employee(id, name, Address(...), salary). Массивы производных типов поддерживаются: type(Employee) :: emps(N). Можно извлечь массив одного компонента: emps(:)%id даёт целочисленный массив. Компоненты allocatable позволяют создавать динамические коллекции (например, отдел с переменным числом сотрудников). Эта модель композиции — основа для построения сложных структур данных (деревьев, графов, списков) в Fortran. Используйте композицию (has-a) вместо наследования (is-a), когда нет явного отношения подтипа.
program nested_types
implicit none
! Nested derived types
type :: Address
character(50) :: street
character(30) :: city
character(10) :: zip
end type Address
type :: Employee
integer :: id
character(30) :: name
type(Address) :: home ! nested type
real :: salary
end type Employee
type :: Department
character(30) :: name
type(Employee), allocatable :: employees(:) ! array of types
integer :: count = 0
end type Department
! Construct with nested structure constructor
type(Employee) :: emp
emp = Employee(101, "Alice", &
Address("123 Main St", "Springfield", "12345"), 75000.0)
! Access nested components
print *, emp%name ! Alice
print *, emp%home%city ! Springfield
print *, emp%home%zip ! 12345
! Department with array of employees
type(Department) :: dept
dept%name = "Engineering"
allocate(dept%employees(3))
dept%employees(1) = emp
dept%employees(2) = Employee(102, "Bob", &
Address("456 Oak Ave", "Shelbyville", "54321"), 68000.0)
dept%count = 2
! Iterate over array of types
block
integer :: i
do i = 1, dept%count
print *, dept%employees(i)%id, trim(dept%employees(i)%name), &
dept%employees(i)%home%city
end do
end block
! Array of derived type components
print *, dept%employees(1:2)%id ! array of id field
end program nested_typesФайловый ввод-вывод и форматирование
Открытие и закрытие файлов
open() связывает файл с номером устройства. newunit=u позволяет компилятору выбрать уникальный номер устройства (избегает конфликтов) — всегда предпочитайте это жёстко заданным номерам устройств. status: 'old' (файл должен существовать), 'new' (не должен существовать), 'replace' (удалить + создать), 'scratch' (временный, автоматически удаляется при закрытии). action: 'read', 'write', 'readwrite'. position: 'rewind' (начало), 'append' (конец), 'asis' (где остановились). Всегда проверяйте iostat после open и read — ненулевое значение означает ошибку (отрицательное = EOF, положительное = ошибка). iomsg даёт описательное сообщение об ошибке. close() отключает файл. Для надёжной работы с файлами всегда проверяйте iostat и аккуратно обрабатывайте ошибки.
program file_open
implicit none
integer :: u, ios
character(100) :: msg
! newunit: compiler picks a unique unit number (Fortran 2008)
! status: 'old' (must exist), 'new' (must not exist),
! 'replace' (overwrite), 'scratch' (temporary)
! action: 'read', 'write', 'readwrite'
! position: 'rewind', 'append', 'asis'
open(newunit=u, file="data.txt", status="replace", &
action="write", iostat=ios, iomsg=msg)
if (ios /= 0) then
print *, "Open failed: ", trim(msg)
stop 1
end if
write(u, *) "First line"
write(u, *) "Second line"
close(u)
! Append to existing file
open(newunit=u, file="data.txt", status="old", &
action="write", position="append", iostat=ios)
if (ios == 0) then
write(u, *) "Appended line"
close(u)
end if
! Scratch file (auto-deleted on close)
open(newunit=u, status="scratch", action="readwrite")
write(u, *) "Temporary data"
rewind(u)
! read back...
close(u) ! file disappears
! Reading with end-of-file detection
open(newunit=u, file="data.txt", status="old", action="read")
do
read(u, '(A)', iostat=ios) msg
if (ios /= 0) exit ! EOF or error
print *, trim(msg)
end do
close(u)
end program file_openФорматированный ввод-вывод
Спецификаторы формата управляют вводом-выводом: '(A, I0, F8.2)'. A=символьный, I0=целочисленный минимальной ширины, F8.2=вещественный ширины 8 с 2 знаками после запятой. write(unit, fmt) выполняет запись; read(unit, fmt) — чтение. unit=* означает stdout/stdin. Для файлов используйте номер устройства из open(). Списочный ввод-вывод (*) гибок: read(u, *) a, b, c автоматически читает значения, разделённые запятыми/пробелами. Для CSV списочный ввод работает, если значения разделены запятыми. Строки формата можно переиспользовать: '(3I4)' применяет I4 три раза. Всегда соответствуйте формату типу данных — несоответствие форматов вызывает ошибки времени выполнения. Для смешанного текста и чисел читайте как строку, затем разбирайте, либо используйте явные форматы.
program formatted_io
implicit none
integer :: u, n = 42
real :: pi = 3.14159265
character(20) :: name = "Alice"
! Write formatted data to file
open(newunit=u, file="output.txt", status="replace")
write(u, '(A, I0)') "Count: ", n
write(u, '(A, F8.4)') "Pi: ", pi
write(u, '(A, A)') "Name: ", trim(name)
write(u, '(3(I0, 1X))') 1, 2, 3 ! "1 2 3 "
close(u)
! Read formatted data back
open(newunit=u, file="output.txt", status="old", action="read")
block
character(100) :: line
integer :: i
do i = 1, 5
read(u, '(A)') line
print *, trim(line)
end do
end block
close(u)
! Reading structured data
open(newunit=u, file="data.csv", status="replace")
write(u, '(I0, ",", I0, ",", I0)') 1, 10, 100
write(u, '(I0, ",", I0, ",", I0)') 2, 20, 200
close(u)
! Read back as numbers
open(newunit=u, file="data.csv", status="old", action="read")
block
integer :: a, b, c
! List-directed read handles commas as separators
read(u, *) a, b, c
print *, a, b, c ! 1 10 100
read(u, *) a, b, c
print *, a, b, c ! 2 20 200
end block
close(u)
end program formatted_ioНеформатированный (бинарный) ввод-вывод
Неформатированный (бинарный) ввод-вывод быстрее и компактнее форматированного (текстового) — нет преобразования строк. form='unformatted' включает его. access='stream' (Fortran 2003) даёт байтовый потоковый доступ (как файловый ввод-вывод C, без маркеров записей). access='sequential' (по умолчанию) использует маркеры записей (каждая запись/чтение — это запись с префиксами длины) — переносимо в пределах Fortran, но не в другие языки. Для совместимости с C/Python используйте потоковый доступ. Бинарные файлы не читаются человеком, но идеальны для больших числовых наборов данных. Всегда записывайте метаданные (размеры массивов, информацию о типе) перед данными, чтобы правильно прочитать их обратно. Неформатированный ввод-вывод сохраняет полную точность (без потери разрядов при преобразовании текста).
program binary_io
implicit none
integer :: u, i
real :: arr(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
real :: arr_in(5)
integer :: n
! Write binary (unformatted) - faster, no format conversion
open(newunit=u, file="data.bin", status="replace", &
form="unformatted", access="stream")
write(u) size(arr) ! write the count first
write(u) arr ! write the whole array
close(u)
! Read binary back
open(newunit=u, file="data.bin", status="old", &
form="unformatted", access="stream", action="read")
read(u) n ! read the count
print *, "Count: ", n
read(u) arr_in ! read the array
print *, arr_in ! 1 2 3 4 5
close(u)
! Sequential unformatted (default) - with record markers
open(newunit=u, file="data_seq.bin", status="replace", &
form="unformatted") ! access defaults to "sequential"
do i = 1, 5
write(u) i, arr(i) ! each write is a "record"
end do
close(u)
! Read sequentially
open(newunit=u, file="data_seq.bin", status="old", &
form="unformatted", action="read")
do
read(u, iostat=i) n, arr_in(1)
if (i /= 0) exit
print *, n, arr_in(1)
end do
close(u)
end program binary_ioNamelist (сгруппированный ввод-вывод)
namelist группирует переменные для структурированного текстового ввода-вывода — как JSON/YAML, но изначально для Fortran. Определение: 'namelist /name/ var1, var2, ...'. write(u, nml=name) выводит в формате &NAME var=val, var=val, /. read(u, nml=name) разбирает обратно. Namelist идеально подходит для файлов конфигурации: пользователи редактируют текстовый файл, программа его читает. Переменные сохраняют объявленные значения по умолчанию; переопределяются только те, что есть в файле. Формат прощающий (нечувствителен к пробелам, запятые опциональны). namelist поддерживает все встроенные типы и массивы. iostat отлавливает ошибки разбора. Это простейший способ сделать программу на Fortran настраиваемой без написания собственного парсера.
program namelist_demo
implicit none
! Namelist groups variables for easy I/O
integer :: max_iter = 100
real :: tolerance = 1.0e-6
logical :: verbose = .true.
character(20) :: method = "newton"
real :: params(3) = [0.1, 0.2, 0.3]
! Define a namelist group
namelist /config/ max_iter, tolerance, verbose, method, params
! Write namelist to file
block
integer :: u
open(newunit=u, file="config.nml", status="replace")
write(u, nml=config)
close(u)
end block
! The file looks like:
! &CONFIG
! MAX_ITER=100,
! TOLERANCE=1.0000000E-06,
! VERBOSE=T,
! METHOD="newton",
! PARAMS=0.100000, 0.200000, 0.300000,
! /
! Read namelist from file (overrides defaults)
block
integer :: u, ios
open(newunit=u, file="config.nml", status="old", action="read")
read(u, nml=config, iostat=ios)
close(u)
if (ios /= 0) then
print *, "Error reading config"
else
print *, "max_iter=", max_iter
print *, "tolerance=", tolerance
print *, "method=", trim(method)
print *, "params=", params
end if
end block
! User can edit config.nml in a text editor
! then re-run to pick up new values
end program namelist_demoВнутренние файлы и обработка ошибок
Внутренние файлы позволяют читать из / писать в символьную строку вместо файла — аналог sprintf/sscanf в Fortran. write(str, fmt) форматирует в строку; read(str, fmt) разбирает из строки. Это стандартный способ преобразования между строками и числами. Всегда используйте iostat для обработки ошибок: 0 = успех, отрицательное = EOF, положительное = ошибка. Устаревшие метки end= и err= работают, но iostat чище (без goto). Для надёжного разбора проверяйте iostat после каждого read. Внутренний ввод-вывод отлично подходит для: построения выходных строк, разбора пользовательского ввода, преобразования значений из файла конфигурации. Строка выступает как «внутренний файл» — те же операторы ввода-вывода, просто строка-приёмник.
program internal_io
implicit none
character(100) :: buffer
integer :: n = 42
real :: x = 3.14159
integer :: ios
! Internal write: format to a string (like sprintf)
write(buffer, '(A, I0, A, F8.4)') "n=", n, " x=", x
print *, trim(buffer) ! n=42 x= 3.1416
! Internal read: parse from a string (like sscanf)
character(50) :: input = "100 3.14 hello"
integer :: a
real :: b
character(20) :: c
read(input, *, iostat=ios) a, b, c
if (ios == 0) print *, a, b, trim(c) ! 100 3.14 hello
! Robust number parsing with error handling
character(20) :: num_str = "3.14abc"
real :: val
read(num_str, *, iostat=ios) val
if (ios /= 0) then
print *, "Parse error: '", trim(num_str), "' is not a number"
else
print *, "Value: ", val
end if
! End-of-file and error handling on file reads
block
integer :: u
character(100) :: line
open(newunit=u, file="data.txt", status="old", action="read")
do
read(u, '(A)', iostat=ios) line
if (ios < 0) then
print *, "End of file"
exit
else if (ios > 0) then
print *, "Read error at line"
exit
end if
print *, trim(line)
end do
close(u)
end block
! err= and end= labels (legacy style)
block
integer :: u, val
open(newunit=u, file="nums.txt", status="old", action="read")
do
read(u, *, end=100, err=200) val
print *, val
end do
100 print *, "Reached EOF"
close(u)
goto 300
200 print *, "Read error!"
close(u)
300 continue
end block
end program internal_ioЧисленные вычисления
Параметры kind и точность
Параметры kind управляют точностью/размером. Современный переносимый способ: используйте iso_fortran_env (int32/int64, real32/real64/real128). Устаревший: selected_real_kind(digits, exponent_range). real32 ≈ 7 значащих цифр, real64 ≈ 15 цифр (двойная точность), real128 ≈ 33 цифры (четверная). КРИТИЧНО: всегда добавляйте суффикс kind к литералам (3.14_dp, а не 3.14) — иначе литерал разбирается как одинарная точность, ЗАТЕМ преобразуется с потерей цифр. precision() возвращает количество значащих цифр; range() — диапазон десятичной экспоненты. epsilon() даёт машинный эпсилон (наименьший различимый прирост). tiny/huge дают минимум/максимум. Для научных вычислений по умолчанию используйте real64 (двойная точность).
program precision_demo
use iso_fortran_env, only: int32, int64, real32, real64, real128
implicit none
! Portable kind selection via iso_fortran_env
integer(kind=int32) :: i32 = 100
integer(kind=int64) :: i64 = 9223372036854775807_int64
real(kind=real32) :: r32 = 3.14159265_real32 ! single (~7 digits)
real(kind=real64) :: r64 = 3.14159265358979_real64 ! double (~15 digits)
real(kind=real128) :: r128 = 3.14159265358979_real128 ! quad (~33 digits)
! Legacy: selected_real_kind (still works)
integer, parameter :: dp = selected_real_kind(15, 307) ! double
integer, parameter :: sp = selected_real_kind(6, 37) ! single
real(kind=dp) :: pi = 3.14159265358979_dp
print *, "real32 precision: ", precision(r32), " range: ", range(r32)
print *, "real64 precision: ", precision(r64), " range: ", range(r64)
print *, "real128 precision: ", precision(r128)
! Epsilon and tiny
print *, "epsilon(r32): ", epsilon(r32) ! ~1.19e-7
print *, "epsilon(r64): ", epsilon(r64) ! ~2.22e-16
print *, "tiny(r64): ", tiny(r64) ! smallest positive
print *, "huge(i32): ", huge(i32) ! 2147483647
print *, "huge(i64): ", huge(i64)
! Always use _kind suffix on literals
! WRONG: real(kind=dp) :: x = 3.14 (loses precision!)
! RIGHT: real(kind=dp) :: x = 3.14_dp
end program precision_demoЛинейная алгебра (matmul, решение СЛАУ, BLAS)
В Fortran есть встроенная линейная алгебра: matmul (умножение матрица-матрица/матрица-вектор), dot_product, transpose. Для решения линейных систем (Ax=b), собственных значений, SVD и т.д. используйте LAPACK (стандартная промышленная библиотека Fortran): dgesv решает Ax=b, dgesvd выполняет SVD, dsyev — eigendecomposition. Компонуйте с -llapack -lblas. Пример показывает ручной метод Гаусса для системы 3x3 — для реальной работы используйте LAPACK (быстрее, точнее с выбором ведущего элемента, обрабатывает любой размер). Колонно-ориентированное хранение Fortran естественным образом соответствует ожиданиям LAPACK (транспонирование не требуется). matmul оптимизирована, но для больших матриц BLAS dgemm быстрее. Всегда проверяйте число обусловленности для численной устойчивости.
program linalg
implicit none
real(8) :: A(3,3), B(3,3), C(3,3)
real(8) :: v(3), w(3), x(3)
real(8) :: det
integer :: i
! Initialize matrices
A = reshape([1,2,3, 4,5,6, 7,8,10], [3,3]) ! column-major fill
B = reshape([1,0,0, 0,1,0, 0,0,1], [3,3]) ! identity
! Matrix-matrix multiplication
C = matmul(A, B) ! A * I = A
print *, "A * I = A? ", all(abs(C - A) < 1e-10)
! Matrix-vector multiplication
v = [1.0, 2.0, 3.0]
w = matmul(A, v)
print *, "A * v = ", w ! 14 32 53
! Dot product
print *, "v . v = ", dot_product(v, v) ! 14
! Outer product
C = 0.0
do i = 1, 3
C(:,i) = v * v(i)
end do
! Transpose
C = transpose(A)
! Solve linear system Ax = b (need LAPACK or custom)
! Using LAPACK dgesv:
! call dgesv(n, nrhs, A, lda, ipiv, b, ldb, info)
! For demo, manual Gaussian elimination:
x = solve_3x3(A, v)
print *, "Solution: ", x
contains
function solve_3x3(A, b) result(x)
real(8), intent(inout) :: A(3,3)
real(8), intent(in) :: b(3)
real(8) :: x(3), M(3,4), factor
integer :: k, j
M(:,1:3) = A
M(:,4) = b
! Forward elimination
do k = 1, 2
do i = k+1, 3
factor = M(i,k) / M(k,k)
M(i,:) = M(i,:) - factor * M(k,:)
end do
end do
! Back substitution
x(3) = M(3,4) / M(3,3)
x(2) = (M(2,4) - M(2,3)*x(3)) / M(2,2)
x(1) = (M(1,4) - M(1,2)*x(2) - M(1,3)*x(3)) / M(1,1)
end function
end program linalgСлучайные числа
call random_number(x) заполняет x равномерными вещественными числами из [0,1) — работает для скаляров и массивов. call random_seed(size=n) получает размер зерна; random_seed(put=seed) устанавливает зерно для воспроизводимости (необходимо для тестирования/отладки). Для целых чисел в [a,b]: a + int(r * (b-a+1)). Для гауссовых (нормальных) случайных чисел используйте преобразование Бокса-Мюллера (показано) или полярный метод. В Fortran нет встроенного генератора нормального распределения — реализуйте его или используйте библиотеку. Для моделирования Монте-Карло установите зерно для воспроизводимости, затем запустите множество испытаний. random_number НЕ является криптографически стойким — для безопасности используйте криптобиблиотеку. Для параллельного кода каждому образу нужно уникальное зерно.
program random_demo
implicit none
real :: r
integer :: i, n = 10
real :: arr(10)
integer :: seed_size
integer, allocatable :: seed(:)
! Initialize random seed (Fortran 2003: random_seed with no args
! uses a processor-dependent seed; for reproducibility, set it)
call random_seed(size=seed_size)
allocate(seed(seed_size))
! Set a fixed seed for reproducibility
seed = [(12345 + i*6789, i=1, seed_size)]
call random_seed(put=seed)
! Generate uniform [0,1) random reals
call random_number(r)
print *, "Single: ", r
call random_number(arr) ! fills entire array
print *, "Array: ", arr
! Generate integers in [a, b]
block
integer :: a = 1, b = 6
integer :: dice
do i = 1, 5
call random_number(r)
dice = a + int(r * (b - a + 1)) ! [1, 6]
print *, "Dice roll: ", dice
end do
end block
! Normal (Gaussian) via Box-Muller transform
block
real :: u1, u2, z1, z2
integer :: j
do j = 1, 5
call random_number(u1)
call random_number(u2)
u1 = max(u1, 1e-10) ! avoid log(0)
z1 = sqrt(-2.0 * log(u1)) * cos(2.0 * 3.14159265 * u2)
z2 = sqrt(-2.0 * log(u1)) * sin(2.0 * 3.14159265 * u2)
print *, "Gaussian: ", z1, z2
end do
end block
! Monte Carlo: estimate pi
block
integer :: inside = 0, total = 100000
real :: x, y
do i = 1, total
call random_number(x)
call random_number(y)
if (x*x + y*y <= 1.0) inside = inside + 1
end do
print *, "Pi estimate: ", 4.0 * real(inside) / total
end block
end program random_demoЧисленное интегрирование и поиск корней
Численное интегрирование: правило Симпсона точнее правила трапеций (погрешность O(h^4) против O(h^2)). Передавайте функции как аргументы через блок interface. Поиск корней: метод бисекции надёжен (всегда сходится при смене знака), но медленный (линейная сходимость); метод Ньютона быстр (квадратичная сходимость), но требует производной и может расходиться. Для production используйте QUADPACK (интегрирование) или MINPACK (поиск корней) — проверенные библиотеки Fortran. Блок interface необходим при передаче функций как аргументов — он сообщает компилятору сигнатуру функции. Всегда задавайте максимальное число итераций для предотвращения бесконечных циклов. Проверяйте сходимость по значениям функции и допускам шага.
program numerical
implicit none
real(8) :: a, b, result
integer :: n
! Numerical integration: Simpson's rule
! Integrate f(x) = x^2 from 0 to 2 (exact: 8/3 ≈ 2.6667)
a = 0.0; b = 2.0; n = 1000
result = simpson(f_sq, a, b, n)
print *, "Integral of x^2 from 0 to 2: ", result ! ~2.6667
! Integrate sin(x) from 0 to pi (exact: 2.0)
result = simpson(f_sin, 0.0_8, 3.14159265358979_8, 1000)
print *, "Integral of sin(x) from 0 to pi: ", result ! ~2.0
! Root finding: bisection method
! Find root of f(x) = x^2 - 2 (i.e., sqrt(2) ≈ 1.4142)
result = bisection(f_x2_minus_2, 0.0_8, 2.0_8, 1e-12_8)
print *, "sqrt(2) = ", result ! ~1.41421356
! Newton's method (needs derivative)
result = newton(f_x2_minus_2, fp_x2_minus_2, 1.0_8, 1e-12_8)
print *, "sqrt(2) via Newton: ", result
contains
! Function to integrate: x^2
function f_sq(x) result(y)
real(8), intent(in) :: x
real(8) :: y
y = x * x
end function
function f_sin(x) result(y)
real(8), intent(in) :: x
real(8) :: y
y = sin(x)
end function
! Simpson's rule: integral of f from a to b with n intervals
function simpson(f, a, b, n) result(integral)
interface
function f(x) result(y)
import
real(8), intent(in) :: x
real(8) :: y
end function
end interface
real(8), intent(in) :: a, b
integer, intent(in) :: n
real(8) :: integral, h, x
integer :: i
h = (b - a) / n
integral = f(a) + f(b)
do i = 1, n-1
x = a + i * h
if (mod(i, 2) == 0) then
integral = integral + 2.0 * f(x)
else
integral = integral + 4.0 * f(x)
end if
end do
integral = integral * h / 3.0
end function
function f_x2_minus_2(x) result(y)
real(8), intent(in) :: x
real(8) :: y
y = x*x - 2.0
end function
function fp_x2_minus_2(x) result(y)
real(8), intent(in) :: x
real(8) :: y
y = 2.0 * x
end function
! Bisection: find root in [a, b] (f(a) and f(b) must have opposite signs)
function bisection(f, a, b, tol) result(root)
interface
function f(x) result(y)
import
real(8), intent(in) :: x
real(8) :: y
end function
end interface
real(8), intent(in) :: a, b, tol
real(8) :: root, fa, fb, mid, fmid
integer :: iter, maxiter = 100
fa = f(a); fb = f(b)
if (fa * fb > 0) stop "No sign change in interval"
do iter = 1, maxiter
mid = (a + b) / 2.0
fmid = f(mid)
if (abs(fmid) < tol .or. (b - a)/2.0 < tol) then
root = mid
return
end if
if (fa * fmid < 0) then
b = mid; fb = fmid
else
a = mid; fa = fmid
end if
end do
root = (a + b) / 2.0
end function
! Newton's method: x_{n+1} = x_n - f(x)/f'(x)
function newton(f, fp, x0, tol) result(root)
interface
function f(x) result(y)
import
real(8), intent(in) :: x
real(8) :: y
end function
function fp(x) result(y)
import
real(8), intent(in) :: x
real(8) :: y
end function
end interface
real(8), intent(in) :: x0, tol
real(8) :: root, x, fx, fpx
integer :: iter, maxiter = 100
x = x0
do iter = 1, maxiter
fx = f(x); fpx = fp(x)
if (abs(fx) < tol) then
root = x
return
end if
x = x - fx / fpx
end do
root = x
end function
end program numericalIEEE-арифметика и исключения
Модуль ieee_arithmetic (Fortran 2003) обеспечивает поддержку IEEE 754: бесконечность, NaN (Not a Number), сигнальные/тихие NaN, флаги исключений и режимы округления. NaN НИКОГДА не равен ничему (включая себя) — используйте ieee_is_nan() для проверки. Бесконечность возникает при переполнении или делении на ноль. Флаги исключений (ieee_divide_by_zero, ieee_overflow, ieee_underflow, ieee_inexact, ieee_invalid) отслеживают возникновение исключений — проверяйте через ieee_get_flag, очищайте через ieee_set_flag. ieee_set_halting_mode управляет тем, останавливает ли исключение программу. Режимы округления влияют на операции с плавающей точкой. Используйте это для надёжного численного кода: обнаружение NaN/Inf, аккуратная обработка исключений и контроль точности. Примечание: -ffast-math в gfortran нарушает совместимость с IEEE (не используйте для кода, полагающегося на эти возможности).
program ieee_demo
use ieee_arithmetic
implicit none
real :: a, b, c
logical :: flag
! IEEE special values
a = ieee_value(a, ieee_positive_inf) ! +Infinity
b = ieee_value(b, ieee_negative_inf) ! -Infinity
c = ieee_value(c, ieee_quiet_nan) ! NaN
print *, "Infinity: ", a ! Infinity
print *, "NaN: ", c ! NaN
print *, "Inf > 1e30: ", a > 1e30 ! T
print *, "NaN == NaN: ", c == c ! F (NaN is never equal!)
! Check for special values
print *, "is_nan(c): ", ieee_is_nan(c) ! T
print *, "is_finite(1.0): ", ieee_is_finite(1.0) ! T
print *, "is_inf(a): ", ieee_is_finite(a) ! F
! Operations producing special values
print *, "1.0/0.0: ", 1.0/0.0 ! Infinity (if -ffast-math off)
print *, "0.0/0.0: ", 0.0/0.0 ! NaN
print *, "sqrt(-1.0): ", sqrt(-1.0) ! NaN
! IEEE exception flags
call ieee_set_halting_mode(ieee_divide_by_zero, .false.) ! don't halt
b = 1.0 / 0.0 ! sets divide_by_zero flag, returns Inf
call ieee_get_flag(ieee_divide_by_zero, flag)
print *, "Divide by zero occurred: ", flag ! T
! Check and clear flags
call ieee_set_flag(ieee_all, .false.) ! clear all flags
b = 1.0 / 0.0
call ieee_get_flag(ieee_divide_by_zero, flag)
print *, "Flag after division: ", flag ! T
call ieee_set_flag(ieee_all, .false.) ! clear
! Rounding modes
call ieee_set_rounding_mode(ieee_nearest) ! default
call ieee_set_rounding_mode(ieee_down) ! round toward -inf
call ieee_set_rounding_mode(ieee_up) ! round toward +inf
call ieee_set_rounding_mode(ieee_to_zero) ! truncate
! Comparing NaN-safe
if (ieee_unordered(c, 1.0)) print *, "c is unordered (NaN)"
end program ieee_demoCoarrays и параллелизм
Базовое объявление coarray
Coarrays — встроенная параллельная модель Fortran (F2008). Каждый «образ» — это параллельный процесс. Объявление с суффиксом [*]. this_image() возвращает ранг; num_images() — количество. Доступ к удалённым данным через x[k]. sync all — барьер. Компиляторы: gfortran (с -fcoarray=lib), ifort, Cray.
program coarray_hello
use iso_fortran_env, only: real64
implicit none
real(real64) :: x[*] ! coarray — one copy per image
integer :: me
me = this_image()
x = real(me, real64) ! local assignment
call co_sum(x, result_image=1) ! reduce sum to image 1
if (me == 1) print *, 'Sum =', x
sync all ! barrier
end programУдалённый доступ и синхронизация
Доступ к удалённым coarray через суффикс [k] — односторонняя коммуникация. Чтение и запись неблокирующие до sync. sync all — глобальный барьер; sync images([1,2]) ожидает конкретные образы. Критические секции: lock/unlock с critical...end critical. Избегайте взаимных блокировок, упорядочивая syncs единообразно.
program remote_access
implicit none
integer :: val[*], neighbor
integer :: me, n
me = this_image()
n = num_images()
val = me * 10
sync all ! ensure all writes complete
! read from neighbor (circular)
neighbor = merge(1, me + 1, me == n)
print *, 'Image', me, 'sees neighbor', neighbor, 'value', val[neighbor]
! write to image 1 from all
if (me /= 1) val[1] = val[me]
sync all
if (me == 1) print *, 'Image 1 received:', val
end programКоллективные операции
Коллективы: co_sum, co_min, co_max, co_broadcast. Они работают с coarray и выполняют редукцию/рассылку по образам. result_image указывает, кто получает результат (по умолчанию: все). source_image для рассылки. Всегда выполняйте sync перед чтением коллективных результатов на других образах. Быстрее, чем ручные циклы с sync.
program collectives
use iso_fortran_env, only: real64
implicit none
real(real64) :: local_sum, global_sum[*]
integer :: i
local_sum = 0.0_real64
do i = 1, 100
local_sum = local_sum + real(i * this_image(), real64)
end do
global_sum = local_sum
call co_sum(global_sum, result_image=1)
if (this_image() == 1) print *, 'Total:', global_sum
! other collectives: co_min, co_max, co_broadcast
call co_broadcast(global_sum, source_image=1)
end programПроизводные типы coarray
Производные типы могут быть coarray — каждый компонент реплицируется на образ. Доступ к удалённым компонентам через p[k]%field. Компоненты allocatable в coarray требуют Fortran 2018+ (некоторые компиляторы могут не поддерживать). Для массивов частиц используйте type(particle), allocatable :: particles(:)[:].
program coarray_types
implicit none
type :: particle
real :: x, y, z
real :: mass
end type
type(particle) :: p[*]
integer :: me
me = this_image()
p%mass = real(me)
p%x = real(me) * 0.5
sync all
! access component of remote coarray
if (me == 1) print *, 'Image 2 mass:', p[2]%mass
end programAllocatable coarrays и команды
Allocatable coarrays размещаются на всех образах одновременно с суффиксом [*]. Команды (F2018) разделяют образы на независимые группы — у каждой свои this_image/num_images. form team создаёт команды; change team входит в область видимости. Полезно для иерархического параллелизма. Поддержка компиляторами различается (ifort, gfortran 9+).
program alloc_coarrays
use iso_fortran_env, only: team_type
implicit none
integer, allocatable :: data(:)[:]
type(team_type) :: odd_team, even_team
integer :: me
me = this_image()
allocate(data(100)[*]) ! allocate on all images
! F2018 teams — split images into groups
form team(merge(1, 2, mod(me, 2) == 1), odd_team, even_team)
if (mod(me, 2) == 1) then
change team(odd_team)
! this_image() and num_images() now refer to team
data(this_image()) = me
end team
end if
deallocate(data)
end programСовместимость с C
Основы ISO_C_BINDING
iso_c_binding предоставляет C-совместимые типы kind (c_int, c_double, c_char и т.д.). bind(C, name='...') экспортирует Fortran в C с заданным именем символа. C-строки требуют терминатора c_null_char. Блок interface объявляет сигнатуру C-функции. Компилируйте C и Fortran отдельно, линкуйте вместе.
program c_interop
use iso_c_binding, only: c_int, c_double, c_char, c_null_char
implicit none
interface
subroutine c_print(msg) bind(C, name='c_print')
import :: c_char
character(kind=c_char), dimension(*) :: msg
end subroutine
end interface
call c_print('Hello from Fortran' // c_null_char)
end program
! Corresponding C:
! void c_print(const char* msg) { printf("%s\n", msg); }Передача массивов в C
Передавайте массивы через c_loc (получение C-указателя) и тип c_ptr. Используйте атрибут 'value' для скалярных C-аргументов (передача по значению, не по ссылке). Массивы Fortran — колоночные; C — строчные — трансп онируйте 2D-массивы или документируйте соглашение. c_f_pointer преобразует C-указатели обратно в указатели Fortran.
program array_interop
use iso_c_binding, only: c_double, c_int, c_loc, c_f_pointer
implicit none
real(c_double), target :: arr(10)
integer(c_int) :: n
type(c_ptr) :: ptr
arr = [(real(i), i=1,10)]
ptr = c_loc(arr(1)) ! get C pointer
! call C function: void process(double* arr, int n);
call process_c(ptr, size(arr, kind=c_int))
interface
subroutine process_c(arr, n) bind(C, name='process')
import :: c_double, c_int, c_ptr
type(c_ptr), value :: arr
integer(c_int), value :: n
end subroutine
end interface
end programC-совместимые типы
Типы с bind(C) имеют C-совместимую структуру памяти — необходимо для передачи структур в C. Разрешены только C-совместимые типы kind (без real/integer по умолчанию). Компоненты allocatable/pointer не допускаются. Массивы char фиксированной длины эмулируют C-строки. Порядок важен — Fortran может переупорядочить компоненты без bind(C).
module data_types
use iso_c_binding, only: c_double, c_int, c_char
implicit none
type, bind(C) :: point
real(c_double) :: x, y
integer(c_int) :: id
end type
type, bind(C) :: string_holder
character(kind=c_char, len=1) :: name(64)
end type
end module
! C equivalent:
! struct point { double x, y; int id; };
! struct string_holder { char name[64]; };Вызов Fortran из C
Подпрограммы с bind(C, name='...') вызываются из C по этому имени. Используйте 'value' для скаляров, которые C передаёт по значению; массивы передаются по ссылке (без value). Результаты функций должны быть C-совместимыми скалярами. Используйте C-имена связывания, чтобы избежать искажения имён компилятором (подчёркивания, изменение регистра).
! Fortran:
module fmod
use iso_c_binding, only: c_double
implicit none
contains
subroutine square_array(arr, n) bind(C, name='square_array')
integer, value :: n
real(c_double), intent(inout) :: arr(n)
arr = arr ** 2
end subroutine
end module
! C caller:
! extern void square_array(double* arr, int n);
! double data[5] = {1, 2, 3, 4, 5};
! square_array(data, 5);Указатели на функции C и обратные вызовы
abstract interface объявляет сигнатуру C-функции. procedure(iface) принимает подходящую функцию как аргумент. Передавайте указатели на C-функции напрямую. c_funloc получает C-адрес процедуры Fortran; c_f_procpointer преобразует c_funptr обратно в процедуру Fortran. Полезно для обратных вызовов в стиле qsort.
module callbacks
use iso_c_binding, only: c_funloc, c_funptr, c_int
implicit none
abstract interface
function comparator(a, b) bind(C)
import :: c_int
integer(c_int), value :: a, b
integer(c_int) :: comparator
end function
end interface
contains
subroutine sort_with_c(arr, n, cmp) bind(C)
integer(c_int), value :: n
integer(c_int), intent(inout) :: arr(n)
procedure(comparator) :: cmp
! ... use cmp(a, b) to compare ...
end subroutine
end module
! C side:
! int descending(int a, int b) { return b - a; }
! sort_with_c(arr, n, descending);Объектно-ориентированное программирование (extends/final)
Расширение типов (наследование)
Расширение типа = наследование. 'extends(parent)' объявляет подкласс. 'class(T)' полиморфен (принимает T или любое расширение); 'type(T)' — точное соответствие. 'abstract' + 'deferred' = абстрактный метод (должен быть переопределён). 'contains' вводи т привязанные к типу процедуры. Переопределение — повторное объявление процедуры с тем же именем.
module shapes
implicit none
type, abstract :: shape
real :: x = 0, y = 0
contains
procedure(area_iface), deferred :: area
procedure :: move => shape_move
end type
abstract interface
function area_iface(this) result(a)
import :: shape
class(shape), intent(in) :: this
real :: a
end function
end interface
type, extends(shape) :: circle
real :: radius
contains
procedure :: area => circle_area
end type
contains
subroutine shape_move(this, dx, dy)
class(shape), intent(inout) :: this
real, intent(in) :: dx, dy
this%x = this%x + dx
this%y = this%y + dy
end subroutine
function circle_area(this) result(a)
class(circle), intent(in) :: this
real :: a
a = 3.14159 * this%radius ** 2
end function
end moduleПолиморфизм и SELECT TYPE
select type выполняет определение типа во время выполнения для полиморфных переменных. 'type is (T)' соответствует точному типу; 'class is (T)' соответствует T и расширениям. Внутри блока переменная рассматривается как соответствующий тип (доступ к специфичным компонентам). Полиморфные массивы содержат смешанные типы через class(shape) — но размещение должно быть поэлементным.
module polymorph
use shapes, only: shape, circle, rectangle
implicit none
contains
subroutine describe(s)
class(shape), intent(in) :: s
select type(s)
type is (circle)
print *, 'Circle radius:', s%radius
type is (rectangle)
print *, 'Rectangle:', s%width, 'x', s%height
class is (shape)
print *, 'Some shape at:', s%x, s%y
class default
print *, 'Unknown type'
end select
end subroutine
subroutine process_all(shapes)
class(shape), intent(in) :: shapes(:)
integer :: i
do i = 1, size(shapes)
call describe(shapes(i))
print *, 'Area:', shapes(i)%area()
end do
end subroutine
end moduleФинализаторы и деструкторы
Процедуры final выполняются автоматически при выходе переменной из области видимости — как деструкторы C++. Определяются через 'final :: name'. Должны быть подпрограммами, принимающими TYPE (не class) — без полиморфизма. Один тип может иметь несколько финализаторов (перегруженных по рангу). Используйте для закрытия файлов, освобождения памяти, release ресурсов. Не могут завершаться ошибкой/выбрасывать исключения.
module resources
implicit none
type :: file_handle
integer :: unit = -1
contains
final :: close_file
procedure :: open
end type
contains
subroutine open(this, filename)
class(file_handle), intent(inout) :: this
character(*), intent(in) :: filename
open(newunit=this%unit, file=filename, status='old')
end subroutine
subroutine close_file(this)
type(file_handle), intent(inout) :: this
if (this%unit /= -1) then
close(this%unit)
this%unit = -1
end if
end subroutine
end module
program demo
use resources
type(file_handle) :: f
call f%open('data.txt')
! ... use file ...
end program ! f goes out of scope -> close_file called automaticallyКонструкторы и размещение
Generic interface с именем типа действует как пользовательский конструктор — перегружает конструктор структуры по умолчанию. Несколько процедур допускают разные наборы аргументов. Конструктор по умолчанию (point(x=..., y=...)) по-прежнему доступен, если не переопределён. Полиморфное размещение: allocate(circle :: shape_var) создаёт circle в переменной class(shape).
module points
implicit none
type :: point
real :: x, y
contains
procedure :: norm
end type
interface point
procedure new_point
procedure new_point_polar
end interface
contains
function new_point(x, y) result(p)
real, intent(in) :: x, y
type(point) :: p
p%x = x
p%y = y
end function
function new_point_polar(r, theta) result(p)
real, intent(in) :: r, theta
type(point) :: p
p%x = r * cos(theta)
p%y = r * sin(theta)
end function
function norm(this) result(n)
class(point), intent(in) :: this
real :: n
n = sqrt(this%x**2 + this%y**2)
end function
end module
program use_points
use points
type(point) :: a, b
a = point(1.0, 2.0) ! cartesian
b = point(3.0, 0.5) ! polar (same name, different args)
print *, a%norm(), b%norm()
end programАбстрактные типы и шаблоны
Абстрактные типы нельзя инстанцировать — только расширять. Отложенные процедуры (deferred) должны быть переопределены в конкретных подклассах. class(*) — неограниченный полиморфизм, содержит любой тип (используйте select type для восстановления). Этот паттерн реализует абстрактные базовые классы и интерфейсы. Конкретные контейнеры (list, stack, queue) расширяют и реализуют отложенные процедуры.