современный фортран , Бортеньев. О. В. Бартеньев Современный Фортран
Скачать 2.24 Mb.
|
Замечание. Когда функция SCAN используется с массивами, входящие в массив константы должны иметь одинаковую длину. Для выравнивания длины констант следует использовать пробелы, например: array = scan ((/'fortran', 'masm '/), (/'a', 'a'/)) print *, array ! 6 2 76 риложение3. Организация данных TRIM(string) - преобразовывающая функция; удаляет завершающие пробелы строки string, например: print *, 'banana ' // 'nbc' ! banana nbc print *, trim('banana ') // 'nbc' ! banananbc VERIFY(string, set [, back]) - элементная функция; возвращает 0, если каждый символ строки string присутствует в строке set. В противном случае возвращает номер позиции символа строки string, которого нет в строке set. Результат имеет стандартный целый тип. Если логический параметр back отсутствует или задан со значением .FALSE., то выдается положение самого левого такого символа. Если back задан со значением .TRUE., то выдается положение самого правого такого символа, например: write(*, *) verify ('banana', 'nbc') ! 2 write(*, *) verify ('banana', 'nbc', .true.) ! 6 write(*, *) verify ('banana', 'nba') ! 0 Замечания: 1. В Фортран 90 дополнительно включены функции: IACHAR, ACHAR, ADJUSTL, ADJUSTR, REPEAT и TRIM. Все остальные символьные функции были доступны в Фортране 77. 2. Вызов встроенных функций может быть выполнен с ключевыми словами (разд. 8.11.4), например: print *, verify('banana', set = 'nbc', back = .true.) ! 6 3. Параметрами элементных функций INDEX, SCAN, VERYFY помимо скаляров могут быть массив и скаляр или согласованные массивы. В этом случае результат может быть записан в целочисленный массив, согласованный с массивом-параметром. 4. Параметром элементных функций ADJUSTL и ADJUSTR может также быть и массив. В этом случае результат может быть записан в символьный массив, согласованный с массивом-параметром. Пример: character(4), dimension(2) :: st = (/ 'abcd', 'dab'/), ch*1 / 'a', 'b' / character(7), dimension(2) :: ast = (/ 'abcd ', 'dab '/), arst integer(4) p(2) p = index(st, 'a') print '(2i4)', p ! 1 2 p = index(st, ch) print '(2i4)', p ! 1 3 print '(i4)', len(st) ! 4 arst = adjustr(ast) print *, arst ! abcd dab 77 О. В. Бартеньев. Современный ФОРТРАН 3.8.9. Выделение слов из строки текста Рассмотрим часто решаемую при работе со строками задачу: выделение слов из заданной строки. Задачу рассмотрим в следующей формулировке: вывести каждое слово текстового файла на отдельной строке, считая, что слова разделяются одним или несколькими пробелами. Такая формулировка является упрощенной, поскольку в общем случае разделителями между словами могут быть сочетания знаков препинания и символов табуляции с пробелами. Поясним содержание задачи примером. Пусть текстовой файл имеет имя 'c:\a.txt' и содержит текст: Увы, на разные забавы Я много жизни погубил! Тогда результатом работы программы будет последовательность слов: Увы, на разные забавы Я Идея алгоритма выделения слова проста: найти позицию начала (wb) и конца (we) слова в строке и выделить слово как подстроку: st(wb:we). Данную процедуру повторять до тех пор, пока не будут проанализированы все строки текста. character(len = 20) words(100) ! Массив слов текста character(len = 80) st ! Строка текста integer j, wb, we, nw, lst integer, parameter :: unit = 1 ! Номер устройства, к которому open(unit, file = 'c:\a.txt') ! подсоединяется файл c:\a.txt ! Запишем все слова текста в массив words nw = 0 ! nw - число слов в тексте do while(.not. eof(unit)) ! Цикл обработки строк read(unit, '(a)') st ! Ввод строки текста write(*, *) st ! Контрольный вывод lst = len_trim(st) ! Длина строки без завершающих пробелов wb = 0 ! wb - начало текущего слова в строке ! Просмотрим поочередно все символы строки st. ! Если найден пробел, то возможны случаи: ! а) предыдущий символ отличен от пробела (wb > 0), следовательно, ! выполнен переход с конца слова на промежуток между словами ! и, зная начало (wb) и конец (we) текущего слова, ! мы можем добавить слово в массив words. ! Используем для этого подпрограмму addword. ! После добавления слова устанавливаем в wb нуль (wb = 0); ! б) предыдущим символом является пробел - выполняем переход на 78 риложение3. Организация данных ! следующий символ. ! Если текущий символ отличен от пробела, то возможны варианты: ! а) мы находимся в начале строки или предыдущий символ является ! пробелом (wb = 0); ! б) предыдущий символ отличен от пробела (wb > 0) - выполняем ! дальнейшее перемещение по текущему слову. do j = 1, lst ! Просмотрим все символы строки if(st(j:j) == ' ') then if(wb > 0) call addword(words, st, wb, we, nw) else if(wb == 0) then ! Обнаружено начало слова wb = j we = j else we = we + 1 ! Перемещение по текущему слову end if end do ! После просмотра всей строки, если строка не была пустой, ! мы обязаны добавить последнее слово в массив words if(wb > 0) call addword(words, st, wb, we, nw) end do close(unit) write(*, *) 'Число слов в тексте nw =', nw do j = 1, nw write(*, *) words(j) end do end subroutine addword(words, st, wb, we, nw) integer wb, we, nw character(len = *) words(*) ! Перенимающий размер массив character(len = *) st ! Строка, перенимающая длину nw = nw + 1 words(nw) = st(wb : we) wb = 0 end 3.9. Производные типы данных 3.9.1. Объявление данных производного типа Рассмотрим табл. 3.3, содержащую экзаменационные оценки. Таблица 3.3. Экзаменационные оценки студентов Ф. И. О. Экзамен 1 Экзамен 2 Экзамен 3 Экзамен 4 Александров В. М. 4 5 3 4 Владимиров А. К. 3 5 4 2 79 О. В. Бартеньев. Современный ФОРТРАН При работе с таблицей может возникнуть ряд задач: сохранить таблицу в файле; прочитать данные из файла; подсчитать среднюю оценку студентов; найти лучших (худших) студентов и т. д. При этом удобно при передаче данных в файл и считывании их из файла оперировать строками таблицы, имея доступ к отдельным элементам строки. Иными словами, при таком подходе строка таблицы должна быть самостоятельной переменной, состоящей из нескольких изменяемых компонентов. Такой переменной в Фортране является запись. Запись - это переменная производного (структурного) типа данных. Записи вводятся оператором TYPE или, как в FPS версии 1, оператором RECORD. Производный тип данных (структура) - это одно или несколько объявлений переменных (как правило, разного типа), сгруппированных под одним именем. Структура должна вводиться в разделе объявлений программы. В Фортране 90 производный тип данных вводится оператором: TYPE [, access-spec] [::] name [PRIVATE | SEQUENCE] component decl [component decl] END TYPE [name] access-spec - определяющий способ доступа к объявленному типу атрибут (PUBLIC или PRIVATE). Атрибуты PUBLIC и PRIVATE могут быть использованы только при объявлении типа в модуле (разд. 8.7). По умолчанию способ доступа PUBLIC (если только модуль не содержит оператор PRIVATE без указания в нем списка объектов). Задание атрибута PUBLIC означает, что тип и его не имеющие атрибута PRIVATE компоненты доступны во всех программных единицах, использующих модуль, в котором производный тип определен. Задание атрибута PRIVATE означает, что тип и/или его компоненты доступны только в модуле. Причем сам тип может иметь атрибут PUBLIC, а его компоненты - PRIVATE. name - имя производного типа данных (структуры); оно не должно совпадать с именем другой переменной или функции, определенных в том же программном компоненте, также оно не может совпадать с именем встроенного типа данных, например COMPLEX. Имя структуры является локальным и поэтому структура как тип должна быть объявлена в каждой программной единице, в которой объявляются переменные введенного типа. Для уменьшения издержек на разработку программы рекомендуется объявлять структуры в отдельных модулях, ссылаясь на них в тексте программной единицы оператором USE. 80 риложение3. Организация данных component decl - любая комбинация одного или нескольких операторов объявления типов переменных, имеющая вид: тип [[, список атрибутов] ::] список компонентов В необязательном списке атрибутов могут присутствовать атрибуты POINTER и/или DIMENSION. Операторы объявления типов могут содержать скаляры и массивы встроенных и производных типов. При этом входящие в component decl операторы TYPE и/или RECORD должны ссылаться на ранее определенные производные типы. Фортран 95 позволяет операторам, входящим в component decl, содержать начальные значения переменных. Они по умолчанию будут являться начальными значениями соответствующих компонентов всех объектов этого типа. Инициализировать можно как все, так и отдельные компоненты. Например: type entry ! Объявление типа entry real(4) :: val = 3.0 ! Инициализация компонента val integer(4) :: index ! Инициализация не выполняется type(entry), pointer :: next => null( ) ! Инициализация компонента next end type entry type(entry) :: erray(10) print *, erray(5)%val ! 3.000000 Если объявление производного типа содержит атрибут SEQUENCE, то его компоненты будут записаны в память в порядке их объявления в типе. Это позволяет использовать переменные производного типа в com- mon-блоках, операторах EQUIVALENCE и в качестве параметров процедур. Замечания: 1. Входящие в состав производного типа переменные называются его компонентами. 2. По умолчанию объявленный в модуле производный тип доступен в любой программной единице, использующей модуль. 3. При определении компонента производного типа могут быть использованы только два атрибута: POINTER и DIMENSION. При этом если компонент объявлен с атрибутом POINTER, то он может ссылаться на объект любого типа, включая и объект объявленного производного типа, например: type entry ! Объявление типа entry real val integer index type(entry), pointer :: next ! Ссылка на объект типа entry end type entry 4. Следующий стандарт позволит задавать в производных типах размещаемые объекты данных. 81 О. В. Бартеньев. Современный ФОРТРАН После введения производного типа данных объекты (переменные или константы) нового типа объявляются оператором: TYPE(type-name) [, attrs] :: vname type-name - имя производного типа, введенного оператором TYPE ... END TYPE. attrs - один или более разделенных запятыми атрибутов vname. vname - одно или более разделенных запятыми имен переменных или констант, называемых записями. Присутствующее в vname имя может быть массивом. Оператор TYPE, как и другие операторы объявления данных, предшествует исполняемым операторам. Оператор должен быть расположен после введения типа type-name. Запись является составной переменной. Для доступа к компоненту записи используется селектор компонента - символ процента (%) или точка (последнее невозможно при задании директивы $STRICT): val = vname%cname или val = vname.cname где cname - имя компонента записи. Если компонент cname является записью, то для доступа к компоненту cname потребуется дважды применить селектор компонента: val2 = vname%cname%cname2 где cname2 - имя компонента записи cname. И так далее. Пример: integer, parameter :: n = 20 ! Можем использовать n внутри character(n) bname ! объявления производного типа type catalog ! Описание каталога character(n) name, phone ! Название, телефон integer cat_id ! Код каталога end type catalog type(catalog) boa ! Объявление записи boa = catalog('JCP', '234-57-22', 44) ! Конструктор структуры bname = boa % name ! Доступ к компоненту записи print *, bname, ' ', boa%phone ! JCP 234-567-22 Замечание. Из примера видно, что заданную до описания производного типа именованную константу можно использовать в объявлении этого типа, например для задания длины символьной строки. 3.9.2. Инициализация и присваивание записей 3.9.2.1. Конструктор производного типа Переменную производного типа можно определить (присвоить значения всем ее компонентам), применив конструктор производного типа, называемый также конструктором структуры: 82 риложение3. Организация данных имя-типа (список выражений) где список выражений задает значение компонентов переменной. Конструктор структуры может быть использован для инициализации записей в операторах объявления записей, в операторе DATA, в операторе присваивания, в выражениях (если выполнена перегрузка операций) и в качестве фактического параметра процедуры. Аналогичный конструктор используется и для генерации констант производного типа: имя-типа (список константных выражений) Пример. Сформируем структуру order, содержащую информацию о заказе покупателя. Каждый заказ может содержать до 10 наименований вещей. type item_d ! Описание заказанной вещи character(20) descr, color, size ! Название, цвет, размер integer(2) qty ! Количество real(4) price ! Цена end type type order ! Описание заказа integer(4) ordnum, cus_id ! Номер заказа, код покупателя type(item_d) item(10) ! Переменная типа item_d end type ! Задание записи - константы type(order), parameter :: e_ord = order(1, 1, item_d('d', 'c', 's', 1, 1.0)) type(order) cur_ord ! Переменная типа order ! Используя конструктор структуры, занесем в заказ cur_ord ! 10 одинаковых вещей ! Одним из выражений конструктора order является конструктор item_d cur_ord = order(1200, 300, item_d('shorts', 'white', 'S', 1, 35.25)) print *, cur_ord%item(1) ! Вывод данных о первом предмете ! Для вывода цвета вещи потребуется дважды применить селектор компонента print *, cur_ord%item(2)%color ! Вывод цвета второго предмета Замечания: 1. Поскольку переменная типа item_d входит в состав типа order, то тип item_d должен быть введен до описания типа order. 2. Определить переменную cur_ord можно, предварительно определив массив item(10). Выполним это в операторе объявления записи: type(item_d) :: item(10) = item_d('shorts', 'white', 'S', 1, 35.25) type(order) cur_ord cur_ord = order(1200, 300, item) print *, item(1) ! Вывод данных о первом предмете print *, cur_ord%ordnum ! Вывод номера заказа 83 О. В. Бартеньев. Современный ФОРТРАН 3.9.2.2. Присваивание значений компонентам записи Продолжим работу с переменной cur_ord только что введенного производного типа order: ! Присвоим значение отдельному компоненту записи cur_ord%cus_id = 1300 ! Изменим код покупателя ! Присвоим значение компоненту записи - элементу массива: cur_ord%item(2)%color = 'blue' ! Изменим цвет второй вещи заказа ! Присвоим значение всему массиву - компоненту записи: cur_ord%item%color = 'none' Если компонентом записи является массив, то для его определения можно использовать конструктор массива (разд. 4.6), например: type vector integer n integer vec(10) end type ! j-му элементу массива vec присвоим значение j * 2 type(vector) :: vt = vector(5, (/ (j * 2, j = 1, 10) /)) print *, vt.n, vt.vec(2) ! 5 4 3.9.2.3. Задаваемые присваивания записей Можно изменить значение переменной производного типа, присвоив ей значение другой переменной, константы, конструктора или выражения того же типа. Однако область действия встроенного присваивания можно расширить, связав с оператором присваивания (=) посредством блока IN- TERFACE ASSIGNMENT модульную или внешнюю подпрограмму, которая будет вызываться каждый раз, когда в программе встречается заданное присваивание (разд. 8.12.2). 3.9.3. Выражения производного типа Если не принять специальных мер, нельзя применять встроенные операции в выражениях с записями. Нельзя, например, сложить две записи, применяя встроенную операцию сложения. Мерой, позволяющей распространить встроенную операцию на производный тип, является перегрузка операций (разд. 8.12.2). Для задания (перегрузки) операции создается функция, которая при помощи интерфейсного блока связывается с задаваемой операцией. Эта функция вызывается каждый раз, когда встречается заданная операция, и возвращает для последующего использования в выражении результат этой операции. Пример. Зададим операцию умножения числа на запись. module deta ! Определим производный тип pair type pair ! в модуле deta real x, y end type pair end module 84 риложение3. Организация данных program paw use deta ! Получаем доступ к типу pair interface operator(*) ! К задающей операцию внешней функции function mu(a, b) ! необходимо явно описать интерфейс use deta type(pair) mu type(pair), intent(in) :: b ! Вид связи параметров задающей real, intent(in) :: a ! операцию функции должен быть IN end function end interface type(pair) :: pt1 = pair(2.0, 2.0), pt2 pt2 = 2.0 * 2.5 * pt1 ! Первая операция умножения встроенная, ! вторая - перегруженная print *, pt2 ! 10.000000 10.000000 end program paw function mu(a, b) ! Функция будет вызываться каждый раз, use deta ! когда первым операндом операции * type(pair) mu ! будет выражение типа REAL, type(pair), intent(in) :: b ! а вторым - выражение типа pair real, intent(in) :: a mu.x = a * b.x mu.y = a * b.y end function 3.9.4. Запись как параметр процедуры Если запись используется в качестве параметра процедуры и ее тип повторно определяется в процедуре оператором TYPE ... END TYPE, то при его определении и в вызывающей программной единице, и в процедуре необходимо задать атрибут SEQUENCE. Это обеспечит одинаковое расположение компонентов записи в памяти. (Порядок размещения компонентов в памяти определяется на этапе компиляции.) Если в определении производного типа встречаются иные производные типы, то они тоже должны иметь атрибут SEQUENCE. Если производный тип определяется в модуле, атрибут SEQUENCE избыточен: модули компилируются отдельно и, следовательно, в каждой программной единице, получающей определение производного типа посредством use-ассоциирования, компоненты такой записи будут размещены в памяти одинаково. Пример: program gopo ! Главная программа type point ! В главной программе и функции pval sequence ! определен один и тот же тип point real x, y end type type(point) pt |