современный фортран , Бортеньев. О. В. Бартеньев Современный Фортран
Скачать 2.24 Mb.
|
Ограничения: 94 риложение3. Организация данных 1. Адресная переменная не может быть формальным параметром, именем функции или элементом общего блока. Адресную переменную нельзя инициализировать при ее объявлении или в операторе DATA. Указатель не может появляться в операторе объявления типа и не может быть инициализирован в операторе DATA. 2. Операторы ALLOCATE и DEALLOCATE не могут быть использованы с целочисленными указателями. Указатель может быть размещен на начало свежей области памяти, выделяемой функцией MALLOC. После использования выделенная память может быть освобождена встроенной подпрограммой FREE. Пример. Сформировать область памяти, занося в байт по адресу p0 + k натуральное число k (k = 0, 127, 1). byte gk integer k, size /127/, p0 pointer(p, gk) ! Функция MALLOC возвращает начальный адрес p0 = malloc(size) ! выделенной памяти p = p0 ! Установим указатель p в начало выделенной do k = 0, size ! памяти gk = int(k, kind = 1) ! В ячейку с адресом p заносится значение gk p = p + 1 ! Переход к следующему байту памяти end do ! Просмотр памяти print '(10i3)', (gk, p = p0, p0 + 5) ! 1 2 3 4 5 6 call free(p0) ! Подпрограмма FREE освобождает выделенную end ! функцией MALLOC память Целочисленные указатели являются расширением CVF и FPS над стандартами Фортран 90 и 95, и поэтому с ними можно работать при отсутствии директивы $STRICT или опции компилятора /4Ys. В основном целочисленные указатели предназначены для организации доступа к произвольной, доступной из программы области памяти ЭВМ. Замечание. Целочисленные указатели Фортрана подобны указателям СИ. Порядок передачи целочисленных указателей в СИ-функции и приема указателей СИ в Фортран-процедуре рассмотрен в [1]. 3.11. Ссылки и адресаты Память под переменную может быть выделена на этапе компиляции или в процессе выполнения программы. Переменные, получающие память на этапе компиляции, называются статическими. Переменные, получающие память на этапе выполнения программы, называются динамическими. Ссылки - это динамические переменные. Выделение памяти под ссылку выполняется либо при ее размещении оператором ALLOCATE, либо после 95 О. В. Бартеньев. Современный ФОРТРАН ее прикрепления к размещенному адресату. В последнем случае ссылка занимает ту же память, которую занимает и адресат. 3.11.1. Объявление ссылок и адресатов Для объявления ссылки (переменной с атрибутом POINTER) используется атрибут или оператор POINTER, который, конечно, не следует смешивать с оператором объявления целочисленного указателя. Адресатом может быть: • прикрепленная ссылка; • переменная, имеющая атрибут TARGET (объявленная с атрибутом TARGET или в операторе с тем же именем); • выделенная оператором ALLOCATE свежая область памяти. Ссылками и адресатами могут быть как скаляры, так и массивы любого встроенного или производного типа. Пример: ! Объявление с использованием атрибутов integer(4), pointer :: a, b(:), c(:,:) ! Объявление ссылок integer(4), target, allocatable :: b2(:) ! Объявление адресата integer(4), target :: a2 ! Объявление с использованием операторов POINTER и TARGET integer(4) d, e, d2 /99/ pointer d, e ! Объявление ссылок target d2 ! Объявление адресата 3.11.2. Прикрепление ссылки к адресатам Для прикрепления ссылки к адресату используется оператор =>. После прикрепления ссылки к адресату можно обращаться к адресату, используя имя ссылки. То есть ссылка может быть использована в качестве второго имени (псевдонима) адресата. Пример: integer, pointer :: a, d, e ! Объявление ссылок integer, pointer, dimension(:) :: b integer, target, allocatable :: b2(:) ! Объявление адресатов integer, target :: a2, d2 = 99 allocate(b2(5)) ! Выделяем память под массив-адресат a2 = 7 a => a2 ! Прикрепление ссылки к адресату b2 = (/1, -1, 1, -1, 1/) ! Изменение ссылки приводит к b => b2 ! изменению адресата, а изменение адресата b = (/2, -2, 2, -2, 2/) ! приводит к изменению ссылки print *, b2 ! 2 -2 2 -2 2 b2 = (/3, -3, 3, -3, 3/) print *, b ! 3 -3 3 -3 3 96 риложение3. Организация данных a => d2 ! Теперь ссылка a присоединена к d2 d => d2; e => d2 ! Несколько ссылок присоединены к одному адресату print *, a, d, e ! 99 99 99 a = 100 ! Изменение a вызовет изменение всех ! ассоциированных с a объектов print *, a, d, e, d2 ! 100 100 100 100 deallocate(b2) ! Освобождаем память nullify(b) ! Обнуление ссылки end Нельзя прикреплять ссылку к не получившему память адресату, например: integer(4), pointer :: b(:) integer(4), allocatable, target :: b2(:) b => b2 ! Ошибочное прикрепление ссылки allocate(b2(5)) ! к неразмещенному адресату b2 = (/1, -1, 1, -1, 1/) print *, b Если объект имеет атрибуты TARGET или POINTER, то в ряде случаев такие же атрибуты имеет его подобъект. Так, если атрибут TARGET (POINTER) имеет весь массив, то и сечение массива, занимающее непрерывную область в памяти, и элемент массива обладают атрибутом TARGET (POINTER), например: integer, target :: a(10) = 3 integer, pointer :: b(:), bi b => a(1:5) ! Ссылка и адресат полностью тождественны ! Ошибочен оператор прикрепления ссылки к нерегулярному сечению ! массива, имеющему атрибут target: b => a(1:5:2) bi => b(5) ! Элемент массива также имеет атрибут POINTER print *, b ! 3 3 3 3 3 Однако подстрока строки, имеющей атрибуты TARGET или POINTER, этими атрибутами не обладает, например: character(len = 10), target :: st = '1234567890' character(len = 5), pointer :: ps2 ps2 => st(:5) ! Ошибка Если адресатом является ссылка, то происходит прямое копирование ссылки. Поэтому адресатом в операторе прикрепления ссылки может быть произвольное заданное индексным триплетом (разд. 4.5) сечение массива- ссылки. Само же заданное векторным индексом сечение не может быть адресатом ссылки. Например: character(len = 80), pointer :: page(:), part(:), line, st*10 allocate(page(25)) part => page part => page(5:15:3) ! Было бы ошибкой, если бы page 97 О. В. Бартеньев. Современный ФОРТРАН ! имел атрибут TARGET line => page(10) ! Элемент массива также имеет атрибут POINTER st => page(2)(20:29) ! Ошибка: подстрока не обладает атрибутом POINTER Если ссылка-адресат не определена или откреплена, то копируется состояние ссылки. Во всех остальных случаях адресаты и ссылки становятся тождественными (занимают одну и ту же область памяти). Тип, параметры типа и ранг ссылки в операторе прикрепления ссылки должны быть такими же, как и у адресата. Если ссылка является массивом, то она воспринимает форму адресата. При этом нижняя граница всегда равна единице, а верхняя - размеру экстента массива-адресата. Например: integer, pointer :: a(:) integer, target :: i, b(10) = (/ (i, i = 1, 10) /) ! Нижняя граница массива a равна единице, верхняя - трем a => b(3:10:3) print *, (a(i), i = 1, size(a)) ! 3 6 9 Такое свойство ссылок позволяет заменить ссылкой часто применяемое сечение и обращаться к его элементам, используя в каждом его измерении индексы от 1 до n, где n - число элементов сечения в измерении. Ссылка может быть в процессе выполнения программы прикреплена поочередно к разным адресатам. Несколько ссылок могут иметь одного адресата. Факт присоединения ссылки к адресату можно обнаружить, применив справочную функцию ASSOCIATED, имеющую синтаксис: result = ASSOCIATED(ссылка [, адресат]) Параметр адресат является необязательным. Если параметр адресат отсутствует, то функция возвращает .TRUE., если ссылка присоединена к какому-либо адресату. Если адресат задан и имеет атрибут TARGET, то функция возвращает .TRUE., если ссылка присоединена к адресату. Функция также вернет .TRUE., если и ссылка и адресат имеют атрибут POINTER и присоединены к одному адресату. Во всех других случаях функция возвращает .FALSE.. Возвращаемое функцией значение имеет стандартный логический тип. Пример: real, pointer :: c(:), d(:), g(:) real, target :: e(5) logical sta c => e ! Прикрепляем ссылки c и d к адресату d => e sta = associated(c) ! В первых трех случаях sta равен .TRUE. sta = associated(c, e) ! в последнем - .FALSE. sta = associated(c, d) sta = associated(g) 98 риложение3. Организация данных 3.11.3. Инициализация ссылки. Функция NULL Ссылку можно инициализировать, применив функцию NULL: real(4), dimension(:), pointer :: pa => null( ) Функция NULL дает ссылке статус "не ассоциирована с адресатом". Этот статус позволяет, например, использовать ссылку в качестве фактического параметра до ее прикрепления к адресату, например: program null_test real(4), dimension(:), pointer :: pa => null( ) interface subroutine poas(pa) real(4), dimension(:), pointer :: pa end subroutine poas end interface call poas(pa) ! Параметр - неприкрепленная ссылка print *, pa(2) ! 3.500000 end program null_test subroutine poas(pa) real(4), dimension(:), pointer :: pa allocate(pa(5)) pa = 3.5 end subroutine poas Функция NULL может быть использована и среди исполняемых операторов: pa => null( ) 3.11.4. Явное открепление ссылки от адресата Ссылку можно открепить от адресата, используя оператор NULLIFY: NULLIFY(pname) pname - список ссылочных переменных или компонентов структуры, которые следует открепить от их адресатов. Все элементы списка должны иметь атрибут POINTER. Например: integer, pointer :: a(:), b, c(:, :, :) nullify(a, b, c) Кроме открепления от адресата, оператор NULLIFY можно использовать для инициализации (обнуления) ссылки. Не является ошибкой открепление неприкрепленной ссылки. Оператор NULLIFY не освобождает адресата. Иными словами, если адресатом ссылки является выделенная оператором ALLOCATE память, то после открепления ссылки память не освобождается и остается недоступной, если к памяти была прикреплена лишь одна ссылка. 99 О. В. Бартеньев. Современный ФОРТРАН Недоступная память не образуется, если предварительно память освобождается оператором DEALLOCATE. Например: integer, pointer :: c(:, :, :) allocate(c(2, 5, 10)) deallocate(c) nullify(c) Та же опасность образования недоступной области памяти существует и при переназначении ссылки к другому адресату посредством оператора прикрепления ссылки =>. Например, в следующем фрагменте образуется недоступная память: integer, pointer :: a(:) integer, target :: b(5) allocate(a(5)) ! Адресатом ссылки a является выделяемая память a => b ! Выделенная ранее под ссылку a память становится ... ! недоступной для последующего использования В следующем фрагменте переназначение ссылки выполнено без потери памяти: allocate(a(5)) deallocate(a) ! Освобождение памяти. Память доступна для a => b ! последующего использования Заметим, что после выполнения оператора DEALLOCATE состояние ссылки становится неопределенным и может быть определено либо в результате присоединения к другому адресату, либо в результате обнуления в операторе NULLIFY. 3.11.5. Структуры со ссылками на себя Компонент производного типа может иметь атрибут POINTER и при этом ссылаться на переменную того же или другого производного типа: type entry ! Объявление типа entry real val integer index type(entry), pointer :: next ! Ссылка на объект типа entry end type entry Это свойство позволяет использовать ссылки для формирования связных списков. Рассмотрим в качестве примера однонаправленный список, представляющий структуру данных, каждый элемент которой содержит две 100 риложение3. Организация данных части - поля с данными и поле с адресом следующего элемента данных списка. Адрес, используемый для доступа к следующему элементу, называется указателем. Первый элемент списка называется вершиной списка. Последний элемент списка содержит нулевой указатель. Однонаправленный список с записями одинаковой длины можно представить в виде рис. 3.2. Рис. 3.2. Схема однонаправленного списка с записями одинаковой длины Разработаем программу формирования и редактирования однонаправленного списка. Пусть помимо поля с указателем каждый элемент списка содержит еще два поля, одно из которых (index) используется для индексирования элементов списка. Редактирование состоит из двух операций: добавления и удаления элемента из однонаправленного списка. Схема добавления элемента в однонаправленный список приведена на рис. 3.3, а удаления - на рис. 3.4. Новый элемент . . . Рис. 3.3. Схема включения элемента в однонаправленный список . . . Удаляемый элемент Рис. 3.4. Схема удаления элемента из однонаправленного списка По приведенным рисункам легко записать алгоритмы формирования списка и его модификации. 101 О. В. Бартеньев. Современный ФОРТРАН Алгоритм формирования списка из n элементов (оформлен в виде внутренней подпрограммы newtree): 1°. Начало (список формируется начиная с последнего элемента). 2°. Обнулить адрес последнего элемента списка tree - NULLIFY(tree). 3°. С параметром k = 1, n выполнить: Выделить память под элемент top. В поле с адресом элемента top занести адрес элемента, добавленного на шаге k - 1, т. е. адрес вершины tree. (Таким образом, каждый вновь добавляемый элемент (кроме последнего элемента списка) будет указывать на предыдущий.) Переместить вершину списка на последний добавленный элемент, выполнив tree => top. конец цикла 3°. 4°. Конец. Приведем далее текст программы, которая позволяет формировать список из заданного числа элементов, выполнять его просмотр и редактирование. module eni ! Модуль описания типа entry type entry real val integer index type(entry), pointer :: next ! Ссылка, которую можно присоединить end type entry ! к переменной типа entry end module eni program one_d_li ! Формирование, просмотр use eni ! и редактирование списка type(entry), pointer :: tree, top, current, newtop call newtree( ) ! Список сформирован; top ссылается ! на последний элемент списка call wali(top) ! Просмотр списка tree => top ! Перемещение в вершину списка call chava(5) ! Изменим поле val у двух соседних call wali(top) ! Просмотр списка call delem(7) ! Исключим из списка элемент с index = 7 call wali(top) ! Просмотр списка tree => top call insel(700.0, 8) ! Вставим элемент после элемента с index = 8 call wali(top) ! Просмотр списка contains subroutine newtree( ) ! Создание списка integer i nullify(tree) ! Обнуляем конец списка. В конце списка ! ASSOCIATED(tree) будет возвращать .FALSE. do i = 1, 10 ! Цикл формирования списка ! Размещаем новый элемент списка и заносим информацию ! в поля с данными 102 риложение3. Организация данных allocate(top) ! Ссылка top содержит в качестве top = entry(11.0*i, i, tree) ! компонента ссылку tree tree => top ! Установим указатель списка на end do ! вновь добавленный элемент end subroutine newtree subroutine wali(top) ! Просмотр списка начиная с элемента top type(entry), pointer :: top tree => top ! Прикрепляем ссылку tree к top do while(associated(tree)) ! Цикл просмотра списка print *, tree%val, tree%index current => tree%next ! Переход к следующему элементу tree => current end do read * ! Ожидаем нажатия Enter end subroutine wali subroutine delem(ind) ! Удаление элемента из списка integer ind tree => top do while(associated(tree)) if(tree%index == ind) then ! Следующий элемент имеет index = 7 ! Присоединим указатель элемента с index = in к указателю элемента ! с index = in + 2, выполнив тем самым исключение элемента с index = in + 1 if(associated(tree%next)) then current => tree%next tree%next => tree%next%next deallocate(current) ! Освобождаем память после исключения end if ! элемента из списка exit end if current => tree%next tree => current end do end subroutine delem subroutine insel(val, ind) ! Добавляет элемент в список tree integer ind real val do while(associated(tree)) if(tree%index == ind) then allocate(newtop) newtop = entry(val, ind - 1, tree%next) tree%next => newtop if(tree%index > 1) then newtop%next => tree%next%next else ! При вставке вслед за последним nullify(newtop%next) ! элементом обнуляем ссылку end if 103 О. В. Бартеньев. Современный ФОРТРАН exit end if current => tree%next tree => current end do end subroutine insel subroutine chava(ind) ! Изменим теперь значение поля элементов integer ind ! списка с index = ind и index = ind - 1 do while(associated(tree)) if(tree%index == ind) then tree%val = 500 ! Поле val элемента списка с index = ind tree%next%val = 400 ! Поле val элемента списка с index = ind - 1 exit end if current => tree%next tree => current end do end subroutine chava end program one_d_li Замечания : 1. В общем случае элементы списка располагаются в свободной памяти ЭВМ произвольным образом. Поэтому подобные списки иногда называют разреженными векторами. 2. Используя ссылки в качестве компонентов производного типа, можно сформировать циклические, двунаправленные списки и более сложные структуры данных. 3.11.6. Ссылки как параметры процедур Ссылка может быть формальным параметром процедуры. В этом случае фактический параметр также должен быть ссылкой. Ранги фактического и формального параметров-ссылок должны совпадать. При вызове процедуры состояние привязки фактического параметра передается формальному, и, наоборот, при завершении работы процедуры состояние привязки формального параметра передается фактическому. Однако при выходе адресат может стать неопределенным, если, например, в процессе работы процедуры ссылка была прикреплена к локальной переменной, не имеющей атрибут SAVE. В случае модульной или внутренней процедуры компилятор знает, является ли формальный параметр ссылкой или нет. В случае внешней процедуры компилятору необходимо явно указать, что формальный параметр является ссылкой. Это достигается за счет задания явного интерфейса. Для формального параметра-ссылки недопустимо задание атрибута INTENT, устанавливающего вид связи параметра. |