Автор: Артёмов И.Л.
Теги: вычислительная математика численный анализ вычислительная техника микропроцессоры программирование информационные технологии языки программирования компьютерные технологии язык программирования фортран
ISBN: 5-86404-206-4
Год: 2007
И. Л. Артёмов
FORTRAN
основы
программирования
издательство
Москва • 21ИДЛОГ/Т1ИОИ • 2007
УДК 519.682
ББК 32.97
А86
Артёмов И. Л.
А86 Fortran: основы программирования. - М.: Диалог-МИФИ, 2007. -
304 с.
ISBN 5-86404-206-4
Книга представляет собой руководство для начинающих исследователей и разра-
ботчиков программ вычислительного характера на языке программирования Fortran в
среде Windows. Материал подготовлен для тех. кто не имеет опыта написания про-
грамм, но желает изучить и использовать Fortran для решения своих задач.
Придерживаясь известной фразы "чтобы изучить язык программирования, надо на
нем писать программы", представлено множество разобранных и доступных примеров.
Продуманное расположение материала, простота изложения позволит читателю с
первых страниц создавать простейшие пршраммы и, постепенно постигая возможно-
сти языка, прийти к самостоятельному созданию законченных вычислительных при-
ложений.
Книга предназначена для первого ознакомления с языком программирования
Fortran и будет помощником для студентов, аспирантов, научных работников и инже-
неров-вычислителей.
УДК 519.682
ББК 32.97
Учебно-справочное издание
Артёмов Игорь Леонидович
Fortran: основы программирования
Редактор О. А. Голубев
Корректор В. С. Кустов
Макет Н. В. Дмитриевой
Подписано в печать 25.09.2006
Формат 60x84/16. Бум. офс. Печать офс. Гарнитура Таймс.
Усл. печ. л. 17,67. Уч.-изд. л. 9,85. Тираж 2 000 экз. Заказ 64
ООО “Издательство Диалог-МИФИ”
115409, Москва, ул. Москворечье, 31, корп. 2. Тел.: 320-43-55, 320-43-77
Http://www.dialog-mifi.ru. E-mail: zakaz@dialog-mifi.ru.
Подольская типография
142100, г. Подольск, Московская обл., ул. Кирова, 25
ISBN 5-86404-206-4
© Артёмов И Л., 2007
© Оригинал-макет, оформление обложки
ООО “Издательство Диалог-МИФИ”, 2007
ПРЕДИСЛОВИЕ
Разработанный в середине 50-х гг. под руководством Джона Бэкуса и совер-
шенствующийся до настоящего времени Fortran накопил богатые возможности для
решения практически любых научно-технических задач, особенно связанных с много-
кратной обработкой числовых данных. Этот мощный инструмент позволит ответить
на многие вопросы, поставленные перед вами, начиная от вычисления электрического
сопротивления проводника и заканчивая расчетом полета сверхзвукового самолета.
Если вы решили использовать язык программирования, на котором более чем
полвека создают вычислительные программы, то настоящая книга будет являться от-
правной точкой. Моя цель - дать вам прочное введение в пршраммирование и пока-
зать, как, используя те или иные средства языка Fortran, добиваться желаемого ре-
зультата.
Когда мне приходится изучать новую компьютерную программу, я стараюсь как
можно быстрее приобрести некоторый практический опыт, разобрать простые опера-
ции и получить первые положительные результаты. Поэтому материал книги полно-
стью придерживается этого правила; начальные теоретические сведения и примеры
работающих программ. Fortran - переводчик формул, поэтому разобранные примеры
ориентированы на вычислительные задачи.
Весь изучаемый материал разбит па уроки, объединенные одной темой, что по-
зволит самостоятельно день за днем изучить основные возможности, увидеть, как
тесно связаны между собой все элементы языка и одновременно почувствовать силы
для создания собственных приложений.
Начиная с Fortran все языки программирования высокого уровня построены на
фундаментальной идее писать программы на понятном для человека языке, быстро,
легко и с минимумом затрат. Поэтому основная цель всех занятий - показать, как рас-
сматриваемые средства позволяют решать поставленные задачи, упрощать и одно-
временно повышать эффективность написания программ. Изучение нового урока на-
чинается с наводящих примеров и вопросов, которые сразу же способствуют тому,
чтобы понять, какую пользу можно получить от изучаемого материала.
Книга не стремится охватить и описать все возможности языка; современные
компиляторы и средства разработки имеют отличную справочную систему. В книге в
первую очередь рассказано о технических приемах, т. е. о том, чего так часто не хва-
тает начинающему исследователю.
Прочитав книгу, вы научитесь создавать вычислительные программы, начиная с
математической постановки и заканчивая использованием средств компьютерной
графики. Изученный материал позволит с легкостью читать другие книги по про-
граммированию и языку Fortran, которые станут более понятными и доступными, что
в конечном счете позволит совершенствовать личный опыт и стиль в разработке про-
грамм.
Эта книга рассчитана на читателя, знакомого с устройством и принципами рабо-
ты современного персонального компьютера, имеющего скромные познания в ин-
форматике и математике, а также готового много работать и экспериментировать.
тИОМИФИ
3
1. ПЕРВЫЕ ПРОГРАММЫ
1.1. Язык программирования, компилятор
и среда разработки
Каждый день мы пользуемся многими прикладными программами, вы-
полняющими повседневные задачи. Однако, как это часто бывает, готовые
программы нс всегда отвечают нашим требованиям и целям, и тогда возника-
ет необходимость в создании собственных приложений.
Известно, что компьютер выполняет инструкции, записанные на особом,
понятном только компьютеру машинном языке, алфавит которого содержит
только две цифры - нуль и единицу.
Однако, написав программу из нескольких десятков строк, состоящую из
нулей и единиц, можно почувствовать себя плохо и, возможно, отказаться от
идеи заняться программированием. К счастью, проблемы, которые возникали
у программистов 40-50-х гг., давно уже не тревожат современных разработ-
чиков программного обеспечения.
Для создания профамм используется язык программирования, основное
назначение которого быстро и легко создавать нужные программы. Про-
грамма записывается на попятном для человека языке, с очевидными словес-
ными конструкциями. Чтобы компьютер мог выполнить такую программу,
применяется специальная профамма-компилятор, которая преобразует текст
программы в нули и единицы (рис. 1.1). Другими словами, компилятор вы-
ступает в роли посредника между программистом и компьютером.
! текст
! программы
! write...
-I read. . .
компилятор
исполняемая
программа
1100110011
1001001100
Рис. 1.1. Назначение компилятора
Программы, выполняющие сложные научные вычисления - разрабаты-
ваются на языке программирования Fortran, имя которого происходит от двух
английских слов - Formula Translating (переводчик формул). Сегодня можно
встретить множество различных версий компиляторов от известных фирм-
производителей программного обеспечения, таких, как Absoft, Intel, Compaq,
Lahey, IBM, Sun, Open Watcom и ряд других. Мы будем рассматривать созда-
ние программ с использованием компилятора от Compaq версий 6.x.
Для более удобной работы по созданию программ используются среды
разработки - профаммы, которые позволяют набирать и редактировать текст
программы, компилировать и находить ошибки, оптимизировать работу соз-
даваемых приложений и содержат множество сервисных возможностей. Од-
ДИЛОГГ11И0И
4
1. Первые программы
ной из таких сред разработки является Developer Studio (Мастерская разра-
ботчика), в которой мы будем писать наши программы (рис. 1.2).
'•.>PROG1 - Compaq Visual Fortran - Ipl.fSO]
file Fdit View Insert Proiect Build Tools Window Help
'I'..’: ЁН® Чй £2’ Tl^'0
s
Workspace'PROG1‘: 1 j
H Ip PROG 1 files
9 Source Files
Д1р1!§о
Г. j Header Files]
£j Resource Files
program progl । имя первой программы
write (*,*) "My first program" ! выводим текст
end I конец программы
. ь.] FileView
--------------------Configuration: PROG1 - Win32 Debug-
Compiling Fortran...
C \Study\Progl\pl.f90
Linking...
PROGl.exe - 0 error(s), 0 varning(s)
' * I f К Build .X-DebMQ X.Find in Files 1 X'f^lnFte$'2|! Л.1.1
‘ c.\Study\Prog1\pl.f90 saved
• Ln 2. Cd 34
Рис. 1.2. Окно программы Developer Studio
Для разработки новой программы сначала создается проект. Выберем
самый простой тип проекта - Console Application (Консоль-приложение).
Простота и строгость (черный экран и клавиатура) этого проекта помогает
сосредоточиться на изучении языка и его возможностей, не отвлекаясь на
красочное великолепие Windows-приложений.
Выполним следующую цепочку действий.
В меню выбираем File -> New... -> Projects -> Fortran Console Application
-> в поле Project name задаем имя проекта (имя будущей программы), напри-
мер Progl, -> в поле Location указываем папку, где будет находиться про-
ект, -> нажимаем ОК -> выбираем An empty project (Пустой проект) -> на-
жимаем Finish и ОК.
После создания в проект необходимо добавить новый файл, который бу-
дет содержать текст программы: File -> Files -> Fortran Free Format Source
File (Свободный формат записи текста программы *) -> в поле File name ука-
* Раньше в языке Fortran 77 использовался фиксированный формат записи текста
программы. Например, максимальная длина строки программы не должна была пре-
вышать 72 символов; символ перехода на следующую строку находился на 6-й пози-
ции. Такой принцип написания программы был связан с использованием перфокарт
в 1970-х гг. В настоящее время в языке Fortran 90, 95 рекомендуется использовать
свободный формат, т. е. местонахождение символов в программе может быть
произвольным.
5
И. Л. Артёмов. Fortran: основы программирования
зываем имя файла, например р 1.190 (190 означает, что программа написана на
языке Fortran 90)-> нажимаем ОК.
В результате создается файл pl .190, который автоматически добавляется
в проект. Как правило, слева отображается окно Workspace (Рабочее про-
странство) с деревом проекта, в котором присутствуют включенные файлы.
В нашем случае файл pl .190 будет отображаться в папке Source Files (Исход-
ные файлы, или Исходники, - файлы содержащие текст программы). Если
файл не нужен, то его можно удалить из Workspace, нажав на клавишу Delete.
Для включения файла в проект можно выполнить такие действия: Project ->
Add to Project (Добавить в проект) -> Files... -> выбрать нужный файл.
В созданном файле наберем текст первой программы, которая будет вы-
водить на экран сообщение Му first program (Моя первая программа).
program progl 1 имя первой программы
write (*,*) "Му first program'' ! выводим текст
end ! конец программы
При наборе программы можно заметить, что некоторые слова отобража-
ются синим цветом, другие - черным, третьи - зеленым. Такое цветовое раз-
деление значительно повышает читаемость пршраммы и снижает риск появ-
ления "глупых" ошибок. Все тексты программ, рассмотренных в этой книге,
также придерживаются этого правила. Слова, выделенные па экране синим
цветом, в книге отображаются полужирным шрифтом.
Убедившись, что все строки программы набраны верно, нажимаем ком-
бинацию клавиш CTRL+F5 для компиляции и запуска программы. Внизу,
в появившемся окне с вкладкой Build (Построение), выдается отчет об ус-
пешной компиляции и создании файла progl.exe: "Ошибок (errors) и преду-
преждений (warnings) нс обнаружено".
После запуска программы progl.exe на экране появится окно консоль-
приложения, в котором отобразится набранный текст в операторе write(*,*).
Текст Press any key to continue (Нажмите любую клавишу для продолжения)
означает завершение выполнения коцсоль-приложения (рис. 1.3). Как видите,
все очень просто - набираем программу, компилируем и запускаем!
jPROGt
2 сИ®! Н л f
Рис. 1.3. Окно консолъ-приложения
В заключение ответим на пару вопросов, которые часто возникают при
первой работе с Developer Studio.
6
1. Первые программы
1. Как открыть ранее использованный проект?
Выполнить следующие действия: File -> Recent Workspaces -> Выбрать
проект или File -> Open Workspace -> Перейти в папку и выбрать файл *.dsw.
Все проекты имеют расширение dsw.
2. Где находится созданный исполняемый файл?
Исполняемый файл находится в папках Debug или Release. Вообще
в проекте существует две стандартные конфигурации - Debug и Release.
Debug - это конфигурация проекта для отладки, Release - конфигурация фи-
нальной версии программы.
1.2. Операторы, комментарии, ввод и вывод данных
Разобравшись, как создавать проект, компилировать и запускать про-
граммы, рассмотрим, что означает каждая строка программы progl из 1.1.
program progl ! имя первой программы
write (*,*) "Му first program" ! выводим текст
end ! конец программы
Первая строка:
program progl ! имя первой программы
содержит оператор program (программа), который задает имя программе -
progl. Program можно не указывать, но считается хорошим тоном, если в на-
чале присутствует заголовок, отражающий сугь и назначение программы.
Восклицательный знак означает начало комментария или пояснительного
текста. Как правило, комментарии располагаются там, где необходимо под-
черкнуть смысл отдельных строк, указать пояснения к фрагменту профаммы,
сделать пометки для дальнейшей доработки, исключить (закомментировать)
отдельные смысловые части. Комментарии могут содержать любой текст
и не влияют на ход выполнения программы.
Вторая строка:
write (*,*) "Му first program" ! выводим текст
содержит оператор write (писать), который выводит на экран текст, заключенный
в двойные кавычки. Оператор write используется для вывода информации из
программы и поэтому называется оператором вывода. Две звездочки в круглых
скобках (*,*) означают вывод на экран с форматом по умолчанию.
Последняя, третья строка:
end ! конец программы
содержит оператор end (конец), указывающий на конец программы.
Таким образом, программа на языке Fortran - это запись последователь-
ности инструкций - операторов. Операторы бывают исполняемыми и неис-
полняемыми. Исполняемые операторы определяют некоторые действия, ко-
7
И. Л. ApmSMoe._Fortran:_ основы программирования
торые должны быть выполнены; неисполняемые задают различные свойства
и соглашения в программе.
Оператор program неисполняемый и является оператором описания. Опе-
ратор write исполняемый, отвечает за вывод данных. Оператор end исполняе-
мый, завершает программу.
Аналогично напишем и запустим вторую программу. Для этого создадим
файл prog2.f90 и включим в проект, а первый файл - progl.190 удалим из
проекта.
program prog2 ! это вторая программа
write!*,*) "Fortran----1954" ! выводим текст
write (*,*) "-FORTRAN66-", &
"-FORTRAN??-", &
"-FORTRAN90-"
write(*,*) "-FORTRAN95-"; write!*,*) "-FORTRAN2003-"
end
Результат работы программы:
Fortran---1954
-FORTRAN66--FORTRAN??--FORTRAN90-
-FORTRAN95-
-FORTRAN2003-
Press any key to continue
Для продолжения вызова оператора на следующей строке используется
символ &. Если выводимых данных несколько, то в операторах write их ука-
зывают через запятую. Иногда бывает удобным записывать операторы про-
граммы в одной строке, в этом случае в качестве разделителя используется
символ;.
Для ввода данных в программу используется оператор read (читать). Са-
мый простой пример - ввести в программу нажатие клавиши ENTER, записав
rcad(*,*). Как и в операторе write, символы (*,*) означают чтение данных
с экрана с форматом по умолчанию.
program read—enter
write!*,*) "Press Enter
write!*,*) "Press Enter
write!*,*) "Press Enter
end
,- read (*, *)
,- read (*, *)
; read(*, *)
После вывода очередного текстового сообщения программа ожидает на-
жатия клавиши ENTER.
Результат работы программы:
Press Enter...
Press Enter... •
8
1. Первые программы
Press Enter...
Press any key to continue
При выводе строки Press Enter... курсор автоматически переходит на сле-
дующую строку экрана, что не всегда удобно. Можно улучшить вывод, если
воспользоваться средствами форматирования. Для этого вместо второй звез-
дочки в операторе write(*,*) указывается спецификация формата вывода.
Спецификация формата - это набор дескрипторов преобразования, заклю-
ченных в "( )". В нашем случае для вывода текста и запрещения перевода
строки используется следующая спецификация:
write(*,"(А,\)") "Press Enter..."
состоящая из дескрипторов:
А - вывод символов,
\ - запрет перехода на следующую строку.
При форматируемом выводе часто используются дескрипторы:
/ - переход на следующую строку,
// - пропуск строки,
пх - вывод п пробелов.
При выводе текстовой строки можно указать количество выводимых
символов в А-дескрипторс. Так, например оператор
write(*,"(АЗ)") "Press Enter..."
выведет на экран "обрезанный" текст:
Pre
Если указать
write(*,"(АЗО)") "Press Enter..."
то оставшееся свободное место дополнится пробелами.
Enter...
Press any key to continue
Если спецификация формата часто повторяется, то ее рекомендуется за-
писывать отдельно при помощи оператора format (формат). Ссылка на фор-
мат осуществляется при помощи числовой метки, например:
write(*,100) "Press Enter..."
100 format(АЗО)
При форматируемом выводе данных одного типа (например, вывод пяти
строк текста) в спецификации формата указывается число повторений, на-
пример:
write(*,100) "progl ", &
"prog2 ", &
"ргодЗ ", &
9
И. Л. Артёмов. Fortran: основы программирования
"prog4 ", &
"prog5 "
100 format(5А)
Здесь спецификация 5А означает вывод пяти символьных, строк.
В заключение отметим, что при написании программ рекомендуется
придерживаться правила рельефной и блочной записи. При таком подходе
читаемость программ значительно возрастает и вероятность допустить ошиб-
ку минимальна.
Пример. Рельефная и блочная запись программы.
program relief_block
оператор ! 1-й блок
оператор
оператор
оператор ! 2-й блок
оператор
оператор
оператор
оператор
оператор ! 3-й блок
оператор
оператор
оператор
оператор ! внутренний блок
оператор
оператор
оператор
оператор
end
1.3. Переменные и константы хранят информацию
Любая программа предназначена для хранения и обработки информации.
Сначала данные поступают в программу, затем обрабатываются по опреде-
ленному алгоритму и выводятся из программы. Чтобы программа могла хра-
нить данные и работать с ними, используются константы и переменные.
Вообще переменная и константа - это ячейки памяти, которым, для лег-
кости работы, дают уникальные имена. Переменная и константа могут со-
держать только одно-единственное значение. Причем во время работы про-
i-раммы значение, хранящееся в переменной, может изменяться, тогда как
значение в константе постоянно.
Перед использованием переменные и константы объявляют при помощи
неисполняемых операторов описания. Программы могут обрабатывать раз-
10
1. Первые программы
ную информацию: целые числа, дробные числа, символы, текст, для хране-
ния которых требуется неодинаковое количество памяти. Поэтому при объ-
явлении применяются разные операторы. Например, оператор описания
integer а, Ь
объявляет две переменные а и Ь, для хранения целых чисел.
Оператор описания
real с, d
объявляет две переменные cad, для хранения дробных (вещественных)
чисел.
Для объявления константьг после оператора описания используется атри-
бут parameter (коэффициент).
Например, оператор
integer, parameter t: k=10
объявляет константу с именем к, которая будет хранить целое значение 10.
Имена переменным и константам даются по следующим правилам:
1. Длина имени не может превышать 32 символов.
2. В имени могут присутствовать:
• английские буквы, причем большие и маленькие буквы не различаются;
• цифры 0-9, начиная со второй позиции, т. е. имя переменной не может
начинаться с цифры;
• знак подчеркивания.
Пример. Имена переменных и констант записанных
правильно’.
RO, Radius, k.5, koef, force, A_l, pk
неправильно’.
RO- присутствует знак пробела;
Im - цифра находится в первой позиции;
скорость - используется кириллица;
М#2 - недопустимый символ #.
Имена также называют идентификаторами. Рекомендуется создавать
идентификаторы как можно ближе к решаемой задаче, осмысленными и со-
держательными. Например, если рассчитывается сумма 10 чисел, то имя пе-
ременной может быть таким: summa, summaJO, sum, s, res или total. Однако
трудно разобраться, что переменная с именем fghr содержит сумму 10 чисел.
При объявлении переменным можно задавать начальные значения или
инициализировать их при помощи знака :: .
Пример. Инициализация переменных.
program initialize
integer :: а=10, b=20
11
И. Л. Артёмов. Fortran: основы программирования
write(*, *) "а = ", а
write(*,*) "Ь = " , Ь
end
Вторая строка в программе:
integer :: а=10, Ь=20
означает, что в начале работы программы переменные а и b содержат значе-
ния 10 и 20.
Пример. Программа сложения двух целых чисел.
program sum
integer а,Ь,с ! a,b,c переменные целого типа
а=100
Ь=20 ! переменным а,Ь присвоим целые числа
с=а+Ь ! переменной с. присваиваем а+Ь
write(*,*) с 1 вывод на экран полученной суммы
end
Как обычно, в начале оператор program задает имя программе. Затем при
помощи оператора описания integer объявляются целые переменные с име-
нами а, Ь, с.
Чтобы поместить в переменные определенные значения, используется
оператор присваивания, который записывается как знак =. Например, д-100
означает, что переменной а присвоили значение 100. Таким образом, в пере-
менных а и b будут храниться числа 100 и 20.
Далее переменной с присваивается сумма значений переменных а и Ь,
т. е. в с будет значение 120. И наконец, при помощи оператора write на экран
выводится результат и прм'рамма завершает работу.
Результат работы программы:
120
Press any key to continue
Написанная программа выдает число 120, однако не совсем очевидно,
что означает этот результат. Следующая программа имеет дружественный
интерфейс:
program sum
integer a,b,c
write (*, *) "----SUM
write(*,*)
a=100; b=20; c=a+b
! заставка к программе
! вывод пустой строки
write!*,*) "First number...", а ! вывод первого числа
write!*,*) "Second number...", b ! второго числа
write!*,*) "Sum...", с ! результата
end
12
1. Первые программы
Результат работы программы:
-----SUM--------
First number... 100
Second number... 20
Sum. .. 120
Press any key to continue
Данная программа неуниверсальна, она позволяет складывать два числа,
заданные в программе. Чтобы программа могла обрабатывать любые целые
числа, следует при помощи операторов read оформить ввод данных в про-
грамму.
program sum
integer a,b,c
write(*,*) "-----SUM-------" ! заставка к программе
write(*,*) ! вывод пустой строки
write(*,"(А,\)") "Enter first number..."; read(*,*) a
write(*,"(A,\)") "Enter second number..."; read)*,*) b
c=a+b
write(*,*) "Sum...", с [результата
end
Результат работы программы:
-----SUM-------
Enter first number...10
Enter second number...20
Sum... 30
Press any key to continue
При выводе текстовой строки
Enter first number...
программа ожидает ввода числа. Пользователь набирает значение 10 и нажи-
мает ENTER. В результате при помощи оператора read значение 10 помеща-
ется в переменную а. Затем аналогично в переменную Ь читается другое чис-
ло. В конце программы происходит вычисление суммы и вывод результата.
Пример. Расчет возраста сотрудника.
program my_years
integer, parameter :: current_year=2005 ! текущий год
integer year, age ! year - год рождения
I age - возраст
write(*,"(A,\)") "Enter your year..."; read(*,*) year
age=current_year-year
write)*,*) "Your age...11, age
end
13
И. Л. Артёмов. Fortran: основы программирования
Результат работы пр01раммы:
Enter your year...1975
Your age... 30
Press any key to continue
В этом примере значение текущего года хранится в константе
current_year. Обычно в роли констант выступают данные, которые в ходе
программы изменяться не должны. Например, физические постоянные, по-
стоянные коэффициенты в выражениях, границы массивов, число повторе-
ний циклов и др.
Во время работы программы значения, заданные константам, не изменя-
ются. Константам нельзя присваивать новые значения. Так, нижеприведен-
ный пример ошибочен.
program parameter_error
integer, parameter :: M=100
M=300 ! ошибка
write(*,*) М
end
Рассмотрим пару полезных примеров.
Пример. Увеличение значения переменной на единицу.
program increase
integer k
k=10
k=k+l
write(*,*) k
end
Результат работы программы:
11
Press any key to continue
В строке k=k+l происходит увеличение значения переменной к на едини-
цу, т. е. берется предыдущее значение переменной к, увеличивается на еди-
ницу и присваивается заново переменной к. Следует заметить, что подобные
присваивания широко используются при написании программ, и важно за-
помнить этот пример. Аналогично следующие операторы присваивания вы-
полняют такие действия:
• уменьшение на единицу: к=к-1
• увеличение в два раза; к=к*2
• смена знака: к=-к
Пример. Даны две переменные Л=10 и Ь=50. Требуется поменять места-
ми значения переменных, т. е. чтобы в переменной к было значение перемен-
ной Ь, а в переменной Ь значение к.
Если написать в программе строку
к=Ь\ Ь=к
14
. 1. Первые программы
то задача будет решена неправильно, так как после присваивания к=Ь, в к бу-
дет содержаться значение 50 и старое значение 10 исчезнет. В результате
в обоих переменных будут значения 50 и 50.
Задачу можно решить, если ввести дополнительную переменную tmp и
записать такую последовательность операторов присваивания: tmp=k\ k=b\
b=tmp *.
program change
integer k, b, tmp
! k - первое число, ,b - второе число
! tmp - дополнительная переменная
k=10; b=50
write(*,*) "k = ",k," b = ",b ! исходные значения
tmp=k; k=b; b=tmp
write (*,*) ”k = ",k, 11 b = ",b ! измененные значения
end
Результат работы программы:
k = 10 b = 50
k - . 50 b = 10
Press any key to continue
Пример. Даны три переменные а=10, 6=20, с=30. Поменяйте местами
переменные так, чтобы а=30, 6=10, с=20.
Аналогично рассмотренному примеру можно составить следующую про-
грамму:
program change
integer :: а=10, b=20, с=30, tmp
tmp=a; a=c; c=tmp ! поменяем местами а и с
tmp=b; b=c; c=tmp ! поменяем местами b и с
write(*,*) "a = '',a," b = ",b," c = ",c
end
Результат работы программы:
a = 30 b = 10 c = 20
Press any key to continue
Итоги
1. Язык Fortran - мощный инструмент создания вычислительных про-
грамм. Программы создаются в средах разработки, включающие компилятор,
текстовый редактор и множество сервисных утилит.
2. Программа начинается с оператора program, заканчивается оператором
end и представляет собой последовательность операторов, каждый из кото-
рых записан в отдельной строке либо разделен символом ;.
* Есть и такой "экзотический" вариант без использования дополнительной перемен-
ной: k'=k+b; h=k-b; k=k-b.
15
И. П. Артёмов. Fortran: основы программирования
3. Для вывода данных из программы используется оператор write, для
ввода данных - оператор read. Форматированный ввод-вывод расширяет воз-
можности в обработке данных. Оператор write(*,*) означает вывод пустой
строки, оператор read(*,*) - ожидание нажатия клавиши ENTER.
4. Переменные и константы хранят обрабатываемые данные и могут со-
держать только одно-единственное значение. Значение переменной в ходе
работы программы может изменяться, значение константы постоянно. Для
задания имен используются английские буквы, цифры 0-9, знак подчеркива-
ния. Большие и маленькие буквы не различаются.
5. Для помещения в переменную определенного значения употребляется
оператор присваивания. Запись к=к+1 означает увеличить значение перемен-
ной к на единицу, к=~к - смену знака.
6. Чтобы поменять местами значения двух переменных, используют до-
полнительную переменную и зри оператора присваивания: tmp=cr, a=b;
b=tmp.
7. Рекомендуется придерживаться рельефной и блочной записи исходно-
го текста программы, снабжать программу комментариями.
16
2. ТИПЫ ДАННЫХ
Перед созданием программы рекомендуется определить, сколько потре-
буется переменных и какие данные будет обрабатывать программа. Или это
будет обработка только целых чисел или дробных, или разбор текстовой ин-
формации, или одновременно и то, и другое, и третье. В связи с этим в про-
граммировании существует очень важное понятие - тин данных. Это ключе-
вой термин не только в языке Fortran, но и во многих других языках про-
граммирования, и важно понять, какие типы данных бывают, чем они
различаются, где и когда используются, какие есть Офаничения и правила.
2.1. Тип integer - целочисленный тип
Целочисленный тип integer используется для описания переменных, ко-
торые будут хранить целые числа, например -900, 0, 1245, 1, 7. Оператор
описания
integer а,Ь
объявляет переменные с именами а и Ь целого типа. По умолчанию также
действует правило: если переменная в начале программы была не объявлена
и имя переменной начинается с букв i, j, к, I, т, п, то переменная считается
целого типа. Например, в следующей программе переменные с именами П,
к5, nt не объявлены, но имеют целый тип.
program silence
il=100; k5=500; nt=900 ! il, k5, nt -- целого типа
write(*,*) il+k5+nt
end
Однако данным "средством" следует пользоваться аккуратно, так как по-
добные "умолчания" могут приводить к долгому поиску ошибок. Для отклю-
чения такого правила следует пользоваться оператором implicit none (ни од-
ного неявного), который требует явного объявления всех переменных. Так,
в следующем примере будут ошибки компиляции, так как переменные <1 и jl
не объявлены.
program attention
implicit none
il=20; jl=30
end
Существует несколько разновидностей целого типа (табл. 2.1), каждый из ко-
торых отличается отводимой памятью под переменные и диапазоном пред-
ставления.
21И4/1О17ИИ0И
17
И. Л. Артёмов. Fortran: основы, программирования
Таблица 2.1. Разновидности целого типа
Типы Число байтов Диапазон представления
byte или integer (I) 1 от-128 до +127
integer (2) 2 от -32768 до +32767
integer (4) 4 от -2147483648 до+2147483647
Например, integer(2) R означает, что переменная R целого типа занимает
в памяти 2 байта и может принимать значения от -32768 до +32767. Оператор
присваивания R=125 ошибки не содержит, но Я=50000 содержит ошибку -
переменной R присвоили недопустимое значение, так как максимально воз-
можное значение для этого типа данных 32 767. Для обработки значения
50 000 следует применить тип integer(4). Если используются еще ббльшие
числа, то можно посоветовать другие типы данных, которые будут рассмот-
рены позже, либо изменить алгоритм решения задачи.
Пример. Объявление переменных целого типа.
integer(4) S
integer(2) :: k=10000
integer :: A=2#1000101, B=8#135, C=#FFEE55 ! или C=16#FFEE55
В последнем операторе переменным А, В, С присвоены целые значения
в двоичном, восьмеричном и шестнадцатеричном представлении.
По умолчанию все переменные, объявленные как integer, объявляются
как integer(4). Данную настройку можно изменить, выполнив цепочку дейст-
вий: Project -> Settings -> Fortran -> Category -> Fortran Data -> Default
Integer Kind.
Пример. Программа увеличения целого числа на шаг.
program increase_dx
integer(1) dx ! шаг
integer(l) :: x0=10 ! начальное число
write(*,*) "xO = ", xO
write(*,"(A,\)") "Enter step = read(*,*) dx
x0=x0+dx
write(*,*) "Next = ", xO
end
Результат работы программы:
xO = 10
Enter step = 1
Next =11
Press any key to continue
При вводе значения 250 программа выдаст ошибку и завершится, так как
диапазон числа типа integer( 1) от -128 до +127.
18
2. Типы данных
Интересным будет результат, если ввести значение для шага 125
хО = 10
Enter step = 125
Next = -121..
Press any key to continue
Получился отрицательный ответ! Здесь также возникает ошибка с выхо-
дом из диапазона представления. Подобные ситуации часто приводят к труд-
нообнаруживаемым ошибкам, поэтому перед составлением программы сле-
дует четко определить, какие допустимые данные может обрабатывать ваша:
программа.
Для форматного ввода и вывода целых чисел используется /-дескрип-
тор преобразования.
Пример:
program format_i
integer k
k=1234
write(*, ' (i4)') k
end
Здесь i4 означает вывод целого числа, длина которого не более четырех по-
зиций.
Результат:
1234
Press any key to continue
Если указать меньше позиций, то будет выведен ошибочный результат.
Например:
write(*,'(13)') 1234
write(*,'(i4)') -1234
Результат:
* * *
•k * * *
Press any key to continue
При задании позиций больше требуемых неиспользованные позиции за-
полняются пробелами.
к=1234
write(*,'(ilO)') к
1234
Press any key to continue
При одновременном выводе текста и целых чисел следует указывать че-
рез запятую соответствующие дескрипторы преобразования, например:
write Г, '(A,i2,A,i4) ’)"Date...",29," April ",2005
19
Й. Л. Артёмов. Fortran: основы программирования
Пример. Составить программу, которая запрашивает у пользователя за-
работную плату каждого месяца и выводит общий доход за 3 месяца.
program money
integer mt ! деньги за текущий месяц
integer : .- sum=0 ! итоговая сумма
write(*,100) "April..."; read(*,*) mt; sum=sum+mt
write(*,100) "May..."; read(*,*) mt; sum=sum+mt
write(*,100) "June...”; read(*,*) mt; sum=sum+mt
write(*,"(A,i5)") "Profit...",sum
100 format (A, \)
end
Результат работы программы:
April...100
May...500
June...400
Profit... 1000
Press any key to continue
Переменная mt используется для поочередного хранения заработной пла-
ты в каждом месяце, переменная sum - для накопления итоговой суммы, ко-
торой задается начальное значение 0, что означает: работник на начало тру-
довой деятельности денег не заработал.
В начале работы профаммы на экран выводится приглашение, ввести
количество денег, заработанных в 1-м месяце:
April...
Допустим, пользователь наберет 100 и нажмет клавишу ENTER. Тогда mt
примет значение 100 и sum будет содержать 100, так как sum=0+100= 100.
Затем запрашивается информация о следующем месяце:
Мау. . .
Пользователь наберет 500 и нажмет ENTER. Переменная mt примет зна-
чение 500, sum будет содержать 600, так как sum= 100+500=600.
И наконец, информация за последний месяц:
June...
Пользователь наберет 400 и нажмет ENTER. Переменная mt примет зна-
чение 400, sum будет содержать 1000, так как sum=600+400=1000.
Таким образом, переменная sum будет накапливать сумму заработанных
денег. Такие переменные называют переменными-сумматорами. Схожими по
смыслу являются переменные-счетчики, которые увеличивают свое значение
на единицу при выполнении определенных действий.
Следующая программа демонстрирует использование счетчика, который
определяет количество выведенных месяцев. В роли счетчика выступает пе-
ременная L
20
2. Типы данных
program money
integer mt
Integer :: i=0 ! переменная-счетчик
integer :sum=0 ! итоговая сумма
write(*,100) "April..."; read(*,*) mt; sum=sum+mt; i=i+l
write(*,100) "May..."; read)*,*) mt; sum=sum+mt; i=i+l
write(*,100) "June..."; read)*,*) mt; sum=sum+mt; i=i+l
write(*,"(2(A, i4))") "Profit about11,!," months ...", sum
100 format(A,\)
end
При каждом вводе заработной платы значение переменной i увеличива-
ется на единицу.
Результат работы программы:
April...100
Мау...500
June...400
Profit about 3 months... 1000
2.2. Тип real - вещественный тип
Целые числа в основном используются в качестве счетчиков, номеров и
индексов. Однако в повседневной жизни нас окружают дробные (не целые)
числа. Для хранения и обработки таких данных применяется вещественный
тип real (реальный, вещественный). Существуют следующие разновидности
этого типа данных (табл. 2.2).
Таблица 2.2. Разновидности вещественного типа
Типы Бай- ты Диапазон представления Точность^ знаков
real(4) 4 от -3.4*10+38 до-1.2*10-38 от +1.2*10-38 до+3.4*10+38 7
real(8) или double precision (двойной точности) 8 от -1.8*10+308 до -2.2*10-308 от +2.2*10-308 до 1.8*10+308 15
При использовании оператора real все переменные объявляются как
real(4). Эту настройку также можно изменить, выполнив действия: Project ->
Settings -> Fortran -> Category -> Fortran Data -> Default Real Kind.
Для задания вещественным переменным значений используются сле-
дующие формы записи:
х=3.5 ! обычная десятичная запись, разделитель точка
у=3.4Е+10 1 экспоненциальная форма 3.4 • 1О10
21
И. Л. Артёмов. Fortran: основы программирования
P=-1.78D+45 ! двойной точности 1.78 • 1045
Пример. Объявление переменных вещественного типа,
real а
double precision :: koef=1.45D0
При форматном вводе и выводе вещественных чисел применяются сле-
дующие дескрипторы преобразования.
F-дескриптор преобразования
Например, f5.2 означает, что под число отводится 5 позиций, из них 2 по-
зиции после занятой, 1 позиция для точки и 2 - для целой части и знака,
т. е. максимальное число будет 99.99, минимальное -9.99.
program format_f
real,parameter :: pi=3.1415926
write(*,'(f5.2)') pi
write(*,'(f15.5)') pi
write(*,' (f5.5) ') pi ! ошибка
end
Результат работы программы:
3.14
3.14159
* * * * *
Press any key to continue
Е-дескриптор преобразования
Например, el2.6 означает, что под число отводится 12 позиций, из них 4
для обозначения десятичной экспоненты, знака и показателя степени (Е+01),
6 под дробную часть, 1 под точку и 1 под знак.
program format_e
real,parameter :: pi=3.1415926
write(*,'(el5.6)') pi
write(*,'(e7.2)') pi
write(*,'(e5.5)') pi ! ошибка
end
Результат работы программы:
0.314159E+01
.31E+01
*****
Press any key to continue
Пример. Рассчитать площадь круга и длину окружности.
Известно, что площадь круга выражается формулой S = п • R2, длина ок-
ружности I = 2 • л • 7?, поэтому программа будет следующей:
22
2. Типы данных
program circle
real, parameter :: pi=3.1415926 ! число ПИ
reals, R, L ! S-площадь круга
! R-радиус круга
! L-длина окружности
write(*,"(A, \)") " Enter R
S=pi*R*R
write(*,*) "Square...", S
L=2*pi*R
write(*,*.) "Length...", L
end
Результат работы программы:
Enter R = 1.0
Square... 3.141593
Length... 6.283185
Press any key to continue
Пример. Даны 5 сопротивле-
ний: /?1, /?2, R3, R4, R5, соединен-
ных параллельно (рис. 2.1). Найти
общее сопротивление цепи.
Обозначая за R0 общее сопро-
тивление цепи и применяя извест-
ную формулу из курса физики, на-
ходим
11 1 1 1 1
RO Rl + R2+ R3 + R4 + R5'
program resistance
real Rl, R2, R3, R4, R5, RO
! R1-R5 сопротивления цепи
! RO - общее сопротивление
read(*,*) R
Рис. 2.1. Параллельное соединение
проводников
write(*,100) "Rl
write(*,100) "R2
write(*,100) ”R3
write(*,100) "R4
write(*,100) "R5
= "; read(*,*) Rl
= "; read(*,*) R2
= "; read(*,*) R3
= "; read(*,*) R4
= "; read(*,*) R5
R0=1.0/Rl + 1.0/R2 + 1.0/R3 + 1.0/R4 +1.0/R5
R0=1.0/R0
write(*,"(A, f7.4)") "Resistance = ", RO
100 format(A,\)
end
23
И. Л. Артёмов. Fortran: основы программирования
Результат работы программы:
R1 = 10
R2 = 5
' R3 = 4
R4 = 1
R5 = 20
Resistance = 0.6250
Press any key to continue
Эту программу можно было записать с использованием только двух пе-
ременных.
program resistance
real R, RO ! R текущее сопротивление,
! RO общее сопротивление
write(*,100) "R1 = read)*,*) R; R0=l/R
write(*,100) "R2 = read)*,*) R; R0=R0+l/R
write(*,100) "R3 = read(*,*) R; R0=R0+l/R
write(*,100) "R4 = "; read(*,*) R; R0=R0+l/R
write(*,100) "R5 = read(*,*) R; R0=R0+l/R
R0=l/R0
write(*,"(A,f7.4)") "Resistance = ", R0
100 format(A,\)
end
Переменная R последовательно принимает значение первого сопротив-
ления, затем второго, третьего и т. д. При этом переменная R0, соответствен-
но также поочередно будет накапливать в себе значения 1/Л1, 1/Я1+1/Л2
и т. д. до 1 /R1+1/R2+1/R3+1/R4+1/R5.
2.3. Программирование
арифметических выражений
При работе с целыми и вещественными числами доступны 6 арифмети-
ческих операций (табл. 2.3).
Таблица 2.3. Арифметические операции
Опера- ция Название Уровень Порядок выполнения Пример
** Возведение в степень Первый <— справа налево а**3
* Умножение Второй —> слева направо 3.0*Ь
/ Деление Второй —> слева направо а/Ь
+ Сложение Третий —> слева направо а+Ь
— Вычитание Третий —► слева направо х-у
— Смена знака Третий —► слева направо —5*х
24
2. Типы данных
При написании арифметических выражений следует придерживаться
простых правил.
1. Соблюдать очередность выполнения арифметических операций. Сна-
чала выполняется возведение в степень (операция первого уровня), затем ум-
ножение и деление (операции второго уровня) и в последнюю очередь сло-
жение, вычитание и смена знака (операции третьего уровня). Операции одно-
го уровня выполняются последовательно друг за другом, в порядке
выполнения.
Пример:
а=2 + 3*2 + 2*2**3 - 1
Сначала выполнится операция возведения в степень и выражение упро-
стится:
а=2 + 3*2 + 2*8 - 1
Затем происходят последовательно слева направо операции умножения:
а=2 + 6 + 16 - 1
Наконец, выполняются последовательно слева направо операции сложе-
ния и вычитания:
а=23
Пример:
а=5*10**2 + 2**2**3 + 1
Сначала выполнятся последовательно справа налево операции возведе-
ния в степень 2**3=8, 2**8=256, 10**2=100 и выражение упростится:
а=5*100 +256+1
Затем выполнится умножение:
а=500 + 256 + 1
Наконец, последовательно слева направо происходят операции сложения,
а=7 57
2. Для изменения очередности выполнения арифметических операций
используются пары круглых скобок. При этом в первую очередь выполняют-
ся операции в самых внутренних скобках.
Пример:
а=(5+3)*(4+2) + ((2+1)*2)**2 +2*5-5
В первую очередь выполнится операция сложения в скобках (2+1):
а=(5+3)*(4+2) + (3*2)**2 +2*5-5
Затем вычисляются выражения в скобках (5+3), (4+2), (3*2):
а=8*6 + 6**2 + 2*5 - 5
После вычислений в скобках выполнится операция возведения в степень:
6**2:
а=8*6 + 36 + 2*5 - 5
25
И. Л. Артёмов. Fortran: основы программирования
Затем слева направо последовательно операции умножения:
а=48 +. 36 + 10 -5
И наконец, друг за другом слева направо операции сложения и вычитания:
а=89
3. Два знака арифметических операций не могут стоять рядом, однако
если это необходимо, то используются скобки.
Правильно: 5+(-3)*(1+4); 3/(-2); 8*(-3).
Неправильно: 5+-3*(1+4); 3/-2; 8*-3.
Примеры записи алгебраических выражений с учетом рассмотренных
правил представлены в табл. 2.4
Таблица 2.4. Примеры записи алгебраических выражений
Математика Fortran Математика Fortran
2a + b(c + d) 2.0*a+b*(c+d) 2m k(a + b) 2.0*m/(k*(a+b))
2 а + Ь 2.0/(a+b) k(a + b) 2m k*(a+b)/(2.0*m)
а + Ь ~2~ (a+b)/2.0 1 (a + h)r+i (a+b)**(1.0/(c+l))
а + Ь c + d (a+b)/(c+d) \l(a + b')2 (a+b)**(2.0/3.0)
1 Ах2 1.0/dx**2 или 1.0/(dx*dx) или 1.0/dx/dx (Q + Ht+1 Vе + d) ((a+b)/(c+d))** ((k+l)/3.0)
При использовании целых и вещественных чисел в выражениях необхо-
димо учитывать следующее:
1. Результат операции над двумя целыми числами всегда целое число.
Поэтому при использовании операций / и ** могут появиться "неожиданные"
результаты.
Выражение Математика Fortran
1/2 0.5 0
7/3 2.333333 2
2**(-2) 0.25 0
64**(l/2) 8 1
При делении целых чисел результат - целая часть от деления. Возведе-
ние целого числа в отрицательную степень также означает деление единицы
на число в положительной степени.
26
2. Типы данных
2. Результат операции над двумя вещественными, над целым и вещест-
венным числами всегда вещественный.
Выражение Математика Fortran
1.0/2 0.5 0.5
7/3.0 2.333333 2.333333
2.0**(-2) 0.25 0.25
64**( 1.0/2) 8 8.0
Однако использовать в выражениях целые и дробные числа следует с ос-
торожностью.
Пример:
program arithmetic
integer i, k;
real a,cl,c2
i=4; k=3; a=l.5
cl=i/k*a; c2=i*a/k
write(*,*) cl, c2 1 результаты 1.5 и 2.0
end
В первом случае (переменная cl) целочисленное деление дает результат
4/3=1, и поэтому 1*1.5=1.5. Во втором (переменная с2) умножение целого на
вещественное приводит к вещественному числу 4*1.5=6.0 и деление вещест-
венного на вещественное дает вещественное число 6.0/2.0=3.0. Для избежа-
ния подобных "ошибок" рекомендуется записывать целые числа как вещест-
венные или использовать вещественные переменные. Так, например, выра-
жение 3/4*dx правильнее записать как 3.0/4.0*dx, или 3.0/4*dx.
Следует также помнить, что нельзя возводить отрицательное число (це-
лое или вещественное) в вещественную степень. Например, запись а=
=(-3)**1.3 вызовет ошибку. В этом случае вычисление выражения происхо-
дит по формуле схр(1.3*1п(-3)), но логарифм отрицательного числа не суще-
ствует. В то же время возводить отрицательное число в целую степень мож-
но. Выражение а=(-3)**3 ошибки не содержит. Здесь возведение в степень
вычисляется как последовательное умножение, т. е. (-3)*(-3)*(-3)=-27. Так-
же если требуется возвести число в целую степень, то предпочтительнее пи-
сать, например, а**3, чем а**3.0, так как извлечение экспоненты и натураль-
ного логарифма требует гораздо большего времени, чем последовательное
умножение.
Пример. Составить программу, которая выводит 0, если введенное число
нечетное, 1 - если четное.
program numbers
integer k, res
write(*, "(A,\)") "Enter number..."; read(‘,‘) k
res=((-1)**k+l)/2
27
И. Л. Артёмов. Fortran: основы программирования
write(*,*) res
end
Результат работы программы:
Enter number...12
1
Press any key to continue
Enter number...11
0
Press any key to continue
В заключение урока заметим, что ограниченность в точности веществен-
ных чисел (7 знаков для типа real и 15 знаков для типа double precision) и, как
следствие, округление результатов вычислений приводит к нарушению пере-
местительного, сочетательного и распределительного правил арифметики.
Это означает, что одни и те же формулы, вычисленные разными способами,
приведут к разным результатам.
Пример:
program paradox
real a, b, с, d, е, f, g, h
a=l.0/1000007; b=l.0/1000005
c=l.0/1000003; d=l.0/1000001
e=l.0/100009; f=l.0/100011
g=l.0/10013; h=l.0/10015
write(*,*) a+b+c+d+e+f+g+h
write)*,*) g+h+f+e+d+c+b+a
write)*,*) (a+c+e+g) + (b+d+f+h)
end
Результат работы программы:
2.2371837E-04
2.2371835E-04
2.2371838E-04
Press any key to continue
Хорошо видно, что три результата, выполненные по обычным правилам
арифметики, получились совершенно разными! Более точным результатом
является первое число.
Пример:
program paradox
real a, b
а=1.0/3; b=4.0/7
write)*,*) (a+b)**2
write)*,*) a**2 + 2*a*b + b**2
end
28
2. Типы данных
Результат работы программы:
0.8185941
0.8185942
Press any key to continue
В результате ошибок округления получили непохожие результаты. Более
точным является первое число.
Опишем некоторые рекомендации, позволяющие сократить накопление
ошибок округления и повысить точность расчетов:
• не следует вычитать близкие числа;
• избегать деления больших чисел по модулю на малые числа;
• сложение или вычитание длинной последовательности чисел начинать
с меньших чисел;
• стремиться уменьшить количество операций;
• не использовать сравнение на равенство вещественных чисел.
Следует также заметить, что точность вычислений можно повысить, если
использовать тип double precision. В этом случае результаты приведенных
выше примеров будут одинаковыми. Поэтому если требуется повысить точ-
ность вычислений, то следует установить настройку компилятора:
Project -> Settings -> Fortran -> Category -> Fortran Data ->
Default Real Kind -> 8
или использовать тип double precision в программе.
2.4. Стандартные математические функции
и подпрограммы
Программирование научных и инженерных задач всегда связано с ис-
пользованием математических функций: тригонометрических, логарифмиче-
ских и др. Fortran предоставляет богатый выбор таких встроенных (стандарт-
ных) функций (табл. 2.5).
Рассмотрим, например, функцию вычисления квадратного корня - sqrt(x).
Здесь sqrt - имя функции, х - аргумент функции. Чтобы вычислить V4 , пи-
шут a=sqrt(4.0).
При использовании функций важно знать, какого типа должен быть ар-
гумент функции и возвращаемое функцией значение. Например, запись
p=sqrt(4) вызовет ошибку, так как по правилам (см. табл. 2.5) аргумент х
должен быть типа real. В то же время запись sqrt(4.0) ошибки не содержит.
Использование функций приводит к следующей очередности выполне-
ния математических операций:
• вычисление аргумента функций в скобках;
• вычисление значения функций;
29
И. Л. Артёмов. Fortran: основы программирования
• операция возведения в степень;
• умножение и деление;
• сложение и вычитание, смена знака.
Таблица 2.5. Некоторые стандартные математические функции
Имя Описание Тип аргу- мента Тип резуль- тата
abs(х) Модуль числа х real real
cos(х) Косинус х задан в радианах * real real
sin(х) Синус real real
tan(х) Тангенс real real
cotan(х) Котангенс real real
acos(х) Арккосинус real real
asin(х) Арксинус real real
atan(х) Арктангенс real real
acotan(х) Арккотангенс real real
cosd(х) Косинус хзадан в градусах real real
sind(x) Синус real real
tand(x) Тангенс real real
cotand(x) Котангенс real real
sqrt(x) Извлечение квадратного корня real real
exp(x) Извлечение экспоненты real real
log(x) Натуральный логарифм real real
loglO(x) Десятичный логарифм real real
sinh(x) Гиперболический синус real real
cosh(x) Гиперболический косинус real real
tanh(x) Гиперболический тангенс real real
cotanh(x) Гиперболический котангенс real real
mod(a,b) Вычисление остатка от деления integer integer
max(xl, . . . , xn) Вычисление максимального значения integer или real integer или real
min(xl,..., xn) Вычисление минимального значения integer или real integer или real
floor(x) Наибольшее целое, меньшее или равное вещественному х real integer
ceiling(x) Наименьшее целое, большее или равное вещественному х real integer
Например, в выражении 2*sin(x+5) сначала выполнится операция сложе-
ния в скобках, затем вычисляется функция sin и полученный результат будет
* Для перевода градусов в радианы используется формула: рад=град я/180.
30
2. Типы данных
умножен на 2. В табл. 2.6 приведены примеры математических выражений
с использованием стандартных функций.
Таблица 2.6. Использование математических функций
Математика Fortran Математика Fortran
4а sqrt(a) COS (Vn + l) cos(sqrt(a+l))
sin2 а sin(a)**2 log3fl log(a)/log(5)
еа ехр(а) -ь±4р 2a xl=(-Z>+sqrt(D))/(2*a) x2=(-Z>-sqrt(D))/(2*a)
cos2 (2х2) cos(2*x**2)**2 x2-4 x|x-2| (x**2-4)/(x*abs(x-2))
Некоторые советы по программированию математических выражений.
1. Набор формул рекомендуется начинать с расстановки основных ско-
бок выражения. Причем открытую скобку следует сразу закрывать и продол-
жать набор формул внутри скобок. В этом случае число открываемых скобок
всегда будет соответствовать числу закрываемых и вероятность допустить
ошибку будет минимальна.
Например:
1-й этап:а=()/()+()/(О+());
2-й этап: а=(х+1)/(х-1)+()/(()+());
3-й этап: а=(х+1)/(х-1)+(3-х)/((х+1)**2+(х+1.0/х**3)).
2. Большие формулы лучше разбивать на отдельные смысловые части.
Если есть повторяющиеся выражения, то их также следует записывать от-
дельно.
Пример:
7—~ J—~
Ь +---Г 1 а +----Т
1 + Ах2 у 1 + Ду2
Здесь можно разбить исходную формулу на две части:
sl=(x-a)**3/(Ь+1.0/(1.0+dx**2))
s2=sqrt((у-b)**2/(а+1.О/(1.0+dy**2)))
lambda=sl+s2
Пример:
sm
/+У~2
„2 ,
Обозначим повторяющиеся выражения за новые переменные:
а=х**2+1; b=y**3+y-2; f=cos(a)-sin(а)/Ь+Ь/а
31
И. Л. Артёмов. Fortran: основы программирования
3. Если имеются похожие формулы, то их лучше записывать друг под
другом для более легкой проверки.
Пример:
а=(aw-2*a+ae)/dx**2 + &
(an-2*a+as)/dy**2*
При программировании математических выражений часто бывает удобным
использовать функции преобразования типов. Например, требуется вычислить
квадратный корень от переменной koef целого типа. При попытке записать
c=sqrt(koef) будет ошибка, так как koef должна быть вещественного типа. Однако
если воспользоваться функцией REAL (приведение к вещественному типу), то
программа будет записана верно; c=sqrt(REAL(koef)). В следующей таблице
представлены некоторые функции преобразования типов.
Имя Описание Тип аргумента Тип результата
INT Преобразование в целый тип, выделение целой части real, double precision integer(4)
INT2 integer(2)
REAL Приведение к вещест- венному типу integer, double precision real
DBLE Приведение к типу двой- ной точности integer, real double precision
Пример. Найти площадь треугольника, заданного тремя точками
(рис. 2.2).
Рис. 2.2. Треугольник на плоскости
Можно было привести выражение к общему знаменателю. Однако практика пока-
зывает, что лучше программировать таким способом: в этом случае легче прове-
рять и изменять программу. Рекомендуется также набирать выражения как мож-
но ближе к исходным математическим формулам.
32
2. Типы данных
Из курса аналитической геометрии известно, что площадь треугольника
1 лс, - х. у, — у,
на плоскости выражается формулой S — ±— •
2 х2-х3 у2-у3
program triangle
real xl,yl, х2,у2, хЗ,уЗ ! координаты точек
real S ! площадь треугольника
write(*,*) "Enter point 1"
write(*,"(А, \)") "xl,yl = read)*,*) xl,yl
write(*,*) "Enter point 2"
write(*,"(A, \) ") "x2,y2 = read)*,*) x2,y2
write)*,*) "Enter point 3"
write(*,"(A,\)") "x3,y3 = read)*,*) x3,y3
S=abs((xl-x3)*(y2-y3)-(yl-y3)*(x2-x3))/2 1 нашли площадь
write)*,*) "Square of triangle",S
end
Результат работы программы:
Enter point 1
xl, yl = 0, 0
Enter point 2
x2,y2 = 4, 0
Enter point 3
x3,y3 = 0, 3
Square of triangle 6.000000
Press any key to continue
Пример. Записать произвольное положительное трехзначное число в об-
ратном порядке. Например: 347 -> 743.
program reverse ! number - исходное число
integer number, il,i2,i3 ! il, i2, i3 - единицы, десятки, сотни
write)*,"(A,\)") "Enter number..."; read)*,*) number
il=mod(number,10); number=number/10
i2=mod(number,10); number^number/10
i3=mod(number,10);
number=il*100+i2*10+i3
write)*,*) number
end
Результат работы программы:
Enter number...235
532
Press any key to continue
Наряду co стандартными функциями имеются стандартные подпрограм-
мы, выполняющие различные математические вычисления. В дальнейшем
нам понадобятся случайные числа. Для генерации случайных чисел исполь-
зуется подпрограмма random_number(x), которая формирует случайное веще-
33
И. Л. Артёмов, Fortran: основы программирования
ственное число х в диапазоне 0.0 < х < 1.0. Для вызова подпрограммы ис-
пользуется оператор call (вызов), например: call random_number(x).
Для формирования целых случайных чисел следует преобразовать полу-
ченное случайное число вещественного типа в целое, используя функцию
INT. Ниже представлены примеры по формированию случайных целых чисел
в заданном диапазоне.
0<Л<10 call random_number(х)
k=INT(ll*x)
—5 < к <5 call random_number(х)
k=INT(ll*x)-5
-23<&<45 call random_number (x)
k=INT(69*x)-23
Подпрограмма random number формирует случайные числа, однако при
каждом новом запуске программы эти числа будут повторяться. Чтобы уст-
ранить повторяемость, следует вызвать подпротрамму random_seed(), которая
инициализирует процесс генерации случайных чисел.
Пример:
program random_numbers
real х ! случайное вещественное число
integer к ! случайное целое число
call randomseed ()
call random_number(x)
k=INT(x*ll)
write (*,*) "Random number...'1, к
end
В течение трех последовательных запусков программы получатся сле-
дующие результаты:
Random number... 10
Press any key to continue
Random number... 3
Press any key to continue
Random number... 2
Press any key to continue
Если закомментировать строку call random_seed(), то результаты запуска
npoipaMMbi будут одинаковыми:
Random number... 0
Press any key to continue
Random number... 0
Press any key to continue
Random number... 0
Press any key to continue
34
2. Типы данных
При решении вычислительных задач могут возникать ошибки деления на
нуль, получения чисел вне диапазона представления, недействительных чи-
сел. Например, в следующей программе при вводе показателя Т=100 полу-
чится слишком большое число и на экран будет выдано сообщение Infinity -
бесконечность.
program infinity
real Т, Р
write (*, *) "-----EXPONENTA:-----"
write(*,"(А,\)") "Т = read(*,*) Т
Р=ехр(Т)
write(*,*) "Р = ",Р
end
Результат работы программы:
----- EXPONENTA -----
Т = 100
Р = Infinity
Press any key to continue
В следующем примере происходит ошибка во время выполнения про-
граммы - вычисление квадратного корня из отрицательного числа,
program sqrt_error
real х
write(*,"(A,\)") "x = ”; read(*,*) x
x=x-1.0
write(*,*) sqrt(x)
end
Результат работы программы:
x = 0.5
run-time error M6201: MATH
- sqrt: DOMAIN error
На практике подобные результаты не редкость, особенно на первых ста-
диях написания программ. Как правило, причиной может являться непра-
вильная запись расчетных формул, неверная постановка математической мо-
дели и ошибочная запись алгоритма расчета. Умение находить причину та-
ких результатов зависит как от внимательного просмотра текста программы,
так и от опыта программирования задач.
2.5. Комплексный тип complex
Достоинством языка Fortran является возможность использования
комплексного типа данных, что важно для решения многих инженерных
задач. Для объявления переменной комплексного типа используется опе-
ратор описания complex (комплексный). Существует две разновидности
комплексного типа:
35
И. Л. Артёмов. Fortran: основы программирования
Типы Байты
complex (4) 4
complex (8) или double complex 8
По умолчанию все переменные, объявленные как complex, объявляются
как complex(4).
Пример. Объявление переменных комплексного типа.
complex :: i=(0.0,1.0) ! мнимая единица
complex :: z=(2.0,3.0) ! число 2+3i
Чтобы присвоить переменной комплексного типа значение, в скобках
сначала записывают действительную часть, затем через запятую мнимую
часть. Таким образом, комплексное число записывается как пара упорядо-
ченных вещественных чисел.
Над комплексными числами доступны все рассмотренные выше арифме-
тические операции и стандартные функции. Причем для комплексных дан-
ных имя функции должно начинаться с буквы "с". Следует помнить, что при
работе с комплексными и вещественными числами результат будет ком-
плексным.
Форматируемый ввод и вывод комплексных данных осуществляется при
помощи удвоенного вещественного формата.
Пример. Вывод комплексных чисел.
program complex_nurriber
complex :: i=(0.0, 1.0) 1 мнимая единица
write(*,"(2f5.2)“) i
end
Результат работы программы:
0.00 1.00
Press any key to continue
При выводе с форматом по умолчанию на экран автоматически выдаются
пары круглых скобок.
program complex_nuinber
complex :: i=(0.0,1.0) ! мнимая единица
write(*,*) i
end
Результат работы программы:
(О.ОООООООЕ+ОО, 1.000000)
Press any key to continue
Пример. Составить программу нахождения корней квадратного уравне-
ния, когда дискриминант D <0.
Известно, что при решении квадратного уравнения могут появляться
комплексные корни, если дискриминант меньше нуля. Принимая во внима-
ние известные формулы
36
2. Типы данных
сгх +b-xic = O,D = b - 4 • а • с, х =----, х -------,
2-а 2 а
программа будет следующей:
program sqr_equation
complex xl,x2,D
real a,b,c
! xl, x2 - корни квадратного уравнения
! D - дискриминант
! a, b, с - коэффициенты квадратного уравнения
write(*," (A,\) ") "Coefficients..."; read(*,*) a,b,c
D=b**2-4*a*c
xl=(-b+csqrt(D))/(2*a)
x2=(-b-csqrt(D))/(2*a)
write(*,*) " xl = ",xl
write(*,*) " x2 = ",x2
end
Результат работы программы для уравнения 2хг + 4х + 4 = 0:
Coefficients...2 4 4
xl = (-1.000000,1.000000)
x2 = (-1.000000,-1.000000)
Press any key to continue
При работе с комплексными числами в математике применяются две
специальные операции: взятие действительной и мнимой частей комплексно-
го числа. Для этого используются две стандартные функции, возвращающие
вещественные значения:
aimag(z) - возвращает мнимую часть;
real(z) - возвращает вещественную часть, где z - переменная комплексно-
го типа.
Пример. Программа проверки формулы Эйлера и формулы Муавра.
Известно, что существует связь между экспонентой и тригонометриче-
скими функциями, выражающаяся формулой Эйлера etlf =cos<p+isin<p
и формулой Муавра е'"9 = cos лф + i sin лф = (cos ф + i sin ф)".
program Euler_Muavr
complex a,b
complex :: i=(0.0,1.0) ! мнимая единица
real :: fi=3.14/3 ! угол в радианах
integer n ! показатель степени
a=cexp(i*fi) ’.проверка формулы Эйлера
b=cos(fi)+i*sin(fi)
write(*,*) " exp = ",a
37
И. Л. Артёмов. Fortran: основы программирования
write(*,*) "cos + sin = ",b
n=3 !проверка формулы Муавра
а= cexp(i*n*fi)
b=(cos(fi)+i*sin(fi)) **n
write(*,*) "sin = ”,sin(n*fi), aimag(b)
write(*,*) "cos = ",cos(n*fi), real(b)
end
Результат работы программы:
exp = (0.5004596,0.8657598)
cos + sin = (0.5004596,0.8657598)
sin = 1.5924288E-03 1.5924361E-03
cos = -0.9999987 -0.9999986
Press any key to continue
Пример. Вычислите значение функции комплексного переменного
Z “Ь Z 7 . .
z) =----Y'e ’ПР11 z = l + i, Z = i.
(г + z)
program funct_complex
complex, parameter :: i=(0.0,1.0) ! мнимая единица
complex z, fz
write(*,"(A,\)") "Enter z = "; read(*,*) z
fz= (z*z+z)/(z+i)**2*cexp(z)
write(*,*) fz
write(*,"(/,A,\)") "Enter z = "; read(*,*) z
fz=(z*z+z)/(z+i)**2*cexp(z)
write(*,*) fz
end
При вводе данных комплексного типа следует задавать вещественную
и мнимую части комплексного числа через запятую, в круглых скобках.
Результат работы профаммы:
Enter z - (£.0,1.0)
(1.718155,5.9727062Е-02)
Enter z = (0.0,1.0)
(0.3454433,7.5292170Е-02)
Press any key to continue
2.6. Логический тип logical
Многие задачи успешно решаются при помощи логических выражений.
Результат логического выражения может принимать только два значения:
.TRUE, (истина) и .FALSE, (ложь). Переменные, которые будут хранить ре-
38
2. Типы данных
зультаты логических выражений, должны быть объявлены при помощи опе-
ратора описания logical. Существует три разновидности логического типа:
Тип Байты
LOGICAL (1) 1
LOGICAL (2) 2
LOGICAL (4) 4
По умолчанию все переменные, объявленные как logical, объявляются
как logical(4). Данную настройку можно изменить, выполнив цепочку дейст-
вий: Project -> Settings -> Fortran -> Category -> Fortran Data -> Default
Integer Kind, т. e. число отводимых байтов памяти по умолчанию одинаково
для целого и логического типов.
Пример. Объявление переменных логического типа.
logical(2) res
logical :: status=.TRUE.
При составлении логических выражений используются следующие опе-
рации отношения (табл. 2.7).
Таблица 2.7. Операции отношения
Операция Название Операция Название
>, или .gt. Больше >=, или .ge. Больше или равно
<, или .It. Меньше <=, или .1е. Меньше или равно
==, или .eq. Равно \=, или .пе. Не равно
Пример. Использование операций отношения.
logical R
R=3<5 ! истина
R=3==8 ! ложь
R=3.1t.7 ! истина
При форматном вводе и выводе логических данных используется L-дес-
криптор преобразования, например при работе оператора
write(*,"(L)") 2<3, 6==9
на экран будут выданы два значения - истина и ложь:
т
F
Пример. Программа, которая выводит истину, если число положитель-
ное, ложь в противном случае.
program if_plus
integer а ! вводимое число
39
И. Л. Артёмов. Fortran: основы программирования
logical res ! результат проверки
write(*,"(А,\)") "Enter number..."; read(*,*) а
res=a>0
write(*,*) res
end
Результат работы программы:
Enter number...100
T
Press any key to continue
Enter number...-200
F
Press any key to continue
Пример. Программа проверки пароля пользователя,
program passw
integer, parameter :: password=123 ! правильный пароль
integer user ! введенный пароль
logical res ! результат проверки
write(*,"(A,\)“)"Enter password...1'; read(*,*) user
res=user==password
write(*,"(A, L)") "Authorization...", res
end
Результат работы программы:
Enter password... 150
Authorization... F
Press any key to continue
Enter password...123
Authorization... T
Press any key to continue
В связи с представлением вещественных чисел с определенной точно-
стью для сравнения на равенство двух вещественных чисел нельзя использо-
вать операцию ==, а следует записывать условие малости погрешности, на-
пример abs(x-y)<eps.
Пример. Вычислить основное тригонометрическое тождество в точке
л/4 и сравнить с единицей.
program trigonometry
real, parameter :: pi=3.14
real T ! результат вычисления тождества
logical res ! результат проверки
T=sin(pi/4) “2+cos (pi/4) “2
write(*,*) T
res=abs(T-l.0)<1Е-5 ! ------ правильное сравнение
write(*,*) res
40
2. Типы данных
end
Результат работы программы:
1.000000
т
Press any key to continue
Обозначая для краткости TRUE=1, FALSE=0, рассмотрим 6 логических
операций, определенных для логических данных.
1. Операция .AND. - логическое И (логическое умножение).
1 .AND. 1 = 1
1 .AND. 0 = 0
0 .AND. 1 = 0
0 .AND. 0 = 0
Пример. Условие попадания
в прямоугольник (рис. 2.3).
Математически условие по-
падания записывается как систе-
ма неравенств:
1-5 < х < 5;
[~3<у<3.
В более подробной записи:
х>-5 и х<5 и у>-3 и у<3,
Рис. 2.3. Использование .AND.
на языке Fortran:
(х>-5).AND.(х<5).AND.(у>-3).AND.(у<3)
program shooting_AND
logical res ! результат проверки
real :: xl=-5.0, x2=5.0, yl=-3.0, y2=3.0
write(*,*) "Enter coordinates”
write (*, ' (A, \) ') " x = 11; read (*, *) x
write(*,1(A,\)') " у = "; read(*,*) у
! координаты области
res=(x>xl) .AND. (x<x2) .AND.&
(y>yl).AND.(y<y2)
write(*,*) res
end
условие попадания
Результат работы программы:
Enter coordinates
х = 0
у = 0
т
41
И. Л. Артёмов. Fortran: основы программирования
Press any key to continue
Enter coordinates
x = 10
У = 1
F
Press any key to continue
2. Операция .OR. логическое ИЛИ (логическое сложение).
1 .OR.
1 .OR.
0 .OR.
0 .OR.
1 = 1
0 = 1
1 = 1
0 = 1
Пример. Условие попадания в область (рис. 2.4).
Рис. 2.4. Использование .OR.
Область представляет собой два прямоугольника. Условие попадания
в первый прямоугольник ИЛИ во второй следующее:
-5<х<0 И 0<у<3
ИЛИ
0<х<5 И -3<у<0,
на языке Fortran:
(х>-5).AND.(х<0).AND.(у>0 ).AND.(у<3) OR &
(х>0 ).AND.(х<5).AND.(у>-3).AND.(у<0)
program shooting_OR
logical res ! результат проверки
real :: xl=-5.0, х2=5.0, yl=-3.0, у2=3.0 ! координаты области
wrlte(*,*) "Enter coordinates”
write(*,'(A,\)') " x = °; read(*,*) x
write(*,'(A,\)') " у = "; read(*,*) у
res=(x>xl ).AND.(x<0.0).AND.(y>0.0).AND.(y<y2 ).OR. &
42
2. Типы данных
(х>0.0).AND.(х<х2 ).AND.(y>yl ).AND.(у<0.0)
write(*,*) res
end
Результат работы программы:
Enter coordinates
х = 2
у = 2
F
Press any key to continue
Enter coordinates
x = 2
У = -2
T
Press any key to continue
3. Операция .XOR. - логическое исключающее ИЛИ.
1 .XOR. 1=0
1 .XOR. 0=1
0 .XOR. 1=1
0 .XOR. 0=0
Пример. Условие попадания в кольцевую область (рис. 2.5).
Рис. 2.5. Использование .XOR.
Применяя операцию .XOR., условие попадания можно представить таким
образом:
-5<х<5 И -3<у<3 ИСКЛЮЧИТЬ
-2<х<2 И -1<у<1,
на языке Fortran:
(Х>-5).AND.(х<5).AND.(у>-3).AND.(у<3).XOR. &
(х>-2).AND.(х<2).AND.(у>-1).AND.(у<1)
43
И. Л. Артёмов. Fortran: основы программирования
т. е. если происходит одновременное попадание или промах в два прямо-
угольника, то результат промах (1.XOR. 1=0, 0.XOR.0=0). Если произошло
попадание только в один прямоугольник (l.XOR.O=l), то результат попа-
дание.
program shooting_XOR
logical res ! результат проверки
real :: xl=-5.0, х2=5.0, yl=-3.0, у2=3.0 I координаты области
real :: хЗ=-2.0, х4=2.0, уЗ=-1.0, у4=1.0
write!*,*) "Enter coordinates"
write(*,1(A,\)1) " x = "; read(*,*) x
write(*,'(A,\)') " у = "; read(*,*) у
res=(x>xl).AND.(x<x2).AND.(y>yl).AND.(y<y2).XOR. &
(x>x3).AND.(x<x4).AND.(y>y3).AND.(y<y4)
write(*,*) res
end
Результат работы программы:
Enter coordinates
x = 0
У = 0
F
Press any key to continue
Enter coordinates
x = 0
У = 2
T
Press any key to continue
4. Операция .NOT. - логическое отрицание HE (унарная операция один
операнд).
.NOT. 1=0
.NOT. 0=1
5. Операция .EQV. - эквивалентность.
1 .EQV. 1=1
1. EQV. 0 = 0
0. EQV. 1=0
0 .EQV. 0=1
6. Операция .NEQV. - неэквивалентность.
1 .NEQV. 1=0
1. NEQV. 0=1
0. NEQV. 1=1
0 .NEQV. 0=0
44
2. Типы данных
Пример. Программа, вычисляющая попадание в область, ограниченную
функциями /(х) = — х2+4, #(х) = 0 (рис. 2.6).
Рис. 2.6. Область, ограниченная функциями f (х) и g(x)
Выделенная область ограничена сверху функцией /(х) и снизу g(x),
поэтому условие попадания для координаты у запишется в виде g(x)<y<
</(х).
program parabol
real х, у, fx, gx
logical res ! x,y - координаты точки
I fx, gx - значения функций f(x) и g(x) в точке x
write(*,"(A,\)") "Enter x = read!*,*) x
write(*,"(A,\)") "Enter у = "; read(*,*) у
fx=-x**2+4; gx=0.0
res=(y>gx).AND.(y<fx)
write(*,*) "Result = ",res
end
Результат работы программы:
Enter x = 1
Enter у = 1
Result = T
Press any key to continue
Пример. Определить, является ли введенный год високосным.
Високосным называется год, делящийся на 4, за исключением тех годов,
которые делятся на 100, но не делятся на 400. Например, 1300, 1700, 1900 не-
високосные, а 1200, 2000 високосные.
program 1еар_уеаг
logical b ! результат проверки
integer year ! исследуемый год
45
И. Л. Артёмов. Fortran: основы программирования
write(*,'(А,\)')"Enter year..." ; read(*,*)year
b=(mod(year;4)==0).XOR.&
((mod(year,100)==0).AND.(mod(year,400)/=0))
write(*,*) b
end
Результат работы программы:
Enter year...2000
T
Press any key to continue
Enter year...1300
F
Press any key to continue
2.7. Символьный тип character
Последним из существующих стандартных типов данных является сим-
вольный тип. Символьные переменные могут использоваться для обработки
нажатых клавиш, ввода и вывода различной текстовой информации (имен
файлов и программ, названий и т. п.). Чтобы объявить переменную символь-
ного типа, используется оператор описания character (буква) с указанием
максимального количества хранимых символов, например:
character(20) str
означает описание символьной переменной str с длиной, равной 20 символам.
Если применяется оператор character без указания длины, то переменная мо-
жет хранить только один символ.
character ch ! переменная ch хранит один символ
Для присваивания значения переменной типа character символы указы-
ваются в одинарных или двойных кавычках:
character(30) :: str="abcdef"
str='АБВГДЕ+1234* * *'
Если необходимо в строке указать апостроф, то пишут подряд два сим-
вола апострофа, которые создают один символ апострофа:
'Igor,,s' - это строка Igor's длиной 6 символов.
Наряду с обычными символьными переменными в Fortran используются
Си-строки. Си-строка - это символьная константа, к которой присоединена
латинская буква "С" или "с".
character(100), parameter :: name='Program'С ! Си-строка
write (*,*) ,,Attention"C
46
2. Типы данных
При описании символьных констант длину строки можно указывать по-
средством символа *. В этом случае длина строки будет равна длине симво-
лов константы, например:
character(*), parameter :: name='Sergey1
При работе с символьными строками можно обращаться к части строки -
подстроке, указывая номер первого И и последнего 12 символа следующим
образом: str(z1 :/2). Так, если задана строка
character(20) :: string='С:\PROGRAMS\Fortran'
то, записав
write(*,*) string(1:3)
на экран выведется строка С:\
Как было разобрано ранее, при вводе и выводе символьных данных ис-
пользуется А-дескриптор преобразования с указанием длины символьной
строки. Например, для вывода на экран строки из 10 символов можно запи-
сать
character(10) str
write(*,"(А10)") str
или
write(*,"(A)") str
Следует отметить, что при чтении строк, содержащих пробелы, с экрана
с форматом по умолчанию строка будет считываться до первого символа
пробела. Так, если задать
read(*,*) str
и ввести 111 22, то в строке str будет содержаться значение 111.
При указании форматного ввода будет прочитана полностью строка
read(*,"(А)") str
в результате в переменной str будет храниться значение 111 22.
Пример. Определить, был ли введен символ "Y" или "у",
program symbol
character ch ! вводимый символ
logical res ! результат проверки
write(*,'(А,\)1) "Enter Y/N... ”; read(*,*) ch
res=(ch=='Y').OR.(ch=='y')
write(*,*) res
end
Результат работы программы:
Enter Y/N...у
T
Press any key to continue
47
И. Л. Артёмов. Fortran: основы программирования
Для данных символьного типа определена одна-единственная операция -
конкатенация (сцепление) строк, обозначаемая //. Например, даны две пере-
менные:
name="Sergey"; surname=" Ivanov"
тогда в переменной str=name//sumame будет значение "Sergey Ivanov".
Пример. Конкатенация строк.
program concat
character(30) string
character(7) strl, str2
write(*,"(A,\)") "Name = "; read!*,*) strl
write(*,"(A,\)") "Country = "; read!*,*) str2
string=strl//" from "//str2
write(*,*) string
end
Результат работы программы:
Name = Peter
Country = Russia
Peter from Russia
Press any key to continue
Перечислим некоторые стандартные функции для работы с символьными
данными.
Функция LEN(string) - возвращает длину строки.
Пример:
program length_string
character(100) string
character(*),parameter ::name='abed1
write!*,*) lentstring)
write(*,*) len(name)
end
Результат работы прщраммы:
100
4
Press any key to continue
Функция LEN TRIM(string) возвращает длину строки без хвостовых
пробелов.
Приме:
program length_string
character(100) string
write(*,"(А,\)") "Enter string..."; read(*,"(А)") string
write(*,"(A,i4)")"Number of symbols...",len_trim(string)
end
Результат работы программы:
Enter string... аа''ЬЬАссА''А''''''АЛЛЛЛ''''ЛЛ (A означает пробел)
48
2. Типы данных
Number of symbols ... 8
Press any key to continue
Функция INDEX(str, substr) возвращает номер позиции, с которой начи-
нается первое вхождение строки substr в строку str. Если вхождение не най-
дено, то функция возвращает 0.
Пример. Найти позицию первого вхождения символа "!".
program position
character(100) string
write (*, " (A,\) ") "Enter string...’’; read( *,’’(A) ") string
write(*, ” (i4)’’) index(string, ’ f ’’)
end
Результат работы программы:
Enter string...5312!aaaaaaa
5
Press any key to continue
Каждому символу поставлено в соответствие определенное целое число,
называемое кодом символа. Американский стандарт обмена данными ASCII
позволяет кодировать 256 символов с номерами от 0 до 255. Причем номера
от 0 до 127 отданы цифрам, служебным символам, знакам препинания, боль-
шим и маленьким английским буквам. Содержание остальной половины мо-
жет быть разным. Так, в России принято, что символ с кодом 128 отведен за-
главной букве "А".
Функция lACHAR(ch) возвращает код символа из стандартной ASCII-
таблицы. Например, чтобы определить код символа ”?", следует записать
ic=IACHAR("?").
Функция ICHAR(ch) возвращает код символа из таблицы символов, под-
держиваемой операционной системой. На практике операционная система
может поддерживать отличную от ASCII таблицу символов. В этом случае
только первые 128 символов будут совпадать с ASCII-последовательностью.
Функция ACHAR(ic) возвращает символ с кодом ic из ASCII-таблицы.
Например, чтобы поместить в переменную значение клавиши ESC, можно
записать ch=ACHAR(27).
В ряде случаев удобной бывает обработка нажатых клавиш без ввода
подтверждения (ENTER). В модуле (см. 6.11) dflib имеются некоторые функ-
ции для решения подобных вопросов. Рассмотрим пару функций, используе-
мых на практике.
Функция getcharqq() возвращает нажатый символ на клавиатуре.
Пример:
program press_any_key
use dflib
character ch
write(*,*) ’’Press any key ... ”
ch=getcharqq()
49
И. Л. Артёмов. Fortran: основы программирования
write(*,*) "You press...",ch
end
Результат работы программы:
Press any key ...
You press...j
Press any key to continue
Пример. Определить, была ли нажата клавиша ESC.
program see_ESC
use dflib
character, parameter :: ESC=ACHAR(27) I символьная константа
character ch ! нажатая клавиша
logical res ! результат проверки
write!*,*) "Press key..."; ch=getcharqq()
res=ch==ESC
write ( *, " (A, L) 11) "Result. . . ", res
end
Результат работы программы:
Press key...
Result... T
Press any key to continue
Функция peekcharqq() возвращает истину, если в процессе работы была
нажата клавиша, и ложь в противном случае.
2.8. Производные типы данных
При создании программ часто встречаются задачи, где стандартными ти-
пами не всегда можно обойтись (точнее, можно, по это приводит к сущест-
венным накладным расходам). Рассмотрим следующий пример. Точка на
плоскости характеризуется двумя координатами х и у. Допустим также, что в
точке задана температура Т, давление Р, электрический заряд Q, т. е. чтобы
описать состояние точки потребуется 5 переменных:
real х, у, Т, Р, Q
Введем в рассмотрение другую точку на плоскости. В этом случае для
описания потребуется еще 5 переменных:
real ха, уа, Та, Ра, Qa.
Если присвоить первой точке значения второй, то, очевидно, следует за-
писать:
х=ха; у=уа; Т=Та; Р=Ра; Q=Qa.
Возникает проблема: если таких параметров будет гораздо больше, то
программа начнет очень быстро разрастаться и простейшие операции будут
занимать слишком много места.
50
2. Типы данных
В подобных случаях можно воспользоваться производным типом дан-
ных, или, другими словами, составным типом данных*, который объединяет
стандартные типы данных под одним именем. Производные типы данных
создаются при помощи оператора описания type (тип). Например, тип точка
на плоскости будет записан следующим образом:
type point
real х
real у
end type point
здесь point - имя производного типа данных; х, у - компоненты производного
типа. Чтобы объявить переменную типа point, следует, как и при работе со
стандартными типами, указать тип и имя переменной:
type (point) pl
Если требуется учитывать температуру, давление, электрический заряд,
то тип point следует немного дополнить:
type point
real х ! координата х
real у ! координата у
real Т ! температура
real Р 1 давление
real Q I электрический заряд
end type point
type(point) pl, p2
Аналогично можно создать, например, производный тип данных student
характеризующий студента,
type student
character(20) name ! Ф. И. 0.
character(5) group ! номер группы
logical status ! учится/отчислен
real money ! стипендия
integer year ! год поступления
end type student
type(student) si ! переменная si типа student
Для присваивания значений переменным производного типа данных сле-
дует указать значения для каждой компоненты, используя разделитель точку
или процент. Например, чтобы переменной pl присвоить значения точки
А(0.5, 3.1), записывают:
р1.х=0.5; р1.у=3.1
или
pl%x=0.5; р1%у=3.1
* В некоторых языках такие типы называются записями, структурами.
51
И. Л. Артёмов. Fortran: основы программирования
Для переменной si типа student можно указать следующие значения:
si.name ='Petrov S.V.'
si.group ='ftO51'
si.status=.TRUE.
si.money =1050.0
si.year =2005
Для присваивания начальных значений переменным производного типа
рекомендуется пользоваться конструктором. Конструктор совпадает с име-
нем производного типа, в котором в порядке следования указываются значе-
ния для компонент. В наших примерах применение конструктора будет та-
ким:
pl=point(0.5,3.1)
sl=student('Petrov S.V.', 'ft051',.TRUE.,1050.0,2005)
что равносильно рассмотренным выше операторам присваивания.
Пример. Вывести информацию о сотруднике фирмы,
program firm
type person
character(20) name
integer age
real money
end type person
type (person) pl
pl=person("Ivanov K.B.", 1973, 534.39)
write(*,*) pl
pl.money=784.82
write(*,*) pl
end
Результат работы программы:
Ivanov К.В. 1973 534.3900
Ivanov К.В. 1973 784.8200
Press any key to continue
Пример. С использованием производного типа составить программу вы-
числения расстояния между двумя точками.
Для описания точки на плоскости воспользуемся типом point. Расстояние
между точками находится по известной формуле
d = ^(x2-xl)2 +(y2-yt)2 .
program Dekart
type point
real x
real у
end type point
type (point) pl, p2
52
__ _ 2. Типы данных
real d.
pl=point(3.0, 4.0)
p2=point(0.0, 0.0)
d=sqrt((pl,x-p2.x)**2+(pl.y-p2,y)**2)
write(*,*) "Distance between pl and p2 = ",d
end
Результат работы программы:
Distance between pl and p2 = 5.000000
Press any key to continue
Безусловно, применение производных типов приводит к большему напи-
санию исходного текста программы. Однако, как будет показано далее (см.
разделы, посвященные программированию графики), использование произ-
водных типов значительно облегчает написание и понимание работы про-
грамм.
Пример. Создать производный тип данных - вектор, заданный в декар-
товой системе координат. Найти скалярное произведение двух векторов
и длину каждого вектора,
program vec
type vector
real x ! координаты вектора
real у
real z
end type vector
type (vector) vl, v2
real scalar, 11, 12 ! scalar - скалярное произведение
I 11, 12 - длина первого и второго векторов
vl=vector(1.0, -2.0, 3.0)
v2=vector(4.0, 2.0, -1.0)
scalar=vl. x*v2.х + vl.y*v2.y + vl.z*v2.z
ll=sqrt(vl.x**2 + vl.y**2 + vl.z**2)
12=sqrt(v2.x**2 + v2.y**2 + v2.z**2)
write(*,100) "Scalar = ", scalar
write!*,100) "Length vl = ", 11
write(*,100) "Length v2 = ", 12
100 format(A,f5.2)
end
Результат работы программы:
Scalar = -3.00
Length vl = 3.74
Length v2 = 4.58
Press any key to continue
53
И. Л. Артёмов. Fortran: основы программирования
Итоги
1. Константы и переменные целого типа (integer) часто используются для
хранения количества значений, номеров и индексов. Следует помнить, что в
целочисленной переменной к=~-ЪП хранится нуль. При использовании целых
переменных возможен выход за пределы представления числа. Если описана
переменная integer(2):: Л=32767, то к=к+\ содержит значение -32768.
2. В большинстве случаев вычислительные задачи обрабатывают данные
вещественного (real) иди комплексного (complex) типов. Запись real ::
s=1.5E-O5, означает хранение в переменной s значения 1.5-10'5, complex :: i=
=(0.0,1.0) - мнимая единица.
3. Следует помнить о старшинстве арифметических операций, особенно-
стей при делении целых чисел, возведения в степень отрицательных чисел, а
также несправедливости правил арифметики при работе с приближенными
числами, вследствие ошибок округления.
4. Эффективно используйте стандартные математические функции. При
наборе формул рекомендуется соблюдать правило по одновременному от-
крытию и закрытию скобок. В первую очередь выполняются операции в са-
мих внутренних скобках.
5. Логические (logical) переменные могут хранить одно из двух значений:
.TRUE.(истина) или .FALSE, (ложь). Для данных логического типа справед-
ливы 6 логических операций: .OR., .AND., .XOR., .NOT., .EQV., .NEQV.
6. При составлении логических выражений используются операции от-
ношения. Вследствие ошибок округления нельзя сравнивать на равенство два
вещественных числа.
7. Переменные и константы символьного типа (character) используются
для хранения нажатых символов на клавиатуре, имен, названий и текстовой
информации. При работе с символьными данными используются операция
конкатенации и стандартные функции. Подстроки позволяют обращаться
к части символьной строки.
8. Производные типы дают возможность объединять стандартные типы
данных под одним именем и значительно упрощают программирование, если
обрабатываемые данные характеризуются многими параметрами.
54
3. УПРАВЛЯЮЩИЕ ОПЕРАТОРЫ
3.1. Конструкции if
До сих пор мы рассматривали программы, которые имели линейную
структуру, т. е. при работе программы последовательно друг за другом вы-
полнялись все до единого оператора. Однако часто требуется, чтобы про-
грамма могла выбирать, какие выполнять операторы в той или иной ситуа-
ции, а какие игнорировать, другими словами, уметь принимать решение. На-
пример, как сделать, чтобы программа выдавала на экран сообщение,
положительное или отрицательное было введено число? Или как составить
программу, которая определяла бы правильность введенного пароля и выда-
вала на экран соответствующие текстовые сообщения? Для этих и других по-
добных ситуаций используется условный оператор if (если), общая схема ко-
торого следующая:
имя: if (логическое выражение) then
операторы_1
else
операторы_2
end if имя
Оператор выполняется следующим образом: если (if) логическое выра-
жение истинно, тогда (then) выполняются операторы_1 и программа продол-
жает работу после end if, иначе (else) выполняются операторы ! и программа
продолжает работу после end if. Схематически конструкция if изображена на
(рис. 3.1). Таким образом, в операторе if, будут выполняться операторы_1
или операторы_2. Операторы if могут содержать внутри себя другие опе-
раторы if, поэтому для удобства можно давать имена для каждой конструк-
ции if.
Пример. Программа, определяющая, положительное или отрицательное
число было введено с клавиатуры, и отображающая соответствующее сооб-
щение на экран.
program number
integer К ! исследуемое число
write(*,'(А,\)') " Enter number..."; read(*,*). k
check: if (k<0) then
write(*,*) “k<0"
else
write(*,*) "k>0"
end if check
•xxd
55
Й. Л. Артёмов. Fortran: основы программирования
Результат работы программы:
Enter number...100 Enter number...-50
k>0 k<0
Press any key to continue Press any key to continue
Пример. Программа, проверяющая пароль пользователя,
program passw
integer, parameter :: password=345678 ! password - пароль
integer user ! user - введенный пароль
write(*,'(A,\)1) " Enter password..."; read)*,*) user
if(user==password) then
write)*,*) "Authorization OK!"
else
write)*,*) "Authorization failed..."
end if
end
Результат работы программы:
Enter password...345678
Authorization OK!
Press any key to continue
Часть else может отсутствовать. В этом случае условный оператор if за-
писывается следующим образом:
if (логическое выражение) then
оператор_1
оператор_Ы
end if
56
3. Управляющие операторы
Если в условном операторе присутствует один оператор, то конструкция
if записывается в самом простейшем виде:
if (логическое выражение) оператор_1
Пример. Вывести на экран квадрат вводимого числа. Если результат
окажется больше 100, то уменьшить его в 2 раза.
program reasoning
real k, res ! k - вводимое число
! res - результат
write (*, ° (A, \) ") ''Enter number..."; read(*,*) k
res=k**2
if (res>100) res=res/2
write(*,"(A,f5.1)") "Result...", res
end
Результат работы программы:
Enter number...20
Result...200.0
Press any key to continue
Рассмотрим программу number из начала раздела. Как отреагирует програм-
ма, если будет введен нуль? В этом случае программа выдаст сообщение к>0, так
как условие (к<(У) будет ложным. Однако, как хорошо известно, нуль ни положи-
тельное, ни отрицательное число. Чтобы программа правильно определяла вве-
денные числа, можно написать три последовательных оператора if:
program number
integer k ! исследуемое число
write(*,'(A,\)') " Enter number..."; read(*,*) k
if (k<0) write(*,*) "k<0"
if (k==0) write(*,*) "k=0"
if (k>0) write(*,*) "k>0"
end
Данную программу можно оформить и с помощью вложенных конструк-
ций if, по приведенной блок-схеме (рис. 3.2). Заметим, что вторая конструк-
ция if должна целиком содержаться в первой.
program number
integer k ! исследуемое число
write(*,'(А,\)') " Enter number...read(*,*) k
first: if (k<0) then
write(*,*) "k<0"
else
second: if (k>0) then !---- внутренний if ----
write(*,*) "k>0"
else
write(*,*) "k=0"
57
И. Л. Артёмов. Fortran: основы программирования
and if second !---------------------------------------------
end if first
end
Рис. 3.2. Вложенные операторы if
Здесь для удобства операторам if присвоены имена, first для внешнего и
second для внутреннего условного оператора. Сначала происходит проверка
числа на возможность быть отрицательным в операторе first: if; если резуль-
тат проверки истина, то происходит вывод сообщения "к<0" и программа
продолжает работу после оператора end if first. Если результат оказался лож-
ным, то происходит проверка числа на возможность быть положительным в
операторе second: if, и если результат истина, то происходит вывод сообще-
ния "к>0", иначе вывод сообщения "Л=0".
Результат работы программы.
Enter number... О
k=0
Press any key to continue
Пример. Написать программу вычисления значения функции (рис. 3.3).
х < -6;
/W
2х + 10, -6<х<0;
10-х, 0<х<5
program interval
real fx, x
! fx - значение функции
58
3. Управляющие операторы
! х - аргумент функции
write(*, '(А,\)')" Enter х coordinate..."; read(*,*) х
if(x<-6) then
fx=x+4
write(*,*) "1 interval"
else
if (x<0) then
fx=2*x+10
write!*,*) "2 interval"
else
if (x<5) then
fx=10-x
write!*,*) "3 interval"
else
fx=5
write!*,*) "4 interval"
end if
end if
end if
write!*,*) "result = ",fx
end
Решение задачи оформлено при помощи вложенных конструкций if. Од-
нако данную задачу легко можно запрограммировать при помощи конструк-
ции elseif, которая записывается в таком виде:
if (логическое выражение) then
операторы
elseif (логическое выражение) then
операторы
elseif ...
else
операторы
end if
59
И. Л. Артёмов. Fortran: основы программирования
В этом случае используется один общий end if и программа читается го-
раздо легче,
program interval
real fx,x
! fx - значение функции
Г х - аргумент функции
write(*,'(А,\)')" Enter х coordinate.; read(*,*) х
if (х<-6) then
fx=x+4
write(*,*) "1 interval"
elseif (x<0) then
fx=2*x+10
write(*,*) "2 interval"
elseif (x<5) then
fx=10-x
write(*,*) "3 interval"
else
fx=5
write(*,*) "4 interval"
end if
write(*,*) "result = ", fx
end
Результат работы программы:
Enter x coordinate-. . . 7
4 interval
result = 5.000000
Press any key to continue
При решении некоторых задач можно запоминать результат логического
выражения и затем использовать его в дальнейшем. Для этого вводится до-
полнительная логическая переменная (переменная-флаг), которая будет хра-
нить состояние истина, если условие было выполнено, и ложь в противном
случае.
Пример. Из трех вводимых целых чисел определить, было ли хотя бы
одно, равное 10.
program remark
integer а,b,с
logical flag
! a,b,c - вводимые числа
! flag - логическая переменная-флаг
write(*,"(А,\)")"а = read(*,*) а
write(*,"(А,\)")"b = "; read(*,*) b
write(*,"(А,\)")"с = read(*,*) с
flag=(a==10)-OR.(b==10).OR.(c==10)
60
3. Управляющие операторы
if(flag) then ! Переменная flag хранит
!результат проверки числа
write (*,*) "Present."
else
write(*,*) "Nothing”
end if
end
Результат работы программы:
a = 12
Ь = 15
с = 10
Present
Press any key to continue
Заметим, что чрезмерное использование оператора if может привести
к плохому стилю программирования. Так, в следующем примере оператор if
является излишним,
if (а==5) then
S=.TRUE.
else
S=.FALSE,
end if
Правильнее и компактнее будет записать
s=a==5
3.2. Оператор множественного выбора select case
и оператор stop
В предыдущем разделе было рассмотрено, что для организации выбора
одного из двух действий используется оператор if. Если необходимо выбрать
одно из трех или четырех действий, то можно воспользоваться последова-
тельными операторами if, вложенными операторами if или цепочкой if -
elseif. Однако если потребуется выбрать одно из 10 действий, то использова-
ние рассмотренных операторов может привести к длительной проверки усло-
вий и к ухудшению читаемости программы. Например, что делать в ситуа-
ции, когда пользователь вводит с клавиатуры номера операций от 0 до 9 и в
зависимости от номера программа должна выполнить те или иные действия?
Для программирования подобных задач используется оператор множествен-
ного выбора select case (выбрать случай), который записывается в следующем
виде:
select case (выражение)
case (множество_значений_1)
операторы
61
И. Л. Артёмов. Fortran: основы программирования
case (множество_значений_2)
операторы
case default
операторы
end select
Сначала вычисляется результат выражения. Затем происходит поиск по-
лученного результата среди предложений case. Если результат найден во
множестве значений, то выполняется соответствующая case-часть и оператор
select case завершает работу. Если значение не найдено, то выполняется часть
case default, которая используется для всех остальных "непредвиденных" си-
туаций.
Схематично различие операторов if и select case показано на (рис. 3.4).
Оператор if позволяет организовать разветвление в программе по двум на-
правлениям, оператор select case - во множестве направлений.
if । select
| case
Рис. 3.4. Схема операторов if и select case
Пример. Программа-калькулятор,
program calculator
real a,b,res ! a - первое число,
! b - второе число
! res - результат
character operation ! операция
logical :: flagERR=.FALSE. ! переменная-флаг
write(*,100)"First number..."; read(‘,‘) a
write(*,100)"Operation.; read!*,*) operation
write(*,100)"Second number..."; read!*,*) b
100 format(A,\)
select case (operation)
case (' +') ! сложение
res=a+b
case (' - ' ) ! вычитание
res=a-b
case (' * ' ) ! умножение
res=a*b
case ('/ ' ) ! деление
res=a/b
62
3. Управляющие операторы
case ('л') ! степень
res=a**b
case default ! для всех остальных операций
flagERR=.TRUE.
write!*,*) "Operation ERROR!"
end select
if (.NOT.flagERR) write(*,*) "Result...", res
end
Программа работает по следующей схеме. Сначала пользователь вводит
с клавиатуры первое число - переменную а, затем операцию - переменную
operation и второе число - переменную Ь. Далее оператор select case проверя-
ет, какая операция хранится в operation и сравнивает с имеющимся casc-спис-
ком. Если встречается похожая операция, например case('+'), то выполняется
оператор rcs=a+b и выполнение оператора select case заканчивается.
Переменная flagERR используется для контроля правильности ввода ма-
тематической операции. Если ошибки ввода нс обнаружено (flagERR содер-
жит .FALSE.), то происходит вывод результата на экран. В случае если поль-
зователь наберет неверную операцию, то выполнится часть case default (для
всех остальных случаев), переменная-флаг flagERR примет значение .TRUE,
(введена неверная операция) и выведется сообщение об ошибке.
Результат работы профаммы:
First number...2
Operation...+
Second number...3
Result... 5.000000
Press any key to continue
First number...1
Operation...#
Second number...4
Operation ERROR!
Press any key to continue
Пример. Обработка нажатий клавиш.
program menu
use dflib
character ch ! нажатая клавиша
write(*,*) "------------- SELECT ITEM --------------"
write(*,"(5 (A,/) ) “) "File...l", &
"Edit...2”, &
"View...3", &
"Insert...4", &
"Project...5"
ch=getcharqq()
select case(ch)
63
И. Л. Артёмов. Fortran: основы программирования
case (111)
write(*,*) "File"
case ('2 1 )
write(*,*) "Edit"
case ('3 1)
write(*,*) "View"
case (' 4 ')
write(*,*) "Insert"
case ('5')
write(*,*) "Prоject"
case default
write(*,*) "No"
end select
end
Программа menu позволяет выводить сообщения в зависимости от
нажатых клавиш. Переменная ch содержит символ нажатой клавиши, по-
лучаемый при помощи функции getcharqq() из модуля dflib, оператор
select case производит обработку нажатых клавиш. Данная программа мо-
жет быть использована как основа для создания простейшего пользова-
тельского меню.
Результат работы программы:
------------ SELECT ITEM ---------------
File...1
Edit...2
View...3
Insert... 4
Project...5
Project
Press any key to continue
В следующем примере показано, как использовать множество значений
в операторе select case.
Пример. Программа, определяющая, в какой из интервалов попадает
вводимое число,
program interval
integer k ! исследуемое число
write(*,"(А, \) ") "Enter "number >= 0 ..."; read(*,*) k
select case(k)
case (0)
write(*,*) "0"
case (1:9)
64
3. Управляющие операторы
write(*,*) "1..9"
case (10:99)
write(*,*) "10..99"
case (100:999)
write(*,*) ”100..999"
case (1000:9999)-
write(*,*) "1000..9999"
case default
write(*, *) "-------OVERFLOW----------"
end select
end
Результат работы программы:
Enter number >= 0 ...24
10..99
Press any key to continue
Enter number >= 0 ...89482
------ OVERFLOW --------
Press any key to continue
Иногда одним из действий, предусмотренных в условном операторе if
или в операторе множественного выбора case, является прекращение выпол-
нения программы. Оператор
stop 'Сообщение'
позволяет остановить выполнение программы и вывести на экран текстовое
сообщение. Так, в следующем примере при делении на нуль выдается сооб-
щение об ошибке и npoi-рамма завершает работу,
program error
integer a,b,c
write (*, " (A, \) ") "Enter a = 11; read(‘,‘) a
write (*, 11 (A, \) ") "Enter b = 11; read(‘,‘) b
if (b==0) stop "ERROR! b=0 "
c=a/b
write(*, " (3 (A, i2)) ") "Result ", a, " / ", b, '' = ", c
end
Результат работы программы:
Enter a = 48
Enter b = 24
Result 48/24= 2
Press any key to continue
Enter a = 48
Enter b = 0
ERROR! b=0
Press any key to continue
65
И. Л. Артёмов. Fortran: основы программирования
Итоги
1. Условный оператор if позволяет программе принимать решение. Опе-
ратор if используется для разветвления последовательности операторов про-
граммы, так что только одна из ветвей может быть выполнена.
2. Если требуется сделать разветвление на три и более направления,
можно воспользоваться вложенными операторами if или цепочкой if
elseif... - else - end if. При записи условных операторов используются логи-,,
ческие операции, операции отношения, переменные флаги.
3. Оператор множественного выбора select case - эффективное средство'
для организации ветвлений во многих направлениях и может использоваться^
для обработки нажатых клавиш, создания меню, выбора вариантов. Часть
case default позволяет организовать полный выбор направлений.
4. Для прекращения работы программы используется оператор stop.
66
4. ЦИКЛЫ
4.1. Do-циклы
Напишем программу, которая выводит на экран 5 текстовых сообщений
"Fortran". Владея рассмотренными операторами, можно написать такой вари-
ант программы:
program show
write(*,*)
write(*,*)
write(*,*)
write(*,*)
write(*,*)
"Fortran"
"Fortran"
"Fortran"
"Fortran"
"Fortran"
end
Как поступить, если понадобится не 5, а 10, 15, 100 сообщений? Про-
грамма, выполняющая простейшие операции, будет огромной! Сразу же воз-
никает вопрос, нельзя ли операторы writc(*,*) записать компактно? Можно,
если воспользоваться циклами.
Для организации циклов в Fortran используется цикл do, который запи-
сывается в следующем виде:
имя do переменная = начальное значение, конечное значение, шаг
операторы ! ---------- тело цикла
end do имя
Переменная, стоящая в операторе do, называется переменной цикла или
управляющей переменной и должна иметь целый или вещественный тип.
Схема выполнения цикла следующая:
1. Переменной цикла присваивается начальное значение.
2. Выполняется тело цикла.
3. Переменная цикла увеличивается на шаг цикла.
4. Выполняется проверка; если переменная цикла больше конечного зна-
чения, то цикл завершает работу, иначе переход на шаг 2.
Выполнение одного раза цикла называется итерацией. Если шаг цикла не
задан, то он принимается равным единице. Каждому циклу можно задать не-
обязательное имя, которое используется для более легкого написания вло-
женных циклов.
Таким образом, программу по выводу пяти строк текста можно легко и
компактно записать с использованием цикла do:
program show
integer k ! переменная цикла
ДИДИОГТПИФИ
67
И. Л. Артёмов. Fortran: основы программирования
do к=1,5
write(*,*) "Fortran"
end do
end
что означает выполнить ровно 5 раз оператор write внутри цикла. Так как пе-
ременная к при работе цикла последовательно увеличивает свое значение на
единицу и происходит проверка, то после работы цикла значение в перемен-
ной к будет равным шести.
Пример. Вывести на экран целые числа от 1 до 10.
program show_nurtibers
integer k
do k=l,10
write(*,"(i4,\) ") k
end do
end
Работа программы аналогична рассмотренному выше примеру. Перемен-
ная к последовательно принимает значения от 1 до 10 с шагом 1, оператор
write выводит значения к на экран. Заметим также, что после прекращения
работы цикла в переменной к будет содержаться значение 11.
Результат работы программы:
123456789 10
Press any key to continue
Пример. Вывести на экран четные числа от 2 до 20.
program even ! четные
integer i
do i=2,20,2 ! шаг цикла = 2
write ( *, 11 (i4, \) ") i
end do
end
В данном случае начальное значение i равно двум, шаг равен двум.
Результат работы программы:
2 4 6 8 10 12 14 16 18 20
Press any key to continue
Пример. Вывести на экран числа, кратные трем, от 30 до 3 (в обратном
порядке).
program multiple_3 ! кратные трем
integer к
do к=30,3,-3
write(*, " (i4,\) ") к
end do
end
68
4. Циклы
Для вывода чисел в обратном порядке следует указать отрицательный
шаг, равный минус трем.
Результат работы программы:
30 27 24 21 18 15 12 9 6 3
Press any key to continue
При использовании переменной цикла вещественного типа следует пом-
нить, что сравнивать вещественные числа на равенство между собой нельзя.
Так, в следующем примере первая программа может напечатать числа от 1.1
до 1.7, во втором случае - до 1.9.
program progl
real р
do р=1.1, 1.8, 0.1
write(*,*) р
end do
end
program prog2
real p
do p=l.l, 1.9, 0.1
write(*,*) p
end do
end
В подобных случаях можно порекомендовать конечное значение увели-
чивать на половину шага. В следующем примере данные выведутся от 1.1
до 1.8.
program ргодЗ
real :: р, р0=1.1, pn=1.8, dp=0.1
! р - переменная цикла
! рО - начальное значение
! рп - конечное значение
! dp - шаг цикла
do p=p0,pn+dp/2,dp
write(*,"(f5.1,\) ") p
end do
end
Результат работы программы:
1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8
Press any key to continue
Пример. Вывести на экран таблицу значений функции (протабулировать
функцию) f (х) = х2 в точках от х0=0.0 до хи=2.0 с шагом <£с=0.1.
Табулирование значений функции широко используется при создании
вычислительных программ, поэтому рассмотрим несколько способов реше-
ния данной задачи.
1-й способ. Используя рассмотренную выше программу с применением
переменной цикла вещественного типа, можно написать такой вариант про-
граммы:
program tabFl
real :: х0=0.0, xn=2.0, dx=0.1
69
И. Л. Артёмов. Fortran: основы программирования
real х, f
! хО - начало интервала
! хп - конец интервала
! dx - шаг
! х - текущее значение х
! f - текущее значение f(х)
do х=хО, xn+dx/2, dx
f=x*x;
write(*,"(2(a,f5.2,7х))")"х=", х, ”f(x)=",f
end do
end
Переменная цикла x последовательно увеличивается на шаг dx (рис. 4.1)
и на экран выводятся текущее значение х и значение функции f (х).
Г---------1-------1........tе---------------•---------1----►
x=xO x+dx x+dx+dx xn
Рис. 4.1. Вычисление значений функции в точках (1-й способ)
Результат работы программы:
х= .00 f(x) = .00
х= 1.90 f(x)= 3.61
х= 2.00 f(х) = 4.00
Press any key to continue
2-й способ. Очевидно, что на экран будет выведена 21 точка, так как
(2.0-0.0)/0.1+1=21. Поэтому если записать x=i*dx+xQ и организовать цикл,
где / будет изменяться от 0 до 20, то получим требуемые значения х. Так как
х0=0, то можно просто записать x=i*dx (рис. 4.2)
i=0
i=21
----f-------------------.
0*dx l*dx 2*dx
----. „
21*dx
Рис. 4.2. Вычисление значений функции в точках (2-й способ)
program tabF2
integer i
real :: dx=0.1
real x, f
! i - переменная цикла
I dx - шаг
! x - текущее значение x
! f - текущее значение f(x)
do i=0,20
70
4. Циклы
x=i*dx
f=x*x
write(*,"(2(a,f5.2,7x))") "x=",x, "f(x)=",f
end do
end
3-й способ. Программу можно записать с использованием переменной,
накапливающей сумму (рис. 4.3).
----.-------11—►
xO-dx х=хО x+dx x+dx+dx xn
Рис. 4.3. Вычисление значений функции в точках (3-й способ)
program tabF3
integer i
real :: dx=0.1
real :: x=0
real :: f
! i - переменная цикла
! dx - шаг
! x - текущее значение х,
! переменная, накапливающая сумму
I f - текущее значение f(х)
x=x-dx ! начальное значение х=-0.1
do i=0,20
x=x+dx ! х накапливает сумму
f=x*x
write(*,"(2(a,f5.2,7х) ) ") "х=",х, "f(х)=",f
end do
end
В этом примере для общей записи расчета текущему значению х при-*
своено начальное значение -dx. В самом начале работы цикла при <=0 пере-
менная х принимает начальное значение хО, так как x=x+dx.
Пример. Вывести на экран поочередно -11-11-11-11-11.
1-й способ. В последовательности единиц есть очевидная закономер-
ность: единица на нечетной позиции имеет знак минус, на четной - плюс.
Алгоритм программы может быть таким:
1. Организовать цикл, который будет выполняться 10 раз.
2. Внутри цикла поставить проверку, если переменная цикла число не-
четное, то выводить на экран -1, иначе 1.
program period
integer i ! переменная цикла
integer р ! текущая единица
do i=l,10
71
И. Л. Артёмов. Fortran: основы программирования
if (mod(i,2)/=0) then I если i нечетное
p=-l
else ! иначе, i четное
р=1
end if
write(*, '(i2,\) ') p
end do
end
Результат работы программы:
-1 1-1 1-1 1-1 1-1 1 .
Press any key to continue
2-й способ. Программу можно записать более компактно, если учесть,
что -I в нечетной степени-1, в четной 1.
program period
integer i, p
do i=l,10
p=(-l)**i ! конструкция if отсутствует
write(*,'(i2,\) ') p
end do
end
3-й способ. Чтобы переменная изменила знак, достаточно записать р=-р.
program period
integer i, p
p=l
do i=l,10
P=-P
write (* , 1 (i2, \) ') p
end do
end
Пример. Написать программу, которая находит все числа, делящиеся на
5 или на 10, в диапазоне от 10 до 50.
Чтобы решить поставленную задачу, напишем цикл, в котором перемен-
ная цикла будет изменяться от 10 до 50 и проверяться на деление без остатка
на 5 или на 10.
program multiple
integer k ! проверяемое число
do k=10,50
if ((mod(k,5)==0).OR.(mod(k,10)==0)) write!*,1(14,\)') k
end do
end
Результат работы программы:
10 15 20 25 30 35 40 45 50
72
_ ____ _ _ 4.Циклы
Press any key to continue
В заключение урока дадим некоторые рекомендации по работе с do-
циклами.
1. Нельзя изменять переменную цикла в теле цикла.
Следующий пример ошибочен:
dp k=l,10
k=k+l ! нельзя изменять переменную к
end do
А вот так можно:
do k=l,10
end do
k=k+l
2. Если в теле цикла используется конструкция if, select case, то они
должны содержаться целиком внутри тела цикла:
Неправильно
do k=l,10
if (k<5) then
write!*,*) "<5"
else
end do
write!*,*) “>=5"
end if
Правильно
do k=l,10
if (k<5) then
write!*,*) ”<5"
else
write!*,*) “>=5"
end if
end do
3. He следует записывать внутри цикла постоянные выражения, так как
подобные "холостые" вычисления снижают быстродействие и читаемость
программы. Например, следующий цикл
do i=l,10
х=1.52 ! эта строка программы
! не меняется во время работы цикла
write!*,*) i
end do
следует записать в виде
х=1.52
do i=l,10
write!*,*) i
end do
4.2. Переменные-счетчики
В рассмотренном выше примере по поиску чисел, кратных 5 или 10, мы
написали программу, которая простым перебором находила числа в диапазо-
не от 10 до 50 и проверяла деление без остатка. Если условие выполнялось,
73
И. Л. Артёмов. Fortran: основы программирования
то происходил вывод числа на экран. Ответим на вопрос, сколько было выве-
дено таких чисел. Конечно, можно легко подсчитать и ответить - 9 чисел. Но
очень часто требуется, чтобы программы могла самостоятельно проводить
подсчет.
Для этого используются переменные-счетчики, о которых шла речь ра-
нее. Это обычные переменные, основная задача которых увеличивать свое
значение на единицу при выполнении определенных условий.
Запишем предыдущий пример с возможностью подсчета количества вы-
веденных чисел,
program count
Integer k ! проверяемое число
integer :: s=0 ! переменная счетчик, сброс счетчика
do k=10,50
if ( (mod(k, 5)==0).OR.(mod(k,10)==0)) then
write(*, 1 (i4, \ )1) k
s=s+l ! если условие выполнено,
! то увеличим содержимое счетчика на единицу
end if
end do
write(*,'(/,A,i2)') "Total = ",s
end
В программе применяется целочисленная переменная-счетчик s. Перед
использованием счетчику присваивается начальное значение, как правило
нуль. Иногда такую операцию называют сбросом счетчика, инициализацией
счетчика, или установкой счетчика в исходное состояние.
В теле цикла условный оператор if проверяет кратность переменной к.
В случае успешного деления переменная-счетчик s увеличивает свое значе-
ние на единицу. В итоге после работы цикла в s будет храниться количество
найденных чисел.
Результат работы программы:
10 15 20 25 30 35 40 45 50
Total = 9
Press any key to continue
t
Пример. Вычислить определенный интеграл J
о
дом Монте-Карло.
Методы Монте-Карло (методы статистических испытаний) используются
для приближенного вычисления определенных интегралов. Имея невысокую
точность расчета, данные методы просты в реализации.
74
4. Циклы
Пусть определенный интеграл представлен в виде J f(x)dx, где
о
0</(х)< 1 на интервале хе[0;1]. Будем задавать равномерно, случайным
образом точки с координатами х и у в единичном квадрате (рис. 4.4). За при*
ближенное значение интеграла принимается отношение количества Р точек,
попавших под /(х) (черные точки), к общему количеству N испытаний (чер>
ные и белые точки), т. е. j f(x)dx ~ .
Рис. 4.4. Вычисление определенного интеграла методом Монте-Карло
Для программирования данного метода потребуются переменная, кото-
рая будет хранить общее число испытаний, и переменная-счетчик, для под-
счета количества попаданий под функцию /(х). Условие попадания в замк-
0<х<1;
нутую область можно записать так: <!
[0<у</(х).
program Monte_Karlo
integer И ! переменная цикла
real х, у ! случайные координаты точки
integer :: р=0 ! число попаданий
integer :: п=50000 ! число испытаний
real integral ! вычисленное значение интеграла
do k=l,n
call randcm_number(x)
call random_number(y)
if (y<x+cos(2*x)-0.5) p=p+l
end do
75
И. Л. Артёмов. Fortran: основы программирования
integral=REAL(р)/REAL(n)
write(*,*) integral
end
При делении двух целых чисел следует помнить, что результат есть це-
лое число, поэтому при вычислении интеграла используется функция REAL,
преобразующая целое число в вещественный тип.
Точное значение интеграла — sin (2) = 0.4546487.
Результат работы программы:
4.552000Е-01
Press any key to continue
Пример. Вывести на экран 10 случайных чисел из диапазона -5 < х <, 4
и подсчитать количество нулей, отрицательных и положительных чисел.
Для формирования 10 случайных чисел следует организовать цикл, кото-
рый будет повторяться 10 раз. Для подсчета количества положительных, от-
рицательных и нулевых чисел потребуется три переменных-счетчика.
program count
Integer :: sp=0, sm=0, sz=0 ! счетчики положительных,
! отрицательных и нулевых чисел.
Integer k, n ! п - случайное целое число
! к - переменная цикла
real а ! а - случайное вещественное число
do к=1,10
call random_number (а) n=INT(а*10)-5
if (n>0) then
sp=sp+l
elseif (n<0) then
sm=sm+l
else
sz=sz+l
end if
write!*,'(i4,\)') n
end do
write(*,"(/,A,i4)") " PLUS = ", sp
write ( *, 11 (A, i4) ") " MINUS = ", sm
write(*,"(A,i4)") " ZERO = ", sz
end
Результат работы программы:
-5 -5 1 3 4 -4 0 -2 -3 2
PLUS = 4
MINUS = 5
ZERO = 1
Press any key to continue
76
4. Циклы
4.3. Сумматоры - переменные,
накапливающие сумму
Рассмотрим, как найти сумму чисел от 1 до 10. Для решения данной за-
дачи можно с легкостью воспользоваться формулой для вычисления суммы
и(и + 1)
арифметической прогрессии, т. е. 5=1 + 2 + 3 + ... + и = —, и = 10,
10(10 + 1) е
поэтому 5 =--------- = 55.
Можно составить простую программу и, записав вышеприведенную
формулу, получить правильный результат. Однако наша цель не получить
решение задачи, а научить программу последовательно складывать числа и
находить нужный результат.
Чтобы программа могла самостоятельно находить сумму чисел, исполь-
зуются переменные, накапливающие сумму, или просто переменные-
сумматоры.
Следующий пример показывает применение таких переменных.
program summa
integer :: sum=0 ! Перед использованием переменной-сумматору
! присваивается начальное значение.
integer к
do к=1,10
sum=sum+k
end do
write(*,*) sum
end
Переменная-сумматор sum очень похожа на переменную-счетчик, с той
лишь разницей, что в переменную-сумматор последовательно прибавляются
разные числа, а в переменную-счетчик постоянное значение - единица.
Если рассмотреть работу цикла, то значения в переменной sum будут
следующими:
• перед началом цикла в переменной sum находится значение 0;
• при£=1 sum=0+l=l, так как до этого в sum находилось значение 0.
k=2 sum=l+2= 3
к=3 sum=3+3= 6
к=9 sum=9+36=45
к=10 sum=10+45=55
В итоге переменная sum последовательно, шаг за шагом накопила сумму
чисел от 1 до 10.
Результат работы пршраммы summa:
55
Press any key to continue
77
И. Л- Артёмов. Fortran: основы программирования
Сделаем важное замечание. Известно, что в математике подсчет суммы
можно записать с использованием оператора сумма. Так, в нашем случае
ю
5 = = 1 + 2 + 3 + ... + 10. Если известен нижний и верхний пределы сумми-
Л=1
рования, то данную формулу очень легко запрограммировать, если восполь-
ю
зоваться циклом do. Выражение S = £п эквивалентно следующему фраг-
Л=1
менту программы:
s=0 [сначала обнулили результат суммирования
do п=1,10
s=s+n ! в цикле рассчитываем сумму чисел от 1 до 10
end do
Пример. Программа подсчета суммы четных и нечетных чисел.
1-й способ. Сначала найдем и выведем на экран сумму нечетных чисел
(цикл от 1 до 10 с шагом 2), затем найдем сумму четных чисел (цикл от 2 до
10 с шагом 2).
program summa
Integer :: suml=0, sum2=0, i
! suml - сумма нечетных чисел
! sum2 - сумма четных чисел
! i - переменная цикла
do i=l,10,2 1 подсчет суммы нечетных чисел
suml=suml+i
end do
write(*,*) " Summa odd =",suml
do 1=0,10,2 ! подсчет суммы четных чисел
sum2=sum2+i
end do
write (*, *) " Summa even =11, sum2
end
Результат работы программы:
Summa odd = 25
Summa even = 30
Press any key to continue
2-й способ. Рассмотренную программу можно написать с использовани-
ем оператора if.
program summa
integer :: suml=0, sum2=0, i
78
4. Циклы
! suml - сумма нечетных чисел
! sum2 - сумма четных чисел
! i - переменная цикла
do i=l,10
if (mod(i,2)/=0) then
suml=suml+i ! подсчет суммы нечетных чисел
else
sum2=sum2.+i ! подсчет суммы четных чисел
end if
end do
write(*,*) " Summa odd =",suml
write (*, *) 11 Summa even =", sum2
end
10 1
Пример. Написать программу подсчета суммы ряда у—- + k .
£5*
program summa
real :: sum=0 ! переменная, накапливающая сумму
integer k ! переменная цикла
do k=5,10
sum=sum+l.0/k**2+k
end do
write(*,"(A,f7.3)") “Summa = sum
end
Результат работы программы:
Summa = 45.126
Press any key to continue
Пример. Вычислить число л по приближенной формуле
, 4 4 4 4 4, 4
tv — 4----1-----1-------р... И-.
3 5 7 9 11
Легко заметить следующее:
1. Сумма состоит из знакочередующихся слагаемых, т. е. сначала в чис-
лителе 4, затем -4.
2. В знаменателе находятся нечетные числа.
С учетом рассмотренных примеров из предыдущих разделов можно на-
писать такую программу:
program pi
real :: р=0
integer :: i=-l, k
integer, parameter :: N=10000001
do k=l,N,2
i=-i
p=p+4.0*i/k
79
И. fl. Артёмов. Fortran: основы программирования
end do
write(*,*) p
end
Результат работы программы:
3.141597
Press any key to continue
Интересным будет результат, если сначала найти сумму всех положи-
тельных слагаемых, затем сумму всех отрицательных слагаемых и потом
сложить полученные суммы.
program pi
real :: pl=0, р2=0, р=0
! pl - сумма положительных слагаемых
! р2 - сумма отрицательных слагаемых
! р - искомое число "пи"
integer к
integer, parameter :: N=10000001
do k=l,N,4
pl=pl+4.0/k
end do
do k=3,N,4
p2=p2-4.0/k
end do
p=pl+p2
write(*,*) p
end
Результат работы программы:
2.450361
Press any key to continue
Вследствие ошибок округления получен неверный результат. Поэтому в
этом случае рекомендуется оформить цикл в обратном порядке, чтобы начать
сложение с меньших чисел либо использовать тип double precision.
Эти советы также касаются и первого варианта программы. При оформ-
лении цикла в обратном порядке:
do k=N,1,-2
i=-i
р=р+4.0*i/k
end do
будет получен более точный результат (л = 3,141592653589).
3.141593
80
4. Циклы
. ' 3 5 7 5
= 2- x + —+ — + — + ... при -1<х<1.
3 5 7
Пример. Известно, что In -
^1-х
Написать программу подсчета In (2).
~ 1+я а-1
Обозначив а = 2 , получим а =-или х =----.
1-х а+1
program logarifm
real :: res=0.0, x
integer к
x= (2.0-1) / (2.0+1)
do k=l,ll,2
res=res+x**k/k
end do
res=2*res
write(*,'(A,f9.5,\)') "Result...", res
write(*, '(A,f9.5,\)') "Answer...", log(2.0)
end
Результат работы программы:
Result... .69315 Answer... .69315
Press any key to continue
Пример. Написать программу вычисления факториала.
Факториал (!) числа есть произведение последовательности целых чисел,
например 5! = 1-2-3-4-5 = 120 . Очевидно, в этом случае можно использовать
переменную, которая будет накапливать не сумму чисел, а произведение.
При накапливании произведения следует помнить, что начальным значением
для переменной будет не нуль (иначе произведение всегда будет равно ну-
лю), а единица.
program factorial ! вычисление факториала
integer :: f=l
integer i, n
write(*,"(A,\)") " Enter n = read(*,*) n
do i=l,n
f=f*i
end do
write(*,"(A,ilO)") " Factorial...", f
end
Результат работы программы:
Enter n = 5
Factorial... 120
Press any key to continue
81
И. Л. Артёмов. Fortran: основы программирования
Аналогично оператора суммы в математике является оператор произве-
дения, Так, если требуется найти произведение натуральных чисел от 1 до 5,
5
то можно записать: P = J~[n = l-2-3-4-5. Данную формулу также можно
легко запрограммировать при помощи цикла do,
р=1.0 ! начальное значение отлично от нуля
do п=1,5
р=р*п
end do
п!
Пример. Вычисление количества сочетаний С"' = —-----г-.
т\(п-т)1
_ л! т-... л (л1 + 1) (лг + 2)-...-л
Так как С" = —--------г- =--------------г- = -----------, то
лг!(л-лг)! 1-2-...-лг-(л-лг)! (л-лг)!
количество сочетаний можно найти, вычислив сначала числитель, а затем
знаменатель.
program combination
integer m, n, C, resl, res2 ! resl - числитель,
I res2 - знаменатель
integer i
write(*,"(A,\)") " m = read(*,*) m
write(*, " (A, \) 11) " n = read(*,*) n
resl=l
do i=m+l,n
resl=resl*i
end do
res2=l
do i=l,n-m
res2=res2*i
end do
C=resl/res2
write ( *,(A, i4) ") "C(m,n) = ",C
end
Результат работы программы:
m = б
n = 15
C(m,n) = 5005
Press any key to continue
82
4. Циклы
Пример. Используя степенной ряд е* = 1 + —+ — ,
где < х<°°, рассчитать ег.
Для решения данной задачи можно воспользоваться следующим подхо-
дом:
1. Рассчитаем —.
1!
2. Рассчитаем —. Для этого умножим — на —, так как — =------------.
2! 1! 2 2! 1 2
3. Рассчитаем —. Для этого умножим полученное число — на —,
так как — =------.
3! 2! 3
Легко заметить, что каждое последующее слагаемое к зависит от пре-
дыдущего fc-1:
или, обозначая
к'. к (i-l)l’
имеем:
Л—Л-i
k
где Гд-j рассчитано на предыдущем расчетном шаге.
Для нахождения суммы ряда потребуется сложить полученные слага-
емые:
ех=г0 + Хл =т0+т;+т2+...+7;,
£=1
где То = 1.
program exponenta
real :: Е=1.0 ! накапливает сумму, 1.0 - первое слагаемое ряда
real :: Т=1.0 ! накапливает произведение
real х ! показатель экспоненты
integer к
83
И. Л. Артёмов. Fortran: основы программироввния
х=2.0
do к=1,10
Т=Т*х/к ! в левой части Тк, в правой Тк-1
Е=Е+Т
end do
write!*,*) Е, exp(2.0)
end
Результат работы программы:
7.388995 7.389056
Press any key to continue
Пример. Имеется два вида банковских вкладов. Первый - ФИКСИРО-
ВАННЫЙ. В конце каждого месяца начисляется доход, равный месячному
проценту (месячный процент = годовой/12) от первоначальной суммы вкла-
да. Второй - С НАКОПЛЕНИЕМ. В конце каждого месяца начисляется до-
ход, равный месячному проценту от имеющихся средств на вкладе (сумма
вклада + доход). Требуется написать программу, которая печатает процесс
накопления дохода на первом и втором вкладах с интервалом в 1 месяц.
Сумма вклада составляет 5 000 руб., 12 % в год.
Очевидно, для печати данных по обоим вкладам потребуется организо-
вать цикл по 12 месяцам. Согласно условию по первому вкладу доход за ка-
ждый месяц будет фиксированным и составляет 1 % от первоначальной сум-
мы вклада.
доход = вклад * годовой процент/12/100
Второй вклад, С НАКОПЛЕНИЕМ определяет доход от имеющихся средств
на вкладе, т. е. учитывается сам вклад и доходы с предыдущих месяцев.
1-й месяц: доход = вклад * годовой процент/12/100;
2-й месяц: доход = (вклад+доход 1-го месяца)*годовой процент/12/100;
3-й месяц: доход = (вклад+доход 1-го и 2-го месяцев)*годовой про-
цент/12/100 и т. д.
program percent
real :: money=5000 ! сумма вклада
real :: ml=5000 ! сумма по фиксированному вкладу
real :: m2=5000 ! сумма по вкладу с накоплением
real :: рг=12 ! годовой процент
integer i
do i=l,12
ml=ml+money*pr/12/100
m2=m2+m2*pr/12/100 I FIXR - фиксированный вклад
! COLL - вклад с накоплением
write(*,100) " month ",i," FIXR = »,ml," COLL = ",m2
end do
100 format(A,i2,2(A,f7.2))
end
84
4. Циклы
Результат работы программы:
molith 1 FIXR = 5050.00 COLL = 5050.00
month 10 FIXR = 5500.00 COLL = 5523.11
month 11 FIXR = 5550.00 COLL = 5578.34
month 12 FIXR = 5600.00 COLL = 5634.13
Press any key to continue
Интересными будут результаты, если задать 1 200 % в год и положить на
оба вклада по 1 руб.
Результат работы программы:
month 1 FIXR = 2.00 COLL = 2.00
month 2 FIXR = 3.00 COLL = 4.00
month 3 FIXR = 4.00 COLL = 8.00
month. 4 FIXR = 5.00 COLL = 16.00
month 5 FIXR = 6.00 COLL = 32.00
month 6 FIXR = 7.00 COLL = 64.00
month 7 FIXR = 8.00 COLL = 128.00
Press any key to continue
Таким образом, по фиксированному вкладу постоянно месяц за месяцем
получается доход в 1 руб. В случае же с накоплением доходы ежемесячно
удваиваются, т. е. 100 % в месяц от суммы, находящейся на вкладе!
4.4. Конструкция do while
Мы уже рассмотрели цикл do, который выполняется определенное число
раз. Однако при решении многих задач требуется, чтобы цикл выполнялся
заведомо неизвестное число раз. Например, рассмотрим такую задачу. Пусть
пользователь вводит с клавиатуры произвольные числа от 0 до 99 и всякий,
раз после ввода программа выводит на экран квадрат введенного числа. Од-
нако, как только будет введен нуль, программа выведет нуль и тут же завер-
шится. Очевидно, нам не известно, когда пользователь захочет ввести нуль,
поэтому понадобится цикл, который будет выполняться неопределенное чис-
ло раз. В таких случаях используется цикл do while (выполнять пока), общая
форма записи которого следующая:
имя цикла: do while (логическое условие)
операторы
end do имя цикла
Дословно приведенная конструкция означает: выполнять, пока логиче-
ское условие истинно, операторы в теле цикла.
Схема выполнения цикла do while следующая:
1. Вычисляется логическое условие.
85
И. Л. Артёмов. Fortran: основы программирования
2. Если результат условия истина, то выполняется тело цикла и происхо-
дит переход к п. 1, если ложь, то цикл завершает работу.
Сделаем пару важных замечаний:
• тело цикла может вообще не выполниться, если условие было изначально
ложно;
• если условие после работы тела цикла не изменяется, то это может при-
вести к зацикливанию цикла.
Ниже представлен текст программы для решения рассмотренной задачи.
program see_zero
integer :: k=l ! начальное значение
do while (k/=0) 1 цикл выполняется, пока к не равно нулю
write(*,"(А,\)") "Enter number..."; read(*,*) k
write(*,"(A,i4,/)") "Result...", k**2
end do
end
Для начала работы цикла необходимо, чтобы переменная к содержала
начальное значение отличное от нуля, например 1 (в противном случае цикл
не выполнится и программа тут же завершится). В цикле переменной к при-
сваивается значение, прочитанное с экрана. Затем, после вывода на экран ре-
зультата, происходит очередная проверка условия на равенство нулю пере-
менной к. Если в последний раз был введен нуль, то цикл прекращает выпол-
няться, в противном случае происходит запрос ввода следующего числа.
Результат работы программы:
Enter number... 5
Result... 25
Enter number...4
Result... 16
Enter number... 0
Result... 0
Press any key to continue
Пример. Ранее была рассмотрена задача о проверке пароля пользователя
с использованием условного оператора if. Используя цикл do while с неопре-
деленным числом повторений, можно оформить проверку пароля до тех пор,
пока не будет введен правильный пароль.
program pass
integer, parameter :: password=123
integer :: user=0
do while (user/=password)
write(*,"(A,\)") "Enter password..."
86
4. Циклы
read(*,*) user
end do
write(*,*) "Authorization OK!”
end
Результат работы программы:
Enter password...1
Enter password...2
Enter password...123
Authorization OK!
Press any key to continue
Пример. Найти сумму ряда F— пока слагаемое больше 0.001.
t=i к +1
рассчитать, если сделать простые арифметические
Значение k можно
действия:
= 0.001
£2+l
—L_ = £2+1 £=.—--------1 =31.606 = 31,
0.001 V 0.001
т. e. при £=31
имеем
—т~ 0.00104 > 0.001; при £=32 имеем
312+1
—— = 0.000976 < 0.001.
322 +1
Таким образом, при £=31 будем иметь последнее слагаемое для вычисле-
ния суммы ряда. Теперь записав цикл do=l ,31 можно легко решить задачу.
Однако, нам хотелось бы не проводить указанные арифметические дей-
ствия (вдруг слагаемые ряда будут иметь более сложный вид). Пусть про-
грамма сама определит, до какого момента следует складывать слагаемые ря-
да. Раз заведомо не известно число повторений, то можно воспользоваться
циклом do while, который выполняется при истинности определенного логи-
ческого выражения. В нашем случае цикл можно сформулировать так, повто-
рять суммирование пока > 0.001.
program summa
integer :: k=l
real :: sum=0, slag
! slag - текущее слагаемое
! k - переменная цикла
! sum - сумма ряда
slag=l.0/(k*k+l) ! первое слагаемое
do while (slag>0.001) ! проверка условия
sum=sum+slag ! накапливаем сумму
k=k+l
87
И. Л. Артёмов. Fortran: основы программирования
slag=l.О/(к*к+1) ! находим следующее слагаемое
end do
write(*, "(A,f9.6)")
write(*, 11 (A, i4) ")
write(*,"(A,f9.6)")
end
"summa = ",sum
"k = ",k
"slag = ",slag
Последовательно начиная c k=l (к - переменная-счетчик) вычисляется и
проверятся каждое слагаемое ряда. Если слагаемое больше значения 0.001,
т. е. выражение slag>0.001 истинно, то добавляем его в переменную sum. Ес-
ли же slag<0.001, то цикл подсчета суммы прекращается и в переменной sum
будет находиться искомая сумма.
Результат работы программы:
summa = 1.044941
к = 32
slag = .000976
Press any key to continue
Значение £=32 выведено на экран для контроля работы цикла.
При неверном использовании цикла do while могут появтЬься неприят-
ные ошибки во время выполнения программы. Так, если в приведенном выше
примере в цикле пропущена строка £=£+1, то в этом случае в переменную
sum все время будет прибавляться одно и то же значение 0.5. Подобная
ошибка приведет к зацикливанию, т. е. цикл будет выполняться бесконечное
число раз и никогда не закончится, пока вы не закроете программу.
4.5. Вложенные циклы do
Для решения многих задач бывает недостаточным записать только один
10 5 <• /
цикл. Например, требуется вычислить сумму S = XX -—;, которая анало-
“' + 7
гична выражению
1-1 1-2 1-3 1-4 1-5
-------1------1-------1------1------(•
1+1 1+2 1+3 1+4 1+5
2-1 2-2 2-3 2-4 2-5
+-------Ь ----1-----1-------1-----ь
2 + 1 2 + 2 2 + 3 2 + 4 2 + 5
7 = 1,
7=1—5
7 = 2,
7=1-5
10-1 10-2 10-3 10-4 10-5 ”
-----1-------1-----1--------1-----i ~ 10 7 = 1 5
10+1 10 + 2 10 + 3 .10 + 4 10 + 5 J
Мы рассматривали, что между циклом do и оператором суммы есть ана-
логия. В нашем случае, очевидно, потребуется внешний цикл по переменной
88
4. Циклы
i и внутренний цикл по переменной j. В итоге внешний цикл выполнится
10 раз, внутренний - 50 раз.
program summa
integer i,j
real :: s=0 ! обнуление переменной, накапливающей сумму
! i - переменная внешнего цикла
I j - переменная внутреннего цикла
do i=l,10 ! ====================== внешний цикл-
do j =1 , 5 ! - внутренний цикл
s=s+REAL(i*j)/REAL(i+j)
end do !-----------------------
end do !====================================
write(*,"(A, f9.3) ") "Summa = ", s
end
В некоторых случаях вложенные циклы удобнее читать, если задать име-
на циклам:
ci: do i=l,10
cj: do j=l, 5
s=s+REAL(i*j)/REAL(i+j)
end do ci
end do cj
Здесь ci - имя внешнего цикла, cj - имя внутреннего цикла.
Результат работы программы:
Summa = 84.882
Press any key to continue
Пример. Вывести на экран таблицу чисел
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
Как поступить, если проделать данную работу вручную? Очевидно, сна-
чала следует написать в ряд 5 целых чисел (это первый цикл - внутренний)
и повторить эту операцию 4 раза (это второй, внешний цикл).
program table
integer i,j
do i=l,4 ! меняется по строкам
do j=l,5 ! меняется по столбцам
write(*,"(i4,\)") j ! вывод нескольких столбцов
end do
write(*,*) ! переход на следующую строку
end do
end
89
И. Л. Артёмов. Fortran: основы программирования
Результат работы программы:
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
Press any key to continue
Пример. Немного усложним рассмотренную выше программу. Выведем
таблицу чисел в шахматном порядке.
13 5
2 4
13 5
2 4
13 5
Прежде чем создавать программу, попробуем найти закономерность. Ес-
ли мы ее найдем, то сможем легко оформить в виде алгоритма, а значит за-
программировать. Если внимательно посмотреть на номера клеток шахмат-
ной доски (рис. 4.5), то можно обнаружить, что для заштрихованных клеток
сумма номера строки и столбца всегда четное число, для белых - всегда не-
четное. Поэтому в цикле по формированию таблицы из пяти строк и пяти
столбцов следует поставить условие на проверку четности суммы i+j.
Рис. 4.5. Расположение клеток в шахматном порядке
program chess
integer i,j
do i=l,5
do j=1,5
if (mod(i+j,2)==0) then ! проверка четности i+j
write(*,"(i2,\)") j
else
90
4. Циклы
write(*,'(А,\)') " "
end if
end do
write(*,*) ! переход на следующую строку
end do
end
Результат работы программы:
1 3 5
2 4
13 5
2 4
13 5
Press any key to continue
Пример. Вывести на экран треугольную таблицу, состоящую из чисел
(рис. 4.6).
i=j=l
...—।
2 i=j=2
j
Рис. 4.6. Треугольная таблица
В такой таблице число столбцов равно номеру строки. Так в первой строке -
1 столбец, во второй - 2 и т. д. Поэтому текст программы будет таким:
program table
integer i,j
do i=l,5
do j =1, i
write(*,"<i2,\)")j
end do
write(*,*) 1 следующая строка
end do
end
91
И. Л. Артёмов. Fortran: основы программирования
Результат работы программы:
1
1 2
12 3
12 3 4
1 2 3 4 5
Press any key to continue
Пример. Вывести на экран значения функции f (х, у) = х2 + у2 в прямо-
угольной области (рис. 4.7). Результатом работы программы должна быть
таблица, содержащая значения /(х, у) в каждой точке прямоугольной об-
ласти.
Данная задача аналогична рассмотренной ранее задаче по табулирова-
нию функции Дх). Очевидно, в этом случае табулирование следует проводить
как по направлению х, так и по направлению у. Для формирования таблицы в
программе будет использоваться двойной цикл. Первый, внешний цикл по
направлению у, второй, внутренний по направлению х.
program func_table
integer, parameter :: Mi=5, Mj=7 ! размерность сетки
real x, у, fxy, dx, dy
I x, у - текущие координаты
! fxy - значение функции
! dx, dy - шаги по направлениям х и у
integer i,j ! переменные для организации циклов
real :: Y0=1.0; Х0=2.О ! размеры области
dx=X0/(Mj-1); dy=Y0/(Mi-1)
92
4. Циклы
do i=Mi, 1, -1'
y=(i-l)*dy
do
x=(j-1)*dx
fxy=x**2+y**2
write(*,1(f7.2,\) ’) fxy
end do
write(*,*); ! переход на следующую строку
end do
end
Внешний цикл оформлен, с отрицательным шагом, в соответствии с на-
правлением координатной оси у.
Результат работы программы:
1.00 1.11 1.44 2.00 2.78 3.78
0.56 0.67 1.01 1.56 2.34 3.34
0.25 0.36 0.69 1.25 2.03 3.03
0.06 0.17 0.51 1.06 1.84 2.84
0.00 0.11 0.44 1.00 1.78 2.78
5.00
4.56
4.25
4.06
4.00
КЛОП
+ клоп
клоп
клоп
полк
Рис. 4.8. Ребус
Press any key to continue
Пример. Дано четырехзначное число. Если
число сложить 4 раза и полученный результат
прочитать в обратном порядке, то получится ис-
ходное число. Данный ребус представлен на рис.
4.8. Здесь под каждой буквой находится опреде-
ленная цифра, кроме нуля. Требуется расшифро-
вать данную запись и найти неизвестное число.
Данную задачу решим двумя способами.
1-й способ. Оформим простой перебор чисел от 1234 (это наименьшее из
возможных чисел, в котором цифры не повторяются, т. е. к=1, л=2, о=3, п=4)
до 9999/4=2499 (конечное берется из расчета, что, если умножить 2500 на 4,
получим 5-значное число, что противоречит условию задачи). Можно орга-
низовать цикл от 1234 до 2499 (точнее до 2498) и подбирать каждое число на
возможность расшифровки ребуса. Программа будет следующей:
program klop_polk
integer klop, tmp
integer polk
integer k,l,o,p
do klop=1234, 2498
polk=4*klop
tmp=klop
p=mod(tmp,10); tmp=tmp/10 ! находим каждую цифру
l=mod (tmp, 10) ; tmp=tmp/10
93
И. Л. Артёмов. Fortran: основы программирования
o=mod(tmp, 10) ; tmp=tmp/10
k=mod(tmp,10)
if (polk==p*1000+l*100+o*10+k)
write!*,*) "Result = ", klop
end do
end
Результат работы программы:
Result = 2178
Press any key to continue
2-й способ. Будем подбирать число следующим образом:
program klop_polk
integer к,1,о,р, klop, polk
do k=l,9
do 1=1,9
do o=l, 9
do p=l,9
klop=k*1000+1*100+o*l0+p
polk=4*(p*1000+o*100+l*10+k)
if (klop==polk) write(*,*) "Result = ", klop
end do
end do
end do
end do
end
Возьмем 1 - это будет тысяча.
Еще раз 1 - это будет сотня.
Еще раз 1 - это десятки.
И последний раз 1, получим число 1111. Теперь, меняя единицы от 1
до 9, будем умножать на 4 и сравнивать с исходным числом, т. е. от 1111
до 1119.
Следующий этап - десятки начинаются с 2, поэтому будут'проверяться
числа от 1121 до 1129
Далее аналогично от 1131 до 1139.
Затем изменятся сотни, и процесс повторится сначала, и наконец, тысячи.
В итоге будет найдено нужное число 2178.
Пример. Поиск простых чисел от 2 до 50 Простым является число, ко-
торое делится без остатка на единицу и на само себя.
Алгоритм можно построить следующим образом. Для каждого прове-
ряемого числа num (внешний цикл от 2 до 50) будем перебирать все возмож-
ные делители, исключая единицу и проверяемое число (внутренний цикл от 2
до num-1). Результат проверки будем заносить в логическую переменную-
Единица не является простым числом.
94
4. Циклы
флаг. Если по окончании проверки не найдено ни одного делителя, то число
простое.
program prime_number ! поиск простых чисел
integer num, div ! num - проверяемое число
! div - делитель
logical flag ! flag - результат проверки
do num=2,50
flag=.TRUE.
do div=2 , num-1
flag=flag.AND.(mod(num,div)/=0)
end do
if (flag) write(*,"(i4,\)") num
end do
write(*,*)
end
Результат работы программы:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
Press any key to continue
Пример. Нарисовать таблицу в виде ромба,
program rhomb
integer, parameter :: M=10
integer i,j
do i=l,M 1 формирование верхнего треугольника
do j=l,M-i
write(*,100) 1 '
end do
do j=M-i+l,M
write(*,100) ' *’
end do
do j=l,i
write(*,100) ' *'
end do
write(*,*)
end do
do i=M,1,-1 ! формирование нижнего треугольника
do j=l,M-i
write(*,100) ' '
end do
do j=M-i+l,M
write(*,100) 1 *’
end do
do 3=1,i
write(*,100) 1 * 1
end do
95
И. Л. Артёмов. Fortran: основы программирования
write(*,*)
end do
100 format(A,\)
end
Результат работы программы:
**★* ★★**
Press any key to continue
Хорошо видно, что формирование верхнего и нижнего треугольников
одинаковы; поэтому можно предложить следующий компактный, однако ме-
нее читаемый текст программы:
program rhomb
integer, parameter :: M=5
integer i,j,k
integer :: il = l, i2=M, istep=l
do k=l,2
do i=il,i2,istep
do j=l,M-i
write(*,100) '
end do
do
write(*,100) '
end do
do j=l,i
write(*,100) '
end do
write(*,*)
end do
end do
il=M; i2=l; istep=-l
end do
100 format(A,\)
end
Пример. Поиск Героновых треугольников. Героновыми треугольниками
называются треугольники, у которых стороны и площадь есть целые числа.
Учитывая, что в треугольнике сумма двух любых сторон должна быть
больше третьей стороны, будем перебирать возможные целые значения сто-
96
4. Циклы
рон треугольника. Во избежание повтора размеров сторон следует организо-
вать тройной цикл, как показано в программе.
program Geron
integer, parameter :: N=10
integer a,b,c ! стороны треугольника
real S,p IS- площадь, p - полупериметр
do a=l,N
do b=a,N
do c=b,N
if ((a+b>c) .AND. (a+ob) .AND. (b+oa) ) then
p=(a+b+c)/2.0;
S=sqrt (p* (p-a) * (p-b) * (p-c) ) ! если площадь целое число
if (S-INT(S)<abs(IE-6)) write(*,"(4i4)") a,b,c,INT(S)
end if
end do
end do
end do
end
Результат работы программы:
3 4 5 6
5 5 6 12
5 5 8 12
6 8 10 24
Press any key to continue
4.6. Выходы из циклов.
Бесконечные циклы и зацикливания
Для эффективного управления работой циклов используются два опера-
тора - exit и cycle.
Оператор exit (выход) прекращает выполнение цикла.
Пример. Допустим, требуется запросить у пользователя 5 произвольных
целых чисел и подсчитать их сумму. Однако если будет введено отрицатель-
ное число, то необходимо прекратить подсчет и вывести имеющуюся сумму
на экран,
program use_exit
integer a, k I а - вводимое число, к - переменная цикла
integer :: s=0 I s - сумма чисел
do k=l,5
write(*,"(A,\)") "Enter number..."; read!*,*) a
if (a<0) exit I если число отрицательное, то выход из цикла
s=s+a
97
И. Л. Артёмов. Fortran: основы программирования
end do
write(*,*) "Summa...",s
end
Результат работы программы:
Enter number...10
Enter number...15
Enter number...5
Enter number...-8
Summa... 30
Press any key to continue
Оператор exit является эффективным средством для выхода из несколь-
ких вложенных циклов. Рассмотренный ранее пример по расшифровке ребу-
са содержит 4 вложенных цикла. Есть маленький недостаток в этой npoipaM-
ме, а именно: когда найдено требуемое число 2178, программа будет про-
должать искать еще возможные комбинации. Чтобы помочь программе не
делать лишних действий, можно воспользоваться оператором exit и прекра-
тить работу четырех вложенных циклов.
program klop_polk
integer k, 1,о,р, klop, polk
ext: do k=l,9 ! внешний цикл с именем ext (external внешний)
do 1=1,9
do o=l,9
do p=l,9
klop=k*1000+l*100+o*10+p
polk=4*(p*1000+o*100+l*10+k)
if (klop==polk) then
write)*,*) "Result = ", klop
exit ext! выход из внешнего цикла ext
end if
end do
end do
end do
end do ext
end
Результат работы программы:
Result = 8712
Press any key to continue
Оператор cycle (цикл) используется для прекращения текущей итерации
цикла.
Пример. Вывести на экран элементы закрашенной области (рис. 4.9).
program region
integer :: i,j,s=0
do i=l,5
98
4. Циклы
do j=l,9
s=s+l
if ((i<=3).AND.(j>=5)) cycle ! обход области
write (*, " (i4, \) ") s
end do
write(*,*) ! переход на следующую строку
end do
end
j>=5
Puc. 4.9. Обход области
При помощи двойного цикла происходит перебор элементов, находя-
щихся в закрашенной области. Однако при выходе за границы области (J>=5
и i<=3) текущая итерация цикла прекращает выполняться при помощи опера-
тора cycle. Таким образом, происходит обход незакрашенной области.
Результат работы программы:
12 3 4
10 11 12 13
19 20 21 22
28 29 30 31 32 33 34 35 36
37 38 39 40 41 42 43 44 45
Press any key to continue
В ряде задач требуется сформировать цикл, который выполнялся бы бес-
конечное число раз.
Пример. Бесконечный цикл.
do ! бесконечный цикл.
write(*,*) "Infinity..."
end do
В результате работы программы на экран будут выводиться строки тек-
ста Infinity, что означает бесконечность. Данный цикл будет выполняться,
пока пользователь нс закроет исполняемую программу.
99
И. Л. Артёмов. Fortran: основы программирования
Еще один пример бесконечного цикла:
do while (2<3)
end do
Условие (2<3) всегда истинно, поэтому цикл будет выполняться беско-
нечное число раз.
При помощи бесконечных циклов и оператора exit легко можно офор-
мить следующие два вида циклов:
1. Аналог цикла do while,
do
if (логическое условие) exit
end do
2. Цикл, который выполняется хотя бы один раз.
do
if (логическое условие) exit
end do
Пример. Программа угадывания чисел.
program want_to_know
integer :: n=123, k
do
write(*,"(A,\)")"Enter..." ; read!*,*) k
if (k<n) write)*,*) "YOUR number smaller..."
if (k>n) write!*,*) "YOUR number bigger..."
if (k==n) exit
end do
write!*,*) "YES..."
end
Результат работы программы:
Enter...200
YOUR number bigger...
Enter...100
YOUR number smaller...
Enter...150
YOUR number bigger...
Enter...125
YOUR number bigger...
Enter...123
YES...
Press any key to continue
Часто бесконечные циклы возникают при неосторожном обращении с
циклами, что приводит в конечном итоге к зависанию программы. Рассмот-
рим некоторые типичные примеры.
100
_ __ _ _______ 4. Циклы
Пример:
program infinity ! зацикливание цикла
Integer :: а=0
do while(a/=10)
а=а+3
write(*,*) а
end do
end
Если разобрать данный цикл, можно увидеть, что условие а/=10 всегда
истинно. Действительно:
а=0 условие а/=10 истинно
а=3 условие а/=10 истинно
а=6 условие а/=10 истинно
а=9 условие а/=10 истинно
а=12 условие а/=10 истинно ! проскочили значение 10
Отсюда следует, что условие равенства является очень жестким. Аналогич-
ная ситуация может возникнуть при сравнении на равенство двух вещественных
чисел, которые вследствие ошибок округления не равны между собой.
Пример:
program infinity
integer(2) :: i=5
do while(i<10)
i=i-l
write(*,*) i
end do
end
В этом примере в цикле стоит условие /<10. Данное условие будет посто-
янно выполняться, так как с каждой итерацией цикла значение переменной
будет уменьшаться на единицу и условие будет истинным, т. е. переменная i
будет принимать значения 5, 4, 3, 2, 1, 0, -1 и т. д. В результате произойдет
зацикливание программы *.
Пример:
program infinity
real :: s=0
do
s=s+0.05
write(*,*) s
if (s==0.5) exit
end do
* Здесь мы не рассматриваем предельный случай, когда значение переменной достиг-
нет своего минимально допустимого значения —32768 для типа integer(2) (см. табли-
цу допустимых значений для типа integer). В этой ситуации дальнейшее уменьшение
числа на единицу приводит к результату +32767, т. е. -32768-1=32767, и цикл за-
вершится.
101
И. Л. Артёмов. Fortran: основы программирования
end
Результат работы программы:
5.000000Е-02
1.000000Е-01
1.500000Е-01 .
2.000000Е-01
2.500000Е-01
3.000000Е-01
3.500000Е-01
4.000000Е-01
4.500000Е-01
5.000001Е-01
5.500001Е-01
Зацикливание связано с ошибкой округления при работе с вещественны-
ми числами. Поэтому следует записать if (abs(s-0.5)<lE-6).
Итоги
1. Циклы позволяют многократно выполнять операторы программы, за-
писав их только один раз.
2. Оператор do организует циклы с фиксированным числом повторений.
Запись
do k=3,54
end do
означает, что цикл выполнится 54-3+1=52 раза. После работы цикла значе-
ние переменной составит Л=55. Переменная цикла к должна быть целого или
вещественного типа.
3. Шаг в цикле do позволяет легко организовать нужный перебор значе-
ний.
4. Переменные счетчики - это целочисленные переменные, которые уве-
личивают свое значение на единицу при выполнении определенных условий.
Перед использованием значение счетчика необходимо инициализировать или
установить в исходное состояние.
5. Для накопления суммы чисел используются переменные сумматоры,
увеличивающие свое значение на определенный шаг. Все программирование
держится на понятии счетчика и сумматора, и главное увидеть, как их ис-
пользовать в своих программах.
6. Для поочередной смены знака значения переменной в цикле применя-
ется оператор присваивания к=-к.
N N
7. Для подсчета выражений, содержащих ГЬ используется цикл do
*=i *=i
к=\, N... end do.
102
4. Циклы
8. Конструкция do while применяется в случаях, когда цикл будет выпол-
няться заранее неизвестное число раз. Цикл выполняется до тех пор, пока ло-
гическое условие истинно. Если условие в процессе работы цикла не изменя-
ется, то это может приводить к зацикливанию цикла и зависанию программы.
9. Если циклы содержат внутри себя другие циклы, то говорят о вложен-
ных циклах. Для вывода таблиц, обработки значений функции двух перемен-
ных используются двойные циклы.
10. Оператор exit прерывает выполнение цикла. Оператор continue пре-
рывает текущую итерацию цикла.
11. Самый простой способ организации бесконечного цикла записать
оператор do ... end do. Бесконечные циклы широко используются для группы
операторов, которые должны постоянно выполняться на всем протяжении
работы программы.
103
5. МАССИВЫ
5.1. Знакомство с массивами
Допустим, в программе требуется хранить и обрабатывать информацию о
количестве жильцов каждого этажа девятиэтажного дома. Владея рассмот-
ренными средствами программирования, можно объявить 9 целочисленных
переменных:
integer ql, q2, q3, q4, q5, q6, q7, q8, q9
Однако немного "поработав" с группой таких переменных, можно
прийти к очевидному заключению, что гораздо удобнее, если номер этажа
может изменяться в программе (например, для организации циклов). Для
этого следует представить переменную q как массив, объявив это сле-
дующим образом:
integer q(9)
или
integer, dimension (9) :: q
Данные операторы описания означают, что создан массив с именем q,
элементы которого нумеруются от 1 до 9 и являются данными целого типа.
Схематично массив можно изобразить как последовательность элементов,
каждый из которых имеет порядковый номер (индекс) (рис. 5.1).
I ! ГП I 1
: : i
_______1____________________________—I___________________
q(l) q(2) q(3) q(4) q(5) q(6) q(7) q(8) q(9)
Puc. 5.1. Массив - последовательность пронумерованных элементов
Массив можно определить, указав явно первый и последний индексы
элементов массива,
integer q(l:9)
или так:
integer s(—10:19)
Первый и последний индексы массива часто называют нижней и верхней
границами массива. Следует помнить, что индексами массива могут быть
только целые числа.
Массив может содержать данные только одного типа данных. Так, опи-
сав массивы
integer а(10)
тмог/ииои
104
5. Массивы
logical Ь(10)
real с(10)
character d(10)
мы объявляем 4 массива, все элементы которых являются : a - целыми числами,
b - логическими данными, с - вещественными числами, d - данными символьно-
го типа. В то же время невозможно создать, например, массив, у которого часть
элементов целого типа, а другая - логического типа.
Доступ к отдельному элементу массива осуществляется при помощи
имени массива и индекса, указанного в круглых скобках. Следующие приме-
ры показывают, как можно присваивать значения элементам массива q.
q(l)=20 ! присвоили первому элементу массива значение 20.
k=4; q(k)=5 ! четвертому элементу значение 5.
i=8; q(i+l)=17 ! последнему элементу значение 17.
Таким образом, массив - это группа данных, которые имеют одинаковый
тип данных и одно имя, доступ к которым осуществляется при помощи ин-
декса.
При описании границы массива рекомендуется задавать в виде констант,
что в дальнейшем позволит легко использовать циклы для обработки, ввода
и вывода элементов массива. Например:
integer, parameter :: Mi=20
real a(Mi)
integer, parameter :: Mil=0, Mi2=20
double precision b(Mil:Mi2)
character c(Mil+10:Mi2)
logical d(Mil-10:Mi2+10)
Массив a объявлен вещественного типа, первый элемент имеет индекс 1,
а последний - 20.
Массив b содержит элементы двойной точности, индекс первого элемен-
та 0, последнего - 20.
Массив с содержит элементы символьного типа с 10-го по 20-й индексы.
Массив d начинается с элемента с индексом -10 и заканчивается элемен-
том с индексом 30. Элементы этого массива логические данные.
Если при объявлении массива первый индекс превосходит последний, то
массив имеет нулевую длину.
real с (1:0) ! массив нулевой длины
При объявлении массиву можно задавать начальные значения или ини-
циализировать, например:
integer, parameter :: n=7
integer a(n) /1,5,2,3,4,8,77
105
И. Л. Артёмов. Fortran: основы программирования
Описанный массив а из семи элементов показан иа рис. 5.2.
1 5 2 3 4 8 i 7
а(1) а(2) а(3) а(4) а(5) а(6) а(7)
Рис. 5.2. Значения элементов массива
Массив - это последовательность элементов, доступ к которым осущест-
вляется при помощи целочисленного индекса. Индексы всегда следуют по
порядку, и поэтому очевидным является использование циклов для работы с
массивами.
Используя цикл do, выведем массив из пяти элементов на экран,
program column_row
integer, parameter :: Mi=5 ! количество элементов
integer a(Mi) 1 целочисленный массив из пяти элементов
integer i ! индексная переменная
а(1)=100; а(2)=300; а(3)=70; а(4)=-100; а(5)=90
do i=l,Mi
write(*,"(i4)") a(i) ! -------------- вывод элементов в столбец
end do
do i=l,Mi
write(*,“(5x,i4,\)") a(i) ! ------- вывод элементов в строку
end do
end
В данном примере переменная цикла i последовательно принимает зна-
чения индексов элементов массива. В первом цикле происходит вывод каж-
дого элемента на новой строке, что приводит к формированию на экране
столбца. Во втором задан формат - обратный слеш (\) - на запрет перехода к
новой строке, приводящий к выводу элементов в строку.
Результат работы программы:
100
300
70
-100
90
100 300 70 -100 90
Press any key to continue
Пример. Сформировать и вывести на экран массив, состоящий из эле-
ментов 10 20 30 40 50 60 70 80 90.
program make_massif
integer, parameter :: Mi=9 ! количество элементов
integer a(Mi) ! целочисленный массив
integer i .
106
5. Массивы
do i=l,Mi
a(i)=i*10
write(*,”(i4,\) ") a(i)
end do
end
Результат работы программы:
10 20 30 40 50 60 70 80 90
Press any key to continue
Пример. Создать и вывести на экран массив из 10 элементов, значения
которых - квадраты чисел от 1 до 10.
program make_massif
integer, parameter :: Mi=10 ! количество элементов
integer q(Mi) ! целочисленный массив
integer i ! индексная переменная
do i=l,Mi
q(i)=i**2
write(*,"(i4,\)") q(i) ! вывели массив в одну строку
end do
end
Результат работы программы:
1 4 9 16 25 36 49 64 81 100
Press any key to continue
Вывод массива можно организовать неявно, используя циклический спи-
сок. Например, цикл, формирующий вывод элементов массива в строку:
do i=l,Mi
write(*,"(i4,\)") a(i)
end do
можно заменить таким компактным выражением:
write(*,"(5(i4))") (a(i),i=l,Mi)
Для общей записи оператора write рекомендуется в формате задавать
большее количество элементов, например
write!*,"(500(i4))") (a(i),i=l,Mi)
что дает свободу изменения значений констант, задающих границы массива,
и не затрагивает изменение формата.
Пример. Вывод массива с использованием циклического списка.
integer, parameter :: Mi=5
integer a(Mi) /2,4,1,10,11/
integer i
write!*,"(100(i4))") (a(i),i=l,Mi)
end
Результат работы программы:
2 4 1 10 11
107
И. Л. Артёмов. Fortran: основы программирования
Press any key to continue
Аналогичным образом при помощи цикла do и циклического списка
можно организовать ввод элементов массива.
Пример. Ввести с клавиатуры 5 элементов массива и вывести их на эк-
ран.
1-й способ:
program read_ massif
integer, parameter :: M=5
integer k, a(M)
do k=l,M ! элементы вводить последовательно, нажимая Enter
write(*,"(il,А,\)") k, ” Element..."; read(*,*) a(k)
end do
write(*,"(100(i4))") (a(k),k=l,M)
end
Результат работы программы:
1 Element... 10
2 Element... 15
3 Element... 53
4 Element...-7
5 Element... 12
10 15 53 -7 12
Press any key to continue
2-й способ:
program read_massif
integer, parameter :: M=5
integer k, a(M)
read)*,*) (a(k),k=l,M) ! элементы вводить через пробел
write(*,"(100(i4))") (a(k),k=l,M)
end
В этом случае элементы массива можно сразу набрать в одной строке,
разделяя пробелами, и нажать клавишу ENTER.
Результат работы программы:
12 15 0 7 8
12 15 0 7 8
Press any key to continue
Для присваивания значений в некоторых случаях является полезным ис-
пользование конструктора массива. Например, чтобы поместить в массив чи-
словые значения можно записать:
integer, parameter :: М=7
integer q(M)
q=(/10, 30, -50, 22, 70, 25, 12/) ! вызвали конструктор массива
108
5. Массивы
В конструкторе можно использовать циклический список. Например, за-
дать массив из десяти элементов: 111115555 5.
integer, parameter :: М=10
integer s(М), i
s=(/(1,i=l,5),(5,i=l,5)/)
Пример. Сформировать логический массив с из массивов а и Ь, как пока-
зано на рис. 5.3.
Рис. 5.3. Формирование массива с из массивов aub
program constructor
integer, parameter :: M=5
logical a(M), b(M), c(M)
! а и b исходные массивы
! с результат
а=(/(.TRUE.,i=l,M)/)
b=(/(.FALSE.,i=l,M)/)
c=(/a(l),b(2) , a(3) , b(4) , a(5) /)
write(*,"(5(12))")
write(*,"(5(12))")
write(*,*)
write(*,”(5(12))")
end
(a(i),i=l,M)
(b(i),i=l,M)
(c (i) , i=l, M)
Результат работы программы:
т т
F F F F F
Т F Т F Т
Press any key to continue
Пример. Даны два вектора в декартовой системе координат. Найти их
скалярное произведение.
109
И. Л. Артёмов. Fortran: основы программирования
Скалярное произведение двух векторов a = {ax,ay,az} и b = ,by,bz},
выражается известной формулой а • b - ах -Ьх + ау • by + az • bz.
program skalar
integer, parameter :: M=3
integer a(M), b(M) ! векторы
integer i 1 индекс
integer :: s =0 ! результат скалярного произведения
! читаем с экрана координаты векторов а и b
write(*, " (А, \)") "Enter vector а.; read(*,*) (a(i),i=l,M)
write(*,"(А,\)") "Enter vector b..."; read(*,*) (b(i),i=l,M)
! Находим скалярное произведение
do i=l,M
s=s+a (i)*b (i) ! Переменная s накапливает сумму
end do
write(*,"(A,i4)") "Scalar products
end
Результат работы профаммы:
Enter vector a...l 2 0
Enter vector b...2 4 5
Scalar product... 10
Press any key to continue
Пример. Вывести на экран сумму нечетных и четных элементов массива *.
program sum_massif
integer, parameter :: n=10 ! число элементов массива
integer a(n) /5,4,-2,3,-5,9,7,5,9,1/ ! инициализация массива
integer i, si, s2 I si - сумма нечетных элементов
I s2 - сумма четных элементов
! i - индексная переменная
write(*,"(10(14))") (a(i),i=l,n) 1 вывод элементов масрива
sl=0; s2=0
do i=l,9,2
sl=sl+a(i) ! сумма нечетных элементов
s2=s2+a(i+l) 1 сумма четных элементов
end do
write(*,"(A,i2)") "SI = ", si
write(*,"(A,i2)") "S2 = ”,s2
end
Результат работы профаммы:
54 -2 3 -5 97591
* Для нахождения суммы элементов массива рекомендуется пользоваться стан-
дартной функцией sum (см. 5.5).
110
5. Массивы
SI = 14
S2 = 22
Press any key to continue
Пример. Найти максимальный и минимальный элементы массива *.
Для нахождения максимального и минимального элементов сделаем
предположение, что первый элемент является и максимальным и минималь-
ным. Далее в цикле будем сравнивать текущий элемент с данным максималь-
ным и минимальным элементом. Если обнаружится, что текущий элемент
больше максимального, то в максимальный элемент поместим текущий. Ана-
логичным образом поступим, если текущий элемент окажется меньше мини-
мального.
program max_min
integer, parameter :: n=10
integer s(n)
integer :: i, smax, smin
s=(/10,20,-19,30,2,12,14,80,90,100/)
write(*,"(100(i4))") (s(i),i=l,n)
smax=s(1); smin=s(1)
do i=l,n
if (s(i)<smin) smin=s(i)
if (s(i)>smax) smax=s(i)
end do
write(*,100) "Maximum.smax
write(*,100) "Minimum.smin
100 format(A,i4)
end
Результат работы программы:
10 20 -19 30 2 12 14 80 90 100
Maximum... 100
Minimum... -19
Press any key to continue
5.2. Многомерные массивы
Нами уже были рассмотрены массивы, доступ к элементам, которых
осуществлялся при помощи одного индекса. Такие массивы называются од-
номерными или векторами. Аналогично одномерным массивам можно ввести
и двумерные массивы, доступ к элементам которых осуществляется при по-
мощи двух индексов.
* Для нахождения максимального и минимального элементов массива рекомендуется
пользоваться стандартными функциями maxval и minval (см. 5.5).
111
И. Л. Артёмов. Fortran: основы программирования
Так описав в программе
integer а(2,3)
создается двумерный массив с двумя строками и тремя столбцами (рис. 5.4).
Двумерные массивы также называются матрицами или таблицами.
При объявлении двумерного массива гра- ницы массива также рекомендуется задавать в виде констант: integer, parameter :: Mi=2, Mj=3 integer a(Mi,Mj) Для правильного использования массивов — а (1/1) а (1,2) • а (1,3)
а (2,1) а (2,2) а (2,3)
необходимо представлять, как хранится в па-
Рис. 5.4. Двумерный массив
мяти двумерный массив.
Память компьютера является одномерной,
поэтому двумерный массив
должен быть представлен как одномерный. В Fortran двумерный массив хра-
нится по столбцам, т. е. приведенный выше массив располагается в памяти
следующим образом (рис. 5.5).
г
а а а
(1,1) (2,1) j (1,2)
3 3 3
(2,2) (1,3) (2,3)
Рис. 5.5. Представление в памяти компьютера двумерного массива
Пример. Сформировать двумерный массив, у которого элементы равны
произведению своих индексов,
program massif—2d
integer, parameter :: Mi=4, Mj=6
integer b(Mi,Mj), i, j
do i=l,Mi
do j=l,Mj
b(i,j)=i*j
write(*,"(i4,\)") b(i,j) ! вывод элементов строки
end do
write(*,‘) ! переход на следующую строку
end do
end
Результат работы программы:
1 2 3 4 5 6
2 4 6 8 10 12
3 6 9 12 15 18
4 8 12 16 20 24
Press any key to continue
112
5. Мвссивы
Пример. Сформировать и вывести на экран 2 1 2 1 2
следующую квадратную матрицу 5-го порядка:. 12 12 1
Легко видеть, что если сумма номеров строки А = 2 12 12
и столбца число четное, то элемент равен двум, 12 12 1
в противном случае - единице. 2 12 12
Поэтому программа будет состоять из двойного цикла по формированию
матрицы с проверкой на четность суммы индексов.
program matrix
integer, parameter :: M=5
integer i, j
do i=l,M
do j=l,M
if (mod(i+j,2)==0) then! проверка
a (i, j)=2
else
a(i,j)=1
end if
write(*,"(i2,\)") a(i,j) (вывод строки
end do
write(*,*) 1 переход на следующую строку
end do
end
Результат работы программы:
2 12 12
12 12 1
2 12 12
12 12 1
2 12 12
Press any key to continue
Пример. Найти сумму элементов главной и произведение элементов по-
бочной диагоналей квадратной матрицы.
В качестве примера рассмотрим квадратную матрицу 5-го порядка. Вы-
пишем отдельно элементы главной и побочной диагоналей (рис. 5.6).
Легко заметить, что элементы главной диагонали имеют одинаковые ин-
дексы, поэтому для суммирования элементов можно использовать цикл:
summa=0
do i=l,M
summa=summa+a(i,i)
end do
113
И. Л. Артёмов. Fortran: основы программирования
. 1) - .. a (1,2) a (1,3) a (1,4) •x a “(1,1)” a “(1,5)
a (2,1) a (2,3 )] a (2,5) a (2,2) . a (2,4)
a (3,1) a (4,1) a (3,. . ,;:) 3 vS \ , 3fl a (4,3) a (3,4) (;, л a (3,5) a (4,5) a .0,3) a (4,4) a (i, i) a (3,3) a (4,2) „ a (i,M-i+1)
. 1) a (5,2) a (5,3) a (5,4) a (5,5) главная a (5,1) юбочна Я
диагональ диагональ
Рис. 5.6. Главная и побочная диагонали квадратной матрицы 5-го порядка
В элементах побочной диагонали индекс i увеличивается, индекс j
уменьшается. Уменьшение индекса можно выразить через i и порядок матри-
цы М выражением j=M-i+l.
При i=l j=M-l+l=M
i=2 j=M-2+l=M-l
i=M j=M-M+l=l
Цикл нахождения произведения элементов побочной диагонали будет
следующим:
prod=l
do i=l,M
prod=prod*a(i,M-i+1)
end do
Объединяя циклы в один, получим следующую программу:
program diagonal
integer, parameter :: M=5
integer a(M,M), summa, prod, i, j
! summa - сумма элементов главной диагонали
! prod - произведение элементов побочной диагонали
! а - исходная матрица
do i=l,M
write (*, " (A, i2 , А,\) ") "Enter ",1," row | "
read(*,*) (a(i,j),j=l,M)
end do
summa=0; prod=l
do i=l,M
summa=summa+a(i,i)
prod=prod*a(i,M-i+1)
114
5. Массивы
end do
write(*,"(2(A,i4))")"Summa = ",summa," Product = ", prod
end
Результат работы программы:
Enter 1 row
Enter 2 row
Enter 3 row
Enter 4 row
Enter 5 row
Summa = 7
1 2 0 0 5
1 3 4 4 1
2 0 2 2 3
112 11
11110
Product =
40
Press any key to continue
Аналогично двумерным массивам можно создавать трехмерные массивы,
которые широко используются при решении многих вычислительных задач.
Например, в следующих операторах описания:
real р(10,2,5)
integer, parameter :: Mi=10, Mj=20, Mk=15
integer q(0:Mi,0:Mj,0:Mk)
объявлены два трехмерных массива. Массив р предназначен для хранения
вещественных данных, массив q будет содержать целые значения.
Пример. Сформировать трехмерный массив размером (7x7x7) следующим
образом: закрашенные элементы содержат единицу, все остальные равны нулю
(рис. 5.7). Вывести на экран данные в плоскости (х,у) при z=l, г=4, г=7.
Рис. 5.7. Элементы трехмерного массива
Пусть г, j, к будут индексами вдоль направлений х, у, z соответственно.
Формирование массива проделаем в три этапа. На первом этапе формируется
115
И. Л. Артёмов. Fortran: основы программирования
центральная область из единиц вдоль направления х, на втором этапе - вдоль
направления у, и на третьем - вдоль z.
Цикл для формирования области вдоль направления х будет следующим:
do i=l,Mi
do j=3,5
do k=3,5
a(i,j,k)=l
end do
end do
end do
Аналогично можно организовать циклы вдоль двух других направлений.
(Как можно значительно сократить размер приведенной программы, будет
показано в 5.3.)
program massif_3d
Integer, parameter :: Mi=7, Mj=7, Mk=7
Integer a(Mi,Mj,Mk), i,j,k
do i=l,Mi
do j=l,Mj
do k=l,Mk
a(i, j,k)=0
end do
end do
end do
do i=l,Mi 1 направление x
do j=3,5
do k=3,5
a(i,j,k)=l
end do
end do
end do
do j=l,Mj ! направление у '
do i=3,5
do k=3,5
a(i , j,k)=1
end do
end do
end do
do k=l,Mk ! направление z
do i=3,5
do j=3,5
a(i, j,k)=1
end do
end do
end do
! вывод значений в плоскости (х,у) при z=l z=4 z=7
116
5. Массивы
do к=1,Мк,3
write(*,"(A,i2)") "Number ",k ! вывод номера плоскости
do i=l,Mi
do
write(*,"(i2,\)") a(i,j,k)
end do
write(*,*) ! переход на следующую строку
end do
read(*,*) ! переход на следующую плоскость
end do
end
Результаты работы программы. (Для удобства результаты сведены в три
колонки.)
Number 1 Number 4 Number 7
0 0 0 0 0 0 0 0 0 11 10 0 0 0 0 0 ООО
0 0 0 0 0 0 0 0 0 11 10 0 0 0 0 0 0 0 0
0 0 11 10 0 1111 111 0 0 11 10 0
0 0 11 10 0 1111 111 0 0 11 10 0
0 0 11 10 0 1111 111 0 0 11 10 0
0 0 0 0 0 0 0 0 0 11 10 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 11 10 0 0 0 0 0 0 0 0
Press any key to continue
5.3. Более глубокий взгляд на массивы
Рассмотрим массивы несколько подробнее. Пусть описан трехмерный
массив вещественного типа real а(10,20,40).
Массив имеет три измерения. Количество измерений массива принято
называть рангом массива, т. е. массив а третьего ранга. Массив хранит
10x20x40=8000 элементов. Количество элементов массива называется разме-
ром массива. Совокупность числа измерений и количества элементов вдоль
каждого измерения называется формой (shape) массива. Массив а имеет фор-
му (10,20,40). Если форма массивов совпадает, то говорят, что массивы со-
гласованны (конформны).
Пример:
integer а(10,3)
integer Ь(-5:4,4:6)
Массивы а и b второго ранга. Размер массивов - 30 элементов. Форма
массивов (10,3). Массивы а и b конформны.
В первых программах для работы с массивами мы применяли циклы do.
Однако во многих случаях есть возможность отказаться от использования
циклов и сократить программу. Дело в том, что Fortran позволяет работать с
массивами как с обычными переменными.
117
И. Л. Артёмов. Fortran: основы программирования
Например, для присваивания всем элементам двумерного массива значе-
ния -1 вместо вложенных циклов можно применять один оператор присваи-
вания (рис. 5.8).
Рис. 5.8. Массив как обычная переменная
Такие элементарные операции, как сложение и вычитание матриц, умно-
жение матрицы на число, записываются так же как и в обычной математиче-
ской записи:
с=а+Ь
d=a-b
p=lambda*a,
что равносильно следующим циклам
do i=l,Mi
do j=l,Mj
c(i,j)=a(i,j)+b(i,j)
d(i,j)=a(i,j)-b(i,j)
p(i,j)=lambda*a(i,j)
end do
end do
Очевидно, что первый способ записи гораздо предпочтительнее послед-
ней с использованием циклов.
Если два массива согласованны, то к ним также справедлив рассмотрен-
ный подход.
integer а(10, 3)
integer b(-5:4,4:6)
integer с(0:9,0:2)
а=4; Ь=9; с=а-Ь
т. е.
с (0,0) =а (1,1)-Ь (-5,4)
с(1,0)=а(2,1)-Ь(-4,4)ит. д.
Следует отметить, что запись а*Ъ приведет к перемножению соответст-
вующих элементов массивов, но не к перемножению двух матриц, принятому
в линейной алгебре. Аналогично запись а**(-1) не приводит к нахождению
обратной матрицы.
Обращаясь с массивами как с обычными переменными, можно также ор-
ганизовать их ввод и вывод.
118
S. Массивы
Пример. Вывести на экран одномерный массив.
program showjnassif
integer, parameter :: M=7
integer a(M) /45, 85, 90, 11, 94, 45, 23/
write(*,"(100 (i4)) " j a
end
Результат работы программы:
45 85 90 11 94 45 23
Press any key to continue
Однако при выводе рассмотренным способом двумерного массива следу-
ет проявить осторожность. Так, если указать вывод массива с(3х4)
12 3 4
5 6 7 8
9 10 11 12
оператором write(*,*) с, то на экране будет результат не тот, который ожи-
дался: так как в памяти двумерный массив хранится как одномерный по
столбцам, то на экране будет следующий результат:
1 5 9 2 6 10 3 7 11 4 8 12.
program attention
integer, parameter :: M=3
integer b(M,M+l), i, j, s
s=0
write(*,*) " Correct..."
do i=l,M
do j=l,M+l
s=s+l
b(i,j)=s
write(*," (i5,\) ") b(i,.j)
end do
write(*,*)
end do
write(*, *) " Error..."
write(*,"(4(15))") b
end
Результат работы программы:
Correct...
1 2 3 4
5 6 7 8
9 10 11 12
Error.
1 5 9 2
6 10 3 7
119
И. Л. Артёмов. Fortran: основы прогреммирования
11 4 8 12
Press any key to continue
Массивы также можно использовать как аргументы подпрограмм и
функций. Так, записав sqrt(a) мы извлекаем квадратный корень из всех эле-
ментов массива.
Пример. Сформировать вещественный и целочисленный массив из слу-
чайных чисел.
program random_massif
Integer, parameter :: M=5
real a(M)
Integer b(M)
call randam_number(a)
b=INT(a*100)-50
writer, ” (100(f5.2)) ") a
write(*,"(100(i5) )") b
end
Результат работы программы:
0.00 0.03 0.35 0.67 0.96
-50 -48 -15 16 46
Press any key to continue
Можно обращаться не только к массиву целиком, но и к группе его эле-
ментов, которые называются сечениями массива. Сечения массива очень эф-
фективное средство, которое позволяет устранить многие циклы и повысить
читаемость про1раммы. Доступ к сечению массива может быть получен дву-
мя способами, при помощи:
• индексного триплета,
• векторного индекса.
1. Получение сечения при помощи индексного триплета.
В этом случае вместо индекса подставляется выражение
нижняя граница : верхняя граница : шаг
Причем все три параметра являются необязательными.
Пример. Элементам массива с 4-го по 7-й номер присвоить значения 1
(рис. 5.9).
а(1) а(2). а(3) а(4) а(5) а(6) а(7> а(8) а(9)
... । -j । .
0 0 0 1 ' 1 1 1 ; . 0 0
- 1 i ! 1
а(4:7)=1
Рис. 5.9. Сечения позволяют делать выборочное присваивание
120
5. Массивы
а(4:7)=1
Данная запись равносильна циклу
do k=4,7
a(k)=l
end do
Пример. Каждому нечетному элементу массива а присвоить значение 3
(рис. 5.10).
а(1) а(2) а(3) а(4) а(5) а(6) а(7) а(8) а(9)
3 0 3 0 3 0 3 0 з '
а(1:9:2)=3
Рис. 5.10. Выборочное присваивание с шагом
а(1:9:2)=3
С использованием циклов
do k=l,9,2
a(k)=3
end do
Пример. В двумерном массиве а(4,6) внутренним элементам массива
Рис. 5.11. Использование сечений в двумерном массиве
Если нижняя или верхняя границы в индексном триплете отсутствуют, то
они принимаются значениями соответствующих верхних или нижних границ
Массива. Например, в квадратной матрице а(4,4)
integer а(4,4)
а(:,3)=5
«(4,:) =1
Произойдет присваивание 3-му столбцу значений 5, 4-й строке значений 1.
121
И. Л. Артёмов. Fortran: основы прогрвммирования
При использовании индексного триплета вывод массива на экран стано-
вится проще и понятнее:
do i=l,Mi
write(*,"(100(13))") a(i,l:Mj)
end do
2. Получение сечения при помощи векторного индекса.
Векторный индекс - это одномерный массив, который содержит номера
определенных (избранных) элементов массива. Векторный индекс задается
вместо индексов и позволяет выводить в сечение произвольные элементы
массива.
Пример. Дан одномерный массив из 20 случайных чисел:
34560924521532187093
Выделенным элементам присвоить значения 0.
program vect_index
integer, parameter :: M=20
integer a(M), indx(ll) ! выделенных элементов - 11
а=(/3,4,5,6,1,9,2,4,5,2,1,5,3,2,1,8,7,4,9,3/)
indx=(/2,3,4,7,9,10,14,15,16,17,20/)! номера избранных
! элементов
write(*,"(20(i3))") а
a(indx)=0 1 используем векторный индекс
write (*, " (20 (i3) ) 11) а
end
Массив indx(ll) - векторный индекс, который содержит номера выде-
ленных элементов.
Результат работы программы:
345619245215 3 2187493
300019040015 3.000049,0
Press any key to continue
Нами раньше был разобран пример по формированию трехмерных мас-
сивов (см. рис. 5.7). Покажем, как с использованием сечений удается значи-
тельно упростить написание и читаемость программы,
program massif_3d
integer, parameter :: Mi=7, Mj=7, Mk=7
integer a(Mi,Mj,Mk), i,j,k
a=0 ! обнуление исходного массива
a(:,3:5,3:5)=l ! направление x
a(3:5,:,3:5)=l ! направление у
a(3:5,3:5,:)=l ! направление z
! вывод значений в плоскостях z=l z=4 z-7
122
5. Массивы
do к=1,Мк,3
write(*,"(А,12)") "Number ",к ! вывод номера плоскости
do i=l,Mi
write(*,"(7(i2))") a(i,:,k)
end do
read(*,*) ! переход на следующую плоскость
end do
end
5.4. Операторы where и forall
При работе с массивами может возникнуть ситуация, когда элементам,
удовлетворяющим некоторым условиям, следует присвоить определенные
значения. Например, в одномерном массиве отрицательным элементам тре-
буется присвоить квадраты их значений. С использованием цикла do и конст-
рукции if можно написать такую программу:
program prog
integer, parameter :: М=б
real :: a(M)=(/1.0, -3.0, 2.0, 3.0, -4.0, -5.0/)
integer k
write(*,"(6(f7.1))") a
do k=l,M
if (a(k)<0) a(k)=a(k)**2
end do
write(*, 11 (6 (f7.1)) ) a
end
Для решения подобных задач следует воспользоваться более гибким и
удобным средством - оператором where (где), общая схема которого сле-
дующая:
where (логическое выражение-массив)
операторы присваивания массивов
elsewhere
операторы присваивания массивов
and where
Конструкция where может и не содержать часть elsewhere. Если операто-
ров несколько, то используется конструкция where ... endwhere если один
оператор, то можно использовать оператор where.
Приводим текст рассмотренной программы с использованием конструк-
ции where.
Program use_where
integer, parameter :: M=6
₽eal :: a(M)=(/1.0, -3.0, 2.0, 3.0, -4.0, -5.0/)
123
И. Л. Артёмов. Fortran: основы программирования
integer к
write(*,"(6(f7.1))") а
where (а<0) а=а**2 ! где меньше нуля - возводить в квадрат
write(*,"(6(f7.1))") а
end
Пример. Даны три одномерных массива а,Ь,с. Если элементы массива с от-
рицательные числа, то умножить соответствующие элементы массива а на 2, ес-
• ли положительные, то умножить соответствующие элементы массива b на 5.
program use_where
integer, parameter :: M=8
integer a(M), b(M), c(M)
с=(/1,-2,-2,4,5,7,-1,-1/)
а=10; Ь=5
where (с<0)
а=а*2
elsewhere
b=b*5
end where
write (*, *) " ----------C----------------"
write(*,"(8(i4),/)") (c(k),k=l,M)
write (*, *)" -----------A----------------"
write (*, " (8 (i4) , /) ") (а (к) , k=l, M)
write (*, *) " ----------В----------------"
writef*,"(8(i4),/)") (b(k),k=l,M)
end
Результат работы программы:
--------------с--------------
1 -2 -2 4 5 7 -1 -1
------------А---------------------
10 20 20 10 10 10 20 20
В
25 5 5-25 25 25 5-5
Press any key to continue
Еще одна конструкция, которая используется для выборочного присваива-
ния массивам значений, - оператор forall (для всех). Так же как и оператор where,
оператор forall используется для эффективной замены циклов и условий if.
Общий вид записи конструкции следующий:
forall (спецификация триплета, выражение маска)
операторы присваивания
end forall
124
5. Массивы
Пример. В одномерном массиве обнулить отрицательные элементы.
program use_forall
integer, parameter :: M=10
integer a(M)
a=(/2,-2,6,2,-8,1,-1,4,-6,2/)
forall (i=l:M,a(i)<0)
a(i)=0
end forall
write(*,"(100(i4))") a
end
Результат работы программы:
2062010402
Press any key to continue
Следует заметить, что между циклом do и оператором forall есть принци-
пиальные различия. В цикле оператор присваивания выполняется при каж-
дом вызове. В forall сначала полностью вычисляется правая часть оператора
присваивания и затем результат присваивается массиву.
Пример:
program difference
integer, parameter :: M=5
integer a(M)
а=1
do k=2,M
a(k)=a(k-1)+1
end do
write(*,"(10(i4))") a
a=l
forall (k=2:M) a(k)=a(k-1)+1
write(*,"(10(i4))") a
end
В данной программе в цикле do происходит увеличение каждого после-
дующего элемента на единицу, в операторе forall сначала для всех к=2:М вы-
числяется а(к-1)+1, т. е.
Л=2 а(1)+1=2 - результат пока не присваивается элементу массива;
к=3 а(2)+1=2 - результат пока не присваивается элементу массива.
и если все значения рассчитаны, происходит присваивание вычисленных
значений элементам массива.
125
. И. Л. Артёмов. Fortran: основы программирования
Результат работы программы:
1 2 3 4 5
1 2 2 2 2
Press any key to continue
5.5. Функции по работе с массивами
Многие часто встречающиеся операции с массивами, матрицами, векто-
рами в Fortran оформлены в виде стандартных функций. Рассмотрим некото-
рые из них.
1. Функция matmul перемножения двух матриц целого, вещественного,
комплексного и логического типов.
Из курса линейной алгебры известно, что для умножения двух матриц
используются согласованные матрицы (не пугать с согласованными масси-
вами), т. е. количество столбцов первой матрицы равняется количеству строк
второй матрицы. Далее по правилу "строка на столбец" происходит умноже-
ние двух матриц. Данная распространенная операция реализована в виде
функции c=matmul(a,Z>), где а - первая матрица, b - вторая матрица, с -
результат умножения.
Пример:
program use_matmul
integer, parameter :: m=3,n=4,k=2
integer a(m,n), b(n,k), c(m,k)
a(l,:)=(/2,3,4,0/)
a(2,:)=(/5,0,1,2/)
a(3,:)=(/!,7,1,0/)
b(l,:)=(/!,5/)
b(2, :) = (/2,l/)
b(3, :) = (/0,0/)
b(4,:)=(/l,2/)
c=matmul(a,b)
do i=l,m
write(*,*) (c(i,j),j=l,k)
end do
end
Результат работы программы:
8 13
7 29
15 12
Press any key to continue
2. Функции maxval и minval, находящие максимальное и минимальное
значения в массиве.
126
5. Массивы
В самом простом случае вызов функции выглядит таким образом:
a_max=maxval(а) или a_min=minval(а),
где a - исследуемый массив.
Поиск максимальных и минимальных элементов можно расширить, если
воспользоваться дополнительными параметрами.
Например, при поиске можно указать условие, которому должен удовле-
творять максимальный элемент, используя необязательный параметр mask
(маска).
a_max=maxval'(a,mask=mod (а, 2) ==0) ! максимальный
! четный элемент
Пример. Сформировать квадратную матрицу 5-го порядка из целых слу-
чайных чисел и найти максимальное значение, которое делится без остатка
на 17.
program use_maxval
integer, parameter :: n=5
real s(n,n)
integer a(n,n),i,j,imax
call random_number(s)
a=INT(s*100)-50
do i=l,n
write(*,"(100(14))") (a(i,j),j=l,n)
end do
imax=maxval(a,mask=mod(a,17)==0) ! используем
! функцию maxval
write(*,*) imax ! в mask указываем условие
! деления нацело на 17
end
Результат работы программы:
-50 33 -16 23 -46
-48 -17 37 -20 -42 '
-15 41 -42 -46 5
16 29 38 40 42
46 33 20 -41 -43
-17
Press any key to continue
3. Функция sum находит сумму элементов массива. Например, для под-
счета суммы элементов двумерного массива достаточно записать ,s=sum(a).
Также при суммировании можно указывать выполнение определенного усло-
вия (параметр mask) для суммируемых элементов.
Пример. Найти сумму элементов одномерного массива. Найти сумму
°трицательных элементов. Сумму элементов с нечетными номерами.
127
И. Л. Артёмов. Fortran: основы программирования
program use_sum
integer, parameter :: M=10
integer a(M), summa
a=(/1,5,-6,-3,2,4,5,1,6,-9/)
write(*, "(10 (i3))") a
summa=sum(a) ; write(*,100) "Summa = ",summa
summa=sum(a,mask=a<0) ; write(*,100) "Summa = ", summa
summa=sum(a(l:M:2) ) ; write(*,100) "Summa = ",summa
100 format(A,i4)
end
Результат работы программы:
1 5-6-3 2 4 5 1 6-9
Summa = 6
Summa = -18
Summa = 8
Press any key to continue
4. Функция LBOUND возвращает одномерный массив, содержащий
нижние границы всех измерений.
Пример:
program use_lbound
integer а(-1:4,-4:5), res(2)
res=LBOUND(a)
write(*,*) res
end
Результат работы программы:
-1 -4
Press any key to continue
Если требуется узнать нижнюю границу нужного измерения, то следует
при вызове функции указать необязательный параметр dim с номером изме-
рения.
Пример:
program use_lbound
integer а(-1:4,-4:5), res
res=LBOUND(a,dim=l) ! нижняя граница строк
write(*,*) res
end
Результат работы программы:
-1
Press any key to continue
5. Аналогичной по работе является функция UBOUND для вычисления
верхней границы массива.
6. Функция TRANSPOSE выполняет по правилам, принятым в матема-
тике, транспонирование матрицы. Если размер исходной матрицы (2x3), то
транспонированная матрица будет размера (3x2).
128
5. Массивы
Пример:
program use_transpose
real a(2,3), res(3,2)
call randam_number(a)
res=transpose(a)
do i=l,2
write(*,"(3f5.2)-) a(i,:)
end do
write(*,*)
do i=l,3
write(*,"(2f5.2)") res(i,:)
end do
end
Результат работы программы:
0.00 0.35 0.96
0.03 0.67 0.84
0.00 0.03
0.35 0.67
0.96 0.84
Press any key to continue
7. Функция циклического сдвига массива CSIIIFT. Например, если тре-
буется из матрицы А получить матрицу В:
А = '2 5 6' 6 7 2 8 0 9 ? 3 \ -> В = '1 3 З'' 2 5 6 6 7 2 .8 0 9,
то следует вызвать функцию CSHIFT следующим образом:
b=CSHIFT(а,-1,dim=1),
что означает циклически сдвинуть элементы вниз на одну позицию, по строкам.
Если необходимо
А = "2 5 6' 6 7 2 8 0 9 J 3 \ -> В = '5 6 2' 7 2 6 0 9 8 ? 3 Ч
то следует записать
B=CSHIFT(а,1,dim=2),
что означает циклический сдвиг влево на одну позицию, по столбцам.
129
И. Л. Артёмов. Fortran: основы программирования
Пример. Написать программу нахождения минора в квадратной матрице
порядка М.
1-й способ. Известно, что минором какого-либо элемента квадратной
матрицы называется определитель, получаемый из данной матрицы путем
вычеркивания той строки и того столбца, на пересечении которых стоит эле-
мент. Пусть а - исходная матрица, b - искомый минор. Рассмотрим матрицу
5-го порядка и найдем минор элемента a(ikjk), ik=3, jk=2 (рис. 5.12).
г • -г-. . _ . - _ —
а а а а
(1,1' (1,3) (1,4) (1,5)
а а а а а
(2,: 2,2) (2,3) (2,4) (2, 5)
— —
а j
(3,1) (3,2) (3,3) (3,4) (3, 5>j
а а а а а
(4,1 4,2), (4,3) (4,4) (4,5)
а а а а
(5,1 , 2 )t (5,3) (5,4) (5,5)
Рис. 5.12. Нахождение минора
Вычеркивая 3-ю строку и 2-й столбец, находим, что в минор войдут сле-
дующие 4 сечения массива а.
а (1:2,1:1)
а(1:2,3:5)
а (4 : 5,1:1)
а(4:5,3:5)
а(1:ik-1,1:jk-1)
а(1:ik-1,jk+1:М)
a(ik+1:М,1:jk-1)
a(ik+1:М,jk+1:М)
= b(l:2,l:l) =
= b(l:2,2:4) =
= b(3:4,l:l) =
= b(3:4,2:4) =
b(l:ik-l,l:jk-1)
b(l:ik-l,jk:M-l)
b(ik:M-l,1:jk-1)
b(ik:M-l,jk:M-l)
Поэтому для формирования минора b будут использоваться следующие
4 оператора присваивания (рис. 5.13).
b(l:ik-1,1:jk-1)=а(1:ik-1,1:jk-1)
b(1:ik-1, jk:M-l)=а(1:ik-1,jk+1:М)
b(ik:M-l,1:jk-1)=a(ik+1:M,1:jk-1)
b(ik:M-l,jk:M-l)=a(ik+1:M,jk+1:M)
Интересно рассмотреть, какие сечения будут использоваться при гранич-
ных значениях, например ik=jk=l.
b(1:0,1:0)=а(1:0,1:0) I сечение нулевой длины
Ь(1:О,1:М-1)-а(1:0,2:М) ! сечение нулевой длины
b(l:М-1,1:0)=а(2:М,1:0) ! сечение нулевой длины
b(l:М-1,1:М-1)=а(2:М,2:М)
130
5. Массивы
Рис. 5.13. Связь полученного минора и исходной матрицы
Первые три сечения имеют нулевую длину, поэтому никаких побочных
действий не возникает. Таким образом, массивы нулевой - длины эффектив-
ное средство для записи общих алгоритмов.
program minor
integer, parameter :: M=5
real b(M-l,M-l), i, j, ik, jk
! a - исходная матрица
! b - искомый минор
! ik, jk - номера вычеркиваемых строк и столбцов
call random_number(a)
write(*,*) "Matrix..."
do i=l,M
write(*,100) a(i,:)
end do
write (*, " (A, \) 11) "Enter i = "; read(*,*) ik
write (*, " (A,\) ") "Enter j = read!*,*) jk
b(l:ik-l,l:jk-1)=a(1:ik-1,1:jk-1)
b(1:ik-1,jk:M-l)=a(1:ik-1,jk+1:M)
b(ik:M-l,1:jk-1)=a(ik+1:M,1:jk-1)
b(ik:M-l,jk:M-l)=a(ik+1:M,jk+1:M)
write(*,*) "Minor..."
do i=l,M-l
write(*,100) b(i,:)
end do
100 format(100(f5.1))
end
131
И. Л. Артёмов. Fortren: основы программирования
Результат работы программы:
Matrix...
0.0 0.8 0.3 0.7 0.0
0.0 0.3 0.9 0.3 0.1
0.4 0.9 0.1 0.0 0.6
0.7 0.8 0.9 0.9 0.9
1.0 0.8 0.7 0.1 0.1
Enter i = 3
Enter j = 2
Minor...
0.0 0.3 0.7 0.0
0.0 0.9 0.3 0.1
0.7 0.9 0.9 0.9
1.0 0.7 0.1 0.1
Press any key to continue
2-й способ. Рассмотрим использование функции CSHIFT (циклический
сдвиг) для вычисления минора. Циклический сдвиг сечения массива
(рис. 5.14) позволяет вынести вычеркиваемые строки на периферию и, взяв
сечение от 1 до М-1, найти искомый минор.
а I (1,1) а Л (1,2)? а (1,3) а (1,4) а (1,5)
а (2,1) а Й (2,2)^ а (2,3) а (2,4) а (2,5)
а (3,1) а (3,2) а i5'q) а (3 , И ) а я (3,5|
а (4,1) а (4'2Й а (4,3) а (4,4) а (4,5)
а (5,1) а (5,2); а (5,3) а (5,4) а (5,5)
а (1,1) а (1,3) а (1,4) а (1,5) I а (1, (
а а а а а ;
(2,1) (2,3) (2,4) (2,5) (2,2)
а а а а а 1
(4,1) (4,3) (4,4) (4,5) (4,2)
а а а а а
(5,1) (5,3) (5,4) (5,5) Г.2)
а а а а 5
(",’.) (3,3) (3,4) (3,5) (3,2)1 . i-wal
Рис. 5.14. Использование функции CSHIFT для нахождения минора
Чтобы функция CSHIFT позволила циклически сдвинуть строки с 3-й
по 5-ю и столбцы со 2-го по 5-й, следует записать:
a(ik:M,:)=CSHIFT(a(ik:M,:),l,dim=l)
a(:,jk:M)=CSHIFT(a(:,jk:M),l,dim=2)
Полученный минор легко находится из сечения массива:
Ь=а(М-1,М-1)
132
5. Массивы
Для возвращения матрицы в исходное состояние, следует проделать об-
ратный циклический сдвиг:
a(ik:M,:)=CSHIFT(a(ik:M,:),-l,dim=l)
а(:,jk:M)=CSHIFT(a(:,jk:M),-l,dim=2)
program minor
integer, parameter :: M=5
real a(M,M), b(M-l,M-l),. i, j, ik, jk
call random_number(a)
write (*,*) "Matrix...11
do i=l,M
write(*,100) a(i,:)
end do
write(*, " (A,\) ") "Enter i = read(*,*) ik
write(*,"(A,\)") "Enter j = "; read(*,*) jk
! циклический сдвиг строк и столбцов
a(ik:M,:)=CSHIFT(a(ik:M,:),l,dim=l)
a(:,jk:M)=CSHIFT(a(:,jk:M),l,dim=2)
b=a(l:M-l,l:M-l)
! обратный циклический сдвиг
a(ik:M,:)=CSHIFT(a(ik:M,:),-l,dim=l)
a(:,jk:M)=CSHIFT(a(:,jk:M),-l,dim=2)
write(*,*) "Minor..."
do i=l,M-l
write(*,100) b(i,:)
end do
100 format(100(f5.1))
end
Результат работы программы:
Matrix...
0.0 0.8 0.3 0.7 0.0
0.0 0.3 0.9 0.3 0.1
0.4 0.9 0.1 0.0 0.6
0.7 0.8 0.9 0.9 0.9
1.0 0.8 0.7 0.1 0.1
, Enter i = 1
Enter Minor j = 1
0.3 0.9 0.3 0.1
0.9 0.1 0.0 0.6
0.8 0.9 0.9 0.9
0.8 0.7 0.1 0.1
Press i my key to continue
133
И. Л. Артёмов. Fortran: основы прогрвммирования
5.6. Динамические массивы
Мы изучили, что при описании массива указывается число элементов
массива для каждого измерения. Например:
integer q(10)
или
double precision р(30,30,30)
Во время компиляции профаммы под массивы выделяется память, кото-
рая сохраняется до конца работы программы. Такие массивы называются ста-
тическими.
Однако работать со статическими массивами не всегда оказывается
удобным и экономически выгодным. Допустим, пользователю, работающему
с npoi-раммой, требуется сначала матрица из 400 элементов, затем из 5000,
далее из 1000 000 и, наконец, из 100. Возникает вопрос, как сделать границы
двумерного массива меняющимися в процессе работы программы? Конечно,
можно сразу задать максимально возможный размер массива, например
(1000x1000), и, задавая ограничения, работать с нужными размерами. Однако
такой подход приводит к слишком большому расходу памяти. Для решения
подобных задач используются динамически размещаемые массивы, размер
которых не задается во время компиляции, а определяется во время выполне-
ния программы и может контролироваться программистом.
Для описания таких массивов используется атрибут allocatable (разме-
щаемый), например:
real, allocatable :: s (:, :)
При объявлении массива вместо размеров указывается двоеточие, знак
того, что размер массива может быть произвольным. При этом изменять
можно только размер, количество измерений массива остается неизменным.
Примеры размещаемых массивов:
logical, allocatable :: g(:) ! размещаемый массив 1-го ранга
integer, allocatable :: с(:,:) ! размещаемый массив 2-го ранга
real, allocatable :: s(:,:,:) ! размещаемый массив 3-го ранга
Для размещения в памяти компьютера массива используется оператор
allocate (размещать). После окончания работы с динамическим массивом за-
нимаемую память следует освободить, используя оператор deallocate (осво-
бодить).
Пример. Создать одномерный динамический массив со значениями
квадратов его индексов.
program use_allocate
integer, allocatable :: b(:)
134
5. Массивы
integer М
write(*,"(А,\)") "Number of elements..."; read(*,*) M
allocate(b(M)) ! выделили память для M элементов
do к=1,М
b(k)=k**2
write!*,(16,\)") Ь(к)
end do
deallocate(b) ! освободили память
end
Результат работы про;раммы:
Number of elements... 10
1 4 9 16 25 36 49 64 81 100
Press any key to continue
В операторах allocate и deallocate может присутствовать дополнительный
параметр STAT, который позволяет определять результаты выделения и ос-
вобождения памяти. Совместно с параметром STAT используется целочис-
ленная переменная error, возвращающая нуль, если операция выполнена ус-
пешно, и код ошибки в противном случае. Для проверки того, выделена ли
память для динамического массива, следует использовать логическую функ-
цию allocated (размещен).
Пример. Создать динамический массив и проверить результаты выделе-
ния и освобождения памяти.
program allocate_results
integer, allocatable :: p( :, :)
integer Mi, Mj
integer error
write(*,”(A,\)") "Number of rows..."; read!*,*) Mi
write(*,"(A,\)") "Number of columns..."; read!*,*) Mj
allocate(p(Mi,Mj),STAT=error)
if (error==0) then ! проверка удачного выделения памяти
write(*,*) "Memory OK"
else
write!*,*) "Error number 11, error
end if
if (allocated(p)) deallocate(p,STAT=error)
if (error==0) then ! проверка удачного освобождения памяти
write!*,*) "Free memory OK"
else
write(*,*) "Error number ", error
end if
and
135
И. Л. Артёмов. Fortran: основы программироввния
Результат работы программы:
Number of rows...20
Number of columns...30
Memory OK
Free memory OK
Press any key to continue
Итоги
1. Массивы хранят данные одного типа, доступ к которым осуществляет-
ся при помощи имени и индекса, записанного в круглых скобках. При описа-
нии границ массивов рекомендуется использовать константы. В ряде случаев
оказывается удобным присваивать значения элементам массива при помощи
конструктора массивов.
2. Массивы могут быть одномерными (векторы), двумерными (матрица,
таблица), трехмерными (куб), многомерными. Двумерные массивы хранятся
в памяти по столбцам.
3. Сечения массивов позволяют работать с массивами и их секциями как
с обычными переменными, записывая код про1раммы компактно, без лишних
операторов цикла и условий if. Сечения массива можно организовать при по-
мощи индексного триплета и векторного индекса.
4. Для выборочного присваивания значений элементам массива исполь-
зуются операторы where и forall.
5. Многие традиционные векторно-матричные операции реализованы
в Fortran в виде стандартных гюдпрофамм и функций.
6. Если в профамме необходимо использовать массивы, размер которых
должен меняться в десятки, сотни раз, то рекомендуется применять динами-
ческие (allocatable) массивы.
136
6. ФУНКЦИИ, ПОДПРОГРАММЫ
И МОДУЛИ
Рассмотренные ранее типы данных и операторы позволяют с уверенно-
стью решать простейшие вычислительные задачи. Однако по мере написания
все более усложняющихся со временем программ можно прийти к очевидно-
му выводу: программу, состоящую из сотен, тысяч и более строк, достаточно
тяжело воспринимать как единое целое. Другими словами, существует опре-
деленный предел, когда можно с легкостью изменить программу и заставить
ее работать по-иному. Кроме того, при написании больших программ, как это
часто бывает, отдельные фрагменты просто дублируют друг друга с неболь-
шими изменениями. В результате становится трудно определить, какие дей-
ствия выполняет программа в том или ином месте.
Допустим, требуется создать (в одной книге) единую и подробную инст-
рукцию о постройке жилого дома, рассматривая все детали, вплоть до кладки
кирпичей. В результате долгого и кропотливого труда получится книга объе-
мом в десятки, сотни тысяч страниц, что покажется устрашающим не только
для желающего построить дом, но и для самого разработчика инструкции!
В итоге мы придем к выводу, что руководство следует разбить на книги, кни-
ги на разделы, главы, параграфы, отдельные абзацы. Тогда руководство будет
более понятным и читаемым, его легко можно будет разделить между людь-
ми, каждому дать определенную задачу и работа по созданию дома выпол-
нится гораздо быстрее.
Аналогичная ситуация наблюдается и в процессе создания программного
обеспечения, по сложности ничем не отличающегося от строительства дома.
Сложную программу разбивают на отдельные части, каждая из которых вы-
полняют строго определенную задачу. Причем, написав одну какую-то часть,
ее можно повторно использовать, экономя время и допуская минимум оши-
бок. В итоге программу можно легко составлять из отдельных частей, как из
кубиков, манипулируя ими и добиваясь желаемого результата.
Fortran предлагает разбивать программируемую задачу на подзадачи с
использованием процедур. Процедура - это самостоятельная программная
единица, которая предназначена для выполнения четко определенных дейст-
вий. Процедуры бывают двух видов - функции и подпрограммы. Первые, как
правило, сокращают размер программы, позволяют уходить от дублирующих
частей исходного кода, упрощают математические и логические выражения;
вторые позволяют разбивать задачу на подзадачи, выполггять комгглексные
вычисления, а также уменьшают размер исходного текста программы и по-
вышают его читаемость.
Следует заметить, что уяснение и понимание работы процедур потребует
значительного времени, это одна из важных тем данной книги. Все настоя-
/1И/МОГГ11И(ЗИ 137
Fortran: основы программирования
щее прсираммирование базируется на понятиях функции и подпрограммы
(в некоторых языках есть только функции).
6.1. Функции
Рассмотрим следующую задачу. Требуется вычислить выражение
/(х + Дх) . .
s = —.----£ + /(•*).
/(х-Дх) v ’
, . sin(x)+cos(x) л . ЛЛ1
где f (х) = —, х = —, Дх = 0.01.
х + ех 8
Владея изученными средствами программирования, можно легко составить
программу.
program understanding
real, parameter :: pi=3.1415926
real x, dx, s, fp, fin, fO
x=pi/8; dx=0.01
f 0= (sin (X) +cos (x) ) / (x+exp (x) ) !----f(x)
fp=(sin(x+dx)+cos(x+dx))/(x+dx+exp(x+dx)) ! ----- f(x+dx)
fm=(sin(x-dx)+cos(x-dx))/(x-dx+exp(x-dx)) ! ----- f(x-dx)
s=fp/fm+fo
write(*."(A, £5.2)") "Result.,." , s
end
Результат работы программы:
Result... 1.68
Press any key to continue
Для вычисления выражения в программе созданы три дополнительные
переменные fp, fin, fl) для подсчета функции в точках x+dx, x-dx и лЮ. Однако
при наборе программы возникает очевидная мысль, нельзя ли повторяющие-
ся выражения
f0=(sin(х)+cos(х))/(х+ехр(х))
fp=(sin(x+dx)+cos(x+dx))/(x+dx+exp(x+dx))
fm=(sin(x-dx)+cos(x-dx))/(x-dx+exp(x-dx))
заменить на fixfflx+dxffix-dx) и затем использовать в том месте программы,
где это необходимо. Тогда программу можно будет набрать достаточно бы-
стро и допустить минимум ошибок. Эта мысль полностью поддерживается!
В Fortran существует специальный механизм - функции. Следующий пример
показывает, как, используя функцию, упростить написание npoi-раммы.
program uirderstanding_function ! ----- начало программы ----
real, parameter :: pi=3.1415926
138
6. Функции, подпрограммы и модули
real х, dx, s
x=pi/8; dx=0.01
s=f(x+dx)/f(x-dx)+f(x) ! вызов функции f(x)
write(*,"(A,f5.2)") "Result..." ,s
i -------------------- конец текста программы ------------
contains
real function f(xt) ! --------------- функция -------
real xt
f=(sin(xt)+cos(xt))/(xt+exp(xt))
end function f ! -------------------- конец функции ---
end ! ----------------------конец программы---------------
После основного текста программы расположен оператор contains (со-
держит, вмещает), означающий, что далее будет описана функция исполь-
зуемая в текущей программе. Немного опережая изучаемый материал, заме-
тим, что функция, объявленная внутри программы после оператора contains,
называется внутренней функцией.
Итак, первая строка:
real function f(xt) ! -------------- функция -------
описывает функцию (function) с именем/. Функция/вычисляет значение вы-
ражения, результат которого вещественное число, поэтому перед function
стоит оператор real. В скобках после имени функции присутствует список ар-
гументов. В нашем случае создана функция от одного вещественного аргу-
мента xt, на что указывает вторая строка:
real xt
Вспомним, как мы использовали стандартную функцию sqrt(x), которая
возвращает квадратный корень из числа х. Причем результат извлечения
квадратного корня из вещественного числа - вещественное число. Аналогич-
но в нашем примере функция Длт) будет возвращать вещественное число, где
xt также должен быть вещественного типа. Таким образом, отличие функции
sqrt от/, то что sqrt - стандартная функция,/- "собственного производства".
Третья строка:
f=(sin(xt)+cos(xt))/(xt+exp(xt))
вычисляет выражение и присваивает результат имени функции. Это очень
важная строчка в теле функции. Если ее не указать, то функция будет воз-
вращать в программу неопределенное значение, а это приводит к предупреж-
дениям компилятора и к ошибкам в вычислениях.
Наконец, четвертая строка заканчивает описание функции.
end function f ! ------------------- конец функции -------------
Итак, чтобы описать функцию, после оператора contains используется
следующая схема:
139
И. Л. Артёмов. Fortran: основы программирования
тип function имя_функции (формальные параметры)
типы формальных параметров
операторы описания
исполняемые операторы
имя_функции=вычисленное значение
end function имя_функции
Когда программа встречает выражение flx+dx), то происходит вызов
функции Дх?). Значение x+dx связывается с переменной xt и вычисляется вы-
ражение y=(sin(x?)+cos(x?))/(xz+exp(x?)). Далее функция возвращает результат
в программу и передает ей управление. Затем следует вызов функции flx-dx}
ит. д.
Таким образом, аргументы x+dx, x-dx, х, xt организуют передачу данных
в функцию. Аргументы, используемые при описании функции, носят назва-
ние формальных параметров. Аргументы, которые используются при вызове
функции, называются фактическими параметрами.
В нашем примере:
• формальный параметр - xt-,
• фактические параметры - x+dx, x-dx, х.
Другими словами, формальные параметры - это шаблоны для будущих
фактических параметров, т. е. формальный параметр один, а связанных с ним
фактических параметров может быть сколько угодно.
Вызов функции может быть выполнен одним из следующих способов:
write(*,*) f(2.0, 3.149) ! 2.0, 3.149 - фактические параметры
или
s=sin(xl)+f(xl,yl) ! xl, yl - фактические параметры
или
if(f(xl,yl+dy)<eps)then ! xl, yl+dy - фактические параметры
т. e. функции используются в операторах, математических выражениях.
Однако так писать недопустимо:
xl=2.0; у1=5.9
f(xl,yl) ! ошибка
Пример. Написать функцию вычисления выражения
, ч f-х, х<0;
/(•«)= I . I (рис. 6.1).
|sinx|, х>0
program funct
real х
write(*,"(А\)") "Enter x = "; read(*,*) x
write(*,"(A,f7.2)") "Function f(x) = ", f(x)
contains
real function f(xt)
140
6. Функции, подпрограммы и модули
real xt
if (xt<0) then
f=-xt
else
f=abs(sin(xt)).
end if
end function f
end
Рис. 6.1. Функция f(x)
Результат работы программы:
Enter x = -3 Enter x = 0.523598
Function f(x) = 3.00 Function f(x) = .50
Press any key to continue Press any key to continue
Пример. Протабулировать функцию f (x,y) = хг + у1 на интервале
хе [0,1], уе[0,2].
program tab_funct
zeal x,y,dx,dy 1 x,y - текущие координаты, dx,dy - шаги по'х,у
integer i, j
integer, parameter :: Mi=ll, Mj=ll ! число точек
dx=1.0/(Mi-1); dy=2.0/(Mj-1)
do i=l,Mi
x=(i-1)*dx
do j=l,Mj
y=(j-l)*dy
write(*,"(f6.2,\)“) F(x,y)
end do
write(*,*)
end do
contains
real function F(xt,yt)
real xt,yt
F=xt*xt+yt*yt
end function F
end
Результат работы программы:
.00 .04 .16 .36 .64 1.00 1.44 1.96 2.56 3.24 4.00
.01 .05 .17 .37 .65 1.01 1.45 1.97 2.57 3.25 4.01
• 81. .85 .97 1.17 1.45 1.81 2.25 2.77 3.37 4.05 4.81
1.00 1.04 1.16 1.36 1.64 2.00 2.44 2.96 3.56 4.24 5.00
141
И. Л. Артёмов. Fortran: основы программирования
Press any key to continue
Пример. Написать функцию
sinx + cosx
g (x) = ex + x.
program funct
real xO, x, dx
integer k
write(*,"(A\)") "Enter x = "; read(*,*) x
write(*,"(A,f7.2)") "Function f(x) = ", F(x)
contains
real function P(x) !--------------функция P(x)
real x
P=(sin(x)+cos(x))/(x**2+l)
end function P
real function G(x) !--------------функция G(x)
real x
G=exp(x)+x
end function G
real function F(x) !--------------функция F(x)
real x
if (x<-5) then
F=P(x)
else
F=G(x)
end if
end function F
end
В этом примере используется три функции. Причем функция F(x) вызы-
вает функции G(x) и Р(х). ’
Результат работы программы:
Enter х = 5
Function f(x) = 153.41
Press any key to continue
Пример. Функция, которая возвращает число л.
program funct
write (*,*) Pi О, Pi О/2
contains
real function Pi()
Pi=3.1415926
end function Pi
end
142
6. Функции, подпрограммы и модули
В данном примере функция не содержит формальных параметров. В этом
случае ставится пара круглых скобок, как при описании функции, так и при
ее вызове.
Результат работы программы:
3.141593 1.570796
Press any key to continue
6.2. Подпрограммы
Кроме функций, существует второй вид процедур - подпрограммы. Если
функция возвращает вычисленное значение, то подпрограммы позволяют
выполнять ряд определенных действий, например осуществлять специальные
операции ввода-вывода, обрабатывать массивы, решать поставленные задачи
и др.
Допустим, требуется выводить на экран прямоугольную рамку после ка-
ждого ввода числовых данных. Можно составить такую программу:
program understanding
integer a,b,c
write(*, 11 (A, \) ") "Enter a = •; read(*,*) a
write (*,*) ." ------------------------- " ! выводим рамку
write(*,*)"| | "
write(*,*) " | j "
write (*,*) " ---------------------------- "
write(*, 11 (A, \) ") “Enter b = "; read(*,*) b
write (*,*) " -------------------------- " ! выводим рамку
write(*,*) " | | "
write(*,*)" | | "
write(*,*) " ----------------------------- "
write(*,“(A,\)") "Enter c = read(*,*) c
write(*,*) “ --------------------------- ” ! выводим рамку
write(*,*)"| |"
write(*, *) " | | 11
write(*,*) " ------------------------------- "
end
При составлении программы сразу же возникает идея - избавиться от по-
вторяющихся операторов write и упростить программу. Для этого воспользу-
емся вторым видом процедур - подпрограммами. Следующий пример пока-
зывает, как это реализовать.
program understanding_subroutine I------начало программы------.
integer a,b,c
write (*, " (A, \) 11) "Enter a = read(*,*) a
call Frame() ! вызвали подпрограмму Frame()
143
И. Л. Артёмов. Fortran: основы программирования
write(*,"(А,\)") "Enter b = read(*,*) Ь
call Frame() ! вызвали подпрограмму
write(*,"(А,\)") "Enter с = read(*,*) с
call Frame() ! вызвали подпрограмму
I ------------------- конец текста программы -
contains
subroutine Frame () !-----подпрограмма------
write(*,*) " -----------------------------
write(*,*) "|
write(*,*)"|
write(*,*) " -----------------------------
end subroutine Frame
end
Результат работы программы:
Enter a = 1
Enter b = 2
Enter c = 3
Press any key to continue
Так же как и функция, подпрограмма записывается отдельно от основной
программы, после оператора contains. Напомним, что подпрограммы, опи-
санные внутри программы после оператора contains, называются внутренни-
ми подпрограммами.
Первая строка:
subroutine Frame О ! -------------------подпрограмма--------
указывает, что создается подпрограмма (sub - под routine - шаблон) с именем
Frame. В нашем примере формальные параметры у подпрограммы отсутствуют.
Следующие строки:
write(*,*) " ------------------------------ "
write(*,*) "| I"
write(*,*)"| I "
write(*,*) " ----------------------г--------- 11
144
6. Функции, подпрограммы и модули
содержат операторы, которые выполняются при вызове подпрограммы.
Последняя строка:
end subroutine Frame ! ----------------------------------------
указывает на конец подпрограммы.
Вызов подпрограммы происходит при помощи оператора call (звать), по-
сле которого следует имя подпрограммы и фактические параметры. В нашем
случае вызов процедуры происходит без фактических параметров (формаль-
ные параметры отсутствуют), поэтому просто указывается пара круглых ско-
бок;
Данный пример наглядно демонстрирует выгодность применения под-
программ. Программа значительно уменьшается и читается легче. Использо-
вание собственных подпрограмм ничем не отличается от стандартных под-
npoi-рамм, рассмотренных ранее. Так, чтобы сгенерировать случайное число,
мы записывали call random number(c).
Итак, чтобы создать подпрот-рамму после оператора contains, применяет-
ся следующая схема:
subroutine имя_подпрограммы (формальные параметры)
типы формальных параметров
операторы описания
исполняемые операторы.
end subroutine имя_подпрограммы
Вызов подпрограммы происходит при помощи оператора call,
call имя_подпрограммы(фактические параметры)
Пример. Подпрот-рамма, выводящая рамку с сообщением.
program frame
character(12) s
s=' ATTENTION call message(s)
write(*,*)
s=' STOP call message(s)
contains
subroutine message(text)
character(12) text
t© (* * ) '*★****★★*★★*★★*★★★★*•
write(*,*) '* ',s, ' ★'
* j ’
end subroutine message
end
Результат работы программы:
* ATTENTION *
145
И. Л. Артёмов. Fortran: основы программирования
********************
* STOF *
********************
Press any key to continue
В подпрограмме message описан один формальный параметр символьно-
го типа - text, который используется для вывода строки. Вызов подпрограм-
мы происходит с фактическим параметром s.
Пример. Подпрограмма, выводящая сообщение об отношении двух чи-
сел.
program ratio
Integer а, Ь
call show(90,23)
call show(50,50)
contains
subroutine show(a,b)
Integer a,b
if (a<b) then
write(*,100) A," is smaller than ",B
elseif (a>b) then
write(*,100) A," is bigger than ",B
else
write(*,100) A," is equal ",B
end if
100 format (i4,A,i4)
end subroutine show
end
Результат работы программы:
90 is bigger than 23
50 is equal 50
Press any key to continue
В подпрограмме show объявлены два формальных параметра целого ти-
па - а и b . Вызов подпрограммы происходит с фактическими параметрами
90, 23 и 50,50.
6.3. Внутренние переменные
Между операторами function и end function, subroutine и end subroutine
находятся операторы, выполняющие определенные действия. Очень часто
эти операторы содержат дополнительные, вспомогательные переменные. На-
пример, для функции, которая вычисляет сумму тригонометрического ряда,
, ч cos кх
T(x) = L—j—
*«1 Л
146
6. Функции, подпрограммы и модули
потребуются дополнительные переменные для организации цикла и подсчета
суммы. Чтобы использовать такие переменные в процедурах, их следует объ-
явить сразу после описания формальных параметров, поэтому функция Т(х)
будет следующей:
program internal
write(*,"(A,f5.2)") "Result...", T(0.5)
contains
real function T(x)
real x ! формальный параметр
integer k ! дополнительные переменные
real sum
sum=0
do k=l,100
sum=sum+cos(k*x)/k
end do
T=sum
end function T
end
Переменная x - формальный параметр, используется для передачи дан-
ных в функцию. Переменная к - внутренняя, организует цикл, переменная
sum - внутренняя, накапливает сумму ряда.
Результат работы программы:
Result... .70
Press any key to continue
Таким образом, посредством формальных и фактических параметров
данные поступают в функцию. Функция, в свою очередь, эти данные обраба-
тывает с помощью внутренних переменных и возвращает результат обратно
в главную программу.
Пример. Составить программу для вычисления значения функции
хг +1 Оу
хг + у2 +1
/(^т) =
в произвольной точке (х0, у0).
program funct
real х,у
write(*, " (А, \) “) "х = "read(*,*) х
write(*,"(А,\)") "у = read(*,*) у
write(*,"(A,f7.2)") "Result = ", f(x,y)
contains
real function f(x,y)
real x, у ! x,y - формальные параметры
real si, s2 ! внутренние переменные
sl=x**2+10*y; s2=x**2+y**2+l
147
И. Л. Артёмов. Fortran: основы программирования
f=sl/s2
end function f
end
Здесь для вычисления значения функции используются две внутренние пере-
менные si и s2.
х = 2.0
у = 3.0
Result =2.43
Press any key to continue
Пример. Написать функцию распределения Гаусса и вычислить значение
в точке х=1.0.
Распределение Гаусса выражается формулой
program Gauss
real :: х=1.0
write(*,"(2(A,f5.2))") "х = ",х," Р(х) = ",Р(х)
contains
function Р(х)
real, parameter :: pi=3.14, a=1.0, sigma=2.0
real x, a, sigma
P=1.0/(sigma*sqrt(2*pi))‘exp(-(x-a)**2/(2*sigma**2))
end function P
end
Распределение Гаусса есть функция от х, содержащая коэффициенты а
и о. Поэтому функция содержит дополнительные внутренние константы.
Результат работы программы:
х = 1.00 Р(х) = .20
Press any key to continue
Пример. Написать подпрограмму, которая выводит последовательность
целых чисел.
program numbers
call line(-5,5)
contains
subroutine line(aO,an) ! последовательность чисел
integer a0, an
integer k
do k=a0,an
write(*,"(i4,\)“) k
end do
end subroutine line
end
148
6. Функции, подпрограммы и модули
Подпрограмма line содержит два формальных параметра целого типа -
аО и ап - начало и конец последовательности чисел. Для организации цикла
используется внутренняя переменная Л. Вызов подпрограммы происходит
при помощи оператора call, в качестве фактических параметров выступают
числа -5 и 5.
Результат работы программы:
-5 -4 -3 -2 -1 0 1 2 3 4 5
Press any key to continue
Итак, внутренние переменные используются для организации вычисле-
ний, ветвлений и циклов, что наглядно показывают разобранные примеры.
Само название "внутренние переменные" не случайно. Дело в том, что такие
переменные доступны только в "своей", "родной" функции и недоступны
в основном тексте программы и других подпрограммах и функциях. Если
в следующем примере в тексте программы обратиться к переменным а,Ь,с,
то эти переменные будут недоступны, так как они определены внутри функ-
ции f
program internal
contains
real function f(xt)
real xt
integer a,b, с ! внутренние переменные
end function f
end
С другой стороны, переменные, описанные после оператора program,
доступны как в основной программе, так и в процедурах, описанных после
оператора contains. Ранее было сделано замечание, что процедуры, описан-
ные после оператора contains, называются внутренними. Внутренние проце-
дуры имеют доступ к переменным, константам, типам данных, которые опи-
саны в основной программе.
Так, в следующем примере подпрограмма выведет значение переменной
mas, которая описана после оператора program, но не объявлена внутри под-,
программы show.
program prog
integer s s mas=150
call show()
contains
subroutine show()
write(*,*) mas
end subroutine show
end
149
И. Л. Артёмов. Fortran: основы программирования
Результат работы программы:
150
Press any key to continue
Однако если имена переменных основной программы совпадают с име-
нами переменных во внутренних процедурах, то эти переменные будут со-
вершенно разными. В следующем примере переменные res, описанные в ос-
новной программе и подпрограмме, - разные переменные.
program prog
logical :: res=.TRUE. ! переменная доступная
! в основном тексте программы
call show()
write(*,*) "program res = ", res
contains
subroutine show()
logical :: res=.FALSE. ! внутренняя переменная,
! доступна в show
write(*,*) "subroutine res = ", res
end subroutine show
end
Результат работы программы
subroutine res = F
program res = T
Press any key to continue
end program pl
область видимости переменных
a,e - основная программа и
внутренняя функция f(х)
b,c,d - основная программа
b,c,d - внутренняя функция
Рис. 6.2. Область видимости переменных основной программы
и внутренней функции
150
6. Функции, подпрограммы и модули
Ответим на вопрос, является ли локальной переменной формальный па-
раметр. Ответ очень простой: формальный параметр объявляется внутри
процедуры, а все, что объявлено внутри процедуры, является локальным и
доступно только в текущей процедуре.
Для лучшего понимания рассмотрим рис. 6.2. Переменные а и е, описан-
ные после оператора program, доступны как в основной программе, так и в
функции /, переменные b,c,d, описанные в основной программе, доступны
только в основной программе и недоступны в функции /. Переменные b,c,d,
объявленные внутри функции /, доступны только внутри функции f. Между
переменными b,c,d, описанными после оператора program и после оператора
function, нет ничего общего, это разные переменные. В заключение можно
сказать, что переменные, константы, типы данных, описанные после опера-
тора program, являются глобальными, описанные внутри процедуры - явля-
ются локальными.
В следующем примере тип данных person, описанный после оператора
program, доступен как в основной программе, так и в подпрограмме subl. Тип
данных student доступен только в подпрограмме.
program prog
type person
character(30) name
integer age
logical status
end type person
type (person) :: pl
pl=person("Sidorov A.C.",25,.TRUE.)
contains
subroutine subl()
type student
character(30) name
character(3) group
Integer mark
end type student
type (student) :: si
type (person) :: ql
sl=student("Petrov A.V.","052", 4)
ql=person ("Ivanov C.K.", 25,.FALSE.)
end subroutine subl
end
Пример. Написать логическую функцию, определяющая попадание
в интервал (-5; 2).
151
И. Л. Артёмов. Fortran: основы программирования
program interval
real :: х, а=-5.0, b=2.0
write(*,"(А,\)") "Enter х = read(*,*) х
if (RES(x)) then
write(*,*) "YES"
else
write(*,*) "NO "
end if
contains
logical function RES(x) ! внутренняя функция
real x
RES=(x>a).AND.(x<b)
end function RES
end
Функция RES использует переменные а и b, описанные в главной про-
грамме.
Результат работы программы:
Enter х = О
YES
Press any key to continue
Пример. Подсчет количества нулевых элементов в массиве. Массив,
описанный после оператора program, доступен из функции Zero(), поэтому
формальные и фактические параметры отсутствуют,
program prog
integer, parameter :: M=10
integer a(10)
a= ( /1,4,0,0,9,2,0,2,4, 0/)
write(*,*) "Zero elementsZero()
contains
integer function Zero()
integer s,k
s=0
do k=l,M
if (a(k)==0) s=s+l
end do
Zero=s
end function Zero
end
Результат работы программы:
Zero elements... 4
Press any key to continue
152
6. Функции, подпрограммы и модули
В некоторых случаях, когда в процедуру передается слишком много па-
раметров и используются производные типы данных и глобальные констан-
ты, стоит подумать о целесообразности общего доступа и применения внут-
ренних процедур. Например, если массивы обрабатываются процедурами на
протяжении всей работы программы, то эти массивы можно не передавать
через формальные параметры, а обращаться к ним напрямую, как в рассмот-
ренном выше примере. Вообще выбор способа обмена данными между про-
цедурами и программой зависит от решаемой задачи, используемого алго-
ритма и общей стратегии программирования.
6.4. Статические и автоматические переменные
Рассмотрим несколько подробнее внутренние переменные, описанные
в 6.1 и 6.2 на следующем примере.
program stat
write(*,100) "Fl = ", Fl(), " F2 = ", F2()
write(*,100) "Fl = ”, Fl () , " F2 = ", F2()
write(*,100) "Fl = ", Fl(), " F2 = ", F2()
100 format (2(A,i2))
contains
integer function Fl()
integer :: i=0
i = i + 5
Fl=i
end function Fl
integer function F2()
integer i
i=0
i=i+5
F2=i
end function F2
end
Результат работы программы:
Fl = 5 F2 = 5
Fl = 10 F2 = 5
Fl = 15 F2 = 5
Press any key to continue
На первый взгляд может показаться, что результаты работы функций
Fl() и F2() будут в обоих случаях всегда одинаковыми, однако на самом деле
в первой функции происходит накопление суммы, во второй результат по-
стоянный.
153
И. Л. Артёмов. Fortran: основы программирования
Дело в том, что по умолчанию все переменные, описанные в процедурах,
являются статическими. Это значит, что внутренние переменные, описанные
внутри процедуры, создаются на этапе компиляции и существуют на асем
протяжении работы программы. В функциях Н() и F2() переменная i являет-
ся статической. Однако в функции Fl() эта переменная инициализирована,
т. е. на этапе компиляции ей присваивается начальное нулевое значение. По-
этому переменная i содержит нуль только при первом обращении к функции,
в последующих вызовах это значение увеличивается на 5. Во второй функ-
ции, F2(), переменной i присваивается нулевое значение при каждом обраще-
нии к функции и увеличение на 5 не происходит.
В связи с этим следующий пример для вычисления значений функции
ряда Фурье будет ошибочным:
real function FUR(x,N)
real x
integer N, i
real : : s=0 ! ошибка, s - статическая переменная
! инициализация на этапе компиляции
do i=l,N,2
s=sin(i*x)/i+s
end do
FUR=s
end function
Функция будет правильной, если перед началом цикла переменной $
присвоить начальное, нулевое значение.
Тот факт, что переменные являются статическими, очень удобно исполь-
зовать, например, для подсчета того, сколько раз вызывалась подпрограмма
или функция.
В следующей программе выводится информация о том, в который раз
происходит обращение к подпрограмме ргос. Если число обращений превы-
шает 5, то выводится сообщение Limit...
program count
integer k
do k=l,7
call proc()
end do
contains
subroutine proc()
integer :: i=l
if (i<=5) then
write (* , *) " Call 11, i
i=i+l
else
write(*,*) Limit..."
154
6. Функции, подпрограммы и модули
end if
end subroutine proc
end
Результат работы программы:
Call 1
Call 2
Call 3
Call 4
Call 5
Limit...
Limit...
Press any key to continue
Пример. При каждом втором вызове подпрограммы выводить сообще-
ние "The second",
program count
integer k
do k=l,10
call procf)
end do
contains
subroutine proc()
integer :: i=l
i = i + l
if(mod(i,2)==0) then
write(*,*) " The second"
end if
end subroutine proc
end
Результат работы программы:
The second
The second
The second
The second
The second
Press any key to continue
Следует заметить, что по умолчанию все статические переменные имеют
атрибут save (сохранить). Наряду с атрибутом save существует также атрибут
static (статический), который эквивалентен атрибуту save и указывает, что
переменные сохраняют свои значения после работы процедуры. В следую-
щем примере переменным а и b явно присвоены атрибуты save и static,
real, save :: а
logical, static :: b
155
И. Л. Артёмоа. Fortran: основы программирования
В противоположность статическим переменным существуют автомати-
ческие переменные. Для таких переменных память отводится не на этапе
компиляции, а при каждом обращении к процедуре при выполнении про-
граммы. При вызове процедуры автоматические переменные создаются и
размещаются в стеке (временной памяти) и по завершении работы процедуры
из стека удаляются. Чтобы объявить переменные автоматическими, исполь-
зуется атрибут или оператор automatic.
Например:
integer, automatic :: a,b,c
или
automatic
integer a,b,c
В последнем случае атрибут automatic будет присваиваться неявно ос-
тальным переменным, описанным в процедуре. Чтобы переменная сохраняла
свое значение, т. е. была статической после работы процедуры, ей следует
присвоить атрибут save отдельным оператором.
В следующем примере все переменные являются автоматическими, кро-
ме переменной st, которая является статической.
automatic
integer a, b, с, d, f, g, h, j , st
save /st/
Следует также помнить, что автоматические переменные не допускают
инициализации, так как создаются заново при каждом вызове процедуры.
Правомерным будет вопрос, когда следует использовать статические,
а когда автоматические переменные. Можно сказать, что принципиального
отличия в большинстве расчетных программ не будет наблюдаться. В ряде
случаев, например при разработке многониточных приложений [3] или ре-
курсивных процедур, может потребоваться использование автоматических
переменных.
Ранее было изучено, что существуют также динамические переменные.
Чтобы различать и понимать, к какому виду относятся переменные, еще раз
повторим изученный материал.
Статическим переменным, имеющим атрибуты save или static, память от-
водится на этапе компиляции. Переменные, которые инициализированы все-
гда статические. Автоматическим переменным, имеющим атрибут automatic,
память отводится автоматически программой (без участия программиста)
в специальной области памяти - стеке и освобождается после окончания ра-
боты процедуры. Наконец, динамическим переменным, с атрибутом alloca-
table, память отводится ао время работы программы или процедуры и полно-
стью может контролироваться программистом при помощи операторов
allocate и deallocate.
156
6. Функции, подпрограммы и модули
В следующем примере используются три разновидности переменных: a -
автоматическая переменная, с - статическая переменная, d - динамическая
переменная.
subroutine proc(s)
integer, automatic :: a
integer, static :: c
integer, allocatable :: d
end subroutine proc
6.5. Управление работой процедур
Мы уже знаем, что функции и подпрограммы - это самостоятельные
программные единицы, выполняющие четко поставленные цели. Однако, как
и любая программа, процедуры могут аварийно завершаться. Так, в следую-
щем примере при попытке вызова функции с фактическим параметром х=1
произойдет ошибка деления на нуль.
program error
write(*,*) "F(1.0) = -,F(1.0)
write(*,*) ”F(2.0) = ",F(2.0)
contains
real function F(xt)
real xt
F=1.0/(xt-1.0)
end function F
end
В этом случае вызов функции при х=2.0 не произойдет, так как програм-
ма завершится с ошибкой выполнения при обращении к функции F(1.0). Что-
бы функция могла обработать исключительную ситуацию следует, восполь-
зоваться оператором return (возврат), который прекращает выполнение функ-
ции и возвращает управление вызывающей профамме или процедуре.
program see_error
writer,*) "F(1.0) = ”,F(1.0)
writer,*) ”F(2.0) = -,F(2.0)
contains
real function F(xt)
real xt
if (abs(xt)<le-30) then
F = 0.0
return
end if
F=1.0/(xt-1.0)
end function F
end
157
И. Л. Артёмов. Fortran: основы программирования
Результат работы программы:
Р(х) = О.ООООООЕ+ОО
F(x) = 1.000000
Press any key to continue
Если для проверки применить оператор stop, то произойдет остановка
программы, и поэтому он для прекращения работы процедур использоваться
не может. При создании процедур рекомендуется делать проверку вычисляе-
мых выражений и при необходимости выводить сообщения об ошибках.
Пример. Создать подпрограмму по формированию таблицы из одного
символа, с проверкой на правильность указания диапазона.
program my_table
call table(8,4,'А')
contains
subroutine table(length,width,ch)
integer length, width ! размеры таблицы
character ch ! выводимый символ
integer i,j
! проверка правильности диапазона
if((length<=0).OR.(width<=0)) then
write(*,*) "Error. Can't create table."
return 1 возврат в вызывающую программу
end if
do i=l,width ! вывод таблицы
do j=l,length
write(*,"(A4,\)") ch
end do
write(*,*)
end do
end subroutine table
end
Результат работы программы:
A A A A A A A A
A A A A A A A A
A A A A A A A A
A A A A A A A A
Press any key to continue
program my_table
call table(-3,5,'A')
end
Результат работы программы:
Error. Can't create table.
Press any key to continue
158
6. Функции, подпрограммы и модули
Оператор if проверяет правильность задания размеров таблицы. В случае
обнаружения ошибки на экран выводится сообщение Error. Can't create table
и управление передается в вызывающую глааную программу посредством
оператора return. Таким образом, оператор return позволяет прервать выпол-
нение подпрограммы и продолжить выполнение главной программы.
6.6. Формальные и фактические параметры
Рассматривая подпрограммы и функции, мы часто употребляли два важ-
ных термина: формальные и фактические параметры. Формальные парамет-
ры используются при описании процедур. Фактические применяются при вы-
зове процедур. Посредством формальных и фактических параметров проис-
ходит обмен информацией между процедурами и программой. Количество и
тип формальных параметров естественным образом определяется решаемой
задачей.
Для правильной работы процедур необходимо соответствие фактических
и формальных параметров как по порядку следования, так и по типу данных
(рис. 6.3).
——• logical d
—• «character с
-—• integer b
—• real a
call proc(a, b, c, d)
соответствие
типов и порядка
следования
-•character s
logical p
•nd
Рис. 6.3. Соответствие формальных и фактических параметров
Например, подпрограмма proc(x,O,p) содержит 4 формальных парамет-
ра: х - вещественного типа; к - целого типа; $ - символьного типа; р - логи-
ческого типа.
159
И. Л. Артёмов. Fortran: осноаы программирования
Поэтому при вызове подпрограммы call proc(a,b,c,d) должно выполняться
соответствие:
а - вещественного типа (соответствует формальному вещественному х);
b - целого типа (соответствует формальному целому к);
с - символьного типа (соответствует формальному символьному $);
d - логического типа (соответствует формальному логическому р).
Несоответствие типов формальных и фактических параметров часто при-
водит к долгому поиску ошибок. Например, в следующей программе на экран
выведется 0, хотя ожидалось 10.
program error
double precision xt
xt=10.0
call show(xt) [ошибка
contains
subroutine show(p)
real p
write(*,*) p
end subroutine show
end
Ошибка заключается в несоответствие типов: фактический параметр xt -
двойной точности, формальный р - вещественного типа.
Аналогичная ситуация в следующей программе. Ожидается вывод значе-
ния 9. Однако в результате компиляции будет выдано предупреждение и во
время выполнения на экране отобразится неверный результат
program error
real t
call s(9) ! ошибка
t=9
call s(t) ! ошибки нет
call s(REAL(9)) ! ошибки нет
contains
subroutine s(x)
real x
write(*,*) x
end subroutine s
end
Ошибка в несоответствии типов: формальный параметр - вещественного
типа, фактический параметр - целого типа. Заметим, что в последующих вы-
зовах подпрограммы ошибки отсутствуют, так как переменная t веществен-
ного типа и при присваивании переменной t целого значения произошло ав-
томатическое приведение типов. Приведение типов можно также оформить,
явно используя функцию REAL.
160
6. Функции, подпрограммы и модули
Результат работы программы:
1.261169Е-44
9.000000
9.000000
Press any key to continue
Для создания более универсальных процедур можно оформить некото-
рые формальные параметры дополнительными, что дает определенную гиб-
кость при создании процедур. Например, в следующем примере если требу-
ется, то можно выводить последовательность целых чисел с определенным
шагом step.
program option_param
call line(1,10,2)
contains
subroutine line(xl,xn,step)
integer x,xl,xn,dx
integer, optional :: step
if (present(step)) then
dx=step
else
dx=l
end if
do x=xl,xn,dx
write(*,"(i4,\)") x
end do
end subroutine line
end
В подпрограмме формальный параметр step является необязательным и
поэтому объявлен с атрибутом optional (дополнительный). Чтобы определить,
используется ли при вызове процедуры необязательный параметр или нет,
вызывается функция present (присутствие). В конструкции if выполняется
проверка: если вызов подпрограммы происходит с необязательным парамет-
ром, то функция present возвращает значение .TRUE, и <ir=step, иначе резуль-
тат .FALSE, и dx=l.
Таким образом, если необходимо вывести последовательность натураль-
ных чисел, то можно вызвать подпрограмму с двумя обязательными парамет-
рами, указывающими первое и последнее число последовательности.
call line(1,10)
Подпрограмму можно вызвать также с дополнительным необязательным
параметром step, указывающим на шаг последовательности.
call line(1,10,2)
Выше было описано, что при вызове процедуры порядок формальных
и фактических параметров должен совпадать. Однако это правило можно на-
161
И. Л. Артёмов. Fortran: основы программирования
рушить, если вызывать процедуры с ключевыми словами. Ключевые слова -
это имена формальных параметров. Так, в рассмотренном примере подпро-
грамму можно вызвать таким образом:
call line(xl=l,xn=10,step=2)
или, нарушая порядок следования:
call line(xn=10,step=2,xl=l)
При этом результат работы подпрограммы будет одинаковым.
Результат работы программы:
1 3 5 7 9
Press any key to continue
Возможность указания ключевых слов применяется при создании проце-
дур с необязательными параметрами. Допустим, в подпрограмме line должна
быть предусмотрена дополнительная возможность возведения в целую сте-
пень элементов последовательности. Очевидно, потребуется два необяза-
тельных параметра. Для определения, какой из дополнительных параметров
был использован, при вызове процедуры следует применять ключевые слова,
program option_param
call line(1,10,step=2)
call line(l,10,pok=2)
call line(1,10,step=3,pok=3)
contains
subroutine line(xl,xn,step,pok)
integer x,xl,xn,dx,n
integer, optional :: step
integer, optional :: pok
if (present(step)) then
dx=step
else
dx=l
end if
if (present(pok)) then
n=pok
else
n=l
end if
do x=xl,xn, dx
write(*,“(i4,\)”) x**n
end do
end subroutine line
end
162
6. Функции, подпрограммы и модули
Результат работы программы:
1 3 5 7 9
1 4 9 16 25 36 49 64 81 100
1 64 343 1000
Press any key to continue
Ключевые слова при вызове подпрограмм указывают, какой конкретно
используется необязательный параметр, что положительно сказывается на
удобочитаемости и понимании работы программы.
6.7. Массивы и символьные строки
как параметры процедур
При программировании вычислительных задач иногда требуется переда-
вать массивы в процедуру. Например, в функцию, которая вычисляет опреде-
литель квадратной матрицы, будет передаваться двумерный массив, а в под-
про!рамму сортировки данных следует передавать одномерный массив.
При передаче массивов в процедуру в качестве формальных параметров
должны выступать формальные массивы. Следующий пример иллюстрирует,
как, используя формальный массива, написать функцию подсчета нулевых
элементов в одномерном массиве.
Пример:
program formal_massif
integer :: а (10) = (/1,0,4,0,0, 5,4,2,0,0/)
write(*,*) zero(а)
contains
integer function zero(ms)
integer ms(10)
integer k, s
s=0
do k=l,10
if(ms(k)==0) s=s+l
end do
zero=s
end function zero
end
Результат работы программы:
5
Press any key to continue
В функции zero описан формальный массив ms из 10 элементов. При вы-
зове функции используется фактический массив также из 10 элементов.
В данном случае необходимо соответствие не только типов, но и размеров
163
И. Л. Артёмов. Fortran: основы программирования
формального и фактического массивов. Так, при описании массива integer
«(11) и соответствующего вызова zero(a) будет выдана ошибка компиляции.
С другой стороны, при объявлении массива integer а(-3:6) ошибки не будет,
так как количество элементов в массиве равно 10.
Если требуется, чтобы сохранялась информация о нижней и верхней гра-
нице, следует передавать границы каждого измерения. Если нумерация эле-
ментов начинается с единицы, то достаточно передавать только размер мас-
сива. Следующая функция возвращает индекс первого нулевого элемента в
одномерном массиве.
program formal_massif
integer :: р(-4:5)=(/1,5,9,0,3,4,2,0,0,3/)
write(*,*) Zeroindex(р,-4,5)
contains
integer function Zeroindex(p,il,i2)
integer i,il,i2
integer p(il:i2)
do i=il,i2
if (p(i)==0) then
Zerolndex=i
return
end if
end do
end function Zeroindex
end
В функции Zeroindex используется три формальных параметра: фор-
мальный массив р, верхняя и нижняя границы.
Результат работы программы:
-1
Press any key to continue
В качестве фактического параметра также может выступать сечение мас-
сива,
write(*,*) Zeroindex(р(0:5),0,5)
Результат работы программы:
3
Press any key to continue
Следует отметить, что форма формального массива и фактического мас-
сива могут различаться, но размер должен быть одинаковым.
Пример. Найти количество четных чисел в двумерном массиве,
program formal_massif
integer А (2,5)
A(l,:)=(/2,6,3,4,2/)
164
6. Функции, подпрограммы и модули
А(2,:) = (/2,4,8,9,9/)
write(*,*) “N = ", fn(A,2,5)
contains
integer function-fn(p,mi,mj)
integer mi,mj, p(mi *mj), s
integer i
s=0
do i=l,mi*mj
if (mod(p(i),2)==0) s=s+l
end do
fn=s
end function fn
end
Результат работы программы:
N = 7
Press any key to continue
Пример. Написать функцию вычисления определителя второго порядка*.
। 0,2
Известно, что определитель матрицы второго порядка А =
, а21 Я22
вычисляется по формуле det(A) = ап -а22 -О|2 а2| .
program Determinant2
real t(2,2)
t(1,1)=4.0; t(1,2)=5.0
t(2,l)=7.0; t(2,2)=1.0
write(*,*) det2(t)
contains
real function det2(a)
real a(2,2)
det2=a(l,l)*a(2,2)-a(1,2)*a(2,1)
end function det2
end
Результат работы программы:
-31.000000
Press any key to continue
Пример. Написать программу вычисления объема параллелепипеда, по-
строенного на трех векторах а,, а2, а3.
Если заданы координаты векторов a, ={X1,P1,Z1}, а2 = {X2,Y2,Z2],
а3 = {X3,P3,Z3}, то объем параллелепипеда выражается формулой
Для вычисления определителей любого порядков см. 6.14.
165
И. Л. Артёмов. Fortran: основы программирования
V =±
Х2
х3
У1
Уг
Уз
Z,
Z2
Z3
program v_parall
real vl(3), v2(3), v3 (3)
vl=(/ 1, 3,-4/)
v2=(/-3, 4, 2/)
v3=(/ 3,-2, 1 /)
write(*,"(A,f7.2)") "V parallelepiped =
volume (vl,v2,v3)
contains
real function volume(vl,v2,v3)
real vl(3), v2(3), v3(3)
real ml,m2,m3
ml=v2(2)*v3(3)-v2(3)*v3 (2)
m2=v2(1)*v3(3)-v2(3)*v3(1)
m3=v2(1)*v3(2)-v2(2)*v3(1)
volume=abs(vl(1)*ml-vl(2)*m2+vl(3)*m3)
end function volume
end
Результат работы программы:
V parallelepiped = 59.00
Press any key to continue
Рассмотренные выше примеры показывают способ описания формаль-
ных массивов, когда задана форма массива. Формальный массив также может
перенимать форму соответствующего фактического массива. В этом случае
вместо количества элементов каждого измерения ставится символ : и индек-
сация элементов будет начинаться с единицы. Например, в следующей под-
программе объявлен двумерный массив, который перенимает форму факти-
ческого массива.
program prog
integer р(—1:10,-1:20)
contains
subroutine subl(р)
integer р(:,:)
end subroutine subl
end
To есть в процедуре формальный массив имеет форму (12,22) и индексация
элементов начинается с 1. Чтобы сохранить индексацию в формальном мас-
сиве следует указать нижнюю границу каждого измерения:
integer р(-!:,-!:)
166
6. Функции, подпрограммы и модупи
Для определения границ каждого измерения массива можно воспользо-
ваться функциями lbound(a,H) и ubound(a,n), где а - массив, п - номер изме-
рения.
В следующем примере функция подсчитывает количество истинных зна-
чений в трехмерном логическом массиве. Формальный массив перенимает
форму фактического массива.
program logic_3d
logical а(-1:2,-2:2,0:3) ! всего 80 элементов
a=.TRUE.; а(1,1,1)=.FALSE.; а(2,2,2)=.FALSE.
write(*,*) ''Number of TRUE elements = ", TrueValue(a)
contains
integer function TrueValue(p)
logical p )
integer i,j,k
integer il,i2,j1,j 2, kl, k2
! определяем нижнюю и верхнюю
il=lbound(р,1); i2=ubound(р,1)
jl=lbound(р,2); j2=ubound(р,2)
kl=lbound(р,3); k2=ubound(р,3)
s=0
do i=il,i2
do j=jl,j2
do k=kl,k2
if (p(i,j,k)) s=s+l
end do
end do
end do
TrueValue=s
end function TrueValue
end
границы каждого измерения
I il=l, i2=4
! jl=l, j2=5
I kl=l, k2=4
Так как не указаны нижние границы, то границы формального массива
будут такими: р( 1:4,1:5,1:4).
Результат работы программы:
Number of TRUE elements = 78
Press any key to continue
В заключение рассмотрим, как передавать символьные строки в проце-
дуры. При передаче символьных данных в процедуру длину формальной
строки рекомендуется задавать символов *. В этом случае длина формально-
го параметра равна длине фактического параметра.
Пример. Функция подсчета нулей в строке.
program how_many
write(*,*) count_zero('1000011110000')
167
И. Л. Артёмов. Fortran: основы программирования
contains
integer function count_zero(string)
character(*) string
integer i, M, s
s=0
M=len(string)
do i=l,M
if (string(i:i)=='0') s=s+l
end do
count_zero=s
end function count_zero
end
Результат работы программы:
8
Press any key to continue
6.8. Механизм передачи параметров
В предыдущих разделах были разобраны следующие положения:
• при вызове процедур должно выполняться соответствие формальных
и фактических параметров;
• формальные параметры также могут быть дополнительными.
В этом разделе будет рассмотрено, как данные могут передаваться в про-
цедуру. Другими словами, какие бывают разновидности формальных пара-
метров. Изучив урок, мы ответим на пару интересных вопросов:
1. При вызове подпрограммы call ргос(а,й) изменятся ли значения пере-
менных а и b после работы подпрограммы?
2. Почему в следующем вызове - call ргос(2) содержится ошибка, даже
если тип формального и фактического параметров совпадают?
Используя атрибут intent (намерение), все формальные параметры можно
разделить на три вида:
1. Входные формальные параметры (in). Формальные параметры, объяв-
ленные как in, принимают значение от соответствующего фактического па-
раметра и не могут изменяться при выполнении процедуры. Соответствую-
щими фактическими параметрами могут быть выражения, переменные, зна-
чения, константы.
Пример. Подпрограмма, печатающая на экране последовательность це-
лых чисел.
program numbers
integer :: k=10
call show(k)
168
6. Функции, подпрограммы и модули
contains
subroutine show(M)
integer, intent (in) :: M I. входной параметр
integer i
do i=l,M
write (*, " (14,\) “). i
end do
write(*,*)
end subroutine show
end
Результат работы профаммы:
123456789 10
Press any key to continue
Если в подпрограмме увеличить М в 2 раза, то будет ошибка компиля-
ции, так как формальный параметр М является входным и в процедуре изме-
няться не должен.
2. Выходные параметры (out). Такие параметры передают свое значение
соответствующему фактическому параметру и предназначены для вывода
данных из процедуры. Соответствующий фактический парамезр должен быть
переменной, подстрокой или элементом записи.
Пример. Написать подпрограмму, возвращающую координаты точки пе-
ресечения двух прямых.
Пусть даны уравнения двух прямых. Для нахождения точки пересечения
достаточно решить систему уравнений.
lajX+fyy + q =0;
[a2x + b2y + c2 =0.
Для решения воспользуемся методом Крамера, согласно которому
det х det у
Х =---, у =--
det det
где det =
«1
«2
Ь\
b2
det x = -
C2
Ьг
det у = -
«1
«2
ci
c2
Следует также учесть, что при det = 0 прямые либо совпадают, либо па-
раллельны.
program cross
real х, у ! координаты точки пересечения
write(*,"(А,\)") "coeff 1-st equation..."; read(*,*) al,bl,cl
write(*,"(A,\)") "coeff 2-nd equation..."; read(*,*) a2,b2,c2
call cross_point(al,bl,cl,a2,b2,c2,x,y)
write(*,*) "x = “,x," у = ",y
169
И. Л. Артёмов. Fortran: основы программирования
contains
subroutine cross_point(al, bl, cl, a2, Ь2, c2, x, у)
real, intent (in) :: al,bl,cl,a2,b2,c2 ! коэффициенты
! уравнений
real, intent (out) :: x,y ! координаты точки пересечения
real det, detx, dety
det =al*b2-bl*a2
detx=c2*bl-cl*b2
dety=a2 *cl-al*c2
if (abs(det)<1E-2O) then
stop " Determinant is zero!"
end if
x=detx/det; y=dety/det
end subroutine cross_point
end
Результат работы программы:
coeff 1-st equation...! -2 3
coeff 2-nd equation...4 5 -65
x = 5.000000 у = 9.000000
Press any key to continue
Подпрограмма cross_point содержит 6 входных (in) формальных пара-
метров - коэффициентов уравнений и 2 выходных (out) формальных пара-
метра - координат х и у точки пересечения. Таким образом, данные в подпро-
грамму поступают по одним и выводятся по другим формальным парамет-
рам. В случае неединственности решения в конструкции if происходит вывод
сообщения " Determinant is zero!" и остановка выполнения всей программы.
3. Входные-выходные параметры (inout), которые могут как принимать
значения от фактического параметра, так и передавать данные в вызываю-
щую программу или процедуру. Соответствующие фактические параметры
могут быть переменными, но не могут быть значениями или константами.
Пример. Подпрограмма, меняющая местами значения двух переменных,
program change_plасе
integer х, у
х=10; у=20
write(*,*) х, у
call proc(х,у)
write(*,*) х, у
contains
subroutine proc (a,b)
integer, intent (inout) :: a,b
integer tmp
tmp=a; a=b; b=tmp
170
6. Функции, подпрограммы и модули
end subroutine
end
Результат работы программы:
10 20
20 10
Press any key to continue
Пример. Транспонирование и вывод матрицы.
program my_transpose
integer b(3,3)
b(l,:)=(/4,8,0/)
b(2, :)=(/5,9,l/)
b(3,:)=(/7,6,2/)
call show(b,3) ! показали исходную матрицу
call TRQ(b,3) ! провели транспонирование
call show(b,3) ! отобразили результат
contains
subroutine TRQ(a,M) ! ----- транспонирование матрицы ----
integer, intent (in) :: M
integer, intent (inout) :: a(M,M) ! массив должен
! измениться
integer i,j,tmp
do i=2,M
do j =1,i-1
tmp=a(i,j);
a(i, j)=a (j, i) ; ! меняем местами элементы
a(j,i)=tmp ! выше и ниже главной диагонали
end do
end do
end subroutine TRQ
subroutine show(a,M) ! ---- вывод матрицы ----
integer, intent (in) :: a(M,M), M ! массив только
! отображается
integer k ! на экране, после работы подпрограммы
do k=l,M ! не должен изменяться
write(*, " (100 (i4) ) 11) a(k, :)
end do
write(*,*)
end subroutine show
end
Результат работы программы:
4 8 0
5 9 1
7 6 2
171
И. Л. Артёмов. Fortran: основы программироввния
4 5 7
8 9 6
О 12
Press any key to continue
Ответим на сформулированные в начале раздела вопросы:
1. При вызове подпрограммы call ргос(а,й) изменятся ли значения пере-
менных an b после работы подпрограммы?
Ответ: значения переменных а и b могут измениться, если соответст-
вующие формальные параметры объявлены как out или inout. Значения пере-
менных не изменятся, если формальные парамегры объявлены как in.
2. Почему в следующем - вызове call ргос(2) содержится ошибка, даже
если тип формального и фактического параметров совпадают?
Ответ: соответствующий формальный параметр объявлен как out
или inout.
Возникает правомерный вопрос. В предыдущих примерах мы не исполь-
зовали in, out, inout. Какого же вида были формальные параметры? Если ат-
рибут intent не задан, то вид связи определяется по связанному фактическому
параметру. Панример, если фактический параметр переменная и процедура
изменяет соответствующий формальный параметр, то после работы процеду-
ры изменится и значение фактического параметра (вид связи out, inout).
В случае же если в процедуру передается константа, значение, выражение, то
соответствующий формальный параметр не должен изменяться при работе
процедуры (вид связи in).
Изучив данный урок, можно увидеть различие в использовании функций
и подпрограмм. Так как функция возвращает вычисленное значение, то реко-
мендуется формальные параметры описывать как in, т. е. все поступающие
данные являются входными, а результат функции является выходным. Вооб-
ще можно показать, что функция и подпрограмма с одним выходным пара-
метром - это, по сути, две одинаковые процедуры.
integer function F(a)
integer, intent (in) a
F=a*a
end function
subroutine quadr(q, a)
integer, intent (out) : q
integer, intent (in) : a
q=a*a
end subroutine quadr
В первом случае функция возвращает вычисленный результат. Во втором
случае при вызове подпрограммы результат помещается в выходную пере-
172
6. Функции, подпрограммы и модули
менную q. В обоих вызовах смысл и результаты будут одинаковыми. Поэто-
му если при создании процедуры требуется один выходной параметр, то
можно воспользоваться функцией, в противном случае следует использовать
подпрограмму.
6.9. Внешние и внутренние процедуры.
Интерфейсы процедур
До настоящего урока мы рассматривали внутренние процедуры, которые
были описаны после оператора contains. В языке Fortran также существует
другой вид процедур - внешние процедуры.
Внешними называются процедуры, описанные отдельно от основного
текста программы. Рассмотрим пример.
program prog ! ------- основная программа -------------------
integer res
call ReadResult(res)
call PrintResult(res)
contains
subroutine ReadResult(a) ! -----
integer, intent (out) : a
write(*,"(A,\)") "Enter value =
end subroutine ReadResult
end
внутренняя подпрограмма
"; read(*,*) a
subroutine PrintResult(a) ! ----- внешняя подпрограмма
integer, intent (in) :: a
write(*, *) и*★*★★*★★*★**★*★**★★*★★♦****i»'
write(*,*) "------ Result = ",a**2
write(*,*) "***************************»
end subroutine PrintResult
Результат работы программы:
Enter value = 5
----- Result = 25
Press any key to continue
Внешние процедуры могут находиться в одном файле с основной про-
граммой, в других файлах, библиотеках (lib-файлы), динамически-подключа-'
емых библиотеках (dll-файлы).
При объявлении внешней процедуры в одном файле с программой про-
цедуры описываются после оператора end основной профаммы. В вышепри-
173
И. Л. Артёмов. Fortran: основы программирования
веденном примере подпрограмма PrintResult является внешней, подпрограм-
ма ReadResult внутренней.
По своему устройству внешние процедуры во многом похожи на внут-
ренние, но имеют ряд принципиальных отличий.
1. Внутренние процедуры не могут иметь внутренних процедур, внешние
процедуры могут иметь внутренние процедуры. Например, внешняя функция
проводит вычисления и использует вспомогательную функцию.
program prog ! ------ основная программа ----------------------
write(*,*) ”F(x) = ", F(1.0)
end
real function F(x) ! ----- внешняя функция --------------------
real, intent (in) :: x,s
integer, intent (in) :: k
s=0
do k=l,10
s=s+(G(k*x)**2)**(1.0/3.0)
end do
F=S
contains! ---- внутренняя, вспомогательная функция G(x),
! доступная только из функции F(х)
real function G(x)
real,intent (in) x
G=sin(x)+cos(x)
end function G
end function F
Внешняя функция F(x) содержит внутреннюю функцию G(x). Внутрен-
няя функция G(x) доступна только внутри функции F(x). Можно также ска-
зать, что внутренние функции доступны только в тех программных единицах,
где они описаны.
2. Переменные, описанные во внешних процедурах, недоступны в 'основ-
ной программе. Переменные, описанные в основной программе, недоступны
во внешних процедурах (рис. 6.4).
3. Внутреннюю процедуру нельзя передавать как параметр в другую
процедуру, внешнюю можно (см. 6.12).
4. Внутренние процедуры имеют явно заданный интерфейс, для внешних
следует указывать явно, используя оператор interface (интерфейс). Например,
если описана функция norm, вычисляющая длину вектора, то для того чтобы
компилятор смог разобраться, что тип возвращаемого значения вещественное
число (а не целое, на что указывает имя norm), в главной программе после
оператора program используется оператор interface следующего содержания:
Interface
тип имя_функции(формальные параметры)
174
6. Функции, подпрограммы и модули
тип формальных параметров
конец описания функции
end interface
program pl
integer a,b,c
область видимости -
основная программа
end
Рис. 6.4. Область видимости в основной программе и во внешних процедурах
Отметим, что блок interface рекомендуется всегда задавать для внешних
функций.
Пример:
program vector
interface ! ------ интерфейсная часть ----------------
real function norm(v)
real, intent (in) :: v(3)
end function norm
end interface !-----------------------------------------
real v(3)
v=(/2,6,3/)
write(*,*) " Vector ",v
write (*,*) 11 Norm = ",norm(v)
end! ---------------- конец основной программы ---------
real function norm(v) ! ----- функция вычисления длины вектора
real v(3)
norm=sqrt(v(l)**2+v(2)**2+v(3)**2)
end function norm
Результат работы программы:
Vector 2.000000 6.000000 3.000000
175
И. Л. Артёмов. Fortran: основы программирования .
Norm = 7.000000
Press any key to continue
Пример. Написать функцию, которая возвращает истину, если два целых
числа равны, ложь - в противном случае,
program funct
Interface! ----------- интерфейс к внешней функции equ -----------
logical function equ(ml,m2)
integer ml,m2
end function equ
end interface !------------------- конец интерфейса --------------
integer a, b
write(*,"(A,\)“) "Enter first number..."; read(*,*) a
write(*,"(A,\)") "Enter second number..."; read(*,*) b
if(equ(a,b)) then
write(*,*) "YES"
else
write(*,*) "NO"
end if
end
logical function equ(ml,m2)
integer ml, m2
equ=ml==m2
end function equ
Концепция внешних процедур очень плодотворна. Исходную задачу
можно разбить на подзадачи и поручить каждую для решения отдельному
программисту. Например, одному следует написать подпрограмму first(a,b,c),
второму - second(a.i.c). При этом разработчики могут использовать любые
имена переменных, констант, типов данных, внутренних процедур для созда-
ния процедур first и second. После разработки эти подпрограммы вызываются
из главной программы zero и обмениваются данными посредством формаль-
ных и фактических параметров,
program zero
call first(х,у,z)
call second(x,у,z)
end
При этом разрабатываемые процедуры можно размещать в отдельных
файлах, а затем добавлять в проект. Кроме того, разработанные процедуры
можно откомпилировать отдельно и оформить в библиотеки, о чем будет
рассказано далее.
176
6. Функции, подпрограммы и модули
6.10. Более глубокий взгляд на функции
Ранее было рассмотрено, что функция возвращает результат вычислений.
При этом имени функции присваивается вычисленный результат. Имеется
также возможность указать результирующую переменную, в которую будет
помещаться результат работы функции. Такая переменная должна быть зада-
на предложением result (результат). Тип результирующей переменной указы-
вает тип функции. Если при описании функции задан тип, то результирую-
щая переменная не указывается.
Пример. Использование результирующей переменной.
function F(х) result(res)
real res ! результирующая переменная
res=x*x
end function F
Если результирующая переменная не указана, то по умолчанию за ре-
зультирующую переменную принимается имя функции, что и было рассмот-
рено ранее.
real function F(x)
F=x*x ! F - результирующая переменная
end function F
можно и так
function F(x)
real F IF- результирующая переменная
F=x*x
end function F
Однако одновременно объявлять тип функции и результирующую пере-
менную недопустимо:
real function F(x) ! ошибка: указан тип функции
1 и результирующая переменная F
real F
F=x*x
end function F
Результирующие переменные могут быть массивами любого встроенного
типа данных, ссылками и производного типа.
Пример. Массив как результат функции. Следующая функция - sum_vec
возвращает сумму двух векторов в виде массива.
program massif_as_result
interface
function sumvec(a,b,M)
integer, intent(in) :: M
177
И. Л. Артёмов. Fortran: основы программирования
real, intent(in) :: а(М), b(M)
real sum_vec(M)
end function suni_vec
end interface
real a(5), b(5), c(5)
a=3; b=4
c=sum_vec(a,b,5)
write(*,"(5(£5.2)) ") c
end
function sum_vec(a,b,M)
integer, intent(in) :: M
real, intent(in) :: a(M), b(M)
real sum_vec(M)
sum_vec=a+b
end function
Если функция возвращает массив как результат, то необходимо:
• объявить функцию в interface-блоке (если функция внешняя);
• внутри функции указать результирующую переменную как массив.
В данном примере в функцию sum_vec передаются два одномерных мас-
сива а и Ь, с указанием количества элементов.
Результат работы программы:
7.00 7.00 7.00 7.00 7.00
Press any key to continue
Пример. Функция, возвращающая результат производного типа данных.
program struct_result
type point
real x
real у
end type point
type(point) pl,p2
pl=point(10.0,10.0)
p2 =point(20.0,20.0)
write(*,*) sump(pl,p2)
contains
function sump(pl,p2)
type (point) sump
type (point) pl,p2
sump.x=(pl.x+p2.x)/2
sump .y=(pl.y+p2,y)/2
end function sump
end
178
6. Функции, подпрограммы и модули
Так как функция объявлена как внутренняя, то функцию можно описать
и таким образом:
contains
type (point) function sump(pl,p2)
type (point) pl,p2
sump.x=(pl,x+p2.x)/2
sump.y=(pl.y+p2.y)/2
end function sump
end
При описании внешней функции тип point следует описать внутри функ-
ции и затем описать результирующую переменную.
function sump(pl,р2)
type point
real x
real у
end type point
type (point) sump
type (point) pl,p2
sump,x=(pl,x+p2.x)/2
sump,y=(pl.y+p2,y)/2
end function sump
6.11. Модули
Разберем следующий пример. Требуется написать функцию для вычис-
ления расстояния между двумя точками, заданными в пространстве. При
этом информация о точках должна храниться в переменных производного
типа point.
Сначала рассмотрим подход с использованием внешней функции.
program prog
type point
real x
real у
real z
end type point
type(point) pl, p2
pl=point(0.0,1.0,1.0)
p2=point(0.0,0.0,0.0)
write(*,*) "Distance...", dist(pl,p2)
end
real function dist(ptl, pt2)
179
И. Л. Артёмов. Fortran: основы программирования
type point
real х
real у
real z
end type point
type(point) ptl, pt2
dist=sqrt((pt2.x-ptl,x)**2+ &
(pt2,y-pti.y)**2+ &
(pt2.z-ptl.z)**2)
end function dist
При изучении внутренних процедур было отмечено, что переменные ти-
пы данных, константы, описанные в основной программе, доступны только
во внутренних процедурах. В случае с внешними процедурами такой общей
видимости не существует, поэтому тип point должен быть описан как в ос-
новной программе, так и в функции dist. Подобное дублирование типов дан-
ных не приветствуется, особенно при написании громоздких вычислитель-
ных программ. Можно воспользоваться внутренними процедурами, но, как
было рассмотрено на прошлом уроке, здесь есть некоторые ограничения.
Возникает естественный вопрос, как сделать, чтобы тип point был досту-
пен главной программе и всем внешним функциям.
Fortran предлагает использовать специальные программные единицы -
модули. Модули могут содержать переменные, константы, пользовательские
типы данных и модульные процедуры, которые будут доступны в тех проце-
дурах, где эти модули используются (рис. 6.5).
Рис. 6.5. Связь процедур посредством модуля
Для создания модуля используется следующий блок:
module имя_модуля
описания типов,
констант,
180
6. Функции, подпрограммы и модули
переменных,
модульных процедур
end module имя_модуля
Чтобы иметь доступ в процедуре (основной программе) к данным из мо-
дуля, применяется оператор use (использовать).
subroutine sub(а)
use имя_модуля
end subroutine sub
В нашем примере создание и использование модуля будет следующим:
module distance ! ----- создали модуль
type point
real х
real у
real z
end type point
end module distance
program use_module ! ----- основная программа
use distance ! --- используем модуль
type(point) pl, p2
pl.x=0; pl.y=l; pl.z=l
p2.x=0; p2.y=0; p2.z=0
write!*,*) "Distance...", dist(pl,p2)
end
real function dist(ptl, pt2) ! ---- внешняя функция
use distance ! ---- используем модуль
type(point) ptl, pt2 I --- тип point описан в модуле distance
dist=sqrt((pt2.x-ptl.x)**2+ &
(pt2.y-ptl.y)**2+ &
(pt2.z-ptl.z)**2)
end function dist
Создаваемый модуль distance должен быть описан либо в отдельном
файле, либо перед оператором program основной программы. Для использо-
вания модуля в основной программе и внешней функции применяется опера-
тор use.
Результат работы программы:
Distance... 1.414214
Press any key to continue
Пример. Использование модуля для совместного доступа к элементам
массива.
module massiv !---------------------------------------
181
И. Л. Артёмов. Fortran: основы программирования
integer, parameter :: М=5
integer а(М,М)
end module massiv
program use_module !-------------------------------------------
use massiv
interface
integer function sum_jnain()
end function suirtjnain
integer function sum_sec ()
end function sum_sec
subroutine make()
end subroutine make
subroutine show()
end subroutine show
end interface
call make()
call show()
write(*,*) "Summa main diagonal...", sum_main()
write (*,*) "Summa second diagonal...1', sum_sec ()
end
subroutine make() ! формирование матрицы случайных чисел
use massiv
real tmp(M,M)
call randotn_seed ()
call randotn_number (tmp)
a=int(tmp*10)
end subroutine make
integer function sum_main() ! вычисление суммы
use massiv ! элементов главной диагонали
integer i, s
s=0
do i=l,M
s=s+a(i,i)
end do
sum_main=s
end function sum_main
integer function sum_sec()! вычисление суммы
use massiv ! элементов побочной диагонали
integer i, s
s=0
do i=l,M
s=s+a(i,M-i+l)
end do
182
6. Функции, подпрограммы и модули
sum_sec=s
end function sum_sec
subroutine show() ! вывод матрицы на экран
use massiv
integer i
write(*,*) "Matrix...”
do i=l,M
write(*,"(100(i4))") a(i,:)
end do
write(*,*)
end subroutine show
Функция summain находит сумму элементов главной диагонали. Функ-
ция sum sec находит сумму элементов побочной диагонали. Подпрограмма
make заполняет массив случайными целыми числами. Подпрограмма show
отображает массив на экране. В основной программе происходит последова-
тельный вызов процедур. При этом действия, выполненные над массивом,
сразу становятся доступными всем процедурам.
Результат работы программы:
Matrix...
2 18 9 2
3 6 4 7 3
6 2 3 4 8
7 3 7 2 7
3 5 7 2 8
Summa main diagonal... 21
Summa second diagonal... 18
Press any key to continue
В рассмотренной программе процедуры выполняют действия над дан-
ными, которые описаны в модуле. В таких случаях процедуры можно разме-
щать внутри модуля. Процедуры, описанные внутри модуля, называются мо-
дульными процедурами. Ниже представлена программа с использованием
модульных процедур,
module massiv !---------------------------------------
integer, parameter :: M=5
integer a(M,M)
contains! ----- описания модульных процедур
subroutine make() !---- формирование матрицы случайных чисел
real tmp(M,M)
call randotn_seed ()
call randotn_number(tmp)
a=int(tmp*10)
end subroutine make
183
И. Л. Артёмов. Fortran: основы программирования
integer function sum_main() ! сумма элементов
! главной диагонали
integer i, s
s=0
do i=l,M
s=s+a(i,i)
end do
sum_main=s
end function sum_main
integer function sum_sec() ! сумма элементов
! побочной диагонали
integer i, s
s=0
do i=l,M
s=s+a(i,M-i+l)
end do
sum_sec=s
end function sum_sec
subroutine show() !---- вывод матрицы на экран
integer i
write(*,*) "Matrix..."
do i=l,M
write(*,"(100(14))") a(i,:)
end do
write(*,*)
end subroutine show
end module massiv
program matrix_module !-------- основная программа ------------
use massiv
call make()
call show()
write(*,*) "Summa main diagonal...", sum_main()
write(*,*) "Summa second diagonal...", sum_sec()
end
Процедуры, описанные внутри модуля, обладают явным интерфейсом,
поэтому нет необходимости использовать оператор interface.
При программировании могут потребоваться не все данные и процедуры,
описанные в модуле. Например, если использовать внешнюю подпрограмму
вывода массива на экран и назвать ее именем show. Во избежаниие конфлик-
та имен можно указать только, какие данные и процедуры будут использо-
вать из модуля, применяя атрибут only (только). В следующем примере из
модуля massiv будут доступны только константа М, массив а и подпрограмма
make(). Для вывода массива используется внутренняя подпрограмма с име-
184
6. Функции, подпрограммы и модули
нем show(). Другими словами, only позволяет определить, что конкретно
нужно от модуля, и программировать дальше!
program matrix_module !------- основная программа ----------------
use massiv, only : М, a, make ! из модуля доступны только
! константа М, массив а и подпрограмма make
call make()
call show()
contains
subroutine show() ! внутренняя подпрограмма show
integer i
write(*,*) "My own subroutine to show matrix"
do i=l,M
writer,"(A,5(i2),A)") "| °,a(i,:)," |"
end do
end subroutine show
end
Результат работы программы:
My own subroutine to show matrix
| 2 5 5 2 3 |
j 4 7 8 5 6 j
j 8 1 3 0 4 |
| 9 4 9 9 8 I
| 3 6 2 2 1 |
Press any key to continue
Внутри модуля модульным процедурам могут потребоваться вспомога-
тельные или служебные процедуры. Как сделать, чтобы временные процеду-
ры не были доступны при использовании модуля. Например, в модуле будет
использоваться служебная функция tcmp(.r) и в программе, которая применя-
ет модуль, также есть внутренняя функция temp(x). Как избежать конфликта
имен?
Конечно, можно воспользоваться атрибутом only, но тогда придется опи-
сывать полностью все данные модуля, кроме служебных процедур! Есть бо-
лее удобный и гибкий подход - это использование ачрибутов public и private.
Атрибут private (приватный, личный) используется для данных и проце-
дур, которые применяются внутри модуля и выполняют роль временных
и служебных переменных, второстепенных и дополнительных процедур.
Атрибут public (общий, доступный для всех), наоборот, разрешает дан-
ным и процедурам быть доступными в других программных единицах. По
Умолчанию все данные, описанные в модуле, имеют атрибут public.
Рассмотрим пример по созданию модуля нахождения обратной матрицы
2-го порядка. Для нахождения обратной матрицы можно воспользоваться
следующим подходом:
185
И. Л. Артёмов. Fortran: основы программирования
• вычислить определитель матрицы; если результат отличен от нуля, то
матрица имеет обратную матрицу;
• составить матрицу алгебраических дополнений для каждого элемента
исходной матрицы;
• разделить элементы матрицы на определитель матрицы;
• полученную матрицу транспонировать;
• умножением полученной матрицы на исходную показать, что найденная
матрица есть обратная матрица.
module linear_algebra
integer, parameter :: M=2 ! порядок матрицы
private М, algebr, see_det
contains
real function det(T) ! функция вычисления
! определителя 2-го порядка
real, intent (in) :: T(M,M)
det=T(l,l)*T(2,2)-T(1,2)*Т(2,1)
end function
logical function see_det(res)! приватная функция
real res ! проверка значения определителя
see_det=abs(res)<le-10
end function see_det
subroutine transponir(T) ! транспонирование матрицы
real, intent (inout) :: T(M,M)
real tmp
tmp=T(2,1); T(2,1)=T(1,2); T(l,2)=tnp
end subroutine transponir
subroutine algebr(T)! приватная подпрограмма
! нахождения матрицы алгебраических дополнений
real, intent (inout) :: Т(М,М)
real TMP(2,2)
TMP(1,1)= T(2,2); TMP(1,2)=-Т(2,1)
TMP(2,1)=-Т(1,2); ТМР(2,2)= Т(1,1)
Т=ТМР
end subroutine
subroutine inverse(Т) 1 нахождение обратной матрицы
real, intent (inout) :: Т(М,М)
real detTMP
detTMP=det(T)
if (see_det(detTMP)) then
stop "Cannot find inverse matrix, determinant is zero!"
end if
call algebr(T)
T=T/detTMP
call transponir(T)
186
6. Функции, подпрограммы и модули
end subroutine inverse
subroutine prod(А,В,С) ! подпрограмма умножения
! двух квадратных матриц
real А(М,М), В(М,М), С(М,М)
С (1,1) =А(1,1) *В (1,1)+А(1,2) *В(2,1)
С(1,2)=А(1,1)*В(1,2)+А(1,2)*В(2,2)
С (2,1) =А(2,1) *В (1,1)+А(2,2) *В (2,1)
С (2,2) =А(2,1) *В (1,2)+А(2,2) *В (2,2)
end subroutine prod
subroutine show(T,NAME)
real, intent (in) :: T(M,M)
character(*), optional :: NAME
if(present(NAME)) write(*,*) "Matrix...",NAME
write(*,"(2(f5.1)) ") T(l,l), T(l,2)
write(*,"(2(f5.1) ) ") T(2,l), T(2,2)
end subroutine show
end module linear_algebra
program matrix
use linear_algebra
real A(2,2), E(2,2), AI(2,2)
A(l,l)=2.0; A(l,2)=4.0
A(2,l)=1.0; A(2,2)=1.0
call show(A)
AI=A
call inverse(Al)
call show(Al,NAME="INVERSE")
call prod(A,Al,E)
call show(E,NAME="IDENTITY")
end
Результат работы программы:
2.0 4.0
1.0 1.0
Matrix...INVERSE
-0.5 2.0
0.5 -1.0
Matrix...IDENTITY
1.0 0.0
0.0 1.0
Press any key to continue
Модуль linear algebra содержит следующие модульные процедуры:
• функция det вычисляет значение определителя;
• приватная, служебная функция see_det определяет возможность нахож-
дения обратной матрицы;
187
И. Л. Артёмов. Fortran: основы программирования
• подпрограмма transponir выполняет транспонирование матрицы;
• приватная, служебная подпрограмма algebr находит матрицу алгебраиче-
ских дополнений;
• подпрограмма inverse вычисляет обратную матрицу;
• подпрограмма prod производит умножение двух матриц;
• подпрограмма show выводит матрицу на экран.
При использовании модуля программисту, возможно, кроме нахождения
обратной матрицы, могут потребоваться процедуры det, transponir, prod,
show, выполняющие различные "повседневные" операции над матрицами. По
умолчанию эти процедуры имеют атрибут public и доступны в главной про-
грамме. Напротив, процедуры algebr и see det носят второстепенный харак-
тер и используются для служебных промежуточных вычислений нахождения
обратной матрицы и вряд ли понадобятся в главной программе. Поэтому эти
процедуры, как и константа Л/, описаны с атрибутом private. Таким образом,
главная программа ничего не знает о константе М и процедурах algebr
и see_det.
Приватные данные модуля можно изменять в главной программе, но
только через модульные процедуры. Например, в следующем примере при
попытке установить неверную стипендию для студента будет выдаваться
ошибка. Таким образом, приватные данные, которые описаны в модуле и
важны для работы программы, при помощи атрибута private могут быть за-
щищены.
module person
type scholarship ! стипендия
private
Integer month
Integer total
end type scholarship
type student
character(20) name
character(3) group
integer mark
type(scholarship) money
end type student
contains
logical function SetMoney(p,month,total)
integer month, total
type(student) p
if((total<=0).OR.(total>1000).OR. &
(month<=0).OR.(month>12)) then
p.money.month=0
p.money.total=0
188
6. Функции, подпрограммы и модули
SetMoney=.FALSE.
else
р'. money. mon th=month
p.money.total=total
SetMoney=.TRUE.
end if
end function SetMoney
end module person
program prog
use person
integer month, total
type(student) si
si.name ="Petrov S.V."
si.group="052"
si.mark =4
write(*,"(A,\)") "Enter month..."; read)*,*) month
write(*,"(A,\)") "Enter money.; read)*,*) total
if(SetMoney(si,month,total)) then
write!*,*) "Scholarship is OK!"
else
write?,*) "Error! Scholarship is failed!"
end if
end
Результат работы программы:
Enter month...12
Enter money...500
Scholarship is OK!
Press any key to continue
Enter month...12
Enter money...-900
Error! Scholarship is failed!
Press any key to continue
В заключение отметим, что раньше при программировании на Fortran для
доступа к общим данным использовались common-блоки (общие блоки). Од-
нако по сравнению с модулями они имеют ряд ограничений и не могут реко-
мендоваться для программирования.
6.12. Функции и подпрограммы как параметры
В ряде случаев желательно передавать функцию или подпрограмму как
параметр в другую функцию или подпрограмму. Допустим, требуется под-
189
И. Л. Артёмов. Fortran: основы программирования
программа, которая будет выводить на экран таблицу значений функции Дх)
от а до b в М точках. Легко можно написать программу.
program tab_funct
call tabled.О,2.0,10)
end
subroutine table(a,b,M) ! — подпрограмма вывода таблицы --
real a,b
integer k,M
real xt,dx .
dx=(b-a)/(M-l)
do k=l,M
xt=(k-1)*dx+a
write(*,"(2(A,f5.2))") "x = ",xt," function = ",f(xt)
end do
end subroutine table
real function f(x) ! исследуемая функция
real x
f=sin(x)+x
end function f
Подпрограмма table вызывает функцию fix) и выводит таблицу ее значе-
ний. Однако если на экран потребуется вывести значения функций g(x), р(х),
s(x) и др., то в этом случае придется создать отдельную подпрограмму для
вывода значений функции g(x), отдельную для р(х) и т. д. В итоге подпро-
граммы будут дублировать друг друга и возникает естественный вопрос,
можно ли сделать подпрограмму table универсальной, для любой функции с
одним аргументом. Для этого следует передавать в подпрограмму table функ-
цию fix) как параметр,
program tab_funct
external f, g, p
call table(f,1.0,2.0,4) ! f(x) - фактический параметр
write(*,*)
call table(g,0.0,0.5,5) ! g(x) - фактический параметр
write(*,*)
call table(p,-1.0,-0.5,3) ! p(x) - фактический параметр
end
subroutine table(F,a,b,M) !
real a,b I
integer M !
integer k
real xt,dx
dx=(b-a)/(M-l)
do k=l,M
— подпрограмма вывода таблицы --
— для любой функции одного ------
--- вещественного аргумента -----
190
6. Функции, подпрограммы и модули
xt=(к-1)*dx+a
write(*, "(2(A,f5.2))") "х = ",xt," function = ”,f(xt)
end do
end subroutine table
real function f(x) ! -----------функция f (x)
real x
f=sin(x)+x
end function f
real function g(x) ! -----------функция g(x)
real x
g=x-cos(x)
end function g
real function p(x) ! -----------функция p(x)
real x
p=10*x*exp(x)
end function p
Описаны три внешние функции fix), g(x) и p(x), для которых будут выда-
ваться таблицы значений. Эти функции будут передаваться как фактические
параметры в подпрограмму table. Чтобы компилятор смог разобраться в том,
что имя g - это не просто переменная, а функция, в главной программе ис-
пользуется оператор external (внешний), в котором объявляются имена функ-
ций:
external f, g, р
Чтобы передавать функцию как параметр в подпрограмму, в список фор-
мальных параметров подпрограммы table добавлен формальный параметр F,
который называется функцией-формальным параметром или просто фор-
мальной функцией. При вызове call table(g,0.0,0.5,5) вместо формальной
функции F описанной в подпрограмме, будет подставляться фактическая
функция g.
Нами изучено, что при использовании функций и подпрограмм должно
выполняться соответствие формальных и фактических параметров. В случае
передачи функции как параметра, во-первых, тип фактической функции дол-
жен соответствовать типу формальной функции, во-вторых, количество ар-
гументов и их типы также должны совпадать, т. е. должны совпадать интер-
фейсы формальной и фактической функции. Другими словами, в нашем при-
мере подпрограмма table может выводить таблицу значений функции, если:
• функция возвращает вещественный результат;
• функция имеет один вещественный аргумент.
Также следует отметить, что передаваемая функция должна быть внеш-
ней или модульной.
191
И. Л. Артёмов. Fortran: основы программирования
Пример. Написать функцию приближешюго вычисления определенного
интеграла для любой вещественной функции одного вещественного аргумента.
Для вычисления определенного интеграла воспользуемся методом тра-
пеций:
J/(x)dr=—+
а 2 к=1
Рис. 6.6. Метод трапеций для вычисления определенного интеграла
program integral
real a,b
external F
a=0.0; b=1.0
write(*,100) "y=2*x", " a = ",a, &
" b = ", b, &
" S = ", TR(F,a,b,100)
100 format(A,3(A,f5.2))
end
i_________________________________________
real function F(x)
real x
F=4*x
end function F
!_________________________________________
real function TR(F,a,b,n)
real, intent (in) : a, b
integer, intent (in) :: n
real h, s
integer k
192
6. Функции, подпрограммы и модуле
h=(Ь-а)/(n—1); s=(F(a)+F(Ь))/2
do к=1,п-2
s=s+f(a+k*h)
end do
TR=s*h
end function TR
Результат работы программы:
y=2*x a = .00 b = 1.00 S = 2.00
Press any key to continue
Если в качестве фактического параметра используются имена стандарт-
ных функций, то их в главной программе следует объявить оператором
intrinsic (внутренний).
intrinsic sin, cos
Однако есть некоторые ограничения по использованию таких функций.
Если употреблять пользовательскую функцию с именем стандартной функ-
ции, то ее также следует объявлять в операторе external, в противном случае
будет использоваться стандартная функция. Поэтому рекомендуется приме-
нять свои собственные имена для функций.
program ex
external sin ! используется внешняя функция sin
write(*,*) sin(2.0)
end
real function sin(x)
real x
sin=x**2
end
Заметим также, что при передаче функции как параметра в другую функ-
цию ее можно не объявлять как external, а указать в блоке interface.
6.13. Перегрузка функций и подпрограмм
Выше был рассмотрен пример по вычислению определенного интеграла
для любой вещественной функции. Аналогично можно написать функцию
для вычисления определенного интеграла для любой комплексной функции.
В этом случае будет использоваться две функции: TR для вещественной и ТС
для комплексной. Имеется, однако, возможность объединения функций TR и
ТС в одну с именем integral. Особенность этой функции будет в том, что она
сможет определять, какого типа возвращать результат, если функция вещест-
венного типа или комплексного типа. Для этого следует создать перегружен-
ную функцию. Следующий пример показывает реализацию перегрузки.
193
И. Л. Артёмов. Fortran: основы программирования
program overload
interface
real function S(x)
real x
end function S
complex function Q(z)
complex z
end function Q
end interface
interface Integral ! -------интерфейс перегруженной функции
real function TR(F,a,b,n)
interface! --- интерфейс формальной функции
real function F(x)
real x
end function F
end interface
real, intent (in) : : a, b
integer, intent (in) :: n
end function TR
complex function TC(F,a,b,n)
interface! ----- интерфейс формальной функции
complex function F(z)
complex z
end function F
end interface
complex, intent (in) : a, b
integer, intent (in) :: n
end function TC
end interface !--------------------
real ar, br, ir
complex ac, be, ic
ar=0.0; br=3.1415926; ir=Integral(S,ar,br,100)
write(*,100) ir
ac=(1.0, -1.0); bc=(2.0, 1.0); ic=Integral(Q,ac,be,100j
write(*,101) REAL(ic), IMAG(ic)
100 format ("Integral = " f5.2)
101 format ("integral = ",f5.2," + ",f5.2,"i")
end
real function TR(F,a,b,n)
interface ! ------ интерфейс формальной функции
real function F(x)
real x
end function F
194
6. Функции, подпрограммы и модули
end interface
real, intent (in) : a, b
integer, intent (in) :: n
real h, s
integer к
h= (b-a) / (n-1) ; s= (F (a) +F (b) ) /2
do k=l,n-2
s=s+f(a+k*h)
end do
TR=s*h
end function TR
complex function TC(F,a,b,n)
interface
complex function F(z)
complex z
end function F
end interface
complex, intent (in) :: a, b
integer, intent (in) :: n
complex h, s
integer к
h=(b-a)/(n-1); s=(F(a)+F(b))/2
do k=l,n-2
s=s+f(a+k*h)
end do
TC=s*h
end function TC
real function S(x)
real, intent (in) :: x
S=sin(x)
end function S
complex function Q(z)
complex, intent (in) :: z
Q=3*z**2+2*z
end function Q
Конечно в данном примере программа без использования перегрузки
функций (да и вообще функций) выглядела бы в несколько раз меньше! Од-
нако если расчет будет занимать не пару сгрок, а несколько страниц, то вы-
игрыш будет очевидным!
Данную программу можно записать компактнее, если функции TR и ТС
являются модульными. В этом случае интерфейс явный, а для перегрузки ис-
пользуется следующая конструкция:
195
И. Л. Артёмов. Fortran: основы программирования
interface integral
module procedure TR, TC
end Interface
Здесь в interface-блоке употребляется оператор module procedure, который
объединяет указанные функции TR и ТС под одним именем integral. Таким
образом, программа будет следующей:
module numeric
Interface integral
module procedure TR, TC
end Interface
contains
real function TR(F,a,b,n)
Interface ! ------ интерфейс формальной функции
real function F (x)
real x
end function F
end interface
real, intent (in) :: a, b
integer, intent (in) :: n
real h, s
integer k
h=(b-a)/(n-1) ; s=(F(a)+F(b))/2
do k=l,n-2
s=s+f(a+k*h)
end do
TR=s*h
end function TR
complex function TC(F,a,b,n)
interface
complex function F(z)
complex z
end function F
end interface
complex, intent (in) :: a, b
integer, intent (in) :: n
complex h, s
integer k
h=(b-a)/(n-l); s=(F(a)+F(b))/2
do k=l,n-2
s=s+f(a+k*h)
end do
TC=s*h
196
6. Функции, подпрограммы и модупи
end function ТС
end module numeric
program overload
uae numeric
interface
real function S(x)
real x
end function S'
complex function Q (z)
complex z
end function Q
end interface
real ar, br, ir
complex ac, be, ic
ar=0.0; br=3.1415926; ir=lntegral(S,ar,br,100)
write(*,100) ir
ac=(1.0, -l;0); bc=(2.0, 1.0); ic=lntegral(Q,ac,be,100)
write(*,101) REAL(ic), IMAG(ic)
100 format ("Integral = " f5.2)
101 format ("Integral = ",f5.2," + ",f5.2,"i")
end
real function S(x)
real, intent (in) x
S=sin(x)
end function S
complex function Q(z)
complex, intent (in) :: z
Q=3*z**2+2*z
end function Q
Результат работы программы:
Integral =2.00
Integrals 7.00 + 19.00i
Press any key to continue
Перегрузку функций можно использовать, если тип и количество фор-
мальных параметров функций разные, но выполняемые действия схожи меж-
ду собой. Например, рассмотрим перегруженную функцию summa сложения
следующих данных:
• целых чисел,
• векторов,
• окружностей,
• имен.
197
И. Л. Артёмов. Fortran: основы программирования
module summaJM
type circle
real x
real y
real R
end type circle
interface summa ! перегрузка функции summa
module procedure summa_C, summa_I, summa_V, summa_N
end interface
contains
type(circle) function summa_C(cl,c2)
type(circle) cl,c2,c3
c3.x=(cl.x+c2.x)/2
c3.y=(cl.y+c2.y)/2
c3.R=sqrt(cl,R**2+c2,R**2)
summa_C=c3
end function summa_C
integer function summa_I(il,i2)
integer il, i2
summa_l=il+i2
end function summa_I
function summa_V(vl,v2,v3)
integer, parameter : : M=3
real summa_V(M), vl(M), v2 (M) , v3 (M)
summa_V=vl+v2 +v3
end function summa_V
character(45) function summa_N(nl,n2,n3)
character(*) nl,n2,n3
summa_N=nl//п2//пЗ
end function summa_N
end module summaJM
program overload_sum
use summaji
type (circle) cl,c2,c3
integer a
integer, parameter :: M=3
real vl(M) , v2(M), v3(M), p(M)
character(15) nl, n2, n3
character(45) fio
cl=circle(0.0,0.0,2)
c2=circle(4.0,0.0,l)
c3=summa(cl,c2) ! сложили окружности
198
6. Функции, подпрограммы и модули
. а=summa(1,2) ! сложили целые числа
vl=(/1,4,2/); v2=(/0>-3,7/); v3=(/9,8,5/)
p=summa(vl,v2,v3) ! сложили вектора
nl="Ivanov"; n2="Vladimir"; n3="Viktorovich"
fio=summa(nl,n2,n3) ! сложили имена
write(*,*) сЗ
write(*,*) a
write(*,*) p
write (*,*) fio
end
Сложение целых чисел и векторов осуществляется по правилам, приня-
тым в математике. При сложении окружностей центр вычисляется как сере-
дина между цензрами складываемых окружностей, радиус из суммы площа-
дей кругов. При сложении строк происходит их конкатенация.
Таким образом, перегрузка позволяет под одним общим именем объеди-
нить функции, которые возвращают разные типы данных, используют разное
количество и разный тип фактических параметров, но выполняют похожие по
смыслу действия.
Результат работы программы:
2.000000 0.0000000Е+00
3
10.00000 9.000000
Ivanov Vladimir
Press any key to continue
2.236068
14.00000
Viktorovich
6.14. Рекурсивные процедуры
При программировании вычислительных задач в ряде случаев бывает
удобным записывать алгоритм при помощи рекурсивного вызова процедуры.
Рекурсивным называется вызов, когда процедура, проделав определенные
действия, вызывает сама себя, чтобы созданная копия еще раз повторила вы-
полненные действия.
Рассмотрим пример. Даны 10 комнат, причем 10-я комната содержит
Дверь в 9-ю комнату, 9-я комната содержит дверь в 8-ю и т. д. до 1-й комна-
ты, которая дверь не содержит. Чтобы добраться до 1-й комнаты, сначала
следует открыть 10-ю комнату и проверить, есть ли еще дверь, и, если есть,
повторить выполненные действия.
Данную задачу можно легко решить при помощи цикла do или do while.
Однако нас сейчас будет интересовать, как это сделать подпрограммой, рабо-
тающей рекурсивно, т. е. самой себя вызывающей. Подпрограмму можно за-
писать таким образом:
199
И. Л. Артёмов. Fortran: основы программирования
program recurse
call OpenAndEnter(10) !
end
recursive subroutine OpenAndEnter(N)
integer N
write(*,*) "Open and enter the ", N, " room"
if (N-l==0) return ! точка останова,
1 если открыты все комнаты
call OpenAndEnter(N-1)
end subroutine OpenAndEnter
Чтобы объявить процедуру рекурсивной, используется ключевое слово
recursive.
Рассмотрим работу рекурсивной подпрограммы. Вызов подпрограммы
OpenAndEnter( 10) означает открыть 10-ю дверь. Подпрограмма выводит со-
общение и проверяет, открыты ли все двери (if (N-1=0)), и если не так, то
подпрограмма запускает себя снова для открытия следующей 9-й двери. При
этом подпрограмма которая открыла 10-ю комнату, не завершила работу, а
создала копию самой себя и ждет, пока эта копия выполнится и откроет 9-ю
комнату. В свою очередь, созданная копия открывает 9-ю дверь и порождает
еще одну копию для открытия 8-й двери и т. д., пока не останется ни одной
открытой комнаты. В последнем случае 10-я копия подпрограммы определит,
что все комнаты открыты, сделает сообщение и на этом завершится. Автома-
тически завершатся все "ждущие закрытия" подпрограммы, сначала та, кото-
рая открывала 2-ю дверь, затем 3-ю и т. д. до подпрограммы, которая "поро-
дила" все закрытые подпрограммы и первой открыла 10-ю дверь.
Результат работы программы:
Open and enter the 10 room
Open and enter the 9 room
Open and enter the 8 room
Open and enter the 7 room
Open and enter the 6 room
Open and enter the 5 room
Open and enter the 4 room
Open and enter the 3 room
Open and enter the 2 room
Open and enter the Press any key to continue 1 room
Работу данной программы также можно сравнить с раскрытием матре-
шек. Сначала разбирается самая большая матрешка и проверяется возмож-
ность разобрать вложенную матрешку, и если разбор возможен, то снова вы-
зывается процедура разбора матрешки и т. д.
Следует помнить, что все вызовы процедур помещаются во временную
память-стек, поэтому при достаточно большой вложенности рекурсивных
200
6. Функции, подпрограммы и модули
процедур возможно переполнение стека. Аналогичная ситуация о переполне-
нии возникает, когда в рекурсивной процедуре не указано условие прекраще-
ния рекурсивного вызова, другими словами, до какого момента процедура
может рекурсивно сама себя вызывать.
Так, если в рассмотренной подпрограмме закомментировать строки, от-
вечающие за проверку наличия комнаты, то программа выдаст сообщение об
ошибке и завершится.
! write(*,*) "Open and Enter the ", N, " room"
! if (N-l==0) return ! точка останова,
! если открыты все комнаты
Таким образом, рекурсивный вызов позволяет организовать цикл без ис-
пользования операторов цикла! По аналогии с рассмотренным примером
следующая программа печатает числа в обратном порядке. При этом рекур-
сивная подпрограмма оформлена как внутренняя.
program recurse
call info(5)
contains
recursive subroutine info(k)
if (k==0) return ! условие окончания рекурсивных вызовов
write(*,'(A,il) ’ ) " k = ", k;
call info(k-1) ! рекурсивный вызов
end subroutine info
end
Результат работы программы:
k = 5
k = 4
k = 3
k = 2
k = 1
Press any key to continue
Рассмотрим очень важный пример. Рекурсивная подпрограмма proc вы-
зывает себя подряд два раза и выдает сообщение о том, в какой раз она вызы-
вается (переменная г) и в каком уровне она находится (переменная N). Инте-
ресным будет рассмотреть последовательность вызовов.
Program recurse
integer N,i
N=3; i=0/ call proc(N)
contains
recursive subroutine proc(N)
if(N==0) return
i=i+l;
write(*,"(2(A,il))°) "Subroutine...", i, " N = ", N;
201
И. Л. Артёмов. Fortran: основы программирования
call proc(N-l) ! влево
call proc(N-l) ! вправо
end subroutine proc
end
N = 3
N = 2
N = 1
N = 1
N = 2
N = 1
N = 1
Результат работы программы:
Subroutine... 1
Subroutine...2
Subroutine... 3
Subroutine... 4
Subroutine;.•5
Subroutine...6
Subroutine...7
Press any key to continue
Первым вызов рекурсивной подпрограммы происходит из главной про-
граммы, при этом г=1, М=3 (рис. 6.7). Затем следует рекурсивный вызов "вле-
во", /=2, М=2. Далее еще один вызов "влево", /=3, W=l. И наконец, последний
вызов "влево", i не увеличивается, так как N=0, и происходит выход из под-
программы с г=3. Инициативу берет подпрограмма с i=2, и происходит вызов
"вправо", г=4, W=l, затем аналогично вызов "влево" и завершение подпро-
граммы при г=4, и как следствие подпрограммы с г=2. Инициативу берет
подпрограмма с i=1 и т. д.
Рис. 6.7. Двойной рекурсивный вызов
В некоторых случаях использование рекурсии позволяет легко реализо-
вать вычислительный алгоритм. Одним из таких алгоритмов является вычис-
ление определителя *. Известно, что любой определитель n-го порядка можно
разложить по строке или столбцу и получить п определителей п-1-го порядка.
Данным способом часто пользуются при ручном вычислении определителей,
Данный алгоритм требует громадных вычислительных затрат. Существуют бо-
лее экономичные алгоритмы, например метод Гаусса.
202
6. Функции, подпрограммы и модули
предварительно получая нулевые элементы в строке или в столбце. Попробу-
ем запрограммировать данную задачу.
Определитель 2-го порядка находится по формуле
«и
«21
«12
«22
— Яц'Й22 °12 '«21-
Если рассмотреть определитель 3-го порядка, то его можно разбить на
три определителя 2-го порядка и каадый по отдельности рассчитать по из-
вестной формуле
«11 «12 «13
«21 «22 «23
«31 «32 «33
«13 / 1 \3+1 «12 «13
+ (-1) a3i-
a33 a22 a23
Аналогично можно найти определитель 4-го порядка. Сначала получить
4 определителя 3-го порядка, из которых можно сделать 12 определителей
2-го порядка, вычислить их и найти результат (рис. 6.8).
Рис. 6.8. Разложение определителя 4-го порядка
Таким образом, рекурсивный алгоритм будет заключаться в последова-
тельном понижении порядка, пока не будет получен определитель второго
порядка, который легко вычислить. Программа, реализующая вычисление
определителя по рекурсивному алгоритму, представлена ниже.
program determinant
integer, parameter :: M=10 ! максимальный порядок определителя
integer а(М,М) ! --------------- определитель
write(*,*) "Enter matrix rows..." ! ввод определителя
do i=l,M
read(*,*) a(i,1:M)
203
И. Л. Артёмов. Fortran: основы программирования
end do
kk=det(a,M); write(*,*) "Determinant = ",kk
contains ! рекурсивная функция вычисления определителя
integer recursive function det(A,N) result(p)
integer N !-- порядок определителя
integer A(N,N) !-- определитель
integer MR(N-1,N-1) !-- минор
integer i,k,s,m
MR=0; s=0
if (N==2) then
p=A(l,l)*A(2,2)-A(1,2)*A(2,1)
else
do k=l,N ! количество миноров
m=0
do i=l,N ! формируем миноры
if (i==k) cycle
m=m+l
MR(m,l:N-l)=A(i,2:N)
end do
s=(-l) “ (k+1)*A(k,l)*det(MR,N-1)+s
end do
p=s
end if
end function
end
Рекурсивная функция содержит два аргумента: определитель и его поря-
док. Если порядок определителя второй, то определитель вычисляется, иначе
происходит понижение порядка. Двойной цикл по к, по i составлен для фор-
мирования определителей (миноров) (рис. 6.9). Полученные определители
снова передаются в функцию det и т. д.
k=l 1-й минор k=2 2-й минор k=3 3-Й минор
aii a12 ai3 i=k=l aii a12 a13 aii 1 a12 a13
a21 a22 a23 a21 a22 a23 i=k=2 321 j a22 a23
a31 a32 a33 a31 a32 a33 a31 ! a32 азз i=k=3
Рис. 6.9. Формирование миноров
Рекурсивную функцию det можно немного упростить, если воспользо-
ваться сечениями массивов. В этом случае можно избавиться от лишнего
цикла по i и операторов if и cycle.
integer recursive function det(A,N) result(s)
integer N, A(N,N), MR(N-1,N-1), k
MR=0; s=0
204
6. Функции, подпрограммы и модули
if (N==2) then
s=A(l,1)*A(2,2)-A(l, 2)*A(2,1)
else
do k=l,N
MR(1:k-l,1:N-1)=A(1:k-1,2:N) ! формируем минор
MR(k:N-l,1:N-1)=A(k+l:N, 2:N)
s=(-1)**(k+1)*A(k,1)*det(MR,N-1)+s
end do
end if
end function det
Следует отметить, что при использовании рекурсивных функций следует
применять результирующую переменную, как это рассмотрено в приведен-
ном примере вычисления определителя.
Результат работы программы:
Enter matrix rows...
1230403212
8238712313
1231421210
2130534242
1212432031
1235319031
1245213121
247521345.1
7085040321
1232111035
Determinant = 310424
Press any key to continue
6.15. Объектные файлы, библиотеки lib и dll
Итак, мы рассмотрели основные возможности процедур, правила их опи-
сания и использования. Как нам известно, процедуры позволяют разбивать
исходную задачу на подзадачи. Допустим, вы поручили своему знакомому
написать функцию F(x~) для проведения некоторых вычислений, а сами заня-
лись разработкой основной программы. Когда работа по созданию функции
F(x) будет завершена, у вас возникнет вопрос, как подключить написанную
функцию к вашей программе. Конечно, можно взять исходный текст функ-
ции и скопировать в свою программу. Однако если такая функция будет
слишком большой, или ваш партнер не пожелает раскрывать секрет создания
этой функции, или таких функций будет несколько?
При разработке крупных программ такие вопросы не редкость. В этом
случае рекомендуется готовые и отлаженные процедуры помещать в специ-
альные файлы - библиотеки. На этапе создания исполняемого файла эти биб-
205
И. Л. Артёмов. Fortran: основы программирования
лиотеки будут подключаться к вашей программе и вы сможете использовать
нужные функции и подпрограммы.
Рассмотрим несколько подробнее создание исполняемого файла про-
граммы. Создание программы происходит, как правило, в два этапа: компи-
ляция (Compiling) и компоновка (Linking), которые происходят последова-
тельно друг за другом при нажатии клавиш CTRL+F5.
В процессе компиляции (Compiling) из текста программы создается объ-
ектный файл (obj-файл), который содержит машинные инструкции. Однако
сразу выполнить такие инструкции компьютеру не удастся. В объектном
файле присутствуют ссылки на множество системных, стандартных и поль-
зовательских функций (даже если вы их не применяли). На этапе компонов-
ки, или, как иногда говорят, сборки (Linking), происходит связь объектного
файла с функциями, на которые есть ссылки. В результате данного процесса
получается готовый исполняемый файл, т. е. программа.
Объектные файлы можно помещать в библиотеки, которые затем исполь-
зуются при создании программ. Библиотеки бывают двух видов: статические
(lib-библиотеки) и динамически-подключаемые (dll-библиотеки).
Как создать и использовать библиотеку LIB
Создадим функцию у(х) = х2 и поместим ее в библиотеку с именем
mylibrary.lib.
Проделаем следующую цепочку действий: File -> New -> Projects ->
Fortran Static Library -> Project Name mylibrary -> OK.
Создадим файл funclib.f90, включим его в проект и наберем текст функции:
real function f(х)
f =х*х
end function f
Выбираем Build -> Build mylibrary.lib
В окне компиляции, будет выдано сообщение:
mylibrary.lib - 0 error(s), 0 warning(s)
Созданная библиотека mylibrary.lib по умолчанию будет находиться в
папке (Debug или Release), где размещается исполняемый файл.
Так как в дальнейшем другие программы будут использовать из библио-
теки функцию F(x), которая является внешней, то следует позаботиться о
создании модуля с интерфейсным блоком. Для этого создадим файл mylib.f90
со следующим текстом:
module mylib
interface
real function F(x)
real x
end function
206
6. Функции, подпрограммы и модули
end interface
end module mylib
Созданную библиотеку mylibrary.lib применим в консоль-проекге для
вывода значения функции.
Создаем новый консоль-проект и записываем следующую программу:
program use_lib
use mylib
write(*,*) F(2.0)
end
Добавим в проект созданный ранее файл mylib.f90 с интерфейсом к
функции F(x). Если сразу нажать CTRL+F5, то появится сообщение об ошиб-
ке сборки программы:
error LNK2001: unresolved external symbol _F@4
т. e. используется неизвестная функция с именем F.
Для этого следует подключить библиотеку mylibrary.lib в проект. Выпол-
няем действия: Project -> Add То Project -> Files -> находим и выбираем
файл mylibrary.lib.
Результат работы программы:
4.000000
Press any key to continue
Как создать и использовать библиотеку DLL
Создадим новый проект: File -> New -> Project Workspace -> выбираем
Dynamic-Link Library -> указываем имя проекта mydll-> Create.
Создадим файл funcdll.f90, который будет содержать функцию Fix). Эту
функцию мы поместим в библиотеку DLL.
real function F(x)
!MS$ATTRIBUTES DLLEXPORT :: F
real x
F=x*x
end function F
Атрибут DLLEXPORT указывает, что функция F(x) будет экспортиро-
ваться из файла библиотеки mydll.dll другой программой.
После компиляции отобразится следующий результат:
Compiling Fortran...
Linking...
Creating library Debug/mydll.lib and
object Debug/mydll.exp
rnydll.dll - 0 error(s), 0 warning(s)
207
И. Л. Артёмов. Fortran: основы программирования
Следующий шаг - напишем программу, которая будет использовать соз-
данную библиотеку mydll.dll и вызывать функцию F(x).
Создаем проект Console Application со следующей программой:
program use_dll
interface
real function F(x) !MS$ATTRIBUTES DLLIMPORT :: F
real x
end function
end interface
write!*,*) "Result...", F(10.0)
end
Функция F(x) является внешней, поэтому должна иметь явно объявлен-
ный интерфейс. Кроме того, она содержится в библиотеке DLL и поэтому
должна иметь атрибут DLLIMPORT, указывающий, что функция будет им-
портирована или подключена из библиотеки DLL. Следующий шаг - сооб-
щаем компилятору местонахождение библиотеки DLL. Для этого добавляем
в проект файл mydll.lib, который автоматически был создан в первом проек-
те. Библиотеку mydll.dll размещаем в папке, где будет создан исполняемый
файл (папка Debug или Release), компилируем и запускаем программу.
Результат работы программы:
Result... 100
Press any key to continue
Теперь если скопировать созданную программу и файл mydll.dll в одну
отдельную папку, то при запуске программы библиотека будет автоматиче-
ски подключаться при вызове функции F(x). Удаление файла mydll.dll приве-
дет к невозможности выполнения функции F(x) и будет выдана ошибка.
Итоги
1. Процедуры бывают двух видов: функции (function) и подпрограммы
(subroutine). Функция возвращает вычисленное значение, подпрограмма вы-
полняет комплекс вычислений.
2. Процедуры могут содержать внутренние переменные, константы, типы
данных, которые доступны только в той процедуре, где они описаны.
3. Все внутренние переменные являются статическими (static), т. е. соз-
даются на этапе компиляции. Если необходимо, то переменные можно объ-
явить автоматическими (automatic), которые будут создаваться при каждом
вызове процедуры.
4. Для управления работой процедур используется оператор return. Опе-
раторов return может быть несколько, т. е. процедура может завершить рабо-
ту одним из нескольких способов.
5. Формальные параметры применяются при описании процедур, факти-
ческие параметры используются при вызове процедур. Следует помнить
208
6. Функции, подпрограммы и модули
о соответствии формальных и фактических параметров. Формальные пара-
метры могут быть необязательными (optional), в этом случае применяются
ключевые слова при вызове процедур.
6. Посредством формальных параметров можно передавать и получать
данные из процедур. В качестве формальных параметров могут выступать
переменные, массивы, процедуры. Все формальные параметры делятся на
три вида:
• входные (intent), фактический параметр должен быть переменной, значе-
нием или константной;
• выходные (out), фактический параметр должен быть переменной;
• входные-выходные (inout), фактический параметр должен бьггь переменной.
7. Процедуры могут быть внутренними и внешними. Внутренние проце-
дуры объявляются после оператора contains. Внешние описываются после
оператора end основной про1раммы. Между внутренними и внешними про-
цедурами есть существенные отличия.
8. Передача данных между процедурами, между программой и процеду-
рами может происходить посредством:
• формальных и фактических параметров;
• общей видимости (только для внутренних процедур);
• использования модулей.
9. Модули позволяют объединить переменные, константы, типы данных
и процедуры, которые обрабатывают эти данные. Оператор use указывает
имя используемого модуля. Посредством атрибута only можно указать, какие
данные будут использоваться из модуля. Все данные в модуле делятся на два
вида: общие (public) и частные (private). По умолчанию все данные, описан-
ные в модуле, обладают атрибутом public, т. е. доступны в той программной
единице, где используется модуль. Атрибут private применяется для служеб-
ных, внутренних данных модуля или для данных "особой важности".
10. Перегрузка позволяет объединить под одним именем процедуры, ис-
пользующие разное количество формальных параметров, возвращающие зна-
чения разного типа данных.
11. Рекурсивными называются процедуры, вызывающие самих себя.
В некоторых случаях рекурсивные процедуры значительно облегчают напи-
сание алгоритма решения задачи, однако требуют большого времени выпол-
нения. В рекурсивных функциях должна объявляться результирующая (result)
переменная.
12. Библиотеки - файлы содержащие отлаженные и готовые к использо-
ванию подпрограммы и функции. Библиотеки бывают статическими (lib) и
динамичсски-подключаемыми (dll). Для построения библиотек используются
соответствующие разновидности проектов. При использовании dll-библиотек
применяются атрибуты DLLIMPORT и DLLEXPORT.
209
7. ССЫЛКИ, УКАЗАТЕЛИ,
ДИНАМИЧЕСКИЕ СТРУКТУРЫ ДАННЫХ
7.1. Ссылки, адресаты
Ссылка - это переменная, которая может быть связана с другой перемен-
ной, называемой адресатом, так что при обращении к ссылке будет происхо-
дить обращение к адресату и наоборот. Другими словами, ссылка может рас-
сматриваться как псевдоним адресата. Если в переменную-ссылку поместить
значение 10, то и в переменной-адресате также будет значение 10. Если в пе-
ременную-адресат поместить значение 50, то и в переменной-ссылке также
будет значение 50.
Для описания ссылки используется атрибут pointer (стрелка, указатель),
например
integer, pointer :: р х
означает, что переменная р будет являться ссылкой на переменную типа
integer.
Переменная, на которую будет ссылаться ссылка, как мы уже сказали,
называется адресатом и должна иметь атрибут target (цель). Например,
integer, target :: а
Для прикрепления ссылки к адресату используется специальный опера-
тор =>, по виду удачно напоминающий стрелку.
Пример:
program prog
integer, pointer :: p
integer, target :: a
a=100
p=>a ! прикрепили ссылку к адресату
write(*,*) р
end
Результат работы программы:
100
Press any key to continue
т. e. ссылка и адресат стали содержать одно и то же значение 100.
Возникает вопрос, будет ли следующий пример аналогичным рассмот-
ренному:
program prog
integer р
integer а
21И41ОГЛ1И<2>И 210
7. Ссылки, указатели, динамические структуры данных
а=100
р=а
wrlte(*,*) р
end
В этом примере переменные р и а также будут содержать одинаковое
значение. Однако если в переменной а изменится значение, то значение в р
останется прежним. В случае с первой программой значение, содержащееся
в ссылке, будет таким же, как и в переменной а:
program prog
integer, pointer :: p
integer, target :: a
a=100
p=>a ! прикрепили ссылку к адресату
write(*,*) р
а=500
write(*, *) р
end
100
500
Press any key to continue
Таким образом, если ссылка прикрепилась к адресату, то все изменения,
происходящие с адресатом, дублируются в ссылке.
В процессе работы программы-ссылку можно прикреплять к разным ад-
ресатам.
program prog
integer. pointer : : P
integer, target : a, b, c
а=100,- b=200; c=300
р=>а; p=p*2 ! увеличили а в 2 раза
Р=>Ь; p=p/2 1 уменьшили b в 2 раза
Р=>с; p=p+100 ! увеличили с на 100
write(' *,*) a, b, с
end
Результат работы программы:
200 100 400
Press any key to continue
Важно помнить, что ссылки - это динамические переменные, т. е. выде-
ление памяти происходит во время работы программы и может корродиро-
ваться программистом. Память может отводиться при использовании опера-
тора allocate либо после прикрепления ссылки к размещенному адресату.
211
И. Л. Артёмов. Fortran: основы программирования
program prog
integer, pointer : : p (:)
integer, allocatable, target :: w(:)
allocate(w(3))
w=100
p=>w ! присоединили ссылку к динамическому массиву
write(*,*) р
end
При работе со ссылками используются следующие функции и операторы:
Функция associated(pt, addr) (связан)
возвращает .TRUE, если ссылка pt прикреплена к адресату addr, в частности
если две ссылки указывают на один адресат. Параметр адресат является до-
полнительным.
Пример:
program prog
integer, pointer :: pl, p2, p3
integer, target :: a,b
a=100; b=2
pl=>a; p2=>a
write(*,*) associated(pl,p2) 1 результаты TRUE
write(*,*) associated(pl)
write(*,*) associated(p2,a)
pl=>b
write(*,*) associated(рЗ) ! результаты FALSE
write(*,*) associated(pl,p2)
write(*,*) associated(pl,a)
end
т
т
т
F
F
F
Результат работы программы:
Press any key to continue
Для открепления ссылки от адресата используется оператор nullify (ан-
нулировать).
Пример:
program prog
integer, pointer :: pl, p2
integer, target :: a
a=1000; pl=>a; p2=>a
212
7. Ссылки, указатели, динамические структуры данных
! если к адресату прикреплены две ссылки,
! то отсоединим последнюю
if (associated(pl,р2)) nullify(p2)
write(*,*) associated(pl), associated(p2)
end
Результат работы программы:
T F
Press any key to continue
Оператор nullify следует использовать аккуратно, чтобы не допускать
появления недоступной памяти. Например, если адресату была выделена па-
мять, то после открепления ссылки от адресата память не освобождается
и становится недоступной.
Пример:
program bad_memory
integer, pointer :: p(:)
integer, allocatable, target :: a(:)
allocate(a(10)); a=10
p=>a
nullify(a)
end
В этом примере память, отведенная под массив а, становится недоступ-
ной. Для освобождения памяти следует использовать оператор deallocate.
С другой стороны, в следующем примере при освобождении памяти зна-
чение ссылки становится неопределенным.
program bad_memory
integer, pointer :: p(:)
integer, allocatable, target :: a (:)
allocate(a(10)); a=10
p=>a
deallocate(a)
end
Поэтому правильным будет использование такой последовательности
операторов:
program good_memory
integer, pointer :: p(:)
integer, allocatable, target :: a(:)
allocate(a(10)); a=10 ! выделили память
p=>a ! прикрепили к выделенной памяти ссылку
deallocate(а) ! освободили память
nullify(р) ! обнулили ссылку
end
213
И. Л. Артёмов. Fortran: основы программирования
7.2. Списки и структуры со ссылками на себя
Ссылки позволяют организовывать специальные динамические структу-
ры - списки, стеки, деревья, очереди, которые широко используются в ос-
новном при программировании информационных и логических задач. В ка-
честве примера рассмотрим односвязный список. Список - это динамическая
структура, каждый элемент которой содержит данные и ссылку на следую-
щий элемент списка. В односвязном списке каждый элемент связан только со
следующим или предыдущим (рис. 7.1). В линейном односвязном списке ка-
ждый элемент состоит из двух полей: первое, информационное содержит
данные; второе, ссылочное поле содержит ссылку на следующий элемент
списка. Ссылочное поле последнего элемента ссылается на пустой элемент
NULL, означающий конец списка. Так как в односвязном списке элементы
ссылаются друг на друга только в одном направлении, то самым "ценным"
элементом является начало (голова списка).
Рис. 7.1. Односвязный список
Для описания элемента списка используется, например, следующий
структурированный тип данных:
type element
integer info
type (element), pointer :: arrow
end type element
Таким образом, тип element определен рекурсивно. В нем присутствует
информационное поле (info) для данных и ссылочное поле (arrow) на пере-
менную типа element.
Пример. Создать односвязный список и вывести содержимое этого спи-
ска на экран. Пусть элементами односвязного списка будут выступать числа
100, 200 и т. д.
program list
type element
integer index
type (element), pointer :: arrow
end type element
type(element), pointer :: phead, Pt
214
7. Ссылки, указатели, динамические структуры данных
nullify(phead) ! сначала голова указывает в "никуда"
do i=l,5 ! сформируем список
. allocate(pt) ! создаем новый элемент
pt=element(i*100,phead) ! вызвали конструктор
phead=>pt ! голова стала новым элементом
end do
pt=>phead ! установим ссылку на голову
! выведем список
do while (associated(pt))
write(*,"(A,i4)") "Index.pt.index
pt=>pt.arrow
end do
end
Результат работы программы:
Index... 500
Index... 400
Index... 300
Index... 200
Index... 100
Press any key to continue
В про1рамме используются две ссылки: phead - ссылка на голову списка,
pt - ссылка на текущий элемент списка. Сначала оператор nullify(phead) объ-
являет список пустым. Затем в цикле выделяется область памяти под ссылку
pt, т. е. создается элемент списка. При помощи конструктора происходит
инициализация элемента, в поле index заносится значение 100, поле arrow
указывает на обнуленную ссылку phead. Затем в результате присваивания го-
лова списка становится текущим элементом. Таким образом, в памяти фор-
мируется связанная структура: голова списка ссылается на соседний элемент,
тот, в свою очередь, на следующий и так до последнего элемента, который
ссылается на обнуленную ссылку.
Таким образом, чтобы вывести список, потребуется организовать про-
стой do while-цикл с проверкой на прикрепления ссылки к адресату,
воспользовавшись функцией associated.
В качестве дополнительного примера рассмотрим процесс удаления про-
извольного элемента из списка. Дан список животных, поступающих в по-
рядке очередности в больницу. Требуется оформить список и удалить перво-
го выздоровевшего.
program animals_hospital
type animal
character(20) name
type(animal), pointer :: arrow
end type animal
type(animal), pointer :: pt, phead, plast
215
И. Л. Артёмов. Fortran: основы программирования
character(20) sn
nullify(pt)
do i=l,5 ! сформируем список
allocate(phead)
write(*,100) "Animal..."; read(*,"(A)") sn
phead=animal(sn,pt) pt=>phead ! вызвали конструктор
end do call show_animals() ! выведем список
! удалим какой-нибудь элемент и выведем список
write(*,100) "Enter animal for deleting..."; read(*,*) sn
pt=>phead ! переходим в голову списка
plast=>phead
do while (associated(pt))
if (pt.name==sn) exit
plast=>pt
pt=>pt.arrow
end do
if(.NOT.associated(pt)) then ! если имя животного
write(*,*) "Animal not found!" ! отсутствует в списке
else
if (associated(pt,plast)) then ! ----- если имя в голове
phead=>plast.arrow
deallocate(pt)
else
plast.arrow=>pt.arrow ! если имя внутри списка
deallocate(pt)
end if
call show_animals()
end if
100 format(A,\)
contains
subroutine show_animals() ! вывод списка животных
pt=>phead
do while (associated(pt))
write(*,"(2a)") "Animal...",pt.name
pt=>pt.arrow
end do
end subroutine show_animals
end
Формирование и вывод списка на экран аналогичны действиям в разо-
бранном выше примере. Заметим только, что для повышения читаемости
программы используется внутренняя подпрограмма show_animals для вывода
216
7. Ссылки, указатели, динамические структуры данных
списка животных. В случае использования внешней подпрограммы потребу-
ется задание интерфейсного блока в программе и описание типа animal внут-
ри подпрограммы.
Для удаления элемента списка потребуются две ссылки: на текущий эле-
мент pt и на предыдущий - plast. Ссылка pt используется для удаления эле-
мента, plast - для связывания элементов списка. Схема удаления элемента
представлена на рис. 7.2.
7.3. Целочисленные указатели
Целочисленный указатель - это переменная типа integer(4), содержащая
адрес некоторой переменной, называемой адресной переменной. Если ссыл-
ка - это переменная, которая ссылается на другую переменную, при этом
имеет такой же тип, как и переменная-адресат, то целочисленный указатель
это переменная, которая содержит адрес переменной.
Для описания целочисленного указателя используется оператор pointer.
Например, указатель на вещественную переменную описывается так;
real а
pointer (р,а)
Здесь а выступает для хранения значения, р - для хранения адреса перемен-
ной. Другими словами, для описания целочисленного указателя используют-
ся базируемая переменная и указатель.
Пример. Указатель на символьную переменную.
character с
Pointer(р,с)
Пример. Указатель на переменную типа student.
type student
character(10) name
integer year
end type student
type (student) s
Pointer(p,s)
217
И. Л. Артёмов. Fortran: основы программирования
Чтобы вычислить адрес переменной, используется функция 1ос (от слова
location-размещение). Например:
program prog
integer pa ! используется для помещения
! значения в указываемую переменную
pointer(р,ра) ! р - указатель на .целый тип
integer :: b=1000
. р=1ос(Ь) ! вычислили адрес переменной b
ра=500 ! положим туда значение 500
write(*,*) Ь
Ь=700
write(*,*)"address = ",р, " value = ",ра ! вывели адрес
! и значение, хранящееся по указанному адресу,
end
Результат работы программы:
500
address = 4403268 value = 700
Press any key to continue
При работе с указателями можно выполнять арифметические действия.
При этом следует учитывать количество байтов памяти, отводимое под тип
базированной переменной. Рассмотрим следующий пример.
Пример. Вывести элементы одномерного массива.
program show_massiv
integer(2) pa ! указатель
pointer(p,pa)
integer(2) m(10), k ! 2 байта на элемент массива
m= (/1,-5,2,4,6,7,2,-9,0,4/)
! установим указатель на начало массива
р=1ос(ш(1))
do k=l,10
write(*,*) pa
p=p+2
end do
end
Перед выполнением цикла целочисленный указатель указывает на эле-
мент т(1). Затем в цикле к целочисленному указателю последовательно при-
бавляется 2 байта, что означает смещение к следующему элементу массива
[2 байта, так как элементы массива типа integer(2)] (рис. 7.3). Отметим также,
что по окончании цикла указатель выйдет за границу массива.
218
7. Ссылки, указатели, динамические структуры данных
т(1) т(2) т(3)
1 -5 2 4 6 7 2 1 -9 0 4
n n п р адрес
ра - значение
р,ра р+2,ра р+8,ра
Рис. 7.3. Перемещение по элементам массива при помощи указателя
Если элементами массива были бы данные типа double precision, то для
перемещения на следующий элемент потребовалось бы прибавить р=р+8.
Результат работы программы:
1 -5 24672 -9 04
Press any key to continue
Итоги
1. Ссылка - переменная, связанная с переменной-адресатом. Ссылка -
псевдоним адресата. Все изменения, происходящие с адресатом, автоматиче-
ски происходят со ссылкой. Все изменения, происходящие со ссылкой, авто-
матически происходят с адресатом. Ссылка объявляется при помощи атрибу-
та pointer, переменная-адресат обладает атрибутом target. Для прикрепления
ссылки к адресату используется операция =>.
2. Ссылки являются динамическими переменными.
3. Функция associated определяет прикрепление ссылки к адресату.
4. Оператор nullify открепляет ссылку от адресата. Если адресату была
выделена память, то перед откреплением необходимо вызвать оператор
deallocate.
5. Ссылки используются для организации динамических структур дан-
ных.
6. Целочисленный указатель - переменная, которая содержит адрес неко-
торой переменной. Для описания целочисленного указателя используется пе-
ременная для хранения значения и собственно указатель.
7. Для вычисления адреса переменной используется функция 1ос.
219
8. ФАЙЛЫ
8.1. Знакомство с файлами.
Файловый ввод и вывод
Каждая программа во время выполнения выдает определенные результа-
ты вычислений, которые иногда требуется сохранить для дальнейшей рабо-
ты. Чтобы сохранить полученные результаты, используются файлы. Файл -
это именованная последовательность записей, хранящаяся на жестком диске,
дискете и др.
Перед началом работы с файлом необходимо связать имя файла с опре-
деленным номером (номер устройства). Например, если требуется работать с
файлом results.txt, то первым вызывается оператор open (открыть), вызов ко-
торого в самом простом виде следующий:
open(1,file='с:\results.txt')
т. е. с файлом results.txt связан номер 1. Заметим, что файл может как сущест-
вовать, так и создаваться.
Теперь, чтобы записать информацию в файл, используется обычный опе-
ратор write. Однако вместо привычного первого символа * используем номер
нашего файла:
write(1,"(14)") 100
В файл записано число 100.
Если работа с файлом завершена, то его закрывают, используя оператор
close(l) (закрыть), с указанием номера файла. Таким образом, в простейшем
случае запись данных в файл происходит по такой схеме:
ореп(1, £Ие="имя файла") ! открыть файл
write(1,формат) данные ! записать данные
close(1) ! закрыть файл
Пример. Записать в файл numbers.txt числа от 1 до 10.
program write_to_file
integer k
open (1,file="numbers.txt")
do k=l,10
write(1,"(i4)") k
end do
close(1)
end
/1ИДИОГЛ1И(5И
220
8. Файлы
Заметим, что на экран вывод информации не происходит, весь вывод
осуществляется в файл. Файл numbers.txt будет создан в папке, где размещен
файл с текстом программы. Если открыть в текстовом редакторе numbers.txt,
то здесь будет представлен список из 10 чисел.
Следующий пример показывает, как создавать файл с нужным именем и
расположением.,
program write_to_file_name
integer k
character(255) filename
write(*," (A, \) ") "Enter file name...";
read(*, " (A) 11) filename
open (1,file=filename)
do k=l,10
write(1, " (i4)") k
end do
close(1)
end
Результат работы программы:
Enter file name...c:\data.txt
Press any key to continue
В корневой папке диска С будет создан файл data.txt.
Для чтения данных из файла используется оператор read. Напишем про-
грамму, которая будет считывать из созданного файла results.txt первые пять
чисел.
program read_from_file
integer k
character(255) filename
write(*,"(A,\)") "Enter file name to read data..."
read(*,"(A)") filename
open (1,file=filename)
do k=l,5
read (1, 11 (i4) ") k
write(*,*) k
end do
closed)
end
Результат работы программы:
Enter file name to read data... results.txt
1
2
3
4
5
221
И. Л. Артёмов. Fortran: основы программирования
Press any key to continue
Таким образом, операторы write и read используются для организации
файлового ввода-вывода. Причем в рассмотренных примерах запись и чтение
данных из файла аналогичны работе с клавиатурой и экраном.
Запись и чтение данных при работе с файлами происходит при помощи
файлового указателя. Файл состоит из записей, которые расположены после-
довательно друг за другом. Например, если в файл были записаны 10 целых
чисел, объявленных как integer, то в каждой записи будет храниться целое
число. При использовании операторов write и read происходит перемещение
файлового указателя по записям файла.
В качестве примера рассмотрим процесс чтения данных из файла.
Оператор
openfl,file="reults.txt")
открывает существующий файл, и файловый указатель помещается на пер-
вую запись (рис. 8.1).
1 2 2 | 4 5
_________________________J_______________
указатель
Рис. 8.1. Файловый указатель
После вызова оператора
read(1,"(14) ") п
будет прочитано первое число - 1 (первая запись) и файловый указатель пе-
реместится на следующую запись - 2. Затем после вызова оператора read
файловый указатель будет указывать на число 3 и т. д. до 5-й записи. После
завершения цикла, когда будут прочитаны все 5 чисел, файловый указатель
будет указывать на 6-ю запись.
Очевидным является вопрос, куда будет указывать указатель файла, если
будут прочитаны все 10 чисел. Каждый файл содержит специальную служеб-
ную запись - "конец файла" (end of file), которая автоматически создается по-
сле завершения записи данных в файл. Поэтому после прочтения 10-й записи
указатель файла достигнет "конца файла" и дальнейшее чтение вызовет
ошибку выполнения программы. Так, в следующем примере будег ошибка
чтения данных из файла.
program read_file_error
integer k, n
open(l,file="results.txt")
222
8. Файлы
do к=1,11
read(1, " (i 4) ") n
end do
closed)
end
Как сделать, чтобы программа при считывании данных могла опреде-
лять, достигнут ли конец файла? В этом случае используется стандартная ло-
гическая функция определения конца файла EOF (end of file). Функция воз-
вращает .TRUE., если достигнут конец файла, .FALSE. - в противном случае.
Следующая программа позволяет считывать и выводить на экран целые
числа из файла с любым количеством записей.
program read_file_good
integer k, n
opend/ file="results.txt")
do while (. NOT. EOF,( 1) )
read(1,"(i 4) ") n
write(*,"(14,\) ")
end do
closed)
end
Результат работы программы:
123 456789 10
Press any key to continue
Пример. Программа, которая печатает текст Fortran-программы на экра-
не, заменяя все write на WRITE, а все read на READ.
program modify_program
character(255) filename ! имя файла
character(100) string ! считаем, что длина строки программы
! не более 100 символов
write(*,"(А,\)") "Enter file..."; ! запрос имени файла
read (*, " (А) 11) filename
open(l,file=filename)
do while (.NOT.EOF(l))
read(1,"(A)“) string
call modify(string)
write(*,"(A)") string(1:len_trim(string))
end do
closed)
contains
subroutine modify(str)
character(100) str
integer k
223
И. Л. Артёмов. Fortran: основы программирования
к=1
do while (к/=0)
k=index(str,"read")
if (k/=0) str(к:k+3)="READ"
end do
k=l
do while (k/=0)
k=index(str,"write")
if (k/=0) str(k:k+4)="WRITE"
end do д
end subroutine modify
end
Результат работы программы:
Enter file...prog.f90
program text
integer a, b, c, d, e
WRITE(*, *) "a = ";
WRITE(*,*) ”b =
WRITEf*,*) "c = ";
WRITE(*,*) " d = " ;
WRITE(*,*) ”e =
READ(*,*) a
READ (*,*) b
READ(*, *) c
READ(*,*) d
READ(*,*) e
end
Press any key to continue
8.2. Разновидности файлов. Обработка файлов ’
По способу организации доступа к записям файлы бывают последова-
тельного и прямого доступа.
К данным последовательных файлов доступ осуществляется по порядку,
т. е. чтобы добраться до значения 10, надо прочитать все первые 9 значений.
В таких файлах новые записи можно добавлять только в конец файла. Все
рассмотренные выше примеры относились для последовательных файлов.
Файлы прямого доступа состоят из ячеек определенной, фиксированной
длины. В каждой ячейке хранится часть файла. Каждая ячейка имеет свой
уникальный номер и содержит одну запись или ни одной. Данные в таких
файлах читаются и записываются в любом порядке. Типичный пример ис-
пользования - базы данных.
Файлы также могут быть форматными, неформатными и двоичными.
В форматных файлах данные хранятся в символьном представлении. Эти
данные можно редактировать в текстовом редакторе. Все рассмотренные
примеры относились к форматным файлам.
В случае неформатных файлов данные записываются во внутреннем
представлении. При записи и чтении неформатные файлы обрабатываются
224
8. Файлы
гораздо быстрее, так как не требуется время на преобразование из внутреннего
представления во внешнее форматное представление. Чтобы задать такие файлы,
следует указать в операторе open параметр FORM-UNFORMATTED'.
В двоичных, или бинарных, файлах данные хранятся в двоичном пред-
ставлении. Длина записи фиксирована и равна 1 байту. В этом случае в опе-
раторе open следует указать FORM-BINARY'. Двоичные файлы очень ком-
пактны, и время доступа к данным минимально. Поэтому такие файлы ис-
пользуются для хранения больших объемов информации.
В качестве примера покажем, сколько места будет занимать запись, если
записать 0 в форматный, неформатный или двоичный файл.
Файл form.dat- 14 байт;
файл unform.dat - 8 байт;
файл binary.dat - 4 байта.
Следует отметить, что при чтении и записи данных в неформатные и би-
нарные файлы формат не указывается.
Пример:
program files
open (1,file='form.dat',FORM='FORMATTED');
write(1,*) 0 ! формат по умолчанию *
close(1)
open (2,file='unform.dat',FORM='UNFORMATTED');
write(2) 0 ! формат отсутствует
close(2)
open (3,file='binary.dat1,FORM='BINARY');
write(3) 0 ! формат отсутствует
close(3)
end
При программировании вычислительных задач часто требуется сохра-
нять промежуточные результаты для дальнейшей обработки. Например, если
проводить численный расчет гидродинамики трехмерных течений, то для по-
лучения более точных решений может потребоваться значительное время.
В этом случае возникает необходимость в контрольной записи данных. Сле-
дующий пример демонстрирует резервное, промежуточное сохранение ре-
зультатов.
program save_data
integer, parameter :: Mi=200, Mj=15O, Mk=200
real UR(Mi,Mj,Mk), UZ (Mi, Mj , Mk) , UF(Mi,Mj,Mk)
real P(Mi,Mj,Mk), T(Mi,Mj,Mk)
write(*,*) "Enter 1 ------ NEW SOLUTION, 2 ------ CONTINUE";
read(*,*) k
if (k==l) then
write (*, *) "New SOLUTION. . . ”
225
И. Л. Артёмов. Fortran: осноаы программирования
elseif (к==2) then
opend, file="data.dat", FORM=' BINARY' )
read(l) UR,UZ,UF,P,T ! прочитаем сохраненные данные
closed)
write(*,*) "Continue"
else
stop "PRESS ONLY 1 or 2”
end if
I -------- ЧИСЛЕННЫЙ РАСЧЕТ ЗАДАЧИ -----------
write(*,*) "Solving. Please wait..."
write(*,*) "Press 1 — save results, otherwise get results"
read(*,*) к
if (k==l) then
open(1,file="data.dat",FORM='BINARY')
write(1) UR, UZ,UF, P,T ! запишем данные
closed)
else
write)*,*) "Results saved."
end if
end
Результатом работы программы будет файл размером 114 Мбайт. Если
при этом воспользоваться форматным файлом, то размер будет гораздо
большим и время записи и чтения будет значительно отличаться.
Все рассмотренные файлы были внешними, т. е. они записывались на
диске. Однако можно сделать и внутренние файлы, которые будут храниться
в оперативной памяти компьютера.
Внутренний файл - это символьная строка или массив. Запись данных во
внутренний файл происходит гораздо быстрее, чем во внешний файл. Внут-
ренние файлы широко используются для преобразования данных из одного
типа в другой.
Пример. Допустим, с клавиатуры читаются данные о клиенте, фамилия,
имя и год рождения:
Ivanov Peter 1979
Требуется из последовательности символов выделить год рождения и при-
своить его целочисленной переменной. Будем считать, что длина вводимой
строки не более 50 символов.
program inner_file
character(50) file
integer year, k
write(*,"(A,\)") "Person...";
read(*,"(A)") file ! прочитали информацию
! во внутренний файл
226
8. Файлы
k=index(file,"1") ! определили позицию
! цифры 1 - начало года
read(file(к:к+3), " (14)") year ! прочитали из подстроки
! (внутреннего файла) целое число длиной 4 позиции
write(*,*) year
end
Для нахождения года рождения Используется функция index, возвра-
щающая позицию символа "1" в строке file. Затем при помощи подстроки
выделяется символьное значение года и при помощи оператора read из сим-
вольной подстроки, как из обычного файла, читается год рождения в цело-
численную переменную year.
Результат работы программы:
Person... Ivanov Peter 1979
1979
Press any key to continue
При работе с файлами используются следующие операторы, позволяю-
щие реализовать некоторые дополнительные возможности.
Оператор rewind устанавливает файловый указатель на начало файла.
Применяется, например, если необходимо еще раз прочитать данные из
файла.
Оператор inquire делает запрос о характеристиках файла. Оператор
inquire можно выполнять независимо от того, подсоединен ли файл к устрой-
ству или нет.
Пример. Вывести сообщение о существовании файла.
program see_file
character(255) filename
logical res
writef*,"(A,\)") "Enter file name..."; read)*,*) filename
inquire(file=filename, EXIST=res)
if (res) then
writef*,*) "File is ok!"
else
writef*,*) "File not found!"
end if
end
Параметр EXIST определяет существование указанного файла.
Результат работы программы:
Enter file name...data.dat
File is ok!
Press any key to continue
227
И. Л. Артёмов. Fortran: основы программирования
Операторы open и close содержат многие необязательные параметры, ко-
торые предназначены для выполнения определенных действий с файлами,
обработке ошибок, совместного доступа и т. п. В следующих примерах рас-
смотрим наиболее часто встречаемые на практике параметры.
Пример. Программа, дописывающая данные в файл.
program history
character(8) D
character (10) T.
logical res
inquire(file="log.txt",EXIST=res)
if (.NOT.res) then ! если файл не существует
open(1,file="log.txt") ! то создаем файл
else ! иначе, помещаем файловый указатель
open(1,file="log.txt", access='APPEND') ! на "конец
! файла"
end if
call date_and_time(D,T) ! определили текущую дату и время
write(1,"(14А)") " date " ,D(1:4) ,D(5 :6) , " / " , D(7:8) , &
“ time ",T(1:2),”:",T(3:4) , ":",T(5:6)
close (1)
end
Для возможности добавления данных в файл следует использовать пара-
метр access-APPEND' в операторе open. В программе сделана проверка: если
файл существует, то открывается для режима добавления данных, в против-
ном случае происходит создание файла.
В файл log.txt записываются текущая дата и время, полученные при по-
мощи стандартной подпрограммы date_and_time.
Первый параметр - D содержит текущую дату, представленную в сим-
вольном виде, например: 20060207. Здесь символы с 1-го по 4-й содержат
год, следующие 2 символа - месяц и последние символы - число. '
Второй параметр - Т содержит текущее время, которое представлено так
же, как последовательность символов, например 121702.076. Первые 2 сим-
вола - часы, следующие два - минуты, затем секунды, и через точку указаны
миллисекунды.
После пяти запусков программы содержимое файла log.txt будет сле-
дующим:
date 2006/02/07 time 12:17:02
date 2006/02/07 time 12:17:24
date 2006/02/07 time 12:18:05
date 2006/02/07 time 12:19:17
date 2006/02/07 time 12:19:52
Таким образом, файл log.txt накапливает историю запусков программы.
228
8. Файлы
Пример. Программа по удалению файла.
program delete
character(255) filename
writef*,"(A,\)") "Enter file to delete...";
read(*,*) filename
open(1,file=filename)
close(1, STATUS='DELETE') ! закрываем и удаляем файл
end
Для удаления файла используется оператор close с параметром
STATUS='DELETE'.
Кроме стандартных операторов по работе с файлами, в модуле dflib (ин-
терфейсы всех процедур см. в файле dflib.f90 папки include, а также во встро-
енной справочной системе) существуют процедуры по работе с каталогами и
файлами. В качестве примера приведем употребление функции makedirqq пр
созданию каталога.
program dir
logical res
character(10) :: dl="FIRST"
res=makedirqq(dl)
end
В результате работы профаммы в рабочей папке будет создана папка
FIRST.
Итоги
1. Работа с файлом начинается с оператора open, связывающего имя фай-
ла с определенным целым положительным номером.
2. Для записи и чтения данных из файла используются операторы write
и read.
3. Логическая функция определения конца файла eof позволяет прочи-
тать все данные из файла.
4. По способу организации доступа к данным файлы могут быть прямого
и последовательного доступа. По способу хранения данных файлы делятся на
форматные, неформатные и двоичные. При обработке больших объемов дан-
ных рекомендуется пользоваться двоичными файлами.
5. Внутренний файл представляет себой символьную строку и может
применяться для преобразования данных из одного типа в другой.
6. Оператор rewind применяется для повторного чтения или для записи
данных из файла. Оператор inquire позволяет определить характеристики
файла. Используя дополнительные параметры в операторах open, close, мож-
но добиться максимальной гибкости при обработке файлов.
229
9. ГРАФИЧЕСКИЕ ВОЗМОЖНОСТИ
Результаты любой вычислительной программы удобно воспринимать,
если они представлены в виде схем, графиков, изолиний, векторных полей,
поверхностей и т. п. Иногда во время расчета требуется отображать развитие
какого-нибудь процесса во времени, например нагревание тела, обтекание
уступа, полет ракеты и др. В этом случае требуется умение программировать
графику.
Fortran позволяет создавать графические программы следующими основ-
ными способами:
1) с применением стандартных графических процедур;
2) используя интерфейс графических устройств (GDI) системы Windows;
3) использование библиотеки OpenGl, позволяющей быстро и легко соз-
давать реалистичные трехмерные изображения.
В этой теме будут рассмотрены программы на основе стандартных гра-
фических процедур. Достоинством данного подхода является сравнительно
простое изучение материала, позволяющее быстро понять основные средства
создания графических программ и с первых шагов писать собственные гра-
фические приложения.
Для использования в программе стандартных графических процедур сле-
дует создать проект Standard Graphics Applicaton, выполнив такую последо-
вательность действий: File -> New -> Projects -> Fortran Standard Graphics or
QuickWin Applicaton -> указать имя проекта -> OK ~> Standard Graphics ->
Finish -> OK.
Затем, как обычно, создать файл с расширением 190, включить в проект и
начинать писать программу.
9.1. Программы, использующие
стандартные графические процедуры
Создание графических программ основано на использовании набора
подпрограмм и функций, вызов которых может приводить к рисованию и за-
крашиванию геометрических фигур, отображению графического текста, из-
менению цвета фона и т. д. Например, чтобы нарисовать эллипс, достаточно
вызвать одну-единственную функцию Ellipse() с необходимыми входными
данными - и геометрическая фигура отобразится на экране.
Экран представляет собой набор точек - пикселов (pixels, picture
elements - элемент картинки), расположенных в виде прямоугольной сетки.
Наиболее часто используемые размеры сетки (графическое разрешение):
640x480, 800x600, 1024x768, 1280x1024 (отношение длины к высоте 4/3), где
/1И4/1ОГ7ИИ0И
230
9. Гоафические возможности
первое число означает количество пикселов вдоль оси х, второе - вдоль оси у.
Очевидно, что чем больше число точек, тем качественнее будет полученное
изображение. По умолчанию программа использует текущее графическое
разрешение системы Windows, т. е. если на компьютере установлено разре-
шение 800x600, то программе будет доступно 800x600 пикселов.
Пикселы нумеруются сверху вниз, слева направо. Самый верхний левый
пиксел имеет координаты (0,0). При графическом выводе каждый пиксел эк-
рана закрашивается в определенный цвет, получаемое таким образом изо-
бражение можно сравнить с закрашенными клеточками на тетрадном листе
(рис. 9.1).
Перед началом использования графических процедур следует подклю-
чить модуль dflib, который содержит описания графических подпрограмм
и функций, типы данных, а также необходимые константы *.
Создадим первую программу - рисование прямоугольника.
program rect
use dflib
integer(2) ires2
ires2=Rectangle($GBORDER,20,20,300,40)
end
Результат работы программы:
Модуль dflib содержится в файле dflib.f90. Для более глубокого понимания работы
графических процедур, рекомендуется внимательно почитать указанный файл.
231
И. Л. Артёмов. Fortran: основы программирования
Как видите, программа очень простая. Чтобы нарисовать прямоугольник,
достаточно вызвать функцию Rectangle с необходимыми данными. Константа
SGBORDER, описанная в модуле dflib, означает, что будет нарисована только
граница прямоугольника; 20,20 - верхний левый угол прямоугольника;
300,40 - нижний правый. После завершения работы программа выдает соот-
ветствующее сообщение и предоставляет возможность оставить окно про-
граммы открытым:
GRAPH
Program Т erminated with exit code 0
Exit Window?
Да 11[ ..............Нет i|
Следует отметить, что проект Standard Graphics Applicaton создает только
однооконную программу с вертикальной и горизонтальной полосами про-
крутки. С точки зрения Windows-программирования такое окно ограниченно
в своих возможностях. Например, невозможно использовать меню програм-
мы, панели инструментов и т. п. Однако этот проект рекомеггдуется исполь-
зовать в программах, где требуется быстро вывести на экран графические ре-
зультаты, провести анализ полученных решений и не затрагивается вопрос о
внешнем оформлении. Например, вы рассчитываете движение жидкости или
газа и требуется представить аудитории развитие какого-нибудь нестацио-
нарного процесса. Оформив программу с использованием стандартных гра-
фических процедур, можно с минимумом вложений решить поставленную
задачу.
Для успешного написания графических приложений необходимо знать
конфигурацию окна программы. Допустим, результаты вычислений будут
демонстрироваться на различных компьютерах с разным графическим раз-
решением. Так, если программа была написана для режима 640x480, то'центр
экрана будет лежать в точке (320,240), но тогда для режима 800x600 это бу-
дет ошибкой.
Вся информация о конфигурации окна содержится в производном типе
windowconfig, описанном в модуле dflib. Укажем наиболее часто используе-
мые поля.
type windowconfig
integer(2) numxpixels !
integer(2) numypixels !
integer(2) numcolors !
character(80) title !
--- число пикселов по оси х
--- число пикселов по оси у
--- число индексов цвета
--- заголовок окна приложения
! (си-строка)
end type windowconfig
232
9. Гсафические возможности
Для работы с конфигурацией окна предусмотрены две функции:
status=SetWindowConfig(wc)
устанавливает требуемые параметры для конфигурации окна
status=GetWindowConfig(wc)
получает сведения о текущей конфигурации, где
logical(4) status
type (windowconfig) wc
Функции возвращают значение .TRUE., если операция выполнена ус-
пешно, в противном случае .FALSE.. Обратите внимание: графические про-
цедуры, да и вообще процедуры с длинными именами, лучше писать, выде-
ляя заглавными буквами ключевые слова. Так, GetWindowConfig, Get -
Взять, Window - Окно, Config - Конфигурация читается и запоминается го-
раздо быстрее, чем запись getwindowconfig.
Пример. Вывести на экран текущее графическое разрешение.
program graph_resolution
use dflib
type(windowconfig) wc
logical(4) status
integer(2) xmax, ушах
status = Getwindowconfig(wc)
xmax=wc.numxpixels ! получаем текущее графическое разрешение
ymax=wc.numypixels
write (*,*) xmax, углах
end
Результат работы программы:
1024 768
Пример. Изменить заголовок окна.
program change_window_name
use dflib
type(windowconfig) wc
logical(4) status
status=GetWindowConfig(wc) ; wc.title='Используем графику!'C
status=SetWindowConfig(wc)
end
Результат работы программы:
233
И. Л. Артёмов. Fortran: основы программирования
Часто требуется результаты графических программ сохранять для даль-
нейшей работы *. Функция
ires4=Save!mage(filename,xl,yl,х2,y2)
integer(4) ires4
character*(*) filename
integer(4) xl, yl
integer(4) x2, у2
сохраняет графическое изображение в файл, заданный символьной перемен-
ной filename. Здесь координаты xl.yl - верхний левый угол и х2,у2 - нижний
правый угол прямоугольника, ограничивающего графическое изображение.
Функция сохраняют образ в Windows bitmap-формате (растровый формат
Windows, расширение файла .bmp). После сохранения полученный графиче-
ский файл можно вставить в текстовый редактор (например, Microsoft Word)
или отредактировать в графическом редакторе (например, Corel Photo Paint).
9.2. Координатные системы
Вся работа с выводом графических объектов напрямую связана с выбо-
ром системы координат, в которой будет происходить рисование. В проекте
Standard Graphics предусмотрено три вида координатных систем (рис. 9.2).
(wc.NumXpixels,0)
——“физические координаты- '»
(Х1,у1)
г — порт просмотра — —।
Г (их2,иу2)П
I ।
оконная система I
координат
finvert=.TRUE. ।
(wxl,wyl)
(x2,y2)
(0,wc.NumYpixels)
Puc. 9.2. Виды координатных систем
* Есть и более легкий способ, без программирования. Нажатие клавиши PrintScreen
на клавиатуре помещает снимок экрана в буфер обмена.
234
9. Гоафичаские возможности
1. Физическая система координат. Занимает всю область экрана, где еди-
ницей измерения служит 1 пиксел.
2. Координаты порта просмотра. Позволяют отображать графические
данные только в выбранной прямоугольной области экрана, единицей изме-
рения также является пиксел.
3. Оконная система координат. Разрешает измерять расстояние в произ-
вольных вещественных единицах.
Физические координаты
В физической системе координат местоположение каждой точки измеря-
ется в пикселах и задается двумя целочисленными координатами х и у. Нача-
ло координат (0,0), как нами изучено, расположено в верхнем левом углу эк-
рана, т. е. ось х направлена вправо, ось у вниз. Для вывода графических изо-
бражений доступна вся область. Таким образом, если режим 1024x768, то
xmin=0, xmax=1023, ymin=0 и ymax=767.
Иногда удобнее, чтобы начало координат находилось в определенной
области экрана, например в центре. Такой перенос осуществляется вызовом
подпрограммы:
SetViewOrg (хр, ур, t), где
integer(2) хр, ур
type (xycoord) t
Переменные хр и ур задают новое начало координат, переменная t имеет
тип xycoord и содержит значения предыдущего начала координат. Тип
xycoord определен в модуле dflib:
type xycoord
integer(2) xcoord I --- х-координата
integer(2) ycoord I --- у-координата
end type xycoord
Естественно, что при переносе начала координат меняются значения
xmin, ymin и xmax, углах. Так, если при режиме 800x600 начало координат
переносится в точку (500,400), то xmin=-500 ymin=-400 и xmax=299,
ymax=199.
Координаты порта просмотра
Выводить результаты во весь экран не всегда удобно, поэтому на практи-
ке часто графические данные отображают в некоторой меньшей прямоуголь-
ной области, называемой портом просмотра. Чтобы задействовать эту об-
ласть, вызывается подпрограмма
SetViewPort(xl,yl,х2, у2)
integer(2) xl, yl, х2, у2,
где (xl,yl) верхний левый и (х2,у2) нижний правый углы порта просмотра и
задаются в физической системе координат.
235
И. Л. Артёмов. Fortran: основы программирования
После создания порта просмотра начало координат совпадает с верхним
левым углом (xl,yl) порта просмотра. В этом случае xmin=0, ymin=0,
xmax=x2~xl, ymax=y2-yl. Теперь вывод графических данных будет произво-
диться только в созданную ограниченную область экрана.
Оконная система координат
Физическая система координат и координаты порта просмотра требуют,
чтобы координаты точек были целыми числами, однако на практике часто
приходится работать с вещественными числами. Например, если нужно по-
строить график функции в интервале от -0.005 до +0.007, то, очевидно, по-
требуется создать две функции (для координат х и у) преобразования вещест-
венных координат в целые координаты экрана. Однако для облегчения и
ускорения написания таких программ существует специальная функция
SetWindow.
ires2=SetWindow(finvert,wxl,wyl,wx2,wy2), где
Integer(2) ires2
logical(2) finvert
real(8) wxl, wyl, wx2, wy2
Переменная finvert определяет направление оси ординат, если finvert=
=.TRUE., то ось увеличивается снизу вверх, если .FALSE, то сверху вниз.
Окно ограничивается координатами (wxl,wyl) и (wx2,wy2) и занимает
полностью текущий порт просмотра или весь экран, если порт просмотра не
установлен.
В оконной системе координаты имеют тип real(8), поэтому нужно поза-
ботиться и описать соответствующие переменные либо использовать функ-
цию DBLE() для преобразования в тип двойной точности. Следует отметить,
что все процедуры, выводящие графическую информацию в оконную систе-
му координат, заканчиваются символом _w.
Графика оконных координат предоставляет наибольшую гибкость при
решении различных вычислительных задач. ,
Рассмотрим небольшой набросок программы.
program graph
call SetViewPort(20,20,600,400)
res=SetWindow(.TRUE.,-0.5d0,-0.6d0,0.34d0.0.22d0)
end
Вызов подпрограммы SetViewPort приведет к созданию порта просмотра,
и весь вывод данных будет происходить в область, ограниченную прямо-
угольником с вершинами (20,20) и (600,400).
Вызов функции SetWindow создает оконную систему координат, которая
займет всю область порта просмотра. Координата х направлена вправо, коор-
236
9. Гсафические возможности
дината у вверх. Вывод данных теперь можно производить в вещественных
координатах, при этом xmin=-0.5, xmax=0.34, ymin=-0.6, утах=0.22 (рис. 9.3).
(20,20)
(-0.5,-0.6)
(0.34,0.22)
------
(600,400)
Рис. 9.3. Оконная система координат
Иногда при использовании оконной, физической систем координат, ко-
ординат порта просмотра требуется проделывать взаимное преобразование
одной системы в другую. Чтобы разобраться с этим вопросом, рассмотрим
пару примеров.
Пример. Порт просмотра задан следующим образом:
call SetViewPort(10,10,400,400)
и установлена оконная система координат
ires2=SetWindow(.TRUE.,DBLE(-10),DBLE(-10),DBLE(10),DBLE(10))
Задана точка (-10.0, 10.0) в оконной системе координат. Какие координа-
ты имеет эта точка в координатах порта просмотра и физической системе ко-
ординат?
Для этого воспользуемся двумя подпрограммами.
GetViewCoord_w(wx,wy,t)
real(8) wx
real(8) wy
type (xycoord) t
переводит оконные координаты в координаты порта просмотра, с возвратом
их в переменной t.
Перевод координат порта просмотра в физические координаты произво-
дится подпрограммой
GetPhysCoord (x,y,t)
integer(2) х
integer(2) у
type (xycoord) t
237
И. Л. Артёмов. Fortran: основы программирования
Ниже представлен текст программы, поясняющие использование рас-
смотренных подпрограмм.
program coordinate_systems
use dflib
type(xycoord) xy ! коорд. точки в области порта просмотра
type(xycoord) xyphys ! коорд. точки в физич. системе координат
integer(2) ires2
call SetViewPort(10,10,400,400)
ires2=SetWindow(.TRUE.,DBLE(-10),DBLE(-10), &
DBLE( 10),DBLE( 10))
call GetViewCoord_w(DBLE(-10),DBLE(10),xy)
call GetPhysCoord(xy.xcoord,xy.ycoord,xyphys)
write(*,100) "View port: x = ", xy.xcoord, &
" у = ", xy.ycoord
write(*,100) "Physical: x = ", xyphys.xcoord, &
" у = ", xyphys.ycoord
100 format(2(A,i4))
end
Результат работы программы:
View port: x = 0 у = 0
Physical: x = 10 у = 10
Следующие подпрограммы:
GetViewCoord осуществляет преобразование физических координат в ко-
ординаты порта просмотра.
GetViewCoord (x,y,t)
integer(2) х
integer(2) у
type (xycoord) t
GetWindowCoord переводит координаты порта просмотра 6 оконную
систему координат.
GetWindowCoord (x,y,wt)
integer(2) х
integer(2) у
type (wxycoord) wt
В заключение рассмотрим одну из самых распространенных операций
при создании графических программ - очистку экрана. Очистка экрана реа-
лизуется посредством вызова следующей подпрограммы:
ClearScreen (area)
integer(4) area
238
9. Гсафические возможности
где параметр area - константа, определенная в модуле dflib, принимающая
значения:
SGCLEARSCREEN или 0 - очистка окна приложения;
SGVIEWPORT или 1 - очистка текущего порта просмотра.
При очистке экрана, область заполняется текущим цветом фона.
9.3. Управление цветом
Чтобы начать использовать тот или иной цвет, его устанавливают теку-
щим при помощи двух видов функций.
К первому относятся функции, устанавливающие цвет по его номеру
(индексу). Стандартная палитра содержит 16 цветов, каждому из которых
присвоен номер:
О - $ BLACK, черный;
1 - SBLUE, синий;
2 - SGREEN, зеленый;
3 - SCYAN, голубой;
4 - $RED, красный;
5 - SMAGENTA, фиолетовый;
6 - SBROWN, коричневый;
7 - SWHITE, белый;
8 - SGRAY, серый;
9 - SLIGHTBLUE, светло-синий;
10 - SLIGHTGREEN, светло-зеленый;
11 - SLIGHTCYAN, светло-голубой;
12 - SLIGHTRED, светло-красный;
13 - SLIGHTMAGENTA, светло-фиолетовый;
14 - SYELLOW, желтый;
15 - SLIGHTWHITE, ярко-белый.
Для установки цвета фона используется функция
ires2=SetBkColor(index)
integer(2) ires2
int eger(2) index
index - номер цвета.
Для рисования графических объектов (линий, точек, фигур и т. д.) задан-
ным цветом следует применять функцию
ires2=SetColor(index)
integer(2) ires2
integer(2) index
239
И. Л. Артёмов. Fortran: основы программирования
Пример. Установить цвет фона ярко-белым.
program ChangeColor
use dflib
integer(2) ires2
ires2=SetBkColor(15) ! установим цвет фона
call ClearScreen(O)
end
Чтобы цвет фона изменился, необходимо вызвать подпрограмму очистки
экрана.
Ко второму виду относятся функции, устанавливающие цвет при задании
интенсивности трех базовых цветов: красного, зеленого, синего (Red, Green,
Blue). Под каждый основной цвет отведено по 256 значений (от 0 до 255).
Значение цвета является комбинацией базовых цветов, например:
белый цвет: Rcd=255, Green=255, Blue=255;
черный цвет: Red=0, Green=0, Blue=0;
синий цвет: Red=0, Green=0, Blue=255.
Функция
ires4=SetBkColorRGB(color)
Integer(4) ires4, color
устанавливает текущий цвет фона, color - RGB-значение цвета.
Функция
ires4=SetColorRGB(color)
integer(4) ires4, color
устанавливает цвет отображения графических примитивов.
Значение цвета по известным RGB-компонентам возвращает функция
color=RGBTo!nteger(red,green,blue)
integer(4) color, red, green, blue
Пример. Задать оранжевый цвет фона *
program Orange_Fon
use dflib
integer(4) ires4, color
color=RGBTo!nteger(250,136,52)
ires4=SetBkColorRGB(color)
call ClearScreen(O)
end
Обратим внимание, что при использовании функций SetColor, SetBkColor
результат и фактические параметры имеют тип, integer(2), для функций
SetColorRGB, SetBkColorRGB это тип integer(4).
* Чтобы посмотреть, как будет выглядеть цвет в RGB-представлении, нужно щелкнуть
на Рабочем столе правой кнопкой - Свойства - Оформление - Цвет - Другой: в резуль-
тате появится окно, где будут показаны значения RGB выбираемого цвета.
240
9. Гоафические возможности
9.4. Графические примитивы
Графические примитивы являются теми составными частями, из которых
получаются готовые графические изображения. К графическим примитивам
относятся:
1) пиксел,
2) отрезок прямой линий,
3) прямоугольник,
4) многоугольник,
5) эллипс (частный случай, окружность),
6) дуга окружности,
7) сектор,
8) произвольная замкнутая область,
9) зависимый от шрифта текст.
Любой рисунок всегда может быть представлен в виде комбинаций от-
резков прямых, прямоугольников, групп пикселов, многоугольников и т. д.
Все графические примитивы при рисовании отображаются с учетом те-
кущего цвета, типа линии и маски заполнения. Текущий цвет устанавливает-
ся функциями SetColor и SetColorRGB.
По умолчанию все графические примитивы рисуются сплошной линией
текущего цвета. Тип линии - это шаблон, но которому профамма отображает
пунктирные, штриховые, сплошные и другие типы линий. Шаблон представ-
ляет собой 16-битовое число. Каждый бит числа - это 1 пиксел экрана. Если
бит равен единице, то пиксел заполняется соответствующим цветом, если ну-
лю то пиксел остается без изменения.
При мер. Различные типы линий.
integer(2) solid(16) , dash(16), dashdot (16)
solid =(/2#llllllllllllllll/) ! --- сплошная
dash =(/2#1111000011110000/) ! -------- штрихи
dashdot=(/2#100011000/) I штрихпунктирная
Для изменения типа линии используется подпрограмма:
SetLineStyle(mask), где
integer(2) mask
Маска заполнения, по аналогии с типом линии, это образец (шаблон) за-
ливки, который устанавливается подпрограммой
SetFillMask(mask)
integer(1) mask
Здесь mask - массив из восьми целых чисел типа integcr(l). Каждый бит чис-
ла представляет собой отдельный пиксел экрана, поэтому имеем 8 бит
(1 байт=8 битам) и, следовательно, массив размера 8x8 бит.
241
И. Л. Артёмов. Fortran: основы программирования
Примеры различных масок заполнения:
! --- маска заполнения "точки"
integer(1) maska(8)
maska=(/2#00000000, &
2#00000000, &
2#00111100, &
2#00111100, &
2#00111100, &
2#00111100, &
2#00000000, &
2#00000000/)
! --- маска заполнения "решетка"
integer(l) maska(8)
maska=/(2#11000011, &
2#11100111, &
2#01111110, &
2#00111100, &
2#00111100, &
2#01101110, &
2#11100111, &
2#11000011/)
(Примеры использования стилей и масок заполнения будут рассмотрены
далее совместно с другими примерами.)
Вывод пикселов
Самым элементарным графическим примитивом является пиксел. Для
отображения одного пиксела используются функции
ires2=SetPixel(х, у) , где
Integer(2) ires2, х, у
и
ires4=SetPixelRGB(х,у,color), где
integer(4) ires4, color '
integer(2) x, у
Здесь хну- координаты пиксела в физической системе координат или коор-
динат порта просмотра. Пиксел отображается текущим цветом, заданный
функцией SetColor или SetColorRGB.
При работе в оконной системе координат используются функции
SetPixel_w(wx,wy), SetPixelRGB_w(wx,wy,color), где wx, wy имеют тип real(8).
Пример. Построить поле распределения температуры, заданное законом
z \2 z \2
7’(x,y) = sin| — +sin| — .
l2J
Очень часто поле температуры изображают при помощи цветов, где са-
мый холодный участок отображается фиолетовым цветом, самый горячий -
242
9. Графические возможности
красным. При помощи функции RGBToIhteger сформируем массив цветов
color (палитра). Учитывая плавность перехода одного цвета в другой, для
Л=1,51 получим следующие цветовые интервалы:
RGBToInteger(255-5*k,0,255)
RGBToInteger(0,5*k,255)
RGBToInteger(0,255,255-5*k)
RGBToInteger(5*k,255,0)
RGBToInteger(255,255-5*k, 0)
! фиолетовый - синий
! синий - бирюзовый
! бирюзовый - зеленый
! зеленый - желтый
1 желтый - красный
Значения поля температуры будем хранить в двумерном массиве, со-
стоящего из 200x200 элементов. Будем также считать, что каждому элементу
массива ставится в соответствие 1 пиксел. Таким образом, поле температуры
займет на экране область 200x200 пикселов.
Введем обозначения:
Т min - минимальная температура;
Т max - максимальная температура;
Т (z, j) - температура каждой точки области;
С min - номер цвета минимальной температуры;
Стах - номер цвета максимальной температуры;
Ct - номер цвета температуры точки области.
Очевидно, что должно выполняться следующее:
Т min соответствует С min ,
Т max соответствует С max ,
T(i,j) соответствует Ct,
поэтому можно составить следующее соотношение:
Q-Cmin _T(i,j)-Tmin
С max- С min Т max- Т min
или
„ T(i, j)-7’min .
Ct = —-— -------(С max- С min)+С mm .
Т max-Т min
Итак, организуя двойной цикл и определяя цвет пиксела в зависимости
от температуры точки, получим желаемый графический результат.
program Temperature
use dflib
type(windowconfig) wc
integer, parameter :: M=200
integer(4) ires4, color(255)
real T(M,M)
integer Стах, Cmin, Ct
real Tmax, Tmin, xmin, xrnax, ymin, ушах, dx, dy, xt, yt
243
И. Л. Артёмов. Fortran: основы программирования
integer i, j , к
logical(4) status
integer(2) ires2
status=GetWindowConfig'(wc) ; wc.title='Поле температуры'С
status=SetWindowConfig (wc)
do k=l,51 1 формируем палитру цветов'
color(к) =RGBToInteger(255-5*k, 0,255)
color(k+51) =RGBToInteger(0,5*k,255)
color(k+51*2)=RGBToInteger(0,255,255-5*k)
color(k+51*3)=RGBToInteger(5*k,255,0)
color(k+51*4)=RGBToInteger(255,255-5 *k,0)
end do
call SetViewPort(10,10,M+10,M+10)
xmin=-4.5; xmax=4.5; ymin=-4.5; ymax=4.5
dx=(xmax-xmin)/М; dy=(ymax-ymin)/М
do i=l,M
xt=(i-1)*dx+xmin
do j=l,M
yt=(j-l)*dy+ymin
T(i,j)=F(xt,yt)
end do
end do
Tmax=maxval(T); Tmin=minval(T) ! находим максимум
! и минимум температуры
Cmin=l; Cmax=255
do i=l,M
do j=l,M
Ct=((T(i,j)-Tmin)*(Cmax-Cmin)7(Tmax-Tmin)+Cmin)
ires4=SetPixelRGB(i,j,color(Ct))
end do
end do
contains
real function F(x,y)
real x,у
F=sin(x/2)**2+sin(y/2)**2
end function F
end
Результат работы программы:
244
9. Гсафические возможности
Пример. Визуализация попаданий в замкнутую область.
При изучении логического типа данных рассматривались программы оп-
ределяющие попадание точки в замкнутую область (см. 2.6). Можно соста-
вить графическую программу, которая будет генерировать случайным обра-
зом координаты точек и закрашивать их, если произошло попадание в об-
ласть, и оставлять точки экрана без изменения в противном случае.
В качестве примера рассмотрим область, представляющую собой треуголь-
ник с круглым вырезом (рис. 9.4).
4 у
(7,9)
Рис. 9.4. Замкнутая область
program to_hit_the_mark ! попадание в цель
use dflib
type (windowconfig) wc
real, parameter :: xmin=0.0, ymin=0.0, &
xmax=10.0, ymax=10.0 ! границы области
Integer, parameter :: N=80000 ! количество выстрелов
Integer k
logical flag ! условие попадания в область
real :: xl=l.О,yl=5.О, & 1 вершины треугольника
х2=7.0,у2=9.О, &
хЗ=5.О,уЗ=1.О
real :: х0=4.О,у0=5.О ! центр окружности
real х, у, ya, yb, ус
Integer(2) ires2
logical(4) status
status=GetWindowConfig(wc) ; wc.С1С1е="Попадание в область"С
status=SetWindowConfig(wc)
ires2=SetBkColor (15) ,- call ClearScreen (0)
call SetViewPort(10,10,300,300)
ires2=SetWindow(.TRUE.,DBLE(xmin),DBLE(ymin),&
DBLE(xmax),DBLE(ymax))
245
И. Л. Артёмов. Fortran: основы программирования.
ires2=SetColor(0)
do k=l,N
call random_number(x)
call random—number(y)
x=x*(xmax-xmin)+xmin
y=y* (ymax-ymin) +ymin
ya=(x-xl)/(x2-xl)*(y2-yl)+yl
yb=(x-xl)/(x3-xl)*(y3-yl)+yl
yc=(x-x2)/(x3-x2)*Cy3-y2)+y2
flag=(y<ya).AND.(y>yb).AND.(y>yc).&
XOR.(sqrt((х-xO)“2+(y-yO)**2)<1)
if (flag) ires2=SetPixel_w(DBLE(x),DBLE(y))
end do
end
Изменяя в программе flag, можно поэкспериментировать с другими об-
ластями, например попадание в три пересекающихся круга.
flag=(sqrt((х-5)**2+(у-5)**2)<2.0).OR. &
(sqrt((х-3)**2+(у-7)**2)<1.5).OR. &
(sqrt((х-7)**2+(у-7)**2)<1.5)
Результат работы программы:
Вывод отрезка прямой линии
При решении математических задач может потребоваться вывод на экран
Iрафиков функций, заданных в виде формул или таблиц. Представим, что
график будет состоять из множества последовательно соединенных отрезков
прямых линий. В результате такого допущения график будет представлять
собой ломаную, и если точек достаточное количество, то все углы и неровно-
сти будут просто незаметными. Отображение отрезка состоит из двух этапов
(рис. 9.5).
246
9. Гсафические возможности
LineTo (х2,у2)
MoveTo(xl,yl)
Рис. 9.5. Рисование отрезка линии
1. Помещаем начало отрезка в первую точку графика при помощи под-
программы:
MoveTo(xl,yl,s) ! поместить в (xl,yl)
integer(2) xl, yl
type (xycoord) s
или
MoveTo_w(wxl,wyl,ws)
real(8) wxl, wyl
type (wxycoord) ws
переменные j и iw содержат координаты предшествующей позиции графиче-
ского вывода.
2. Задаем конец отрезка, вызывая функцию:
ires2=LineTo(х2,у2) ! нарисовать линию до (х2,у2)
integer(2) ires2, х2, у2
или
ires2=LineTo_w(wx2,wy2)
integer(2) ires2
real(8) wx2, wy2
В результате вызова двух процедур рисуется отрезок с начальными
(xl,yl) и конечными (х2,у2) координатами . Цвет линии задается функциями
SetColor или SctColorRGB. Тип отображаемой линии можно изменить, вызы-
вая подпрограмму SetFillMask,
Пример. Построить график функции f (х) = sin (х).
program sinx
use dflib
type(wxycoord) wxy
type(windowconfig) wc
real, parameter :: pi=3.1415926
integer, parameter :: Mi=100
integer(2) ires2, i
real xmin, xmax, ymin, ушах
intrinsic sin
logical(4) status
247
И. Л. Артёмов. Fortran: основы программирования
xmin=-4*pi; xmax=6*pi; ymin=-1.5; ymax=l. 5
status=GetWindowConfig(wc); wc.title='Синусоида'C
status=SetWindowConfig(wc)
call SetViewPort(10,10,500,200)
ires2=SetWindow(.TRUE.,DBLE(xmin), DBLE(ymin),&
DBLE(xmax) , DBLE(углах))
ires2=SetBkColor(15); call ClearScreen(O)
ires2=SetColor(0) ! рисуем координатные оси
call MoveTo_w(DBLE(xmin),DBLE(0.0),wxy)
ires2=LineTo_w(DBLE(xmax),DBLE(0.0))
call MoveTo_w(DBLE(0.0),DBLE(ymin),wxy)
ires2=LineTo_w(DBLE(0.0),DBLE(ymax))
ires2=SetColor(9) ! синусоида
call DrawFunction(sin,xmin,xmax,Mi)
ires2=SetColor(0) ! недостаточно точек
call DrawFunction(sin,xmin,xmax,10)
contains
subroutine DrawFunction(F,xmin,xmax,Mi)
Integer Mi
real xmin, xmax, dx, xt, yt
type(wxycoord) wxy
dx=(xmax-xmin)/(Mi-1)
xt=xmin; yt=F(xt)
call MoveTO—w(DBLE(xt),DBLE(yt),wxy)
do i=2,Mi
xt=(i-1)*dx+xmin; yt=F(xt)
ires2=LineTo_w(DBLE(xt),DBLE(yt))
end
end subroutine DrawFunction
end '
Рисование графика функции происходит подпрограммой DrawFunction; в
которую передаются как параметры функция, начальная точка xmin, конеч-
ная точка xmax и число точек Mi графика функции. Шаг, через который будут
следовать точки, определится как
, xmax-xmin
dx =----------,
Л/г-1
а текущая координата точки - xt = (г -1) dx , где г = 1, Mi.
Установив при помощи подпрограммы MoveTo_w графический вывод в
начальную точку xt = xmin , yt = f(xt), далее в цикле проводятся последо-
вательно от одной точки до другой отрезки прямых линий. В результате по-
248
9. Гоафические возможности
думается требуемый график функции. При уменьшении числа точек Mi, гра-
фик функции будет представлять собой ломаную.
Пример. Построить эпициклоиду, заданную уравнением
х(ср) = (/? + г) cosф-d • cos| Г (р |;
у (ф) = (/? + г)• sin ф - d • sin
program epic
use dflib
type(wxycoord) wxy
type(wi ndowc on f i g) wc
real, parameter :: R=2.0, rm=0.5, d=l.0
Integer, parameter :: Mfi=360
! --- R, rm, d параметры эпициклоиды
logical(4) status
integer(2) ires2
real xt, yt, fi
integer i
status=GetWindowConfig(wc); wc.Г111е="Эпициклоида"С
status=SetWindowConfig(wc)
call SetViewPort(50,50,250,250)
ires2=SetBkColor(15); call ClearScreen(0)
ires2=SetWindow(.TRUE.,DBLE(-R**2),DBLE(-R**2),&
DBLE(R**2), DBLE(R**2))
ires2=SetColor(0)
call SetLineStyle(2#1100110011001100) ! пунктирный тип
! линий
call MoveTo_w(DBLE(0.0),DBLE(-R**2),wxy) ! ось ординат
ires2=LineTo_w(DBLE(0.0),DBLE(R**2))
249
И. Л. Артёмов. Fortran: основы программирования
call MoveTo_w(DBLE(-R**2), DBLE(0.О),wxy) ! ось абсцисс
ires2=LineTo_w(DBLE(R**2), DBLE(0.0))
call SetLineStyle(2#1111111111111111)! ---- восстанавливаем
! сплошной тип линий
xt=(R+rm)*coed(l.0)-d*cosd((R+nn)/rm)
yt=(R+rm)*sind(1.0)-d*sind((R+rm)/rm) ! исходная точка
call MoveTo_w(DBLE(xt),DBLE(yt),wxy)
do fi=l,Mfi I -------- построение графика эпициклоиды
xt=(R+rm)*cosd(REAL(fi))-d*cosd((R+rm)/rm*REAL(fi))
yt=(R+rm)*eind(REAL(fi))-d*sind((R+rm)/rm* REAL(fi))
ires2=LineTo_w(DBLE(xt),DBLE(yt))
end do
end
Результат работы программы:
Вывод прямоугольника
Рисование прямоугольников и квадратов производится при помощи
функции Rectangle.
ires2=Rectangle(control,xl,yl,x2,y2), где
lnteger(2) ires2, control, xl, yl, x2, y2
Параметр control - характеризует заполнение прямоугольника и может
принимать следующие значения:
$GFILLINTERIOR - закраска прямоугольника с использованием текуще-
го цвета и маски заполнения;
$GBORDER - выводятся только границы прямоугольника с использова-
нием текущего цвета и типа линии.
xl,yl,x2,y2 (wxl,wyi,wx2,wy2) - координаты левого верхнего и нижнего
правого угла прямоугольника (рис. 9.6).
250
9. Гоафическиа возможности
(xl,yl)
(х2,у2)
Рис. 9.6. Рисование прямоугольника
Пример. Нарисовать кирпичную стену.
Чтобы нарисовать один ряд кирпичей без учета зазора между кирпичами,
можно организовать следующий простой цикл (рис. 9.7):
xl=10; у1=10; у2=30
do k=l,Mi
x2=xl+50
ires2=Rectangle(2,xl,yl,х2,у2)
xl=x2
end do
С учетом зазора цикл немного изменится:
xl=10; yl=10; у2=30; dx=5
do k=l,Mi
x2=xl+50
ires2=Rectangle(2,xl,yl,х2,у2)
xl=x2+dx
end do
(xl,yl) (xl,yl)
- dx -
(x2,y2) (x2,y2)
Puc. 9.7. Рисование кирпичной стены
Для организации нескольких рядов кирпичей потребуется создать двой-
ной цикл. Учитывая, что каждый нечетный ряд сдвинут относительно четно-
го на половину длины кирпича, программа будет следующей:
program stone
use dflib
type(windowconfig) wc
integer, parameter :: Mi=10, Mj=20
integer xO, yO, dx ,dy, H, L !dx, dy - зазоры между кирпичами
IL, H - длина и ширина кирпича
251
И. Л._Артёмов. Fortran: основы программироввния
integer xl, х2, yl, у2
integer i, j
logical(4) status
integer(2) ires2
status=GetWindowConfig(wc); wc.title="Кирпичная стена"С
status=SetWindowConfig(wc)
ires2=SetBkColor(15); call ClearScreen(0)
ires2=SetColor(0)
L=50; H=20; dx=5; dy=5; yl=10
do j =1, Mj
y2=yl+H
if(mod(j,2)==0) then
xl=10
else
xl=10+L/2
end if
do i=l,Mi
x2=xl+L
ires2=Rectangle($GBORDER,xl,yl,x2,y2)
xl=x2+dx
end do
yl=y2+dy
end do
end
Результат работы программы:
Вывод многоугольника
При выводе многоугольника произвольной формы используется функция
Polygon(control,ppoints, cpoints) , где
integer(2) ires2, control
type (xycoord) ppoints
integer(2) cpoints
Параметр control принимает те же значения, что и для вывода прямо-
угольника, т. е. может быть SGBORDER или SGFILLINTERIOR; ppoints -
массив типа xycoord, содержащий координаты вершин многоугольника;
252
9. Графические возможности
cpoints - число вершин многоугольника. При этом последняя выводимая точ-
ка массива, содержащего координаты вершин многоугольника, всегда соеди-
няется отрезком прямой линии с первой точкой.
Для произвольного пятиугольника в физической системе координат
(рис. 9.8) параметры для Polygon будут следующими:
cpoints=5;
ppoints(l).xcoord=xl; ppoints(l).ycoord=yl
ppoints(2),xcoord=x2; ppoints(2).ycoord=y2
ppoints(3).xcoord=x3; ppoints(3),ycoord=y3
ppoints(4).xcoord=x4; ppoints(4).ycoord=y4
ppoints(5).xcoord=x5; ppoints(5),ycoord=y5
Puc. 9.8. Вывод многоугольника
Пример. Нарисовать правильный восьмиугольник,
program figure
use dflib
type(windowconfig) wc
type(wxycoord) p(N) ! массив вершин многоугольника
integer, parameter :: N=8 I N - число углов
real, parameter :: R=1.7 ! R - радиус описанной окружности
integer k
real f i
integer(2) ires2
logical(4) status
status=GetWindowConfig(wc); wc.title="BocbMnyronbHHK"C
status=SetWindowConfig(wc)
call SetViewPort(50,50,250,250)
ires2=SetBkColor(15); call ClearScreen(0)
ires2=SetWindow(.TRUE.,DBLE(-2),DBLE(-2),DBLE(2),DBLE(2))
ires2=SetColor(9)
do k=l,N ! формируем массив вершин многоугольника
fi=360.0*k/N ! угол поворота в полярной СК
253
И. Л. Артёмов. Fortran: основы программирования
р(к).wx=R*cosd(fi) ! координата х вершины многоугольника
р(k),wy=R*sind(fi) ! координата у вершины многоугольника
end do
ires2=Polygon_w($GBORDER,р,N)
end
Результат работы программы:
Вывод эллипса
Для рисования окружности, эллипса используется функция Ellipse.
ires2=Ellipse(control,xl,yl,х2,у2)
integer(2) ires2, control, xl, yl, x2, y2
Параметр control принимает значения SGBORDER или SGFILLINTERIOR;
х1,у1л2,у2 (wxl,wyl,wx2,wy2) - координаты левого верхнего и нижнего пра-
вого углов ограничивающего прямоугольника.
Пример. "Рог изобилия".
program horn
use dflib
type (windowconfig) wc
integer(2) ires2
logical(4) status <
integer fi
real xl, xc, yl, yc, x2, y2
status=GetWindowConfig(wc); wc.title=" Рог изобилия "С
status=SetWindowConfig(wc)
ires2=SetBkColor(15); call ClearScreen(0)
ires2=SetColor(0)
do fi=l,125
xc=360+80*COS(REAL(fi)/20); yc=290+70*Sin(REAL(fi)/20)
xl=xc-45+REAL(fi)/3; yl=yc-45+REAL(fi)/3
x2=xc+45-REAL(fi)/3; y2=yc+45-REAL(fi)/3
ires2=Ellipse($GBORDER,xl,yl,x2,y2)
end do
end
254
9. Гсафические возможности
Результат работы программы:
Вывод дуги и сектора эллипса
В этом случае вызывается функция Аге (дуга).
ires2=Arc(xl,yl,х2,у2,хЗ,уЗ,х4,у4)
integer(2) ires2, xl, yl, x2, y2, хЗ, уЗ, х4, у4
где xl,yl (wxl.wyl) - координаты левого верхнего угла ограничивающего
прямоугольника; х2,у2 (wx2,wy2) - координаты правого нижнего угла; хЗ,уЗ
(wx3,wy3) - начальный вектор; x4,y4 (wx4,wy4) - конечный вектор (рис. 9.9).
Рис. 9.9. Координаты точек, используемые при вызове ARC и PIE
Центр дуги находится в центре ограничивающего прямоугольника. На-
чальный вектор - вектор, проходящий через центр дуги и точку (хЗ,уЗ). Ко-
нечный вектор - вектор, проходящий через центр дуги и точку (х4,у4). Дуга
рисуется как часть эллипса в направлении против часовой стрелки, от на-
чального до конечного вектора.
По аналогии функция Pie (пирог, кусок) выводит сектор эллипса, только
добавляется еще один параметр - control, смысл которого такой же, как
у функций Rectangle, Ellipse, Polygon.
255
И. Л. Артёмов. Fortran: основы программирования __
ires2=Pie (control, xl,yl, х2,у2 , хЗ,уЗ, х4, у4)
integer(2) ires2, control, xl, yl, x2, y2, x3, y3, x4, y4
Пример. Построение круговой диаграммы,
program diagram
use dflib
type(windowconfig) wc
type(xycoord) xy
integer, parameter :: N=5 ! --- количество секторов
! --- доля каждого сектора в процентах
real :: percent(N)=(/10., 30., 15., 5.,40./)
integer(2) :: R=100, хЗ, y3, x4, y4
logical(4) status
integer(2) ires2
real f i
integer k
status=GetWindowConfig(wc) ; wc.title="KpyroBafl диаграмма"С
status=SetWindowConfig(wc)
call SetViewOrg(wc.numxpixels/2,wc.numypixels/2,xy)
! --- начало координат в центр экрана
ires2=SetBkColor(15); call ClearScreen(O)
x3=R*cosd(0.0); y3=R*sind(0.0) ! --- начальный вектор
fi=0.0
do k=l,N
fi=fi+360*percent(k)/100 ! --- вычисление текущего угла
х4=хЗ; у4=уЗ
x3=R*cosd(fi); y3=R*sind(fi)
ires2=SetColor(k+8) ! — у каждого сектора свой цвет
ires2=Pie($GFILLINTERIOR,-R,-R,R,R,хЗ,уЗ,x4,y4)
end do
end
Результат работы программы:
; GRAPH - (Круговая диаграмма! ВИЭ F
256
9. Гоафические возможности
Закрашивание произвольной области
Рассмотрим закрашивание геометрических областей произвольной фор-
мы. Типичный пример, область, замкнутая функциями у = 4 и у = х2. Сначала
заметим, что закрашиваемая область должна быть замкнутой, т. е. граница
имеет только сплошной тип линии, и имеет на всем протяжении постоянный
цвет границы. Например, не допускается, чтобы ограничивающая кривая бы-
ла пунктирной или состояла попеременно из красного и синего цвета.
Чтобы закрасить произвольную область, используются две функции
FloodFill или FloodFillRGB. Интерфейс функции FloodFi.ll следующий:
ires2=FloodFill(х,у,bcolor)
integer(2) ires2, х, у, bcolor
Точка (х,у) называется старто-
вой точкой заполнения и должна
лежать внутри закрашиваемой об-
ласти (рис. 9.10). С этой точки по
специальному алгоритму начинает
происходить закраска области, или,
как иногда говорят, заливка облас-
ти. Как только "разливаемая" крас-
ка встречает цвет границы bcolor,
заливка "поворачивает" в другую
сторону. Теперь вполне очевидно
требование на вышеперечисленные
ограничения. Аналогично исполь-
зуется функция FloodFillRGB.
Пример. Закрасить область, замкнутую функциями у = 4 и у = х2.
В рассматриваемом примере замкнутая область строится путем пересе-
чения линии у = 4 с ломаной линией, состоящей из отрезков, координаты ко-
торых вычисляются по формуле у = х.
program region
use dflib
type (windowconfig) wc
type (wxycoord) wxy
integer, parameter :: M=100 ! точки для построения параболы
real, parameter :: xmin=-3.0, xmax=3.0
integer(2) ires2
logical(4) status
integer(l) style(8)
real dx, xt
integer i
style=(/2*11100111, &
2*11011011,&
Замкнутая
граница,
сплошной
тип линии
Стартовая точка
заполнения
Рис. 9.10. Закрашивание произвольной
области
257
И. Л. Артёмов. Fortran: основы программирования
2#10111101,&
2#10111101,&
2#10000001,&
2#10111101,&
2#10111101,&
2#11111111/)
status=GetWindowConfig(wc); wc.title="napa6ona"C
status=SetWindowConfig(wc)
call SetViewPort(50,50,400,350)
ires2=SetWindow(.TRUE.,DBLE(-1),DBLE(-10), &
DBLE(10),DBLE(10))
ires2=SetBkColor(15); call ClearScreen(O)
call SetFillMask(style) ! --- маска заполнения - буквы "A”
ires2=SetColor(0) ! ---- цвет параболы черный
dx=(xmax-xmin)/(М-1)
call MoveTo_w(DBLE(xmin**2),DBLE(xmin),wxy)
do i=l,M ! -- рисование замкнутой области
xt=(i—1)*dx+xmin
ires2=LineTo_w(DBLE(xt**2),DBLE(xt))
end do
ires2=LineTo_w(DBLE(xmin**2),DBLE(xmin)) ! замыкаем область
ires2=SetColor(15) ! цвет заполнения области
ires2=FloodFill_w(DBLE(4.0),DBLE(0.0),0) ! закраска области
end
Результат работы программы:
: GRAPH - [Парабола]___________ ВИВ j
ААА
ааааааааааааааа
ааааааааааааааааааааааа
АААААААААААААААААААААААААААААА
аааааааааааааааааааааааааааааааааа
ЙААААААААААААААААААААААААААААААААА
ааааааааааааааааааааааааааааааа
ааааааааааааааааааааааааа
ааааааааааааааааа
аааааа
И
г!
.----------------ШЁ
9.5. Анимация
Рассмотрим, как осуществить движение созданного графического изо-
бражения.
Самый простой способ создать анимацию состоит в следующем: сначала
нарисовать объект, сделать задержку выполнения программы, удалить (за-
красить цветом фона) объект, снова нарисовать объект.
Пример. Отскакивание шарика от стенок прямоугольной области.
258
9. Гоафические возможности
program jump_off
use dflib
type (windowconfig) wc
integer, parameter :: colorfon=15 ! цвет фона
integer :: x,y,dx=l,dy=l ! x,y - текущие координаты
! центра шарика
! dx, dy - смещения по осям х и у
integer :: xmiri, ymin, xmax, ушах ! границы области
integer :: R=10 ! радиус шарика
logical(4) s t atus
integer(2) ires2
status=GetWindowConfig(wc) ; wc. title="lllapnK"C
status=SetWindowConfig(wc)
ires2=SetBkColor(colorfon); call ClearScreen(0)
xmin=wc.numxpixels/10; xmax=wc.numxpixels/4
ymin=wc.numypixels/10; ymax=wc.numypixels/4
ires2=SetColor(12)
ires2=Rectangle($GBORDER,xmin,ymin,xmax,углах)
x=xmin+(xmax-xmin)/2; y=ymin+(ymax-ymin)/2
do
x=x+dx ! вычисление новых координат центра шарика
y=y+dy
! проверка выхода за границы области
if ((x+R+2>xmax).OR.(x-R-2<xmin)) dx=-dx
if ((y+R+2>ymax).OR.(y-R-2<ymin)) dy=-dy
ires2=SetColor(9) ! показ шарика
ires2=Ellipse($GBORDER,x-R,y-R,x+R,y+R)
call SleepQQ(lO) ! задержка программы на 10 мс
ires2=SetColor(colorfon) ! закраска шарика цветом фона
ires2=Ellipse($GBORDER,x-R,y-R,x+R,y+R)
end do
end
В бесконечном цикле вычисляются новые координаты центра шарика с
одновременной проверкой выхода за границы области. После рисования ша-
рика происходит задержка выполнения на 10 мс. и повторное рисование, но
уже цветом фона, что приводит к удалению шарика.
Фрагмент результата работы программы:
259
И. Л. Артёмов. Fortran: основы программирования
Если рисование движущегося объекта занимает достаточное количество
времени, то в этом случае рекомендуется созданный рисунок сначала запи-
сать в оперативную память, а затем выгрузить в нужной точке экрана.
Будем под термином образ понимать прямоугольную область, которая
охватывает полностью созданный для анимации рисунок. Очевидно, что чем
меньше прямоугольная область, тем эффективнее будет происходить воспро-
изведение движущейся картинки.
Перед тем как произвести запись образа в память, предварительно выяс-
няют, какой потребуется объем памяти для выполнения операции. Вызывая
функцию
imsize=ImageSize(xl,yl,х2,у2)
integer(4) imsize
Integer(2) xl, yl, x2, y2
можно определить число необходимых байтов для сохранения образа. Здесь
л!,у1 - координаты левого верхнего и х2,у2 - координаты нижнего правого
ограничивающего образ прямоугольника.
Следующий шаг - создание динамического массива, в который будет
происходить запись образа. Размер размещаемого массива в памяти компью-
тера совпадает по размеру с переменной imsize, т. е.
allocate (buffer(imsize) )
где buffer - одномерный массив типа integer(l) с атрибутом allocatable;
imsize - требуемое число байтов, возвращаемое функцией ImageSize.
После создания массива для хранения графического образа необходимо
записать образ в оперативную память, который выполняется подпрограммой
GetImage(xl,yl,x2,y2,buffer)
integer(2) xl, yl, x2, y2
integer(1) buffer
Координаты xl,yl и x2,y2 имеют такой же смысл, что и для ImageSize,
buffer - рассмотренный ранее динамический массив.
Теперь все готово для вывода картинки в нужной точке экрана с коорди-
натами (х,у). Сохраненный образ выгружается из оперативной памяти при
помощи подпрограммы
Putlmage (х,у,buffer,action)
integer(2) х, у, action
integer(1) buffer
Для осуществления анимации параметр action должен принять значение
SGXOR. В этом случае, если образ выводится дважды на изображение, изо-
бражение остается неизменным. Параметр action может принимать и другие
значения:
$GAND - результирующий образ является логическим И двух образов;
260
____________9^[Р£Ф-чеа<ие возможности
$GOR - результирующий образ является логическим ИЛИ двух образов;
$GPRESET -негатив;
$GPSET - позитив.
Следует заметить, что рассмотренные режимы action справедливы, если
образ выводится поверх изображения, составленного из графических прими-
тивов, при этом цвет фона не оказывает влияния на вывод.
Пример. Движение круга по окружности.
program animation
use dflib
type (windowconfig) wc
type (xycoord) xy
integer, parameter :: Mfi=360,R=100, il=10,j1=10,i2=40,J2=40
integer(l), allocatable :: BUF (.-)
integer(4) imsize 1 il,jl,i2,j2 границы образа
integer(2) ires2 ! imsize количество байт памяти
logical(4) status ! R - радиус окружности
integer fi,ic,jc
real xt, yt
status=GetWindowConfig(wc) ; wc.title="Движение"C
status=SetWindowConfig(wc)
call SetViewOrg(wc.numxpixels/2, wc.numypixels/2,xy)
imsize=ImageSize(il,jl,i2, j2) ! определяем размер образа
allocate(BUF(imsize)) 1 размещаем массив в памяти
ires2=SetColor(9) ! цвет круга
ires2=Ellipse($GFILLINTERIOR,il, jl,i2, j2) ! сплошная заливка
call Getlmage_w(11,jl,12,j2,BUF) ! запись образа в память
ires2=SetBkColor(15); call ClearScreen(0)
ic=(i2-il)/2; jc=(j2-jl)/2 ! центр круга лежит на окружности
ires2=SetColor (12) ! окружность
ires2=Ellipse($GBORDER,INT2(-R+ic) ,INT2( R+ic),&
INT2( R+jc),INT2(-R+jc))
do ! ---- бесконечный цикл
do fi=l,Mfi,2
xt=R*cosd(REAL(fi))
yt=R*sind(REAL(fi))
call Putlmage (INT2 (xt) , INT2 (yt) , BUF, $GXOR) !-вывод
call SleepQQ(5) 1 задержка программы на 5 мс
call Putlmage (INT2 (xt) ,INT2(yt), BUF, $GXOR) I---янвоп
end do
end do
end
261
И. fl. Артёмов. Fortran: основы программирования
Результат работы программы:
9.6. Работа со шрифтом
Шрифт - это шаблон, по которому происходит визуализации текста на
экране компьютера. В папке FONTS, системной папке Windows, содержатся
шрифты, установленные в системе и которые можно использовать при напи-
сании графических программ. Шрифты бывают двух типов: растровые и век-
торные.
Растровые шрифты хранят изображение каждого символа в виде двоич-
ной битовой карты (аналогия с маской заполнения), где каждый бит соответ-
ствует пикселу экрана и равен единице, если пиксел отображается цветом ус-
тановленного для отображения шрифта, и нулю, если цвет пиксела равен
цвету фона.
Векторные шрифты задаются как множество отрезков прямых, являются
масштабируемыми и одинаково выглядят на экране и при печати на принтере.
Чтобы выводить текст в нужном шрифте, в первую очередь выполняется
инициализации или регистрация шрифтов в программе путем вызова функ-
ции
ires2=InitializeFontsО ! Инициализировать шрифты
integer(2) ires2
возвращающей число установленных и инициализированных шрифтов.
Для установки текущего шрифта и стилей его отображения вызывается
функция
ires2=SetFont(options) 1 Установить шрифт
integer(2) ires2
character*(*) options
где options - символьная строка, содержащая имя шрифта и его характери-
стики. Перечислим некоторые из них:
t’fontname’ - имя устанавливаемого шрифта, например t’Arial Суг’;
hy - высота символа в пикселах, например h 10;
wx - ширина символа в пикселах, например wl5.
262
9. Графические возможности
Если заданы несуществующие размеры, то в случае векторного типа
происходит масштабирование шрифта, растровый тип не масштабируется.
v - выбор векторного шрифта (Roman, Script);
г - выбор растрового шрифта (Courier, Helvetica);
е - вывод текста в полужирном стиле;
и - вывод подчеркнутого текста;
i - вывод теста курсивом.
Выаод текста в зависимости от выбранного шрифта осуществляет под-
программа
OutGText(text) ! Выводить графический текст
character*(*) text
Здесь text - символьная строка выводимого текста. Отображение текста про-
исходит вместе с хвостовыми пробелами, с текущей графической позиции
вывода, которую можно изменить, вызывая подпрограмму MoveTo.
Функция
ires2=GetGTextExtent(text)
integer(2) ires2
character*(*)
позволяет определить длину в пикселах выводимого графического текста
с учетом выбранного шрифта.
Выводимый текст можно поворачивать на определенный угол, вызывая
подпрограмму
call GetGTextRotation(degrees)
integer(4) degrees
где degree - параметр, задающий ориентацию текста в десятках градусах, т. е.
если нужно повернуть текст на 45°, то degree - 450. Поворот текста произво-
дится против часовой стрелки.
Пример. Вывод координатной оси.
program scale
use dflib
type (windowconfig) wc
type (wxycoord) wxy
type (xycoord) xy
integer, parameter :: M=10
character(4) file
character(100) text
real xmin, xmax, ymin, ymax, dx, dy, xt, yt, delta
integer i
integer(2) i r es 2
logical(4) status
status=GetWindowConfig(wc); wc.title="KoopnnHaTHaH ось"С
263
И. fl. Артёмов. Fortran: основы программирования
status=SetWindowConfig(wc)
ires2=SetBkColor(15); call ClearScreen(0)
xmin=-2.0; xmax=3.0; ymin=-3.0; ymax=4.0
dx=(xmax-xmin)/(M-l)
call Setviewport(10,10,500,500)
ires2=SetWindow(.TRUE.,DBLE(xmin-dx),DBLE(ymin), &
DBLE(xmax+dx),DBLE(ymax))
ires2=InitializeFonts()
ires2 = SetFont(1t''Arial Cyr''hl0w7b') 1 -- выбор шрифта
• ires2=SetColor(0) 1 рисование оси
call MoveTo_w(DBLE(xmin),DBLE(0.0),wxy);
ires2=LineTo_w(DBLE(xmax),DBLE(0.0))
dy=(ymax-ymin)/100
do i=l,M 1 рисование шкалы
xt=(i-1)*dx+xmin
call MoveTo_w(DBLE(xt),DBLE(-dy),wxy)
ires2=LineTo_w(DBLE(xt),DBLE(dy))
. write(file,"(f4.1)") xt
if ((xt>0.0).AND.(xt< 1.0)) file(l:l)=" 0“
if ((xt<0.0).AND.(xt>-l.0)) file(1:2)="-0"
delta=GetGTextExtent(file)
yt=-2*dy
call GetViewCoord_w(DBLE(xt),DBLE(yt),xy)
call MoveTo(INT2(xy.xcoord-delta/2),INT2(xy.ycoord),xy)
call OutGText(file)
end do
end
В рассмотренном примере для вывода числовых данных используется
внутренний файл.
Результат работы программы:
9.7. Некоторые примеры графических программ
В этом разделе рассмотрим графические программы, которые будут в
дальнейшем использоваться и просто показывают красоту математики и про-
граммирования.
264
9. Гоафичвскив возможности
Пример. Построить расчетную сетку со сгущением вблизи двух проти-
воположных границ (рис. 9.11).
Рис. 9.11. Сгущающаяся и равномерная расчетные сетки
Подобные вопросы очень часто встречаются при численном решении за-
дач вычислительной гидромеханики и теплообмена. Будем использовать сле-
дующее преобразование (подробную информацию см. в [1]), позволяющее
сгустить расчетную сетку вблизи двух противоположных границ прямо-
угольной области:
(Р + 2а) [(₽ +1) /(Р - l)f - Р + 2а
У = h---------Г—-----------’
(2а + 1)|1 + [(Р + 1)/(Р-1)] j
где h = 1.0, а = 0.5, р=1.05.
Построение сетки сводится к последовательному рисованию в цикле сна-
чала горизонтальных, а затем вертикальных линий.
program grid
use dflib
type(windowconfig) wc
type(wxycoord) wxy
integer i, j
real, parameter :: h=2.0, L=3.0
integer, parameter :: Mi=50, Mj=60
real xmin, xmax, ymin, ymax, dx, dy, xl, x2, yl, y2, xt, yt
integer(2) ires2
logical(4) status
status=GetWindowConfig(wc) ; wc.title="Pac4eiHaH сетка"С
status=SetWindowConfig(wc)
call SetViewPort(10,10,460,310)
ires2=SetBkColor(15); call ClearScreen(O)
ires2=SetColor(0)
xmin= 0.0; ymin= 0.0; xmax=L; ymax=h
ires2=SetWindow(.TRUE. , DBLE(xmin),DBLE(ymin),&
DBLE(xmax),DBLE(ymax) )
265
И. fl. Артёмов. Fortran: основы программирования
dy=l.О/(Mj-1); dx=L/(Mi-l)
1 рисование горизонтальных линий
xl=0.0; x2=L
do j=l,Mj
yt=(j-l)*dy
yt=F(yt)
call MoveTo_w(DBLE(xl),DBLE(yt),wxy)
ires2=LineTo_w(DBLE(x2),DBLE(yt))
end do
1 рисование вертикальных линий
yl=0.0; y2=h
do i=l,Mi
xt=(i-1)*dx
call MoveTo_w(DBLE(xt),DBLE(yl),wxy)
ires2=LineTo_w(DBLE(xt),DBLE(y2))
end do
contains
real function F(x)
real x
real, parameter :: alfa=0.5, betta=1.05
real p
p=((betta+1)/(betta-1))**((x-alfa)/(1-alfa))
F=h*((betta+2*alfa)*p-betta+2*alfa)/((2*alfa+l)*(1+p))
end function F
end
Результат работы профаммы:
266
9. Графические возможности
Пример. Построение векторного поля.
Пусть дано множество точек с коорди-
натами (xt.yt), в каждой из которых извест-
ны проекции вектора и на оси декартовой
системы координат их и иу. Требуется по-
строить векторное поле (рис. 9.12).
Рис. 9.12. Векторное поле
Векторное поле состоит из множества геометрических объектов - стре-
лок, для построения которых необходимо знание четырех точек (xt,yt), (хс,ус),
(xa,ya), (xb,yb) (рис. 9.13).
Рис. 9.13. Построение стрелки
Так как точка (xt.yt) считается известной, даны их и иу, то и = {их,иу} =
= {хп - xt, уп - yt] и, следовательно,
хп = xt + их',
уп = yt + иу.
Обозначая длину стрелки L = Jux1 + иу2 , будем считать, что длина на-
конечника каждой стрелки постоянна и составляет a-Lmax, где 0<а<1,
Атах - максимальная длина стрелки. Тогда, записывая очевидное соотно-
шение
L + a-Lmax _ xc-xt _ xc~xt
L xn-xt их
267
И. fl. Артёмов: Fortran: основы программирования __
и аналогичное для направления у, находим:
хс = xt + их • Д;
ус = yt + иу • Д.
. L+aLmax
где Д =----------.
L _
Вектор п перпендикулярный вектору и, будет л={иу,-их} =
= {-иу, их}. Считая, что длина основания наконечника также постоянна и со-
ставляет Р- Lmax , где 0 < Р < 1, получим:
па = {ха -хп,уа- ул} = {иу • h, -их h{,
nb = {xb-xn,yb-yn} = {-uy-h,uxh}',
или
ха = хп + иу • h; xb = хп-иу- h‘,
и
ya = yn-ux-h; yb=yn + ux-h,
, B-Lmax
где h =J.
Для рисования векторного поля применим оконную систему координат,
размеры которой ограничены xmin < х < xmax и ymin < у < ymax . Чтобы
стрелки умещались на экране, необходимо определиться с масштабом. Будем
считать, что самый длинный вектор составляет 1/10 максимального размера
оконной системы координат. В этом случае каждую проекцию их и иу следу-
ет умножить на масштабный множитель
max (х max-х min, у max-у min)
scale =--------------------------.
10-Lmax 1
Построим векторное поле на примере прямоугольной области размера
0 < х < 1.0 и 0 < у < 1.0, в которой заданы случайные значения xt, yt, их, иу.
program vector
use dflib
type (windowconfig) wc
real xt, yt, ux, uy
integer k
logical(4) status
integer(2) ires2
status=GetWindowConfig(wc); wc.title="BeKTopHoe поле"С
status=SetWindowConfig(wc)
ires2=SetBkColor(15); call ClearScreen(0)
268
9. Гоафичвскив возможности
open (1,file="random.dat")
do к=1,200 ! формируем случайные значения
call random__number (xt) ; call randam_numbsr(yt);
call randctn_nuiiiber (ux) ; call random_number (uy) ;
xt=0.5-xt; yt=0,5-yt
ux=0.5-ux; uy=0.5-uy
write(1,*) xt, yt, ux, uy
end do
closed)
call VectorPlot("random.dat",10,10)
end
subroutine VectorPlot(filename,Mx, My)
use dflib
character(*) filename
integer Mx, My
type (wxycoord) wxy, T(3)
real, parameter :: alfa=0.2, betta=0.05, sc=0.1
real, allocatable :: x(:), y(:), ux(:), uy(:), L(:)
real xt, yt, xn, yn, xa, ya, xb, yb, xc, yc
real uxt, uyt, Lmax
real xmin, xmax, ymin, ymax, dx, dy
integer M, i
integer(2) ires2
open (1,file=filename)
M=0
do while (.NOT.EOF(1)) 1 подсчитаем количество точек
read(1,*) xt, уt, uxt, uyt
M=M+1
end do
rewind(1)
allocate(x(M),у(M),ux(M),uy(M))! создадим массивы
i=l
do while (.NOT.EOF(l))
read(1,*) x(i), y(i), ux(i), uy(i)
i=i+l
end do
close(1)
allocate(L(M)) ! создали массив для длин векторов
L=sqrt(ux**2+uy**2)
Lmax=maxval(L) 1 находим самый длинный вектор
xmax=maxval(х); xmin=minval(х)
ymax=maxval(у); ymin=minval(у)
scale=sc*max(xmax-xmin,ymax-ymin)/Lmax
269
И. fl. Артёмов. Fortran: основы программирования
ux=ux*scale; uy=uy*scale
call SetViewPort(Mx,My,Mx+400,(My+400)/(xmax-xmin))
dx=(xmax-xmin)/10; dy=(ymax-ymin)/10
ires2=SetWindow(.TRUE.,DBLE(xmin-dx),DBLE(ymin-dy), &
DBLE(xmax+dx),DBLE(ymax+dy))
ires2=SetColor(15)
ires2=Rectangle_w(3,DBLE(xmin-dx),DBLE(ymin-dy), &
DBLE(xmax+dx),DBLE(ymax+dy))
ires2=SetColor(0) 1 цвет стрелок чёрный
do i=l,M
xt=x(i); yt=y(i)
xn=xt+ux(i); yn=yt+uy(i)
! убираем нулевые и мелкие векторы
if (L(i)/Lmax<0.001) cycle
delta=(L(i)+alfa*Lmax)/L(i)
xc=xt+ux(i)*delta; yc=yt+uy(i)*delta
h=betta*Lmax/L(i)
xa=xn+uy(i)*h; ya=yn-ux(i)*h
xb=xn-uy(i)*h; yb=yn+ux(i)*h
call MoveTo_w(DBLE(xt),DBLE(yt),wxy)
ires2=LineTo_w(DBLE(xn),DBLE(yn)) ! стрелка
T(1).wx=DBLE(xa); T(1).wy=DBLE(ya) 1 наконечник
T(2),wx=DBLE(xb); T(2).wy=DBLE(yb)
T(3).wx=DBLE(xc); T(3).wy=DBLE(yc)
ires2=Polygon_w(3,T,3)
end do
deallocate(x,y,ux, uy, L) ! освободили память
end subroutine VectorPlot
Подпрограмма VectorPlot отображает векторное поле. Входным парамет-
ром является имя файла данных и координаты Мх, Му исходной точки рисо-
вания векторного поля. Такой интерфейс является достаточно простым и
универсальным.
Построение поля начинается с определения количества точек путем чте-
ния содержимого файла. Затем происходит создание динамических массивов
для хранения xt, yt, их, иу и длин векторов L. Проводится поиск максималь-
ных размеров области и максимальной длины вектора. Рисование стрелок
осуществляется в цикле, в котором вычисляются точки хп, уп, ха, уа, xb, yb.
Наконечник стрелки отображается как закрашенный треугольник. В конце
работы подпрофаммы освобождается память, отведенная ранее под динами-
ческие массивы.
270
9. Гоафические возможности
Результат работы программы:
В заключение рассмотрим программы по рисованию фракталов. Фрак-
тал - это геометрический объект, в котором один и тот же фрагмент повторя-
ется при каждом уменьшении масштаба.
Пример. Дерево Пифагора.
В дереве Пифагора самый главный квадрат с номером 1 содержит равно-
бедренный треугольник, от которого растут два других квадрата меньшего
размера. Если площадь первого квадрата равна единице, то сумма площадей
квадратов 2 и 3 также равна единице. При этом квадраты 2 и 3 повернуты
влево и вправо на 45°. Аналогичная картина наблюдается для следующего
уровня - квадраты 4, 5, 6 и 7 (рис. 9.14).
Как легко видеть, для построения дерева Пифагора можно воспользо-
ваться рекурсивной подпрограммой, которая будет работать по следующему
словесному алгоритму:
271
И. Л. Артёмов. Fortran: основы программирования
1. Относительно верхнего левого угла основного квадрата создать новый
квадрат в l/(sqrt(2.0)) раза меньше исходного.
2. Повернуть относительно верхнего левого угла созданный квадрат вле-
во на 135°.
3. Относительно верхнего правого угла основного квадрат создать новый
квадрат в l/(sqrt(2.0)) раза меньше исходного.
4. Повернуть относительно верхнего правого угла созданный квадрат
вправо на 135°.
5. Рекурсивно вызывать подпрограмму для созданных "дочерних" квад-
ратов.
6. Закончить, когда будет нарисован N-й уровень дерева Пифагора.
Для реализации данного алгоритма в программе используются следую-
щие подпрограммы:
Pifagor_Tree - рисует дерево Пифагора;
Turn - производит вращение квадрата на произвольный угол относитель-
но выбранной точки;
Decrease - уменьшает квадрат в l/(sqrt(2.0)) раза.
Normal - вращение квадратов влево происходит относительно 2-й вер-
шины, вправо, относительно 3-й. После поворота порядок вершин меняется.
Для восстановления исходного порядка используется подпрограмма Normal.
Вращение четырехугольника на угол а вокруг точки (х0,у0) осуществ-
ляется следующей матрицей вращения:
' cos a sin a o'
T = -sin a cos a 0
-x0 (cosa-l)+ y0 - sina -x0 -sina-уо -(cosa-1 1
4 У. 1' Г * y')
*2 y2 1 x^ y', 1 xT= г, ,
*3 y3 1 Хз Уз 1
/4 У4 1, lX4 У4
т. е., чтобы повернуть четырехугольник, надо просто умножить матрицу
вершин четырехугольника на матрицу вращения и отобразить результат.
Уменьшение четырехугольника производится по формулам.
< = ^-(х/-Ль)+д^ /=^-(х~^о)+Уо-. 1 = 1>4
program Pifagor
use dflib
type (windowconfig) wc
integer, parameter :: N=4
type (wxycoord) p(N)
logical(4) status
272
9. Гсафические возможности
integer(2) ires2
integer(4) ires4
real xO, yO, xn, yn
status=GetWindowConfig(wc); wc.title="flepeBo Пифагора"С
status=SetWindowConfig(wc)
ires2=SetBkColor(15); call ClearScreen(O)
call SetViewPort(10,10,300, 300)
x0=-6.0; xn=6.0
y0=-4.0; yn=8.0
ires4=SetWindow(.TRUE.,DBLE(xO),DBLE(yO),DBLE(xn),DBLE(yn))
ires2=SetColor(0)
p(l).wx=-l.0; p(l).wy=-1.0
p(2).wx=-l.0; p(2).wy= 1.0
p(3).wx= 1.0; p(3).wy= 1.0
p(4) .wx= 1.0; p(4).wy=-1.0
ires2=Polygon_w($GBORDER,p,N)
call Pifagor_Tree(10,p) ! сохраним результаты
ires4=Save!mage("pifagor.bmp",10,10,400,400)
contains ! матрица вращения относительно произвольной точки
subroutine Turn(xO,yO,рр,teta)
double precision x0,y0
type (wxycoord) pp(4)
real teta
real T(3,3)
double precision pt(4,3)
T(l,l)= cosd(teta); T(l,2)=sind(teta); T(l,3)=0;
T (2,1)=-sind(teta); T(2,2)=cosd(teta); T(2,3)=0;
T(3,1)=-x0*(cosd(teta)-1)+y0*sind(teta);
T(3,2)=-x0*sind(teta)-yO*(cosd(teta)-1); T(3,3)=l;
pt(:,1)=pp(:).wx; pt(:,2)=pp(:).wy; pt(:,3)=l
pt=matmul(pt,T)
pp(:).wx=pt(:,1); pp(:).wy=pt(:,2)
end subroutine Turn
subroutine Decrease(x,y,p)
double precision x,y
type (wxycoord) p(4)
integer k
real c
c=sqrt(1./2.)
do k=l,4
p(k),wx=c*(p(k),wx-x)+x
p(k).wy=c*(p(k).wy—у)+y
end do
end subroutine Decrease
subroutine Normal(p,position) ! возвращаем номера углов
type(wxycoord) p(4)
273
И. Л. Артёмов. Fortran: основы программирования
double precision х(4),у(4)
integer position
x=p.wx; y=p.wy
p.wx=CSHIFT(x,position)
p.wy=CSHIFT(y,position)
end subroutine Normal
recursive subroutine Pifagor_Tree(N,pmain)
integer N
integer(2) ires2
type (wxycoord) pmain(4), pleft(4), pright(4)
double precision xO, yO
if (N==0) return ! предел
pleft=pmain
xO=pmain(2).wx; yO=pmain(2).wy
call Decrease(xO, yO, pleft)
call Turn(xO, yO, pleft,135.0)
ires2=Polygon_w($GBORDER,plef t, 4)
call Normal(pleft, 1)
pright=pmain
x0=pmain(3).wx; y0=pmain(3) . wy
call Decrease(xO, yO, pright)
call Turn(x0, yO, pright,-135.0)
ires2=Polygon_w($GBORDER,pright, 4)
call Normal(pright,-1)
call Pifagor_Tree(N-l,pleft)
call Pifagor_Tree(N-l,pright)
end subroutine Pifagor_Tree
end
Результатом работы программы является следующее окно и файл
pifagor.bmp, содержащий нарисованный фрактал.
Пример. Решето Серпинского.
Возьмем равносторонний треугольник. Проведем для каждой стороны сред-
нюю линию. Для трех появившихся треугольников применим аналогичные по-
274
9. Гоафические возможности
строения. Затем для все более возрастающего количества треугольников будем
проводить данные действия. В результате получим красивый фрактал, построен-
ный польским математиком Вацлавом Серпинским в 1915 г.
Алгорит м построения множества треугольников можно оформить рекур-
сивно следующим образом:
1. Вызвать процедуру, в которую передаются координаты вершин равно-
стороннего треугольника.
2. В процедуре происходит рисование треугольника.
3. Рассчитываются середины каждой из сторон.
4. Формируются вершины трех равносторонних треугольников.
5. Последовательно происходит рекурсивный вызов процедуры для каж-
дого из трех треугольников.
6. Процесс повторяется, пока не будет достигнут определенный уровень
вложенности треугольников.
program Serpinsky
use dflib
type (windowconfig) wc
type (xycoord) t(3) ! вершины основного треугольника
logical(4) status
integer(2) ires2
status=GetWindowConfig(wc) ; wc.title=”PemeTO Серпинского"С
status=SetWindowConfig(wc)
ires2=SetColor(0)
ires2=SetBkColor(15); call ClearScreen(0)
t(l).xcoord=320; t(l).ycoord=10
t(2).xcoord= 55; t(2).ycoord=470
t(3),xcoord=585; t(3).ycoord=470
call SerpinskTriangle(10,t)
contains
recursive subroutine SerpinskTriangle(N,p)
integer N
type (xycoord) p(3)
type (xycoord) pl(3), p2(3), p3(3), pa, pb, pc
integer(2) ires2
if (N==0) return ! предел
ires2=Polygon($GBORDER,p, 3)
! расчет середин каждой из сторон
pa.xcoord=(р(1).xcoord+p(2).xcoord)/2;
pa.ycoord=(p(l),ycoord+p(2).ycoord)/2
pb.xcoord=(p(l),xcoord+p(3).xcoord)/2;
pb.ycoord=(p(1).ycoord+p(3).ycoord)/2
pc.xcoord=(p(2).xcoord+p(3).xcoord)/2;
pc.ycoord=(p(2),ycoord+p(3).ycoord)/2
! формирование вершин полученных меньших треугольников
275
И. Л. Артёмов. Fortran: основы программирования
р1=(/ра,р(2),рс/); р2=(/р(1),ра,рЬ/); рЗ=(7рЬ,рс,р(3)/)
call SerpinskTriangle(N-l,pl)
call SerpinskTriangle(N-l,p2)
call SerpinskTriangle(N-l,p3)
end subroutine SerpinskTriangle
end
Результат работы программы:
Пример. Троичное дерево.
Похожим на решето Серпинского является фрактал-троичное дерево.
program ternary_tree
use dflib
type (windowconfig) wc
logical(4) status
integer(2) ires2
status=GetWindowConfig(wc) ; wc.title="Tpon4Hoe дерево"С
status=SetWindowConfig(wc)
call SetViewPort(10,10,210,210)
ires2=SetBkColor(15); call ClearScreen(0);
ires2=SetColor(0)
call TernaryTree(7,50,100,100)
end
recursive subroutine TernaryTree(N,s,x,у)
use dflib
integer x, y, s, N
type(xycoord) xy
integer xl,yl,x2,y2, хЗ,y3
if (N==0) return ! условие остановки
xl=s*cosd(0.0)+x; yl=s*sind(0.0)+y
x2=s*cosd(120.0)+x; y2=s*sind(120.0)+y
x3=s*cosd(240.0)+x; y3=s*sind(240.0)+y
call MoveTo(x,y,xy); ires2=LineTo(xl,yl) ! рисование веток
call MoveTo(x,у,xy); ires2=LineTo(x2,y2)
276
9. Гсафические возможности
call MoveTo(x,y,xy); ires2=LineTo(хЗ,уЗ)
call TernaryTree(N-l,s/2,xl,yl)
call TernaryTree(N-l,s/2,x2,y2)
call TernaryTree(N-l,s/2,хЗ,y3)
end subroutine TernaryTree
Результат работы программы:
Пример. Фрактал Мандельброта.
Один из самых красивых фракталов - фрактал Мандельброта. Алгоритм
построения основан на простом итеративном процессе
2(+1=^+С,
где Z и С - комплексные переменные.
Рисование начинается с выбора точки С прямоугольной области на ком-
плексной плоскости. Задается Z = 0, и итерационный процесс продолжается,
пока Z, не выйдет за пределы окружности радиуса 2 с центром в точке (0,0)
или после достижения максимальной итерации. В зависимости от того,
сколько итераций точка находилась внутри окружности, выбирается соответ-
ствующий цвет из палитры цветов. Если точка остается внутри окружности
по достижении максимальной итерации, то выбирается черный цвет.
program Mandelbrot
use dflib
type (windowconfig) wc
integer, parameter :: Mi=301, Mj=301, Mc=50
integer(4) ires4, color(Mc+1) I ----- массив значений цветов
logical(4) status
integer k, a, b
real xt , yt, dx, dy
complex :: C, Z0, i=(0.0,1.0) ! мнимая единица
status=GetWindowConfig(wc)
wc.title="®paKTan Мандельброта"С
277
И. Л. Артёмов. Fortran: основы программирования
status =SetWindowConfig(wc);
call SetViewPort(10,10,240,240)
do k=l,10 ! формируем палитру цветов
color(к) =RGBToInteger(255, 25*k, 0)
color(k+10) =RGBToInteger(255-25*k, 255, 0)
color(k+10*2)=RGBToInteger(0, 255, 25*k);
color(k+10*3)=RGBToInteger(0, 255-25*k, 255);
color(k+10*4)=RGBToInteger(25*k,0,255);
end do ! самый максимальный цвет черный
color(Мс+1)=0 !(для максимальной итерации)
х0=-2.25; хп=0.75; у0=-1.5; уп=1.5
ires2=SetWindow(.TRUE.,DBLE(х0) ,DBLE(у0),DBLE(xn),DBLE(yn))
dx=(xn-xO)/(Mi-1); dy=(yn-yO)/(Mj-1)
do a=l,Mi
do b=l,Mj
xt=(a-1)*dx+x0; yt=(b-1)*dy+y0
C=xt+i*yt; Z0=0+i*0
k=l
do while (((k<Mc+l).AND.(cabs(Z0)<2))) ! итеративный
! процесс
Z0=Z0**2+C
k=k+l
end do
iret4=SetPixelRGB_w(DBLE(xt),DBLE(yt),color(k))
end do
end do
end
Результат работы программы:
GRAPH - [Фрактал Мандельброта] М1Й1ЕЗ
278
9. Гоафические возможности
Итоги
1. Однооконный проект Standard Graphics обеспечивает простой и легкий
вывод графических данных. Для использования возможностей стандартной
графической библиотеки необходимо подключить модуль dflib. Вывод гра-
фических изображений происходит по пикселам, расположенным в виде
прямоугольной сетки. Пикселы нумеруются сверху вниз. и слева направо.
Самый верхний левый пиксел имеет координаты (0,0). Самый нижний пра-
вый зависит от текущего графического разрешения.
2. Для запроса информации о текущем графическом разрешении исполь-
зуется функция GetWindowConfig(wc). По умолчанию программа использует
текущее графическое разрешение, установленное в операционной системе
Windows. Для установки нового разрешения, смены заголовка окна и др. ис-
пользуется функция SetWindowConfig.
3. Оконная система координат, устанавливаемая функцией SetWindow,
предоставляет максимальную гибкость при отображении графических ре-
зультатов, так как позволяет задавать размеры в вещественной форме. Перед
использованием оконной системы координат рекомендуется установить порт
просмотра подпрограммой SetViewPort. Все процедуры, используемые для
рисования в оконной системе координат, имеют окончание _w.
4. Функции установки цвета фона SetBkColor и текущего цвета SetColor
используют стандартную палитру из 16 цветов. Функции SetBkColorRGB и
SetColorRGB совместно с функцией RGBToInteger позволяют задавать цвета,
меняя интенсивность красного, зеленого и голубого цветов.
5. Следующие графические примитивы отображаются при помощи сле-
дующих процедур:
• пиксел - SetPixel;
• эллипс, круг, окружность - Ellipse;
• дуга - Аге;
• сектор - Pie;
• отрезок прямой линии - MoveTo, LineTo;
• прямоугольник - Rectangle;
• закрашивание замкнутой области - FloodFill;
• многоугольник - Polygon.
6. Для установки стиля линии используется подпрограмма SetLineStyle,
для текущей заливки - подпрограмма SetFillStyle.
7. Для организации записи и чтения данных в оперативную память ис-
пользуются процедуры ImageSize, Getlmage, Putlmage. Для записи графиче-
ских изображений на жесткий диск применяется функция Saveimage.
8. Процедуры InitializeFonts, SetFont, OutGText, GetGTextExtent, Get-
GTextRotation позволяют работать co шрифтами. При выводе числовых дан-
ных предварительно следует преобразовать их в символьные значения, ис-
пользуя внутренние файлы.
279
10. от поставленной задачи
ДО ВЫЧИСЛИТЕЛЬНОЙ ПРОГРАММЫ
Эта тема посвящена программированию прикладных задач. В качестве
примера рассматриваются задачи вычислительной гидромеханики и тепло-
обмена. В данной теме будет разобрано, как начиная с постановки задачи
создавать законченные рабочие программы.
Быстродействие современных компьютеров позволяет моделировать раз-
нообразные физические явления. Например, при разработке новой модели
двигателя внутреннего сгорания можно рассчитать, как распределится тем-
пература при определенном режиме работы, каким будет давление, какое ме-
ханическое напряжение испытают внутренние части двигателя и т. д.
Чтобы описать подобные физические явления, создается математическая
модель. Как правило, такие модели основываются на дифференциальных
уравнениях в частных производных. Так, моделирование физических явлений
в теории теплообмена, упругости, гидро- и аэродинамике, электродинамике,
акустике построены именно на этих уравнениях. Например, уравнение теп-
лопроводности
дТ 2( д2Т д2Т д2т'\
--S2 а----1----1---
dt дх2 ду2 dz2
основа для моделирования широкого класса явлений, связанных с процесса-
ми распространения тепла в однородном изотропном теле, а также процессов
диффузии.
Как известно, решением дифференциального уравнения есть неизвестная
функция. Причем для уравнений в частных производных искомая функция
должна удовлетворять начальным и граничным условиям.
Начальные условия определяют значения искомой функции или ее про-
изводных во всей рассматриваемой области в начальный момент времени.
Так, если рассматривается задача об остывании тела, то начальным условием
может быть температура, известная в каждой точке этого тела. Обычно на-
чальные условия записываются в виде
Т{х, y,z,t0) = /0(х, у, z)
ИЛИ
7’(x,y,z,r0) = /0(x,y,z),
ЭТ(х,у,г,/0)
----------= /i(x,y,z),
где f0(x,y,z) и fi (х, у, г) - известные функции координат.
/ШОГ/ПИ0И 280
10. От поставланной задачи до вычислительной программы
Граничные условия, или краевые условия, определяют значение искомой
функции или ее производных на границе рассматриваемой области. Гранич-
ные условия делятся на три вида.
Граничные условия 1-го рода (условия Дирихле). В этом случае на гра-
ницах задана известная функция точек границы Ms и времени t
Граничные условия 2-го рода (условия Неймана). Здесь на границе зада-
ются значения нормальной производной искомой функции
он |s
Граничные условия 3-го рода, когда на границе задана линейная комби-
нация искомой функции и ее производной по нормали к границе
s
дТ L
a----+ ЬТ
дп
Чтобы решить уравнения в частных производных, иногда требуется зна-
чительное количество времени и сил. К сожалению, классические методы,
позволяющие получать аналитические решения, имеют ограниченную об-
ласть применения. В этом случае используют численные методы, при помо-
щи которых можно решить практически любую задачу. Одним из таких ме-
тодов является метод конечных разностей.
Основой метода конечных разностей является переход от непрерывной
области к конечно-разностной сетке. Пусть требуется найти решение Т(х,у) в
прямоугольной области 0 < х < ХО, 0 < у < КО. Для этого исследуемую об-
ласть покроем вычислительной сеткой (рис. 10.1), состоящей из (MixMj)
точек, расстояние между узлами которой вдоль оси х и вдоль оси у определя-
* А'О . ГО
ется соответственно как ох =---- Ду =-----.
Mi-1 MJ-1
Соответственно текущее положение точки будет задаваться формулами
х = (/-1)-Дх; у = (/-1)-Ду.
При таком подходе искомая функция Т(х,у) непрерывного аргумента за-
меняется функцией дискретного аргумента T(i,j). Таким образом, решение
уравнения в частных производных будет находиться в узлах расчетной сетки.
Уравнение в частных производных заменяется конечно-разностными
аналогами. Причем для каждого уравнения существует множество его конеч-
но-разностных аналогов, из которых выбрать самый лучший со всех точек
зрения просто невозможно; поэтому здесь следует руководствоваться прежде
281
И. Л. Артёмов. Fortran: основы программирования
всего поставленными целями: точность, экономичность, удобство программ-
ной реализации и др. ж»
Так, при решении задач переноса тепла может использоваться следую-
щая замена для производных:
дТ Ttf -T^j
dt txt
дТ _ ~ dT _ ~ ^j-i
dx 2-Ar dy 2-Ay
д2Т _Т^-2-Ти+Т^ d2T Т^-2-Т..+Т^
dx2 Ar2 dy2
А/
Тогда уравнение Лапласа
d2T d2T n
~+—r^Q
dx2 dy2
в разностном виде будет представлено так:
7;^-27],?+7;.ц . 7^,-27]j+7;
Ах2 Ау2
Таким образом, решение уравнения в частных производных сводится
к решению системы алгебраических уравнений.
Для решения разностных уравнений можно использовать прямые мето-
ды, позволяющие получить точное решение после использования определен-
282
io. От поставленной задачи до вычислительной программы
ных формул, или итерационные методы, уточняющие начальное приближе-
ние корня после выполнения некоторых однотипных вычислений.
Рассмотрим основные этапы работы вычислительной программы для за-
дач, которые будут рассмотрены в дальнейшем (рис. 10.2).
Рис. 10.2. Блок-схема вычислительной программы
для задач гидромеханики и теплообмена
283
И. Л. Артёмов. Fortran: основы программирования
1. Описываются все необходимые переменные, константы и типы данных
для решения вычислительной задачи.
2. Определяются шаги интегрирования Дх и Ду по соответствующим
координатным направлениям. Если требуется, то вычисляются дополнитель-
ные точки расчетной геометрии.
3. Задаются начальные условия для искомой сеточной функции и гра-
ничных условий на границах расчетной области, которые не зависят от рабо-
ты вычислительного цикла.
4. Начинается вычислительный цикл решения разностного уравнения.
Новые значения сеточной функции рассчитываются во внутренних узлах
расчетной сетки, т. е. для точек i = 2, Mi -1, j = 2, Mj -1 по выбранному ме-
тоду.
5. Рассчитываются граничные условия, зависящие от внутренних точек
расчетной области.
6. Происходит проверка сходимости получаемого решения. Если получе-
но решение с заданной точностью, то расчет прекращается. Для контроля
численного расчета рекомендуется выводить промежуточные оценки и теку-
щую итерацию.
7. Окончательный этап работы вычислительной программы - визуализа-
ция данных и если необходимо запись в файл полученных результатов.
10.1. Расчет стационарного поля температур
Дан длинный однородный брусок прямоугольного поперечного сечения
(Х0хУО) (рис. 10.3). На границах бруска поддерживается постоянная тем-
пература
TN = 400 С ; Ts = 0 С ;
Tw = 0°С; ТЕ =400 °C.
Требуется определить распределение температуры внутри бруска. Будем
считать, что температура внутри бруска не зависит от времени, тогда для ма-
тематического описания рассматриваемого процесса воспользуемся уравне-
нием Лапласа. В нашем случае используются граничные условия 1-го рода,
так как на границах поддерживается постоянная температура.
Гу 7’(х,0) = Т$ =0, Т(х,Г0) = TN = 400
дх2 ду2 ’ ’ T(0,y) = Tw =0, (Х0,у) = Т£ =400
* Обозначения , Tg, Т$, выбраны в соответствии со следующим: N -
север (north), Е - восток (east), S -юг (south), W - запад (west).
284
• 10. От поставленной задачи до вычислительной программы
I У.
(ХО,УО)
-------------—Тм =4 О 0 ' С------------------
Ти=О’С
ТЕ =400’С
----------------------------------------------------Ts = о ° с------------------------------------------ ►
(0,0)----------------------------------------------------х
Рис. 10.3. Геометрическая область
Заменим производные, входящие в уравнение, разностными аналогами,
используя центральные разности.
д2Т _ j — 27] у + 7]_,j дгт _ 7] J+1 ~ 27] + 7]
дхг Дх2 ’ дуг Ау1
Используя неявную разностную схему, запишем уравнение Лапласа в
следующем виде:
~ Kj' + ~ 2T*i' + т<j'i
+ - = 0 •
Для нахождения решения разностного уравнения воспользуемся итера-
ционным методом Гаусса-Зейделя:
т’ =
(Tk +Т* Tk 4-Т*
Дх2 Ду2
___________________-_______
2 2
Дх2 Ду2
где 7] j - значение на следующей итерации; - значение на предыдущей
итерации. Для увеличения скорости сходимости численного решения приме-
ним метод верхней релаксации:
7]*+‘ = 7] j • relax+7]‘; • (1 - relax),
285
И. Л. Артёмов. Fortran: основы программирования
где Т**1 - значение, рассчитанное по методу верхней релаксации, на Л + 1
итерации; Tt j - значение, рассчитанное по методу Гаусса-Зейделя; relax -
коэффициент верхней релаксации, 1.0 < relax < 2.0.
Ниже представлена программа, использующая рассмотренную выше
блок-схему (см. рис. 10.2).
! Используемые переменные:
! Mi, Mj - размеры сетки
! TW, TS, TN, ТЕ - температура на границах
! Т - массив для хранения значений сеточной функции Т(х,у)
! TNEW - значение на к+1 итерации
! TZV - значение, рассчитанное по методу Гаусса-Зейделя
! delta - ошибка вычислений (контроль за сходимостью)
! ostt - величина из разностного уравнения
! relax - коэффициент релаксации
! eps - допустимая ошибка численного расчета
! XO, YO, dx, dy - границы области, шаги интегрирования
! i,j - переменные цикла
! it - текущая итерация
! res - дополнительная переменная
program Temperature2D
use dflib
type(windowconfig) wc
integer, parameter :: Mi=300, Mj=150
real, parameter :: TW=0.0, TS=0.0, TN=400.0, TE=400.0
real T(Mi,Mj), TNEW, TZV, delta, ostt .
real, parameter :: relax=1.9, eps=0.01
real :: X0=2.0, Y0=1.0, dx, dy
integer i,j,it
logical(4) status
status=GetWindowConfig(wc); wc.title="Pac4eT поля темпера'тур"С
status=SetWindowConfig(wc)
write(*,*) "Solving numerical Laplace equation. Please wait..."
dx=x0/(Mi-1); dy=Y0/(Mj-l) ! построение расчетной сетки
ostt=2.0/dx* *2 + 2.0/dy**2
T=0.0 ! задание начального приближения
T(1,:)=TW; T(Mi,:)=TE; T(:,1)=TS; T(:,Mj)=TN 1 Г.У. 1-го рода
delta=1.0; it=l
do while (delta>eps) ! ---------------- вычислительный цикл
delta=0.0
do i=2,Mi-l ! расчет поля температур во внутренних точках
do j=2,Mj-l
TZV=((T(i+1,j)+T(i-1,j))/dx**2 + &
286
10. От поставленной задачи до вычислительной программы
(T(i,j+1)+Т(i,j-1))/dy**2)/ostt ! метод
! Гаусса-Зейделя
TNEW=TZV*relax+(1.О-relax)*T(i,j) 1 верхняя релаксация
delta=delta+abs(T(i,j)-TNEW)
T (i, j )=TNEW
end do
end do
delta=delta/Mi/Mj ! вычисление погрешности
it=it+l ! следующая итерация
! вывод промежуточных результатов
! (контроль за сходимостью)
if (mod(it,200)==0) write(*,*) "iterat = ",it,11 ext = ",delta
end do ! --------------------------------------------------
call DrawTemperature()
contains
subroutine DrawTemperature()
integer(2) xs, ys, xt, yt, ires2, N, dxt
integer(4) ires4, color(255)
real Tmin, Tmax, TS, dT
integer k, Стах, Cmin
integer xl,yl,x2,y2
type (xycoord) xy
character(3) file
do k=l,51 ! формируем таблицу цветов
color(k) =RGBToInteger(255-5*k,0,255)
color(k+51) =RGBToInteger(0,5*k,255)
color(k+51*2)=RGBToInteger(0,255,255-5*k)
color(k+51*3)=RGBToInteger(5*k,255,0)
color(k+51*4)=RGBToInteger(255,255-5*k,0)
end do
xs=100; ys=100 1 исходная точка рисования поля температур
Cmin=l; Cmax=255; I миним. и макс, номера цветов
Tmin=minval(Т); Tmax=maxval(Т) ! минимальная
! и максимальная температуры
do i=l,Mi ! рисование поля температур
xt=xs+i
do j=l,Mj
yt=ys+j
k=(T(i,Mj-j+l)-Tmin)*(Cmax-Cmin)/(Tmax-Tmin)+Cmin
ires4=SetPixelRGB(xt,yt,color(k))
end do
end do
xl=20; yl=300; y2=yl+20 ! рисование шкалы температур
do k=l,Cmax
x2=xl+2
287
И. Л. Артёмов. Fortran: основы программирования
ires4=SetColorRGB(color(к))
ires2=Rectangle(3,xl,yl,х2,у2)
. xl=x2
end do
ires2=InitializeFonts()
ires2 = SetFont(11'1 Courier''hl6w8b') !----выбор шрифта
N=15 ! рисование значений шкалы
ires2=SetColor(15)
dxt=(2*cmax-10) /(N-l)
dT=(Tmax-Tmin)/(N-l)
TS=0.0
xt=x2+10; yt=y2+20
do k=l,N
TS=(k-l)*dT+Tmin
xt=(k-1)*dxt+20
write(file,”(i3)") INT2(TS)
call MoveTo(xt,yt,xy)
call OutGText(file)
end do
end subroutine DrawTemperature
end
Результат работы программы:
Рассмотренную программу по расчету стационарного поля температуры
можно оформить с использованием подпрограммы FPS2H из библиотеки
288
10. От поставленной задачи до вычислительной программы
IMSL, которая содержит около 1000 процедур выполняющих всевозможные
математические вычисления *.
Подпрограмма FPS2H содержит в своем алгоритме быстрый пуассонов-
ский решатель и предназначена для решения уравнения Пуассона
д2и дги , v
— + —+с-« = р(х,у).
дхду
Вызов подпрограммы FPS2H происходит со следующими параметрами:
call FPS2H(prhs, brhs, coefu, nx, ny, &
ax, bx, ay, by, ibcty, iorder, u, Ldu)
где prhs - внешняя пользовательская функция - правая часть уравнения Пу-
ассона р(х,у). Так как функция передается как параметр, то она должна об-
ладать атрибутом external. В нашем примере функция записывается следую-
щим образом:
real function prhs(х,у)
real х,у
prhs=0.0 ! правая часть в уравнении Лапласа равна нулю
end function prhs
где brhs - внешняя пользовательская функция, содержащая граничные усло-
вия, имеет атрибут external и описывается в программе следующим образом:
real function brhs(iside,x,y)
integer(4) iside
real x,у
if (iside==l) brhs=400.
if (iside==2) brhs=100.
if (iside==3) brhs=100.
if (iside==4) brhs=400.
end function
Здесь параметр iside указывает номер границы (рис. 10.4). Так как значения
температуры постоянны, то параметры х и у не используются, но должны
быть описаны.
coefu - значение коэффициента при и в решаемом дифференциальном
уравнении. В нашем случае coefu=0.
nx, ny - количество узлов расчетной сетки вдоль направлений х и у соот-
ветственно. В программе принимается nx=Mi, ny=Mj.
ax, bx - размеры области интегрирования по направлению х, т.е ах=0.0,
Ьх=Х0.
’ Полный список процедур, входящих в состав библиотеки IMSL, следует смотреть
в справке или в книгах [7].
289
И. Л. Артёмов. Fortran: основы программирования
ay, by - размеры области интегрирования по направлению у, т. е. ау=0.0,
ау=У0.
by
by
верхняя 4
левая 3
правая 1
а у ---------нижн я я 2 -----—►
ах Ьх х
Рис. 10.4. Номера границ для функции brhs
Следует заметить что расстояния между узлами сетки вычисляются по
формулам
, Ьх-ах . Ьу-ау
Ах =------ и Ау = —---
пх-1 пу-1
и должны быть одинаковыми,
Лх = Ау,
где ibcty - целочисленный массив из четырех элементов, содержащий тип
граничных условий каждой границы. В нашем случае на всех четырех грани-
цах поставлены условия 1-го рода, т. е.
ibcty(1)=1 ! правая граница
ibcty(2)=1 ! нижняя граница
ibcty(3)=1 ! левая граница
ibcty(4)=1 ! верхняя граница
Если, например, на правой границе заданы условия 2-го рода (условия
Неймана) или смешанные условия - 3-го рода, то ibcty( 1 )=2 или ibcty(l)=3.
iorder - порядок точности конечно-разностной аппроксимации. Может
принимать значения 2 или 4. Обычно задают iorder=4.
и - массив содержащий сеточную функцию и(х,у) или решение диффе-
ренциального уравнения. Массив имеет размер
Ldu - ведущий размер массива, обычно полагают Ldu=nx.
Все параметры кроме и являются входными атрибут intent (in), параметр
и - выходной атрибут intent (out).
290
10. От поставленной задачи до вычислительной программы
Итак, для решения рассмотренной задачи с использованием подпрограм-
мы FPS2H следует составить программу. Перед компиляцией надо подклю-
чить библиотеку IMSL, выполнив такие действия: Project -> Settings ->
Fortran -> Category -> Libraries -> Other Library Options -> Use IMSL Math
Library -> OK.
program Temperature2D ! с использованием библиотеки IMSL
use dflib
type(windowconfig) wc
integer, parameter :: Mi=301, Mj=151 ! размеры расчетной сетки
real T(Mi,Mj) ! массив для хранения сеточной функции Т(х,у)
real :: Х0=2.0, У0=1.0
external prhs, brhs
integer ibcty(4), iorder, nx, ny
real coefu, ax, bx, ay, by
logical status
status=GetWindowConfig(wc)
wc.title="Pac4eT поля температур"С
status=SetWindowConfig(wc)
write(*,*) *************** using IMSL *********************
write(*,*) "Solving numerical Laplace equation. Please wait..."
ax=0.0; bx=X0 ! размеры области
ay=0.0; by=Y0
ibcty=l ! все граничные условия 1-го рода
coefu=0.0 ! коэффициент при и равен нулю
iorder=4 ! порядок точности аппроксимаций
nx=Mi; ny=Mj ! размеры сетки
call
FPS2H(prhs,brhs,coefu,nx,ny,ax,bx,ay,by,ibcty,iorder,T,nx)
write(*,*) "Temperature plot..."
call DrawTemperature()
contains
subroutine DrawTemperature()
end subroutine DrawTemperature
end
real function prhs(x,y) ! правая часть уравнения Лапласа
real х,у
prhs=0.0
end function prhs
real function brhs(iside,x,y) ! граничные условия
integer(4) iside
real x,у
if (iside==l) brhs=400.
if (iside==2) brhs=O..
291
И. Л. Артёмов. Fortran: основы программироввния
if (iside==3) brhs=O.
if (iside==4) brhs=400.
end function brhs
Результат работы программы аналогичен предыдущей программе. Одна-
ко по быстродействию последний вариант предпочтительнее, особенно на
мелких сетках. Конечно, вариант с использованием подпрограммы FPS2H
более выгодный. С другой стороны, есть существенные ограничения. Напри-
мер, должна использоваться сетка с одинаковым шагом по осям х и у. Функ-
ция, содержащая правую часть, должна быть представлена в аналитическом
виде, что затрудняет использование подпрограммы, когда функция fix,у) за-
дана только в узлах расчетной сетки.
10.2. Обтекание уступа потоком вязкой
несжимаемой жидкости
Дана геометрическая область - уступ (рис. 10.5). Требуется показать ха-
рактер движения ламинарного потока вязкой несжимаемой жидкости при
Известно, что для описания движения жидкости в прямоугольной облас-
ти используются уравнения Навье-Стокса, записанные в декартовой системе
координат:
292
10. От поставленной задачи до вычислительной программы
dUx д \ д ,г. „ v 1 дР , ~+ ~T~(PX^x)+~Z~(Py^X) = ~~T~ + v dt дх ду р дх f d2Ux дх2 \ d2Ux + ± ду2
dUy д ,т. ч д ,т. ч 1 дР 'd2UY д2иуУ
•" V (Р x^y )"* (Pyuy ) v +
dt дх' х Y> ду' ’ p ду дх2 ду2 )
dUx । _0
дх ду
Для решения системы уравнений воспользуемся подходом в переменных
вихрь Q и функция тока Т. Известно, что для плоского течения
дх ду дх ду
В этом случае уравнение неразрывности будет тождественно выполнять-
ся во всей расчетной области. Переходя к переменным fi и Т и выбирая в ка-
честве масштаба скорости Uх на входе, масштаба длины КО, получим сле-
дующую систему из двух безразмерных уравнений, т. е. Re = UxY0/v .
д \ д ,ПТ. ч 1 (d2Q.
—(Qt/X)+—(Ш/г ) = — YT + —г ;
дх ду Rel дх ду I
д2Ч Э2Т „
дх ду2
Решение полученной системы из двух дифференциальных уравнений бу-
дет зависеть от постановки граничных условий на шести границах, которые
задаются из следующих физических соображений.
Твердая стенка (границы Г1, Г2, ГЗ)
Условие прилипания для компонент скорости Ux = UY = 0; условие посто-
янства расхода для функции тока Т = const = 0; условие Тома для завихренно-
сти йц, =2——— (границы Г 1,ГЗ); =2——— (граница Г2).
dy dx
Входная граница Г4
Полагаем, что на входе задан профиль скорости Ux =1.0, Uy =0.0;
так как
их=-™,
Х ду
293
И. Л. Артёмов. Fortran: основы программирования
то
yt Тг
fcrxdy=-f d4f,
yl Т1
или, интегрируя, получим
Tt = Tl-yt + yl,
где Т/ - условие для функции тока на входной границе; Т1 = 0 - условие на
стенке в точке (0, yl), yt = (i -1) • dy.
Твердая стенка Г5
Условие прилипания для компонент скорости Ux = UY = 0:
Т = Т(1, ГО) = const;
условие Тома для вихря:
Выходная граница Гб
Условие установления потока:
dUx _ dUY JQJY
дх дх дх дх
Используя неявную разностную схему, получим два разностных урав-
нения:
o*+1 .il -Q*+1 .11 Q*+1 -11 -Clk+l -11
U XM,j ‘•‘•i-l.j UXi-l,j ! UYi.j+l _
2-Ax 2-Ay
uM+l _ э I u/i+1 W*+l _? w*+1 I W*+1
z tTu-i _ Qjt+1
Ax2 Ay2
Полученную систему алгебраических уравнений будем решать методом
нижней релаксации для уравнения переноса завихренности и методом верх-
ней релаксации для уравнения функции тока.
1
Re
V
Q*+l,j + ^i-lj ^i.j+1 +
Дх2 Ду2
+ convQ.
1
Re
' 2 2 '
Дх2 + Ду2?
294
10. От поставленной задачи до вычислительной программы
где convSl = Q‘+iJ 'UxM’J ^-x^ + ^+l 'Uyi^1
2 • Ax 2 • Ay
Q**1 = SI*j relaxSl + (1 - relaxSl)
T**1 = ’re/axT+ ’ 0 " •
Коэффициент релаксации для расчета уравнения переноса завихренности
примем relaxSi = 0.5 , для уравнения функции тока relax^Y = 1.5.
Расчетная программа.
! Используемые переменные
! Mi, Mj - размер сетки
! vx, ux, uy, ft - массивы для хранения значений сеточных
! функций завихренности, компонент скорости, функции тока
I vxzv, ftzv - значения, рассчитанные по методу
! Гаусса-Зейделя
! vxnew, ftnew - значения на к+1 итерации
! diff - диффузия вихря или функции тока
! duxvxdx, duyvxdy - конвективные производные
! conv - конвекция вихря
! ostt - величина из разностного уравнения
! relft, relvx - коэффициенты релаксации
! Re - число Рейнольдса
! dx, dy - шаги интегрирования
! XO, YO, Xl, Y1 - размеры вычислительной области
! Mxl, Му1 - граничные точки
! xt, yt - координаты узлов расчетной сетки
! i,j,ii - переменные для организации расчетных циклов
program LaminarFlow
use dflib
impl < nlfc none
integer, parameter :: Mi=50, Mj=50
real vx(Mi,Mj), ux(Mi,Mj), uy(Mi,Mj), ft(Mi,Mj)
real vxzv, vxnew, ftzv, ftnew, &
diff, conv, duxvxdx, duyvxdy, ostt
real :: relft=1.5, relvx=0.5, cxvx, cxft, Re
real dx, dy, XO, YO, Xl, Yl, xt ,yt
integer i,j,ii,Mxl,Myl
X0=2.0; Y0=1.0; Xl=0.5; Yl=0.5; Re=50.0
295
И. Л. Артёмов. Fortran: основы программирования
call Showinfo()
write(*,*) "Solving Navier-Stokes equations. Please wait..."
write(*,*) " iter cxvx cxft "
dx=X0/(Mi-1); dy=Y0/(Mj-l) ! вычисление шагов
Mxl=Xl/dx+l; Xl=(Mxl-1)*dx ! пересчет размеров под сетку
Myl=Yl/dy+l; Yl=(Myl-1)*dy
vx=0.0; ux=0.0; uy=0.0; ft=0.0
I --------------------- входные условия (Г4)
do j=Myl,Mj .
ft(1,j)=Y1-(j-1)*dy
end do
ux(1,Myl+1:Mj)=1.0
! ------------------------условие на верхней стенке (Г5)
ft(:,Mj)=ft(l,Mj)
ostt= 2.0/dx**2 + 2.0/dy**2
do while (ii<3000) ! ограничение по количеству итераций
cxvx=0.0; cxft=0.0
ii=ii+l
! --------- расчет завихренности -----------------
do i=2,Mi-l
do j=2,Mj-l
if ((i<=Mxl).AND.(j<=Myl)) cycle ! обход уступа
ux(i,j)= -(ft(i,j+1)-ft(i,j-1))/(2*dy)
uy(i,j)= (ft(i+1,j)-ft(i-1,j))I(2*dx)
duxvxdx=(ux(i+l,j)*vx(i+1,j)-ux(i-l,j)*vx(i-l,j))/(2*dx)
duyvxdy=(uy(i,j+1)*vx(i,j+1)-uy(i,j-1)*vx(i,j-1))/(2*dy)
conv=duxvxdx+duyvxdy
diff=(vx(i+l,j)+vx(i-l,j))/dx**2 + &
(vx(i,j+1)+vx(i,j-1))/dy**2
vxzv=(diff-Re*conv)/ostt
vxnew=vxzv*relvx+(1-relvx)*vx(i,j)
cxvx=cxvx+abs(vx(i,j)-vxnew)
vx(i,j)=vxnew
end do
end do
I --------- расчет функции тока ----------------
do i=2,Mi-l
do j=2,Mj-l
if ((i<=Mxl).AND.(j<=Myl)) cycle ! обход уступа
diff=(ft(i+1,j)+ft(i-1,j))/dx**2 + &
296
1а От поставленной задачи до якшиппитепьной программы
(ft(i,j+l)+ft(i,j-1))/dy**2
ftzv=(diff-vx(i,j))/ostt
ftnew=ftzv*relft+(1-relft)*ft(i,j)
cxft=cxft+abs(ft(i,j)-ftnew)
ft(i,j)=ftnew
end do
end do
! — расчет граничных условий для завихренности на стенке
do i=Mxl+l,Mi ! стенка Г1
vx(i,l)=2*(ft(i,2)-ft(i,l))/dy**2
end do
do j=l,Myl-l ! стенка Г2
vx(Mxl,j)=2*(ft(Mxl+1,j)-ft(Mxl,j))/dx**2
end do
do i=l,Mxl-l ! стенка ГЗ
vx(i,Myl)=2*(ft(i,Myl+l)-ft(i,Myl))/dy**2
end do
do i=l,Mi
vx(i,Mj)=2*(ft(i,Mj-1)-ft(i,Mj))/dy**2 ! стенка Г5
end do
! угловая точка из уравнения
i=Mxl; j=Myl
diff=(vx(i+1,j)+vx(i-l,j))/dx**2 + &
(vx(i,j+1)+vx(i,j-1))/dy**2
vx(i,j)=diff/ostt
i --------- выходные условия Гб
vx(Mi,:)=vx(Mi-1,:)
ft(Mi,:)=ft(Mi-l,:)
ux(Mi,:)=ux(Mi-l,:)
uy(Mi,:)=uy(Mi-1,:)
1 контроль за сходимостью численного решения
if (mod(ii,100)==0) write(*,100) ii, cxvx, cxft
end do
open(1,file='ux.txt') ! вывод массивов для просмотра
do j=Mj,1,-1
write(1,101) j, ux(:,j)
end do
close(1)
open(l,file='uy.txt')
do j=Mj,l,-l
write(l,101) j, uy(:,j)
end do
297
И. Л. Артёмов. Fortran: основы программирования
close(1)
open(l,file="res.dat")
do i=l,Mi,4 ! отображаем каждый четвертый
do j=l,Mj,2 ! и второй векторы
xt=(i-l)*dx; yt=(j-l)*dy
write(1,*) xt, yt, ux(i,j), uy(i,j)
end do
end do
close(1)
call VectorPlot("res.dat",230,50) ! построение
1 векторного поля
call DrawRegionO
100 format(i6,2(2x,e8.2))
101 format(i4,100e9.3)
contains
subroutine Showinfo() ! вывод информации о программе
type (windowconfig) wc
character(80) title
logical(4) status
write(title,"(3(A,i2))")"Обтекание уступа. Сетка &
(",Mi,"x",Mj,") . Re=" ,INT(Re)
status=GetWindowConfig(wc); wc.title=TR!M(title)//" "C
status=SetWindowConfig(wc)
end subroutine Showinfo
subroutine DrawRegionO ! рисование исследуемой области
use dflib
type(wxycoord) wxy
integer ires2
call MoveTo_w(DBLE(0.0),DBLE(Y0),wxy)
ires2=LineTo_w(DBLE(X0), DBLE(YO))
call MoveTo_w(DBLE(XO), DBLE(0.0),wxy)
ires2=LineTo_w(DBLE(Xl), DBLE(O.O))
ires2=LineTo_w(DBLE(Xl), DBLE(Yl))
ires2=LineTo_w(DBLE(0.0),DBLE(Yl))
end subroutine DrawRegion
end
subroutine VectorPlot(filename,Mx,My)
end subroutine VectorPlot
В программе используется разобранная в разд. 9.7 подпрограмма
VectorPlot для рисования векторного поля.
298
10. От поставленной задачи до вычислительной программы.
Результат работы программы:
. FLOW ♦ (Обтекание угг^па. Сетка 150x50). Re-50 |
Solving Kavier-Stokes equations. Please vait
cxvx^, cxfc^
lOfflK. Э2Е-Л2*^О Л 4Е+0Г
200*14). 12E+O2 ^0.27E+00
300 Jo.64E-K>l; ЧЭ.19Е+00
400 '*43.43Е4ОГ* '0.15E+00
500^ ,0.27Е+01ЛЭ.96Е~01
600 \ О.15ЕЮ1 To.S6E-01
700 4J.84E400 •*p.32E-01
800 *-0.46E+00 . X). 17E-01
900
.1000
1100
Д200
1300
1400
-0.2SE+00
/0.14Е+00 'fcfl.SZE-Oa
"’0.76Е-01
0.42Е-О1 , XL15E-02
О.23Е-О1 УтЭ.82Е-03
О.13Е-О1 0.44Е-03
•0.9SE-02
4L28E-O2
ИЕГ
Итоги
1. Началом в создании вычислительной программы является физико-
математическая постановка задачи и выбор метода решения.
2. Разберите все тонкости вычислительного метода и правила его исполь-
зования.
3. Определите, какие данные будут обрабатываться, и сделайте неболь-
шой набросок программы. Лучше составить план или схему, сопровождаю-
щиеся рисунками, формулами и т. д.
4. На первом этапе составляйте самую простую и частную программу.
5. Разбивайте программу на отдельные смысловые блоки, снабжайте не-
обходимыми комментариями. Похожие формулы, особенно если использу-
ются индексы массивов, записывайте друг под другом для более легкой чи-
таемости и проверки.
6. Проводите сравнение полученных результатов с известными аналити-
ческими, численными и экспериментальными данными. Используйте все
возможные средства для проверки получаемых результатов. Не жалейте вре-
мя на тестирование программы.
7. Для более быстрого нахождения вычислительных ошибок исключайте
(комментируйте) отдельные блоки в программе.
8. Делайте контрольную выдачу содержимого массивов в файлы.
9. Применяйте графические средства для визуализации полученных ре-
зультатов.
10. Эффективно используйте разработанные за последние 50 лет подпро-
граммы и функции, изучайте к ним руководства. Помните: Fortran имеет са-
мые богатые библиотеки, связанные с вычислениями.
299
РЕКОМЕНДУЕМАЯ ЛИТЕРАТУРА
1. Андерсон Д., Таннехилл Дж,, Плетчер Р. Вычислительная гидромеханика
и теплообмен. Т. 1. М.: Мир, 1990. 294с.
2. Боровин Г. К., Комаров М. М., Ярошевский В. С. Ошибки-ловушки при
программировании на Фортране. М.: Наука, 1987. 141 с.
3. Бартеньев О. В. Visual Fortran: новые возможности. - М.: Диалог-МИФИ,
1999. 301 с.
4. Бартеньев О. В. Современный Фортран. М.: Диалог-МИФИ, 2005. 560 с.
5. Бартеньев О. В. Фортран для студентов. М.: Диалог-МИФИ, 1999. 400 с.
6. Бартеньев О. В. Графика OpenGL: программирование на Фортране. М.:
Диалог-МИФИ, 2000. 368 с.
7. Бартеньев О. В. Фортран для профессионалов: математическая библиоте-
ка IMSL. В 3 ч. М.: Диалог-МИФИ, 2000.
8. Соловьев П. В. Фортран для персонального компьютера. М.: Арист, 1991.
223 с.
9. Калверт Ч. Освой самостоятельно программирование в Windows 95: Пер.
с англ. М.: БИНОМ, 1996. 1000 с.
10. Васильченко В. В. Программирование Windows-приложений на языке
Fortran: элементы управления и графика Windows. М.: Диалог-МИФИ,
2006. 400 с.
11. Немнюгин М. А., Стесик О. Л. Современный Фортран: Самоучитель.
СПб.: БХВ-Петербург, 2004. 496 с.
12. Рыжиков Ю. И. Современный Фортран. Учеб. - СПб.: Корона принт,
2004. 288 с.
13. Фортран 90. Международный стандарт: Пер. с англ. - М.: Финансы
и статистика, 1998. 378 с.
14. Штыков В. В. Fortran and Win32 API: создание программного интерфейса
для Windows средствами современного Фортрана. М.: Диалог-МИФИ,
2001.302 с.
300
ОГЛАВЛЕНИЕ
ПРЕДИСЛОВИЕ______________________________________________3
1. ПЕРВЫЕ ПРОГРАММЫ-------------------------------------------------4
1.1. Язык программирования, компилятор и среда разработки........4
1.2. Операторы, комментарии, ввод и вывод данных.................7
1.3. Переменные и константы хранят информацию...................10
Итоги................................................... 75
2. ТИПЫ ДАННЫХ____________________________________________________ 17
2.1. Тип integer - целочисленный тип............................17
2.2. Тип real - вещественный тип................................21
2.3. Программирование арифметических выражений..................24
2.4. Стандартные математические функции н подпрограммы..........29
2.5. Комплексный тип complex....................................35
2.6. Логический тип logical.....................................38
2.7. Символьный тип character...................................46
2.7. Производные типы данных....................................50
Итоги.......................................................54
3. УПРАВЛЯЮЩИЕ ОПЕРАТОРЫ...........................................55
3.1. Конструкции if.............................................55
3.2. Оператор множественного выбора select case и оператор stop.61
Итоги.......................................................66
4. ЦИКЛЫ......................................................... 67
4.1. Do-циклы...................................................67
4.2. Переменные-счетчики........................................73
4.3. Сумматоры - переменные, накапливающие сумму................77
4.4. Конструкция do while.......................................85
4.5. Вложенные циклы do.........................................88
4.6. Выходы из циклов. Бесконечные циклы и зацикливания.........97
Итоги......................................................102
5. МАССИВЫ........................................................104
5.1. Знакомство с массивами....................................104
5-2. Многомерные массивы.......................................111
5.3. Более глубокий взгляд на массивы..........................117
5.4. Операторы where н forall..................................123
5.5. Функции по работе с массивами.............................126
5.6. Динамические массивы......................................134
Итоги......................................................136
6. ФУНКЦИИ, ПОДПРОГРАММЫ И МОДУЛИ-------------------------------- 137
6.1. Функции...................................................138
6,2. Подпрограммы..............................................143
тмошои зо1
И. Л. Артёмов. Fortran: основы программирования
6.3. Внутренние переменные........................... 146
6.4. Статические и автоматические переменные.............153
6.5. Управление работой процедур....................... 157
6.6. Формальные и фактические параметры.................159
6.7. Массивы и символьные строки как параметры процедур..163
6.8. Механизм передачи параметров...................... 168
6.9. Внешние и внутренние процедуры. Интерфейсы процедур.173
6.10. Более глубокий взгляд на функции...................177
6.11. Модули........................................... 179
6.12. Функции и подпрограммы как параметры............. 189
6.13. Перегрузка функций и подпрограмм...................193
6.14. Рекурсивные процедуры..............................199
6.15. Объектные файлы, библиотеки lib и dll..............205
Итоги................................................208
7. ССЫЛКИ, УКАЗАТЕЛИ, ДИНАМИЧЕСКИЕ СТРУКТУРЫ
ДАННЫХ...................................................210
7.1. Ссылки, адресаты....................................210
7.2. Списки и структуры со ссылками на себя..............214
7.3. Целочисленные указатели.............................217
Итоги................................................219
8. ФАЙЛЫ....................................................220
8.1. Знакомство с файлами. Файловый ввод и вывод.........220
8.2. Разновидности файлов. Обработка файлов..............224
Итоги................................................229
9. ГРАФИЧЕСКИЕ ВОЗМОЖНОСТИ..................................230
9.1. Программы, использующие стандартные графические процедуры 230
9.2. Координатные системы................................234
9.3. Управление цветом...................................239
9.4. Графические примитивы...............................241
9.5. Анимация............................................258
9.6. Работа со шрифтом...................................262
9.7. Некоторые примеры графических программ..............264
Итоги................................................279
10. ОТ ПОСТАВЛЕННОЙ ЗАДАЧИ ДО ВЫЧИСЛИТЕЛЬНОЙ
ПРОГРАММЫ............................................. 280
10.1. Расчет стационарного поля температур...............284
10.2. Обтекание уступа потоком вязкой несжимаемой жидкости.292
Итоги................................................299
РЕКОМЕНДУЕМАЯ ЛИТЕРАТУРА....................................300
302