ЛАБОРАТОРНЫЙ ПРАКТИКУМ: «ПРОГРАММИРОВАНИЕ НА ФОРТРАНЕ»
СТРУКТУРА ПРАКТИКУМА
ВВЕДЕНИЕ
ВАЖНАЯ ИНФОРМАЦИЯ
1. ПРАВИЛА ЗАПИСИ ПРОГРАММЫ
1. Набор символов Фортрана
2. Форматы записи программы
3. Фиксированный формат
4. Свободный формат
2. ТРАНСЛЯЦИЯ ПРОГРАММЫ
1. Программа в одном исходном файле
2. Трансляция исходного файла
3. Трансляция нескольких исходных файлов
4. Трансляция модулей
3. КОНЦЕПЦИЯ ДАННЫХ ЯЗЫКА ФОРТРАН
1. Имена (идентификаторы)
2. Понятие типа
3. Буквальные константы
4. Разновидности типов и диапазоны значений
5. Скалярные переменные и константы
6. Массивы
7. Производные типы данных
4. ВЫРАЖЕНИЯ И ПРЕОБРАЗОВАНИЕ ТИПОВ
1. Скалярное присваивание
2. Арифметика Фортрана
3. Логические выражения
4. Работа с текстовыми строками
5. Операции с массивами
5. УПРАВЛЯЮЩИЕ ОПЕРАТОРЫ
1. Условный оператор и конструкция IF
2. Оператор варианта – конструкция CASE
3. Циклы – разновидности конструкции DO
4. Оператор GO TO
6. ВВОД/ВЫВОД ДАННЫХ
1. Простейшие операции ввода/вывода
2. Форматный ввод/вывод данных
3. Ввод/вывод массивов в неявных циклах
4. Файловый ввод/вывод
7. ПРОГРАММНЫЕ КОМПОНЕНТЫ И ЭЛЕМЕНТЫ ООП
1. Структура программных компонентов
2. Внешние подпрограммы
3. Внутренние подпрограммы
4. Модули как библиотеки производных типов
5. Встроенные функции Фортрана
ЗАДАЧИ ДЛЯ ПРОГРАММИРОВАНИЯ
5.3. Циклы – разновидности конструкции DO
При решении математических задач, связанных с вычислением последовательностей, полиномов и рядов возникает необходимость вычисления одинаковых по структуре выражений, зависящих от номера элемента, а также суммирования этих выражений, вычисления произведений и т.д. Для программной реализации таких алгоритмов практически во всех императивных языках программирования предусмотрены циклические конструкции. В Фортране 90/95 для этих целей используется конструкция DO.
Наиболее часто используются циклы в виде конструкции DO с фиксированным числом повторений, имеющие общий вид:
DO переменная цикла = нач_знач, кон_знач, шаг_изм
...блок операторов
END DO
В операторе DO указывается переменная цикла, которой присваивается начальное значение и, через запятую, конечное значение и шаг изменения переменной цикла. В строках, следующих за оператором DO, записываются операторы, выполняемые в цикле. Завершается конструкция цикла оператором END DO. Переменная цикла – это скалярная целая переменная, а ее начальное значение, конечное значение и шаг изменения должны быть скалярными целыми выражениями. Если шаг изменения переменной цикла равен единице, то его можно не записывать – т.е. первая строчка конструкции DO будет выглядеть как:DO переменная цикла = нач_знач, кон_знач
Число итераций цикла может быть вычислено по формуле:
(кон_знач – нач_знач + шаг_изм)/ шаг_изм)
Если результат меньше нуля цикл игнорируется. На практике, чтобы цикл был рабочим (не вырожденным) необходимо чтобы при положительном шаге конечное значение переменной было больше начального, а при отрицательном шаге – наоборот: начальное больше конечного.
Следует особо отметить, что в первой строке цикла, оператор DO должен быть единственным оператором.
В качестве простейшего примера использования конструкции DO можно рассмотреть вычисление суммы целых чисел от единицы до N (Пример 5.7). Здесь переменная цикла I изменяется от единицы до N (введенного с клавиатуры), с шагом единица (можно не писать). На каждой из N итераций цикла к изначально нулевой сумме (переменная SUM) прибавляется очередное число.
Пример 5.7. Вычисление суммы целых чисел от единицы до N.
program SUMN1 integer :: SUM = 0 !Инициализация суммы read*, N do I = 1, N SUM = SUM + I end do print *, “SUM=”, SUM end
Этот пример можно модифицировать несколькими способами:
- Просуммировать числа от N до единицы: заменив заголовок цикла «DO I = 1, N» на «DO I = N, 1, –1».
- Просуммировать только нечетные числа от единицы до N: заменив заголовок цикла «DO I = 1, N» на «DO I = 1, N, 2».
- Просуммировать только четные числа от единицы до N: заменив заголовок цикла «DO I = 1, N» на «DO I = 2, N, 2».
- Вычислить произведение чисел от единицы до N (факториал числа N, обозначаемый N!): инициализировав переменную SUM не нулем «SUM = 0», а единицей «SUM = 1», и заменив накопление суммы чисел «SUM = SUM + I» накоплением произведения «SUM = SUM * I» – можно поменять и имя переменной, в соответствии со смыслом новой задачи.
В нотации ФОРТРАН-77 (поддерживаемой Фортраном 90/95) цикл начинается оператором DO, в котором, после ключевого слова DO, сначала указывается метка (например, «100») последнего оператора тела цикла, а затем, как и в конструкции DO, переменная цикла, диапазон ее изменения и шаг. Далее следует тело цикла, завершающееся оператором, помеченным меткой, указанной в операторе DO. Как правило, меткой помечается «пустой» (не вызывающий какого либо действия) оператор CONTINUE (Пример 5.8). Отличительной особенностью цикла DO в ФОРТРАНЕ-77 является то, что переменная цикла, ее границы и шаг могут быть не только целыми, но и вещественными, однако от такой экзотической особенности в последующих стандартах было решено отказаться.
Пример 5.8. Вычисление суммы целых чисел в ФОРТРАН-77.
program SUMN2 integer :: SUM = 0 !Инициализация суммы read*, N do 100 I = 1, N SUM = SUM + I 100 continue print *, “SUM=”, SUM end
В стандарты Фортран 90/95 была внесена и тут же отнесена к избыточным и не рекомендованным свойствам языка, циклическая конструкция c предусловием DO WHILE имеющая общий вид:
DO WHILE (лог_выр)
...блок операторов
END DO
Если при входе в цикл логическое выражение в условии DO WHILE имеет значение ИСТИНА (.TRUE.), то выполняется первая итерация цикла. Проверка осуществляется перед каждой следующей итерацией – и как только выражение будет иметь значение ЛОЖЬ (FALSE) – итерации прекращаются. В качестве иллюстрации с помощью конструкции DO WHILE проведено вычисление суммы чисел (Пример 5.9), аналогичное Примерам 5.7 и 5.8.
Пример 5.9. Вычисление суммы целых чисел с помощью DO WHILE.
program SUMN3 integer :: SUM = 0, I=0 !Инициализация суммы и переменной цикла read*, N do while(I < N) I=I+1 SUM = SUM + I end do print *, “SUM=”, SUM end
Причиной отказа от конструкции DO WHILE связана с возможными проигрышами в оптимизации программного кода: проблема подробно описана в работе: Optimizing Supercompilers for Supercomputers, M.Wolfe (Pitman, 1989), гл. 10.
Для управления работой цикла в зависимости от условий рекомендуется конструкция (по сути реализующая бесконечный цикл):
DO
...блок операторов
END DO
с включением в тело цикла операторов IF – для контроля условий, а так же операторов EXIT – для выхода из текущего цикла и операторов CYCLE – передающих управление на END DO текущего цикла и, таким образом, инициирующих начало следующей итерации цикла. Операторы EXIT и CYCLE, как правило, используются в составе условного оператора IF.
Вычисление суммы целых чисел от 1 до N в вариантах с предусловием и постусловием представлено в Примерах 5.10 и 5.11. Прекращение работы цикла осуществляется оператором EXIT.
Пример 5.10. Вычисление суммы целых чисел в цикле с предусловием.
program SUMN4 integer :: SUM = 0, I=0 !Инициализация суммы и переменной цикла read*, N do if (I == N) exit I=I+1 SUM = SUM + I end do print *, “SUM=”, SUM end
Пример 5.11. Вычисление суммы целых чисел в цикле с предусловием.
program SUMN5 integer :: SUM = 0, I=0 !Инициализация суммы и переменной цикла read*, N do I=I+1 SUM = SUM + I if (I == N) exit end do print *, “SUM=”, SUM end
Действие оператора CYCLE можно наблюдать (Пример 5.12) при селекции (отборе) и выводе на экран только четных чисел из диапазона от единицы до N. Если на очередной итерации переменная цикла I будет иметь нечетное значение, то оператор CYCLE сразу передает управление на END DO, минуя оператор PRINT и тем самым игнорируя печать нечетных чисел.
Пример 5.12. Печать четных чисел в диапазоне от 1 до N.
Program EVENPRINT read*, N do I = 1, N if (I / 2 * 2 /= I) cycle print *, I end do end
Обзор циклических конструкций Фортрана и их возможностей будет не полным без упоминания о работе с массивами – это одно из наиболее частых применений циклов. В качестве иллюстраций рассмотрим поиск максимального элемента в одномерном массиве (Пример 5.13) и транспонирование двумерной квадратной матрицы размером с экстентами равными N (Пример 5.14).
Пример 5.13. Поиск максимального элемента в одномерном массиве.
program ARRMAX integer, parameter :: N = 5 integer, parameter, dimension(N):: ARR = (/ 1, 4, 5, 2, 3/) MX = ARR(N – 1) ! Разгонное значение для максимального элемента do I = 1, N if( ARR(I) > ARR(MX)) MX = ARR(I) end do print*, NUMAX end
На роль максимального элемента в одномерном массиве (Пример 5.13) сначала назначается любой элемент массива – например, предпоследний (с номером N – 1). Затем назначенное максимальное значение MX поочередно сравнивается со всеми остальными элементами массива в цикле, перебирающем номера элементов. Если на очередной итерации цикла найдется элемент массива больший MX, то в переменную MX запишется новый максимум. Таким образом, по завершении цикла в переменной MX зафиксируется значение максимального элемента массива.
Транспонирование квадратной матрицы (N строк и N столбцов) заключается в том, чтобы поменять местами строки и столбцы матрицы – первая строка должна стать первым столбцом, вторая строка – вторым столбцом – и так вплоть до последней строки. Для хранения такой матрицы потребуется массив ранга два (двумерный массив) с экстентами (протяженностями по каждому измерению) равными N. Исходная и транспонированная матрицы хранятся в разных массивах M – исходная матрица, и T - транспонированная матрица (Пример 5.14). Матрица M инициализируется специализированным конструктором одномерных массивов (Пример 3.21), переформатированным функцией RESHAPE (Пример 3.26). Для работы с двумерными матрицами, в частности. для их транспонирования, обычно используется конструкция из двух вложенных циклов – внешний цикл осуществляет последовательный перебор строк, а внутренний цикл – перебор столбцов. Сама процедура транспонирования заключается в перекрестном изменении номеров строк и номеров столбцов: т.е. индекс I, являющийся номером строки в матрице M, становится номером столбца в матрице T – и наоборот. Последующие циклы используются для вывода на экран исходной и транспонированной матрицы.
В Примере 5.14 применена конструкция из двух вложенных циклов. В общем случае в Фортране допускается произвольная вложенность циклов, при этом пересечение циклов недопустимо.
Пример 5.14. Транспонирование квадратной матрицы.
program TSPMATR integer , parameter ::N=2 integer,dimension(N, N)::M=RESHAPE((/(I, I = 1,N*N)/), (/N, N/) ),T do I = 1, N do J = 1, N T (J, I) = M (I, J) end do end do print*, ″Матрица М″ do I = 1, N print*, (M(I, J), J = 1, N) end do print*, ″Матрица Т″ do I = 1, N print*, (T(I, J), J = 1, N) end do end