КАЛИНИНГРАДСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ
С.А. Григорьев, С.А. Ишанов
СРАВНИТЕЛЬНЫЙ КУРС ЯЗЫКОВ ПРОГРАММИРОВАНИЯ FORTRAN И С
Калининград 1998
3
КАЛИНИНГРАДСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ
С.А. Григорьев, С.А. Ишанов
СРАВНИТЕЛЬНЫЙ КУРС ЯЗЫКОВ ПРОГРАММИРОВАНИЯ FORTRAN И С Учебное пособие
Калининград 1998
4
УДК 681.3. 06 Григорьев С.А., Ишанов С.А. Сравнительный курс языков программирования Fortran и С: Учебное пособие / Калинингр. ун-т; Калининград, 1998. - 94 с. - ISBN 5-88874-097-7. Учебное пособие написано на основе курса лекций “Языки программирования и методы трансляции” и спецкурса “Язык программирования C”, читавшихся авторами на математическом факультете КГУ. Пособие содержит достаточно полное описание языков С и Fortran и сравнение основных конструкций этих языков, а также языка Pascal. Отдельная глава посвящена решению практических задач на языках С, Fortran и Pascal. Учебное пособие предназначено для студентов-математиков и студентов других специальностей, углубленно изучающих программирование.
Рецензент: к.ф.-м.н., доцент кафедры прикладной математики Калининградского государственного технического университета В.М. Смертин.
Печатается по решению редакционно-издательского Совета Калининградского государственного университета.
ISBN 5-88874-097-7 ный университет, 1998
©
Калининградский государствен-
5
Сергей Анатольевич Григорьев, Сергей Александрович Ишанов СРАВНИТЕЛЬНЫЙ КУРС ЯЗЫКОВ ПРОГРАММИРОВАНИЯ FORTRAN И С Учебное пособие Лицензия № 020345 от 14.01.1997 г. Редактор Л.Г. Ванцева. Оригинал-макет Д.В. Голубина. Подписано в печать 25.05.1998 г. Формат 60×90 1/16. Бумага для множительных аппаратов. Ризограф. Усл. печ. л. 5,9. Уч.-изд. л. 6,5. Тираж 120 экз. Заказ . Калининградский государственный университет, 236041, г. Калининград, ул. А. Невского, 14 СОДЕРЖАНИЕ 6
Введение ......................................................................................................... 5 Глава 1. Описание языка C ......................................................................... 5 1.1. Структура C-программы .......................................................................... 5 1.2. Скалярные типы данных и константы, строковые константы ............... 6 1.3. Описание скалярных переменных. Директива #define ........................... 7 1.4. Операции. Выражения ............................................................................. 8 1.5. Стандартные математические функции ................................................ 10 1.6. Ввод-вывод ............................................................................................. 10 1.7. Метки, оператор goto, условные конструкции, оператор break, функция exit ............................................................................................. 14 1.8. Циклические конструкции ..................................................................... 16 1.9. Указатели, адресная арифметика .......................................................... 17 1.10. Массивы ................................................................................................ 18 1.11. Функции ................................................................................................ 19 1.12. Классы памяти. Общие правила описания. Оператор typedef ........... 23 1.13. Строковые переменные, ввод-вывод строк, стандартные функции обработки строк. Функции проверки символов .................................. 25 1.14. Макроопределения ............................................................................... 28 1.15. Внешние файлы .................................................................................... 30 1.16. Структуры, объединения, битовые поля ............................................. 31 1.17. Динамическое распределение памяти ................................................. 33 1.18. Графика ................................................................................................. 34 1.19. Дополнительные возможности языка: тип enum, использование командной строки, функции с переменным числом параметров, средства консольного ввода-вывода, системное время, случайные числа ...................................................................................................... 35 Глава 2. Описание языка FORTRAN ....................................................... 38 2.1. Структура FORTRAN-программы ......................................................... 38 2.2. Типы данных. Константы ...................................................................... 39 2.3. Описание переменных, правила умолчания, операторы IMPLICIT, DATA, PARAMETER ......................................................... 40 2.4. Оператор присваивания. Операции ....................................................... 41
7
2.5. Стандартные математические функции ................................................ 42 2.6. Ввод-вывод ............................................................................................. 46 2.7. Оператор СОNTINUE. Операторы перехода. Условные операторы ... 49 2.8. Операторы цикла .................................................................................... 51 2.9. Функции LOC, LOCFAR, LOCNEAR .................................................... 52 2.10. Массивы ................................................................................................ 52 2.11. Подпрограммы SUBROUTINE, FUNCTION и INTERFACE ............. 54 2.12. Общие области, подпрограмма BLOCK DATA. Оператор EQUIVALENCE .................................................................... 56 2.13. Символьные переменные ..................................................................... 57 2.14. Операторные функции ......................................................................... 59 2.15. Внешние файлы .................................................................................... 59 2.16. Структуры ............................................................................................. 62 2.17. Динамические массивы ........................................................................ 63 2.18. Графика ................................................................................................. 63 2.19. Дополнительные возможности языка: дополнительные точки входа, свободный формат, строки отладки, средства консольного ввода-вывода, системное время, случайные числа ............................. 67 Глава 3. Решение задач на языках C и FORTRAN ................................ 70 Задача 1. Вывод натурального числа ........................................................... 70 Задача 2. Сумма ряда .................................................................................... 71 Задача 3. Медленная сортировка .................................................................. 72 Задача 4. Быстрая сортировка ...................................................................... 74 Задача 5. Слова .............................................................................................. 77 Задача 6. График ........................................................................................... 81 Задача 7. Кубическое уравнение .................................................................. 85 Задача 8. Собственное число ........................................................................ 89 Комментарии к задачам ................................................................................ 91
8
ВВЕДЕНИЕ Эта книга состоит из трех глав. Первые две представляют собой краткие описания языков программирования C и FORTRAN 77, а в третьей главе приведены решения некоторых часто встречающихся задач на этих двух языках и на языке PASCAL. Авторы предполагают, что читатели этой книги уже в достаточной мере знакомы с языком программирования PASCAL, и в ряде мест ссылаются на соответствующие конструкции этого языка. Но владение языком PASCAL не является абсолютно необходимым для пользования этой книгой, читатель должен иметь лишь некоторое представление о языках программирования высокого уровня, поскольку из-за ограниченного объема книги авторы вынуждены излагать материал очень лаконично. Тем, кого интересует только один из представленных языков, можно порекомендовать читать только первую или только вторую главу. Глава 1. ОПИСАНИЕ ЯЗЫКА C Приведенное ниже описание языка C полностью соответствует среде программирования Borland C++ версий 2.0 и 3.0. Многие программисты предпочитают пользоваться языком C++, который является расширением языка C. Оба языка в основном используют один и тот же синтаксис, операторы, выражения, встроенные типы данных, структуры, массивы, объединения, циклы, функции и указатели. Основные расширения языка С++ также описаны и специально оговорены в тексте главы. 1.1. Структура C-программы С-программа размещается в одном или нескольких файлах. Она состоит из оболочки, включающей описания и директивы препроцессора, и одной или нескольких функций. Одна из функций всегда имеет имя main, с нее начинается выполнение программы. Функция состоит из заголовка функции: тип имя ([список параметров])
и тела функции. Тело функции - это блок (последовательность операторов в фигурных скобках). Символы {} в языке С являются логическими скобками. Список параметров может быть пустым, но ( ) должны присутствовать и в этом случае. Для функций, не имеющих параметров, можно указывать список параметров в виде (void) . Если список параметров не пуст, то для каждого параметра указывается его тип. Если функция не должна воз9
вращать никакого значения, то ее тип - void ("пустой" тип). В языке С нет понятия процедуры. Каждый оператор заканчивается символом ; . Идентификаторы могут иметь длину до 31 символа, компилятор различает большие и малые латинские буквы, откуда, в частности, следует, что написание Main или MAIN имени главной функции является ошибкой. Комментарии заключаются в составные символы /* */ . В языке С++ появился еще один вид комментариев: так называемый однострочный комментарий - строка, начинающаяся парой символов // . Директивы препроцессора начинаются с символа # и записываются в отдельной строке, они могут располагаться в любом месте программы, но не внутри функции. Директивы действуют от места появления до конца файла или до их отмены. Препроцессор языка С - это программа, которая, обрабатывая исходный текст до компилятора, выполняет подстановки для макровызовов, осуществляет условную компиляцию, подключает необходимые файлы. Директива #include<файл> или #include"файл" присоединяет к программе внешний файл, "" означают поиск файла в текущем каталоге (пользовательский файл), а <> - поиск в системном каталоге (системный файл). Директиву #include следует использовать в любой программе, вызывающей внешние функции, для подключения т.н. заголовочных файлов - файлов с расширением h (header). В заголовочных файлах содержатся описания (прототипы) функций, необходимые для успешной компиляции программы. 1.2. Скалярные типы данных и константы, строковые константы В языке С определены следующие арифметические типы данных: Тип данных char unsigned char int unsigned long float double
Размер 1 байт 1 байт 2 байта 2 байта 4 байта 4 байта 8 байтов
Диапазон значений от -128 до +127 от 0 до 255 от -32768 до +32767 от 0 до 65535 от -2147483648 до +2147483647 от ± 3.4Е-38 до ±3.4Е+38 (7 верных цифр) от ±1.7Е-308 до ±1.7Е+308 (16 верных цифр)
Все арифметические типы данных имеют аналогии в языке PASCAL: char=ShortInt, unsigned char=Byte, int=Integer, unsigned=Word, long=LongInt, float=Single, double=Double. Целые константы можно запи10
сывать в десятичном, восьмеричном и шестнадцатеричном виде, например, 255 или 0377, или 0XFF. Константа, начинающаяся цифрой 0, считается восьмеричной, а константа, начинающаяся символами 0X или 0x, - шестнадцатеричной. По умолчанию десятичные целые константы имеют тип int, константы типов unsigned и long образуются добавлением справа буквы U (u) или L (l) : -1L, 65535U . Вещественные константы записываются в виде с фиксированной точкой: -2. , 1.3 , .45, или с плавающей точкой: -2e-2, 1.33e0 - и по умолчанию имеют тип double. Константы типа float образуются добавлением справа буквы F (f) : -2.f, 1.22e0f . Символьные константы (они являются в языке C целыми числами и имеют тип int) записываются в одном из четырех видов: - ' символ ' ( например, '!' ) - ' \8-ричная константа ' ( например, '\041' ) - ' \x одна или две 16-ричные цифры ' ( например, '\x21' ) - специальные символы '\a' - звук, '\b' - backspace, '\t' - горизонтальная табуляция, '\n' - новая строка, '\v' - вертикальная табуляция, '\f' - новая страница, '\r' - возврат каретки, '\\' - символ \, '\''- апостроф, '\”' - кавычка, '\%' - символ %. Строковые константы задаются в виде “символы”, в т.ч. могут содержать и специальные символы, например : "abcdef..." или "\n\a\n\t\\\b" . Символы \,'," и в ряде случаев % являются специальными управляющими символами в языке C и не должны входить в строки непосредственно, но лишь в виде составного символа. Компилятор языка C всегда соединяет строковые константы, не разделенные в тексте программы ничем, кроме пробелов. 1.3. Описание скалярных переменных. Директива #define Переменные описываются и инициализируются в виде: тип имя [ = выражение ] , имя [ = выражение] , ... ; Например: int a,b='\020',c=-11; long d=-1L; double e,f=.003,g=1.2E40; Каждая переменная, использованная в программе, должна быть явно описана. Инициализация переменных не является обязательной. Все переменные должны быть описаны раньше, чем будут использоваться. Язык С требует, чтобы все описания локальных переменных внутри блока помещались перед первым исполняемым оператором. В С++ можно описывать переменные в любой точке блока перед их использованием. 11
Простейшая форма директивы препроцессора #define используется для определения символических констант: #define имя строка_замещения Строка замещения, вообще говоря, есть любая последовательность символов. Препроцессор до начала компиляции замещает каждое вхождение имени константы в тексте программы на строку замещения, но только не внутри "". Строка замещения может быть, например, числовой константой: #define PI 3.141592653589793 , тогда идентификатор PI можно рассматривать как именованную константу в языке PASCAL (хотя это и не тождественные вещи). У пользователей языка С существует традиция записывать имена символических констант заглавными буквами, хотя это никак не регламентируется в самом языке. 1.4. Операции. Выражения В языке С определены следующие операции: a) арифметические операции: унарный +, унарный -, сложение +, вычитание - , умножение *, деление /, остаток от деления %, инкремент ++, декремент --. Операция / определена как для вещественных операндов, так и для целых, но для целых операндов результат всегда целый и равен целой части частного. Инкрементная и декрементная операции применимы только к переменным, выражение x++ эквивалентно выражению x=x+1 . Возможны постфиксная и префиксная формы этих операций. При использовании префиксной формы значение переменной изменяется до его подстановки в выражение, а при использовании постфиксной формы - после вычисления выражения. Например, пусть переменная x описана как int x=5; тогда значение выражения ++x*2 будет равно 12, а значение выражения x++*2 равно 10, значение переменной в обоих случаях станет равным 6. б) операции сравнения: равно == , не равно != , больше > , больше либо равно >= , меньше < , меньше либо равно <= . Результат операции сравнения есть целое число 0 или 1. в) логические операции: отрицание ! , логическое “и” && , логическое “или” ||. При вычислении логических выражений любое ненулевое значение считается истинным, результат логической операции - целое число 0 или 1. Таким образом, логические операции применимы к любым скалярным величинам, в языке С вообще нет специального типа данных для логических, или булевских, величин. Логические выражения всегда вычисляются только до получения 12
результата; так в выражении x!=2||1/x>3 при x=0 не произойдет ошибки, т.к. выражение 1/x не будет вычисляться. г) операция sizeof: sizeof ( тип ) или sizeof выражение . Результат операции - размер объекта в байтах, тип результата всегда unsigned. В языке PASCAL эта операция реализована встроенной функцией SizeOf. д) операция явного преобразования типа: ( тип ) выражение Если операнды некоторого выражения имеют разный тип, то они автоматически приводятся к одному (старшему) типу. Старшинство типов возрастает в такой последовательности : char < unsigned char < int < unsigned < long < float < double . е) битовые операции: побитовое отрицание ~ , побитовое "и" & , побитовое исключающее "или" ^ , побитовое "или" | , сдвиг битов влево << , сдвиг битов вправо >> . Битовые операции применимы только к целым операндам, все они имеют аналоги в языке PASCAL : ~ = NOT, & = AND, ^ = XOR, | = OR, << = ShL, >> = ShR. ж) операции присваивания: в отличие от языка PASCAL в языке С конструкция, состоящая из двух операндов и операции присваивания, является выражением, его значение равно значению, полученному переменной в левой части выражения. Кроме обычной операции присваивания = существует еще 10 операций: += , =, *= , /= , %= , <<= , >>= , &= , ^= , |= , смысл которых очевиден из простейшего примера : x+=a означает x=x+a . з) операция адрес: & переменная Эта унарная операция применима только к переменным, ее аналог в языке PASCAL - операция @. и) операция "запятая" - довольно необычная операция, имеющаяся только в языке C. Несколько выражений, разделенных запятыми, вычисляются последовательно слева направо. В качестве результата сохраняются тип и значение самого правого выражения. Приоритеты операций определены следующим образом: унарные операции > {*, /, %} > {+, -} > {<<, >>} > {>, >=, <, <=}> {==, !=} > & > ^ > | > && > || > операции присваивания > операция "запятая". Операции одного приоритета выполняются в последовательности слева направо, кроме унарных операций и операций присваивания, которые вы13
полняются справа налево. Для того чтобы изменить порядок выполнения операций, в выражении используются круглые скобки. 1.5. Стандартные математические функции Библиотека математических функций (прототипы функций содержатся в файле math.h) включает : int abs(int x) long labs(long x) double fabs(double x) double ceil(double x) double floor(double x) double fmod(double x,double y) double sqrt(double x) double hypot(double x,double y) double ldexp(double x,int n) double pow(double x,double y) double pow10(int p) double exp(double x) double log(double x) double log10(double x) double sin(double x) double cos(double x) double tan(double x) double asin(double x) double acos(double x) double atan(double x) double atan2(double x,double y) double sinh(double x) double cosh(double x) double tanh(double x)
- |x| для int - |x| для long - |x| для double - наименьшее целое, не меньшее x - наибольшее целое, не большее x - остаток от деления x на y - √x - √(x2 +y2 ) - x⋅2n - xy - 10p - ex - ln(x) - log(x) - sin(x) - cos(x) - tg(x) - arcsin(x) - arccos(x) - arctg(x) - arctg(y/x), возвращает значение от-π до π - sh(x) - ch(x) - th(x)
1.6. Ввод-вывод Форматный вывод данных осуществляется функцией printf : int printf ( строка формата [ , список вывода ] ) которая возвращает количество выведенных символов. Список вывода может содержать любые выражения, разделенные запятыми (полностью аналогичен списку вывода оператора WRITE в языке PASCAL). Строка формата заключается в “ “ и может содержать : 1) обычные символы, которые просто выводятся на экран;
14
2) специальные символы, управляющие размещением данных на экране; 3) элементы формата данных. Для вывода строки на экран элементы формата можно не использовать, в этом случае список вывода пуст : printf("\n\tВыведем что-нибудь...\a"); Элемент формата представляет собой последовательность : % [ - ] [ длина поля ] [ . точность ] [ l ] спецификация типа “ - “ означает сдвиг выводимых данных к левому краю поля ( по умолчанию данные сдвигаются вправо); длина поля - количество позиций для вывода; точность - количество дробных цифр для чисел или количество выводимых символов строки для строк; “ . ” - разделитель; l - означает, что число должно рассматриваться как long или double. Могут использоваться следующие спецификации типов : d,i - для данных типа int; u - для данных типа unsigned; o - для данных типа unsigned (выводятся в восьмеричном виде); x,X - для данных типа unsigned (выводятся в шестнадцатеричном виде), шестнадцатеричные цифры A,B,C,D,E,F изображаются большими буквами при использовании типа X и малыми буквами при использовании x; c - для данных типа char, unsigned char (выводятся в символьном виде); f - для данных типа float,double (выводятся в виде с фиксированной точкой, по умолчанию выводится 6 дробных цифр); e,E - для данных типа float,double (выводятся в виде с плавающей точкой, по умолчанию выводится 7 цифр мантиссы и две цифры порядка, всего 13 позиций), отличаются изображением буквы E; g,G - для данных типа float,double - величины 0.0001<|x|<1000000 выводятся в виде с фиксированной точкой с шестью значащими цифрами, другие величины выводятся с плавающей точкой с шестью значащими цифрами (всего занимают 12 позиций), спецификации g и G различаются только изображением буквы e(E); s - для строк; p - для данных типа указатель. Перед выводом всегда осуществляется преобразование данных к типу, определенному спецификацией, что может приводить к изменению выводимого значения. Программист сам должен заботиться о правильном выборе формата, поскольку компилятор не обращает на это никакого внимания. Несоответствие количества элементов формата и количества выводимых данных также может приводить к неприятным последствиям. Форматный ввод данных осуществляется функцией scanf : 15
int scanf ( строка формата , список ввода ) которая возвращает количество введенных значений. Список ввода должен содержать адреса переменных, описанных в программе, строка формата должна содержать только элементы формата данных, которые интерпретируются следующим образом : d,u - для ввода целых десятичных чисел; o - для ввода восьмеричных чисел (можно со знаком); x,X - для ввода шестнадцатеричных чисел (можно со знаком); i - для ввода целых чисел, которые могут задаваться любой целой константой языка С (т.е. восьмеричные константы начинаются с 0, а шестнадцатеричные - с 0x); c - для ввода символов; f,e,E,g,G - для ввода вещественных чисел, которые могут изображаться любой десятичной константой с фиксированной или плавающей точкой. Модификаторы “ - ”, длина поля и точность при вводе игнорируются. Введенное значение затем преобразуется к типу вводимой переменной. Для данных типа int и unsigned можно использовать спецификации d,u,o,x,X,i, но для переменных типа long обязательно следует указать модификатор l, т.е. использовать спецификации ld,lu и т.д. Данные типа char и unsigned char можно вводить по формату c. Спецификации f,e,E,g,G применимы для ввода переменных типа float, для переменных типа double добавляется модификатор l. Вводимые числа разделяются любым количеством пробелов. Символы считываются подряд, если перед каким-либо из элементов формата %с не стоит пробел; в противном случае пробелы считаются разделителями. Строка формата может содержать перед первым % некоторую последовательность символов, тогда вводимая строка данных должна начинаться этой последовательностью, которая пропускается функцией scanf и лишь со следующего символа начинается ввод данных. Функция scanf осуществляет ввод до обнаружения первого недопустимого символа во входном потоке и возвращает количество успешно введенных значений (аварийного прерывания при обнаружении неверных входных данных не происходит). Однако функция никак не контролирует соответствие типа вводимых переменных и используемых элементов формата. Для ввода-вывода символов используются функции getchar и putchar. Функция int getchar() возвращает введенный символ, преобразованный к типу int. Функция int putchar(int c) выводит в символьном виде значение c и возвращает выведенное значение. Прототипы функций scanf, printf, getchar и putchar находятся в файле stdio.h . Недостатком функции getchar является необходимость после набора символа нажимать клавишу Enter. В большинстве случаев вместо нее удобно пользоваться функциями, прототипы которых находятся в conio.h : int getch() и int getche() . Эти 16
функции возвращают значение, соответствующее нажатой клавише (аналогичны функции ReadKey в языке PASCAL). При этом getche высвечивает на экране введенный символ, а getch - нет. С помощью этих функций можно опрашивать и клавиши, генерирующие два кода, - стрелки, функциональные клавиши и т.п. Язык С++ полностью поддерживает средства ввода-вывода языка С. Вместе с тем в С++ введен новый способ ввода-вывода, который носит название ввода-вывода потоком. Поток определяется как некоторый абстрактный тип данных, представляющий последовательность элементов данных, направленную от источника (source) к потребителю (consumer). Количество элементов в потоке называется его длиной, порядковый номер доступного в некоторый момент элемента называют текущей позицией (current position). Используемые в программах потоки логически делятся на три типа: 1) входные, из которых читается информация; 2) выходные, в которые передаются данные; 3) двунаправленные, допускающие как чтение, так и запись. В начале выполнения программы автоматически открываются четыре потока: cin, cout, cerr и clog, описанные в файле iostream.h . Поток cin связан со стандартным вводом (по умолчанию с клавиатурой), cout - со стандартным выводом (по умолчанию с дисплеем). Вместо записи: #include <stdio.h> printf("Вывод = %d,%f\n",i,r); scanf("%d",&x); на С++ можно записать: #include
cout<<"Вывод="<>element; Выполнение операции >> (извлечение из потока) заключается в преобразовании последовательности символов потока в значение типизированного объекта. При выполнении операции << (включение в поток) совершается обратное преобразование - типизированное значение выражения преобразуется в последовательность символов потока. Отметим, что операции << и >> распознаются как операции потокового ввода-вывода, если они употребляются справа от имен потоковых объектов cin >> имя_объекта_базового типа cout << выражение_базового_типа Приоритет этих операций такой же, как у операций сдвига, поэтому, чтобы вывести в поток значение выражения, содержащего операции более низкого приоритета, требуется применение скобок: cout << (x+y
cout<<(2<<1); выведет в поток значение 4. В библиотеке потокового ввода-вывода С++ предусмотрена возможность форматирования передаваемых данных, для этого применяются два способа. Первый состоит в использовании флагов форматирования, которые унаследованы всеми потоками библиотеки из базового класса ios. При втором способе форматирования употребляется специальный вид функций, называемых манипуляторами. 1.7. Метки, оператор goto, условные конструкции, оператор break, функция exit Оператор безусловного перехода записывается в виде goto метка ; где метка - это некоторый идентификатор, использованный в данной функции в виде метка : оператор . Имена меток никак не описываются в программе и распознаются компилятором по контексту. Передача управления разрешена на любой помеченный оператор в теле функции. Однако в С++ существует одно ограничение : запрещено обходить описания, содержащие инициализацию объектов. Это ограничение не распространяется на вложенные блоки, которые можно обходить целиком. Перед рассмотрением условных конструкций еще раз отметим, что в языке С в явном виде булевский, или логический, тип данных отсутствует, а значения true и false обозначаются соответственно ненулевым и нулевым значениями. Такой подход лишает компилятор языка С возможности выявления определенного класса ошибок. Например, с точки зрения языка С, каждое из следующих двух выражений корректно и имеет значение true: 1<10<100 , 100<10<1 . Для второго выражения: 100<10 дает 0 (false) и 0<1 дает true. Другой пример : если i и k -целые переменные, то когда вместо выражения i
выбора старше операций присваивания и младше всех других операций, несколько записанных подряд операций выбора выполняются в последовательности справа налево. Например, выражение (a<0)?-a:a дает абсолютную величину a. Условный оператор if в языке С записывается в виде: if ( логическое выражение ) оператор/блок [ else оператор/блок ] Логическое выражение (обязательно заключенное в круглые скобки) это любое выражение, считающееся истинным, если оно не равно нулю. Условный оператор в свою очередь может включать условный оператор, таким образом допустимы вложенные условные операторы. Синтаксис языка предполагает, что при вложениях условных операторов каждое else соответствует ближайшему к нему предшествующему if. Отличие от языка PASCAL состоит лишь в отсутствии ключевого слова THEN и в том, что перед else ставится ; . Условный оператор switch имеет вид : switch ( выражение ) { case кв1 : [ операторы1 ] case кв2 : [ операторы2 ] .......................... default : [ операторы ] } выражение должно иметь целый тип ; кв1 , кв2 и т.д. - константные выражения целого типа. Порядок выполнения оператора switch следующий: вычисляется выражение в скобках, если его значение равно значению какого либо из константных выражений, происходит переход к первому из операторов, стоящих после этого константного выражения, в противном случае - к оператору, стоящему после метки default, затем операторы выполняются последовательно. Метку default не обязательно включать в оператор. Выхода из оператора switch при достижении следующего case или метки default не происходит - в отличие от аналогичного оператора CASE языка PASCAL. Однако эти действия можно задать явно, используя специальный оператор выхода break; , который можно включить в последовательности выполняемых операторов. Оператор break аналогичен по своему действию процедуре Break в языке PASCAL, он вызывает немедленное завершение выполнения условной или циклической конструкции, в частности, оператора switch. В любом месте любой функции для остановки программы можно использовать функцию void exit(int status) , где status - код завершения, передаваемый операционной системе. Функция exit аналогична процедуре Halt в языке PASCAL. 1.8. Циклические конструкции 19
В языке С существует три вида циклов : while , do while и for , являющихся аналогами циклов WHILE, REPEAT и FOR (не вполне) в языке PASCAL. Оператор цикла while записывается в виде: while ( логическое выражение ) оператор/блок - и выполняется до тех пор, пока логическое выражение в скобках истинно, т.е. не равно нулю. Оператор цикла do while записывается в виде: do оператор/блок while ( логическое выражение ); - и выполняется, пока истинно логическое выражение в скобках, но в отличие от цикла while логическое выражение вычисляется после выполнения тела цикла. Цикл for является наиболее сложным из трех видов циклических операторов языка С : for( [ инициализирующее выражение ] ; [ логическое выражение ] ; [ корректирующее выражение ] ) [ оператор/блок ] Цикл for выполняется следующим образом: перед началом цикла (только один раз) вычисляется инициализирующее выражение, затем вычисляется логическое выражение и, если оно истинно, выполняется тело цикла, и после каждого шага цикла вычисляется корректирующее выражение. В цикле for все три выражения могут отсутствовать, но два символа “ ; ” должны быть записаны в скобках. При отсутствии логического выражения предполагается, что его значение всегда истинно. Никаких специальных требований к выражениям, используемым в цикле for, не предъявляется, это совершенно произвольные выражения, а их названия лишь обозначают порядок записи этих выражений в скобках. Запишем, используя три типа цикла, алгоритм вычисления суммы квадратов натуральных чисел от 1 до n : s=0; i=1; while (i<=n) {s+=i*i; i++;} s=0; i=1; do s+=i*i; i++; while(i<=n); s=0; for(i=1; i<=n; i++) s+=i*i; Третий вариант записи можно упростить, если в инициализирующем и в корректирующем выражениях использовать операцию “запятая”. for(i=1,s=0; i<=n; s+=i*i,i++); Таким образом, тело цикла оказалось пустым (но ; должна остаться). Для немедленного выхода из цикла любого типа можно использовать оператор break. Для пропуска части операторов тела цикла используется оператор continue; , полностью аналогичный процедуре Continue в языке PASCAL. 1.9. Указатели, адресная арифметика
20
Указателями называются переменные и константы, значениями которых являются адреса участков памяти, выделенных для объектов конкретных типов. В языке C так же, как в языке PASCAL, существуют типизированные и обобщенные указатели. Типизированные указатели описываются в виде: базовый тип * имя указателя , * имя указателя , ... ; * относится только к тому имени, непосредственно перед которым она стоит. В одном операторе описания можно описать как переменные некоторого типа, так и указатели на такой тип. Символ * , меняющий тип переменной, называется в таком контексте модификатором. Переменные типа «указатель» можно инициализировать так же, как и числовые переменные, например : int a=0, *b=&a, **c=&b; Обобщенный указатель имеет тип void* , в нем можно хранить адреса любых объектов, но его использование ограничено по сравнению с типизированными указателями. Отметим, что любой адрес можно проверить на равенство или неравенство со специальным значением NULL (аналогичным константе NIL в языке PASCAL), которое записывается вместо нуля. Слово NULL позволяет определить указатель, который ничего не адресует. Для указателей определены следующие операции. 1. Операция “значение”, записывающаяся в виде * указатель , результат операции есть значение, записанное по адресу, хранящемуся в данном указателе. В языке PASCAL такая операция записывается в виде указатель^ . Операция “значение” применима только к типизированным указателям. Она имеет такой же приоритет, как и все другие унарные операции. 2. К указателям применима операция явного преобразования типа, причем указатель можно преобразовать не только в указатель другого типа, но и в число, и число можно преобразовать в указатель. 3. Операция присваивания = , при этом оба операнда должны быть указателями одного типа, либо один из них должен иметь тип void* , либо правый операнд должен быть числом 0. 4. Операции сравнения == , != , < , <= , > , >= , при этом оба операнда должны быть указателями одного типа, либо один из них должен иметь тип void* , либо один из операндов должен быть числом 0. 5. Операция вычитания - , оба операнда должны быть указателями одного типа, либо второй операнд должен быть целым числом. 6. Операция сложения + , при этом второй операнд должен быть целым числом. 7. Увеличение и уменьшение указателя ++ , -- . 8. Операции присваивания += и -= , второй операнд в этом случае должен быть целым числом. 21
9. Операция “адрес” & . При выполнении операций 5-8 используется так называемая адресная арифметика, т.е. за 1 принимается размер в байтах типа данных, базового для этого указателя. Это свойство арифметических операций с указателями особенно часто используется при работе с массивами. 1.10. Массивы Описание одномерного массива в языке С имеет вид: тип имя массива [ длина ] [ = { список значений } ] ; Здесь тип - тип элемента массива, имя массива - идентификатор, длина - количество элементов массива, заданное константным выражением, список значений - последовательность разделенных запятыми константных выражений, например: int a[7]={-1,147/3+26/7,33,5,6,0,-11}; Индексы элементов любого массива всегда начинаются с 0, так что последний элемент имеет индекс длина-1. Так, в приведенном примере индекс последнего (седьмого по счету) элемента массива a равен 6. Инициализирующая часть оператора не обязательна; кроме того, длина списка значений может быть меньше длины массива, тогда оставшиеся элементы массива не инициализируются. Если задан полный список значений, то можно не указывать длину массива - память под массив будет распределена в соответствии с количеством инициализирующих выражений, но модификатор [ ] , означающий массив, опускать нельзя. Имя массива считается константой-указателем, равной адресу нулевого элемента массива, т.е. a и &a[0] - это одно и то же. Обращение к элементам массива возможно либо с помощью индексов (точно так же, как в языке PASCAL) : a[i] , либо по адресу : *(a+i) - причем в последнем случае используется адресная арифметика. Фактически в программе всегда происходит обращение к элементу массива по адресу, а использование индексов допускается лишь для удобства программирования. Выход индекса за границы массива никак не отслеживается в языке С и не считается ошибкой. Описание многомерного массива в общем случае имеет вид: тип имя массива [ длина1 ] [ длина2 ] ... [ ={ список значений } ] ; Например, двумерный массив b можно описать в виде int b[3][4]={{1,2,3,4},{3,7,0,2},{3,7,4,8}}; Массив b является массивом из трех элементов, каждый из которых есть массив из четырех элементов типа int, это обстоятельство явным образом отражено в инициализирующей части оператора. Но если инициализиру-
22
ются все элементы массива, как в приведенном примере, внутренние {} можно опускать: int b[3][4]={1,2,3,4,3,7,0,2,3,7,4,8}; Внутренние {} необходимы, если часть элементов не инициализируются: int b[3][4]={{1,2},{4,3,7},{0}}; Здесь инициализированы элементы b00,b01,b10,b11,b12,b20. В случае, если задан полный список значений, можно опускать первую из длин измерений, она будет неявно определена как длина списка значений, последовательно поделенная на явно указанные длины измерений. Так, массив b можно описать как int b[][4]={1,2,3,4,3,7,0,2,3,7,4,8}; Имя многомерного массива также является константой - указателем на самый первый элемент массива (т.е. элемент, все индексы которого - нули). Элементы многомерного массива - массивы - являются указателями на соответствующие подмножества элементов. Обращение к элементам многомерных массивов возможно с помощью индексов: b[i][j] или по адресу: *(b[i]+j) . Каждый индекс элемента многомерного массива должен быть обязательно заключен в собственные [ ] . Запись вида b[i,j] не является ошибкой (вспомним об операции “запятая”), но она определяет не элемент массива - число типа int, а адрес подмножества элементов, начинающегося с элемента bj0 . 1.11. Функции Если в таких языках, как FORTRAN и PASCAL, делается различие между подпрограммами-процедурами и подпрограммами-функциями, то в языках С и С++ используются только функции. Программы на языке С (C++) - это совокупность следующих одна за другой функций, одна из которых main (главная функция). Функция main обеспечивает создание точки входа в откомпилированную программу. Каждая функция имеет статическую продолжительность существования и внешний тип компоновки: в С запрещено вложение одной функции в другую. Функция в языке С состоит из заголовка функции и тела функции. Тело функции - это блок (последовательность операторов в {}), а заголовок в общем случае имеет вид: тип функции имя функции ( тип параметра имя параметра , [ [ тип параметра имя параметра , ... ] ) Заголовок функции не является оператором, поэтому он не заканчивается “;”. Функция может иметь скалярный тип (число, указатель) или тип void (не возвращает никакого значения). Все программные единицы, не вычисляющие какого-либо одного скалярного значения, являются функциями 23
типа void. Каждый параметр в заголовке функции описывается отдельно, даже если параметры имеют один и тот же тип. Допускаются функции без параметров, в этом случае список параметров имеет вид () или (void) . Выход из функции осуществляется оператором return [ выражение ] ; Если выражение задано в операторе return, то его значение преобразуется к типу функции, в противном случае значение, возвращаемое функцией, остается неопределенным. Функция может содержать любое количество операторов return. Присваивать имени функции какое-либо значение, как в языках PASCAL и FORTRAN, не нужно. В большинстве языков программирования параметры передаются либо по значению, либо по адресу. В первом случае подпрограмме доступна не сама переменная, а только ее значение, во втором же случае подпрограмма работает непосредственно с переменной, переданной ей в качестве параметра. Таким образом, переменную, переданную по адресу, подпрограмма может модифицировать, а переданную по значению - нет. В С++, как и в Паскале, реализованы оба способа передачи параметров. В языке С параметры передаются только по значению, функция получает лишь их значение, но не адрес, и поэтому не может изменить значение аргумента. Общепринятый способ обеспечить функции доступ к какой-либо переменной вызвавшей программы состоит в том, что вместо самой переменной в качестве параметра передается указатель на нее. Параметры-массивы допускаются, но в качестве аргумента, соответствующего такому параметру, задается имя массива, т.е. адрес, таким образом параметр-массив есть на самом деле параметр-указатель. Для параметров-одномерных массивов нет необходимости указывать длину. Запишем, например, функцию, вычисляющую наибольший элемент массива целых чисел : int max_el(int x[],int n) {int i,m; for(m=x[0],i=0;im)?x[i]:m; return m;} Такая функция может обрабатывать массивы любой длины, но при каждом вызове следует определять фактическую длину с помощью аргумента n. Описание параметра int x[] практически эквивалентно описанию int* x, так что в функции можно отказаться от использования индексов, которые введены лишь для удобства записи : int max_el(int* x,int n) {int i,m; for(m=*x,i=1;im)?*(x+i):m; return m;} При использовании параметров-многомерных массивов возможно несколько вариантов их передачи в функцию. Например, запишем функцию нахождения наибольшего элемента квадратной матрицы. Пусть матрица описана в программе в виде: 24
#define SIZE 3 void main(void) { int b[][SIZE]={1,2,3,4,5,6,7,8,9}; Тогда соответствующую функцию можно записать в трех вариантах : 1) int max_el(int x[][SIZE],int n) {int i,j,m; for(m=x[0][0],i=0;im)?x[i][j]:m; return m;} 2) int max_el(int x[][SIZE],int n) {int i,j,m; for(m=*x[0],i=0;im)?*(x[i]+j):m; return m;} 3) int max_el(int*x,int size,int n) {int i,j,m; for(m=*x,i=0;im)?*(x+i*size+j):m; return m;} Вызов функции будет соответственно иметь вид: 1) max_el(b,SIZE) 2) max_el(b,SIZE) 3) max_el((int*)b,SIZE,SIZE) Все функции в С являются рекурсивными, т.е. любая функция может вызвать любую функцию, в том числе и функцию main, а также саму себя. В вызывающей функции (или вне функций) функцию можно описать как тип имя ( список параметров ) ; или тип имя ( список типов параметров ) ; Первый вариант описания отличается от заголовка функции только наличием “;” и называется прототипом функции, имена параметров могут быть произвольными, т.к. они никак не используются компилятором. Во втором варианте эти имена вообще опущены, например: int max_el(int*a,int b,int c); int max_el(int*,int,int); Для полного описания функций, не имеющих параметров, список типов задается в виде (void) . Функции, расположенные в том же файле, до их вызова можно вообще не описывать, описанием будет служить заголовок функции. Если функция расположена после вызывающей функции или в другом файле, то ее можно описывать или не описывать. Но для неописанной функции компилятор не проверяет соответствие типов параметров и аргументов и соответствие количества параметров и аргументов, кроме то25
го, тип неописанной функции всегда полагается равным int. Если функция описана, компилятор следит за правильностью списка аргументов. Язык С++ не предусматривает автоматического преобразования в тех случаях, когда аргументы не совпадают по типам с соответствующими им параметрам. С++ требует, чтобы в модуле, где происходит обращение к функции (причем обязательно до соответствующего обращения) присутствовало либо определение этой функции, либо ее прототип. Таким образом, проверка соответствия типов параметров и аргументов всегда выполняется в С++ на этапе компиляции. В С++ тип функции столь же важен, как и ее имя. Две функции могут иметь одинаковое имя, если имеется возможность различить их по сигнатуре. Сигнатура функции задается числом, порядком следования и типами ее параметров. Определение двух или более функций с одним и тем же именем называется перегрузкой функций, поскольку более поздние объявления перегружают, или перекрывают, более ранние. Рассмотрим следующую программу: #include <stdio.h> #include <string.h> int func (int first){ return first*first;} int func (unsigned first){ return -first*first;} char func (char first){ return first+3;} float func(float r){ return r*r;} void main() { printf("%d ",func(4)); printf("%d ",func((unsigned)4)); printf("%c ",func('a')); printf("%f\n",func((float)1.2));} Эта программа, в которой использованы четыре разные функции с именем func, является совершенно корректной и выведет на экран строку 16 -16 d 1.44 В языке С допускаются указатели на функции, им можно присвоить адрес функции и вызвать функцию по адресу, например: int (*p)(int*,int,int)=&max_el; printf("%d",(*p)((int*)b,SIZE,SIZE)); Операции адрес & и значение * можно не записывать явно, они автоматически генерируются компилятором, т.е. приведенный выше фрагмент можно переписать в виде: int (*p)(int*,int,int)=max_el; printf("%d",p((int*)b,SIZE,SIZE)); Используя указатели на функции, можно решать два типа задач: 1) присвоение указателю функции и использование указателя в качестве косвенной ссылки на функцию; 2) передача функций в качестве аргументов для других функций. Это позволяет создавать мощную систему функций, где частью функции является другая функция, выбранная из библиотеки подпрограмм. 26
1.12. Классы памяти. Общие правила описания. Оператор typedef Кроме своего типа каждая переменная в языке С характеризуется также классом памяти. Существует четыре класса памяти : auto - автоматические переменные; extern - внешние переменные; static - статические переменные; register - регистровые переменные. Описатель класса памяти может указываться в описаниях переменных слева от типа: класс памяти тип имя = { список значений } ; Автоматические переменные определены в той функции или блоке, где они описаны и инициализируются при каждом выполнении функции (блока). Такие объекты “живут” лишь во время работы блока (функции). Внешние переменные описываются вне функций (без описателя extern) и определены во всех функциях данного файла, следующих за этим описанием, а также во всех функциях, где эта переменная описана с описателем extern. Внешние переменные инициализируются один раз в начале выполнения программы. Внутри функций внешним переменным нельзя присваивать начальные значения, это разрешено только в главном описании переменной. Статические переменные во всем аналогичны автоматическим, но инициализируются лишь при первом выполнении функции (блока), в которой они описаны, и “время жизни” таких переменных равно времени работы программы. Регистровые переменные могут храниться в регистрах центрального процессора (что приводит к исключительно быстрой обработке этих переменных), в остальном они аналогичны автоматическим. При недостатке регистровой памяти описатель register игнорируется, но к любым переменным, описанным как регистровые, неприменима операция “адрес”. По умолчанию переменные, описанные в функциях, имеют класс памяти auto, описанные вне функций - extern. Внутри функции в случае совпадения имен глобальной и локальной переменных действовать будет только локальная переменная. В С++ предусмотрен доступ к глобальным переменным, скрытым локальными переменными с тем же именем. Операция разрешения области видимости :: позволяет в таких ситуациях воспользоваться глобальной переменной. Например: int x=5; void main() { int x=3; printf("Локальная x= %d\n",x); printf("Глобальная x= %d\n",::x); } 27
В описаниях кроме имен переменных и имен типов используются модификаторы : * - указатель, () - функция и [] - массив, причем в описаниях сложных объектов модификаторов может быть много, что затрудняет чтение и запись таких конструкций. Для правильной интерпретации описаний используются следующие правила приоритетов : 1) чем ближе модификатор к имени, тем выше его приоритет; 2) [] и () имеют более высокий приоритет, чем *; 3) можно использовать круглые скобки для повышения приоритета модификатора *. Модификаторы доступа volatile и const позволяют сообщить компилятору об изменчивости или постоянстве определяемого объекта. Если переменная описана как const, то она недоступна в других модулях проекта, подобно статическим переменным, и не может быть изменена в процессе выполнения программы. Константа должна быть инициализирована при описании. С помощью модификатора const создаются типизированные именованные константы, которые в отличие от символических констант, определенных директивой #define, подлежат контролю типов. Например: const double PI=3.141528; const char yes='Y'; Одно из важных применений переменных типа const - защита параметров функции от модификации, если аргумент передается по ссылке. Модификатор volatile сообщает компилятору, что значение таких подвижных объектов может быть изменено скрытно от компилятора каким-либо фоновым процессом. Например, при обработке прерывания глобальная переменная, содержащая системное время компьютера, может изменить свое значение. Компилятор не должен помещать такие переменные в регистровую память. Пример объявления : volatile unsigned timer; Так же, как и в языке PASCAL, в языке С программист имеет возможность создавать собственные именованные типы, для чего используется оператор typedef . Запись оператора совпадает с записью описания переменной, только вместо имени переменной задается имя типа и описание предваряется ключевым словом typedef. Оператор typedef может находиться внутри функции - тогда он действует только в данной функции, либо вне функций, и тогда он действует до конца файла. Идентификатор, заданный в операторе typedef, становится именем типа и в дальнейшем может использоваться в описаниях переменных. Например, вместо описания #define SIZE 3 int b[][SIZE]={1,2,3,4,5,6,7,8,9}; можно использовать описание #define SIZE 3 typedef int ARRAY[][SIZE]; ARRAY b={1,2,3,4,5,6,7,8,9}; 28
Отметим, что декларация typedef не создает новый тип (как в языке PASCAL), она лишь сообщает новое имя уже существующего типа. 1.13. Строковые переменные, ввод-вывод строк, стандартные функции обработки строк. Функции проверки символов Символьные строки в языке С - это массивы типа char, которые заканчиваются нуль-символом '\0' (отметим, что в языке PASCAL есть полный аналог таких строк - открытые строки или null-terminated strings). Строковые константы (последовательности символов в “”) размещаются в статической памяти и всегда заканчиваются нуль-символом, который явно не задается. Сама строковая константа является константой-указателем типа char* . Строковую переменную можно описать в форме: сhar имя [ длина ] = “ строка “ ; При этом длину строки можно опустить, память будет выделена по фактической длине строковой константы + 1 байт для нуль-символа. Поскольку имя строки является указателем, возможен альтернативный вариант описания: char* имя = “ строка “ ; Различие между двумя описаниями состоит в том, что имя массива есть константа-указатель, ее значение нельзя изменить, а во втором случае описана переменная-указатель, которая лишь первоначально указывает на место в памяти, занимаемое константой “ строка “, но впоследствии может принять любое другое значение. Массив символьных строк можно описать в виде : char* имя [ длина ] = { “ строка1 “ , “ строка2 ” , ... } ; или char имя [ длина массива ] [ длина строки ] = { “ строка1 “ , “ строка2 “ , ... }; Все элементы массива строк в любом случае являются указателями на соответствующую строку, но в первом случае под каждый элемент массива будет отведена память по фактической длине строковой константы, а во втором - количество байт, равное описателю длина строки + 1 байт. Поэтому говорят, что первое описание создает непрямоугольный массив строк, а второе - прямоугольный. Для ввода и вывод строк служат функции char* gets(char* строка ) int puts(char* строка ) Функция gets читает строку до символа '\n' и заменяет его нуль-символом. Она возвращает адрес введенной строки или нулевой адрес NULL при воз29
никновении ошибки. Функция puts выводит символы строки до достижения нуль-символа, который заменяется на '\n', возвращает 0 при успешном завершении или EOF ( число, равное -1 ) при возникновении ошибки. Функция gets “не интересуется” размером вводимой переменной и записывает в память, начиная с адреса строка, все введенные символы, так что ввод слишком длинной строки, вероятнее всего, приведет к порче значений других переменных, расположенных в памяти после вводимой переменной; кроме того, в этом случае в переменной не уместится нуль-символ и она будет непригодна для дальнейшего использования. Функция puts также устроена весьма просто и выводит сколь угодно большое количество символов, пока не встретит в памяти нулевой байт или не выйдет за пределы доступной памяти. Для ввода и вывода строк можно использовать и функции scanf и printf с форматом %s. Функция scanf считывает строку до первого пробела или специального символа. Функция printf в отличие от puts не осуществляет переход на новую строку. Полное описание функций scanf и printf имеет вид: int scanf(char *format, список ввода ); int printf(char *format [ , список вывода ] ); Эти функции можно использовать не только со строкой формата, заданной строковой константой, но и с любой строковой переменной, что делает форматный вывод весьма гибким. Для работы со строками существует специальная библиотека функций, прототипы которых описаны в файле string.h . Эта библиотека включает: 1) char* strcat(char* s1,char* s2) - конкатенирует строку s2 в конец строки s1, возвращает s1; 2) char* strncat(char* s1,char* s2,size_t maxlen) - конкатенирует не более maxlen символов строки s2 в конец строки s1, возвращает s1 (здесь и далее size_t - это тип размера объектов в языке С, он определен как typedef unsigned size_t; ); 3) size_t strlen(char* s) - возвращает длину строки s не считая нулевого символа; 4) char* strcpy(char* s1,char* s2) - копирует s2 в s1, возвращает s1; 5) char* strncpy(char* s1,char* s2,size_t maxlen) - копирует не более maxlen символов s2 в s1, возвращает s1; 6) int strcmp(char* s1,char* s2) - возвращает разницу в ASCII кодах первой пары несовпадающих символов строк s1 и s2; 7) int strncmp(char* s1,char* s2,size_t maxlen) - strcmp, учитывающая не более maxlen первых символов. 8) int stricmp(char* s1,char* s2) - strcmp, не учитывающая регистр; 30
9) int strncmpi(char* s1,char* s2,size_t maxlen) - strncmp, не учитывающая регистр; 10) char* strlwr(char* s) - переводит строку s в нижний регистр, возвращает указатель на s; 11) char* strupr(char* s) - переводит строку s в верхний регистр, возвращает указатель на s; 12) char* strchr(char* s,int c) - возвращает указатель на первое вхождение символа c в строку s или NULL; 13) char* strrchr(char* s,int c) - возвращает указатель на последнее вхождение символа c в строку s или NULL; 14) size_t strspn(char* s1,char* s2) - возвращает длину начального сегмента s1, который содержит только символы из строки s2; 15) size_t strcspn(char* s1,char* s2) - возвращает длину начального сегмента s1, который не содержит ни одного символа из s2; 16) char* strdup(char* s) - распределяет память и возвращает указатель на копию строки s; 17) char* strset(char* s,int c) - заполняет строку s символом c, возвращает s; 18) char* strnset(char* s,int c,size_t n) - заполняет не более n первых символов строки s символом c, возвращает s; 19) char* strpbrk(char* s1,char* s2) - возвращает указатель на первое вхождение какого-либо символа строки s2 в строку s1 или NULL; 20) char* strrev(char* s) - переставляет символы строки s в обратном порядке, возвращает s; 21) char* strstr(char* s1,char* s2) - возвращает указатель на первое вхождение подстроки s2 в строку s1 или NULL. Для преобразования чисел и других данных в строку удобно пользоваться функцией sprintf , описанной в файле stdio.h : int sprintf(char* s,char* формат [ , список вывода ] ) которая работает подобно функции printf, но вместо устройства вывода использует строку s. Для преобразования строки в число служат функции, описанные в файле stdlib.h : double atof(char* s) - преобразует строку s в вещественное число; int atoi(char* s) - преобразует строку s в целое число; long atol(char* s) - преобразует строку s в длинное целое число. Функции возвращают результат преобразования строки до первого ошибочного символа или до нуль-символа. Они не предоставляют средств контроля за правильностью записи числа в исходной строке.
31
Для проверки принадлежности отдельного символа некоторому множеству символов и преобразования символов используются макроопределения (см. 1.14), описанные в файле ctype.h . Макроопределения проверки символов возвращают ненулевое значение, если символ входит в множество, и 0 - в противном случае: isalpha(c) - латинская буква islower(c) - малая латинская буква isupper(c) - заглавная латинская буква isdigit(c) - цифра isxdigit(c) - 16-ричная цифра '0'..'9','A'..'F','a'..'f' isalnum(c) - латинская буква или цифра isspace(c) - пробел или управляющий символ \f,\n,\r,\t,\v iscntrl(c) - невидимые символы ispunct(c) - печатаемый символ, кроме пробела, букв и цифр isgraph(c) - печатаемый символ, кроме пробела isprint(c) - печатаемый символ, включая пробел Макроопределения преобразования символов: toupper(c) - для малой латинской буквы возвращает соответствующую большую букву, в остальных случаях возвращает c ; tolower(c) - для большой латинской буквы возвращает соответствующую малую букву, в остальных случаях возвращает c . 1.14. Макроопределения Макроопределения задаются, подобно символическим константам (которые являются простейшими макроопределениями), директивой препроцессора #define в виде: #define имя ( список параметров ) строка замещения где имя - некоторый идентификатор, список параметров - список идентификаторов, разделенных запятыми, строка замещения - это, как правило, некоторое выражение или последовательность операторов, куда входят параметры, указанные в скобках. В программе макроопределения, подобно функциям, используются со списком аргументов (аргументами могут быть любые правильно записанные выражения). Препроцессор проделывает с встреченными им в тексте программы макроопределениями следующие манипуляции : 1) каждое вхождение параметра в строку замещения заменяется на соответствующий аргумент (если только параметр в строке замещения не находится внутри “ ”);
32
2) параметры, предваряемые символом # в строке замещения макроопределения, заменяются на соответствующий аргумент в “ “; 3) подготовленная таким образом строка замещения подставляется в текст программы. В отличие от функций, предварительного вычисления значений аргументов не происходит, все операции выполняются только над текстом. В качестве примера запишем макроопределение SQR для вычисления квадрата числа : #define SQR(x) ((x)*(x)) и макроопределение, предоставляющее удобное средство отладочной печати: #define OUTPUT(x,f) printf("\n"#x"=%"#f,(x)) Отметим, что использование макроопределений иногда приводит к побочным эффектам, которые возникают при многократном использовании аргументов-выражений. Например, int x=2; y=SQR(x++); В результате выражение x++ * x++ установит x в 4 вместо 3, а y в 6=2*3 вместо ожидаемого значения 4. Кроме того, текстовые подстановки не позволяют применять в качестве аргументов арифметические выражения, поскольку они вычисляются в теле макроподстановки многократно, что замедляет выполнение программы. Еще один недостаток макроопределений - невозможность отличить вызовы функций от вызовов макроопределений в тексте программы. В языке C++ существует возможность вставки фрагмента кода (а не текста) функции, чтобы не выполнять переход к коду функции при ее вызове. Для этого перед описанием функции ставится ключевое слово inline. Например, inline int sqr(int x) {return x*x;} Наиболее эффективно использовать подставляемые функции в тех случаях, когда тело функции состоит всего из нескольких операторов, хотя при многократных вызовах подставляемой функции размеры программы могут увеличиться, однако исключаются затраты на передачу управления к вызываемой функции и возврат из нее. Так как компилятор встраивает код подставляемой функции вместо ее вызова, то определение функции со спецификатором inline должно находиться в том же файле, что и обращение к ней, и размещаться до первого вызова. Функция со спецификатором inline не может быть рекурсивной и не может содержать операторов goto , switch , do , for , while. 1.15. Внешние файлы Для работы с внешними файлами в языке C используются указатели на файл, которые имеют тип FILE* , они аналогичны по своему назначению переменным типа FILE, TEXT или FILE OF тип в языке PASCAL. Для открытия файла служит функция 33
FILE* fopen(char * имя файла , char* режим доступа ) которая возвращает указатель на файл или нулевой адрес NULL при возникновении ошибки. Существуют следующие режимы доступа: “r” - текстовый файл для чтения; “w” - текстовый файл для записи; “a” - текстовый файл для записи в конец файла; “r+” - текстовый файл для чтения и записи; “w+” - новый текстовый файл для записи и чтения; “a+” - текстовый файл с любым режимом доступа. Модификатор b в конце режима доступа означает бинарный файл с соответствующим режимом доступа. Функция fopen выполняет те же действия, что пара процедур Assign и Reset/Rewrite/Append в языке PASCAL. Закрытие файла осуществляется функцией int fclose(FILE* указатель на файл ) которая возвращает 0 при успешном завершении или EOF (=-1) при ошибке. Функция int feof(FILE* файл ) возвращает ненулевое значение, если достигнут конец файла. Посимвольный ввод-вывод в файл осуществляется функциями: int fgetc(FILE* указатель на файл ) - возвращает введенный символ или EOF; int fputc(int символ , FILE* указатель на файл ) - возвращает записанный символ или EOF. Форматный ввод-вывод в файл осуществляется функциями: int fscanf(FILE* указатель на файл , char* формат , список ввода ) возвращает количество введенных значений или EOF; int fprintf(FILE* указатель на файл , char* формат [ , список вывода ] ) - возвращает количество записанных символов или EOF. Без всяких затруднений, так же, как в языке PASCAL, можно читать из текстового файла числа функцией fscanf. Строковый ввод-вывод в файл осуществляется функциями: char* fgets(char* строка , int n, FILE* указатель на файл ) - читает не более n-1 символов, прекращает чтение при достижении символа '\n', добавляет в строку символ '\0', возвращает введенную строку или NULL , если произошла ошибка или достигнут конец файла; int fputs(char* строка , FILE* указатель на файл ) - записывает строку в файл, возвращает последний записанный символ или EOF в случае ошибки. Функция не записывает символ '\n'.
34
Указатели на стандартные файлы ввода и вывода stdin и stdout автоматически инициализируются в начале выполнения программы и могут быть использованы без каких-либо дополнительных описаний. Задав в качестве указателя на файл stdin в функциях fgetc, fscanf, fgets, мы получим соответственно функции getchar, scanf и gets для ввода с клавиатуры; использовав stdout в fputc, fprintf и fputs, получим соответственно putchar, printf и puts для вывода на экран. С любыми файлами, в том числе и с текстовыми, можно работать как с файлами прямого доступа, используя функции: void rewind(FILE* файл ) - устанавливает текущую позицию на начало файла, аналогична процедуре Reset в языке PASCAL; long ftell(FILE* файл ) - возвращает текущую позицию в файле, аналогична функции FilePos в языке PASCAL; int fseek(FILE* файл , long смещение , int начало ) - позиционирует файл, аргумент начало принимает одно из трех значений : SEEK_SET - от начала файла, SEEK_CUR - от текущей позиции, SEEK_END - от конца файла (смещение можно задавать со знаком). Возвращает 0 при успешном завершении и ненулевое значение при ошибке. Аналогична процедуре Seek в языке PASCAL. Ввод-вывод в бинарные файлы осуществляется функциями: size_t fread(void* буфер , size_t r, size_t n, FILE* файл ) - читает не более n записей длиной r , помещая их в память начиная с адреса буфер, возвращает количество считанных записей, аналогична процедуре BlockRead в языке PASCAL; size_t fwrite(void* буфер , size_t r, size_t n, FILE* файл ) - записывает из памяти начиная с адреса буфер n записей длиной r в файл, возвращает количество записанных записей, аналогична процедуре BlockWrite в языке PASCAL. 1.16. Структуры, объединения, битовые поля Структура - это объединенное в единое целое множество семантически связанных именованных элементов разных типов (аналог записей в языке PASCAL). Существует несколько вариантов описания структур, во многих случаях при этом используется структурный шаблон. Структурный шаблон описывается в виде: struct имя шаблона { описания элементов } ; Элементы структуры, описываемые в таком операторе - это то же самое, что поля записей в языке PASCAL. Структурную переменную можно описать, используя предварительно описанный шаблон: 35
struct имя шаблона имена переменных ; Можно объединить описания шаблона и переменной: struct имя шаблона { описания элементов } имена переменных ; или struct { описания элементов } имена переменных ; В этом случае шаблон может и не иметь имени. Имя шаблона не является именем типа, его нельзя использовать в описаниях без слова struct, но можно дать имя структурному типу, используя оператор typedef. Структуры, как и любые другие переменные, можно инициализировать. Допускаются массивы структур, вложенные структуры и указатели на структуры. Пример описания структурных переменных: struct st1 {int Num; unsigned char Ch; char St[64];}; struct st1 a={-11,'H',"123456789"}, b={0,'U',""}, c={777,'\a',"This is C structure"}, *p1=&c; Обращение к элементу структуры осуществляется так же, как в языке PASCAL : имя структуры . имя элемента Для указателей на структуры определена специальная операция косвенной адресации: -> , которая имеет самый высокий приоритет среди всех операций, она используется для обращения к элементу структуры по адресу: указатель -> имя элемента Например: p1->Ch , p1->St[0] . Можно заменить операцию косвенной адресации операцией "значение": (*p1).Ch , (*p1).St[0] . В смысле взаимодействия с функциями структуры рассматриваются как скалярные объекты, поэтому допускаются параметры функций - структуры и функции, возвращающие структуры. Для однотипных структур определена операция присваивания = . К структурным переменным применима операция “адрес” &. Объединение - это объект, который может содержать (попеременно) данные разных типов (фактически объединение является структурой, все элементы которой имеют нулевое смещение относительно ее базового адреса). Для объединения резервируется память по самому большому элементу. Полным аналогом объединений являются вариантные поля записей в языке PASCAL. Описание объединений и обращение к их элементам аналогично соответствующим действиям со структурами, но при этом ключевое слово struct заменяется на union. Инициализировать объединение можно только значением, имеющим тип первого из его элементов. Битовое поле - это последовательность битов, лежащих внутри одного или нескольких машинных слов. Описание поля имеет вид: 36
struct { [ тип ] [ имя ] : длина ; .................... } ; Тип элемента поля всегда int или unsigned. Неименованные элементы можно использовать для выравнивания (пропуска битов). Например, опишем объект, позволяющий осуществлять прямой доступ к любому биту младшего байта слова и к старшему биту: struct BitStr { unsigned b1:1; unsigned b2:1; unsigned b3:1; unsigned b4:1; unsigned b5:1; unsigned b6:1; unsigned b7:1; unsigned b8:1; :7; unsigned UP:1; }; union Word { unsigned N; struct BitStr b;}; union Word Number={0X3FE6}; printf("%u%u%u%u%u%u%u%u",Number.b.b1,Number.b.b2, Number.b.b3, Number.b.b4,Number.b.b5,Number.b.b6,Number.b.b7,Number.b.b8); printf(" Up bit=%u",Number.b.UP); В арифметических выражениях элементы битовых полей рассматриваются как целые числа. 1.17. Динамическое распределение памяти Построение динамических объектов (списков, деревьев) выполняется так же, как в языке PASCAL, т.е. для этого используются структуры. Структуры могут включать в качестве элементов указатели на себя, например, элемент односвязного списка символьных строк можно описать в виде: struct Element { char *String; struct Element *Next;}; Для распределения и освобождения динамической памяти используются функции, описанные в stdlib.h : void* malloc(size_t r) - возвращает указатель на место в памяти, отведенное для объекта размера r ; если память недоступна - возвращает NULL; void* calloc(size_t n,size_t r) - возвращает указатель на место в памяти, отведенное для массива из n объектов размера r ; если память недоступна - возвращает NULL; void* realloc(void* p,size_t r) - изменяет размер объекта, на который указывает p , на r , возвращает новый указатель или NULL; void free(void* p) - освобождает память, распределенную функциями calloc , malloc или realloc . В С++ введены две унарные операции - new и delete - для динамического распределения памяти, освобождающие программиста от необходи37
мости явно использовать библиотечные функции malloc, calloc и free. Операции new имя типа или new имя типа [ инициализатор ] позволяют выделить и сделать доступным свободный участок памяти и возвращают адрес выделенного места. Если память недоступна - возвращают NULL. В выделенный участок заносится значение, определяемое инициализатором, который не является обязательным элементом и представляет собой выражение в круглых скобках. Например: point=new int(15); h=new double(3.1415); string=new char[80]; Последний оператор отводит место в свободной памяти под массив из 80 элементов типа char . Указатель string содержит теперь адрес нулевого элемента массива. Для явного освобождения выделенного операцией new фрагмента памяти используется операция delete указатель , где указатель адресует освобождаемый участок памяти. Например, операторами delete point; delete string; освобождаются участки памяти, связанные с указателями point и string. 1.18. Графика Графические функции описаны в файле graphics.h . Эти функции практически полностью аналогичны процедурам и функциям модуля Graph системы Turbo Pascal, но все имена функций записываются строчными буквами, а имена констант - прописными. Некоторые отличия в вызове графических функций обусловлены свойствами языка С. Так, функция initgraph описана следующим образом: void initgraph(int *graphdriver,int *graphmode,char *pathtodriver); Она используется, например, так: int gdriver=DETECT,gmode; initgraph(&gdriver,&gmode,"D:\\TP\\BGI"); Здесь в качестве аргументов задаются адреса переменных gdriver и gmode, поскольку это выходные параметры; путь к драйверу заключается в “ ” по правилам С и вместо одного символа \ записывается пара таких символов. 1.19. Дополнительные возможности языка: тип enum, использование командной строки, функции с переменным числом параметров, средства консольного ввода-вывода, системное время, случайные числа
38
Оператор enum в языке С предназначен для объявления переменных типа перечисление, а также для объявления именованных целочисленных констант (типа int). Оператор enum записывается в виде enum [ имя перечисления ] { K-имя1 [ = значение1 ] , K-имя2 [ = значение2 ] , ... }; enum имя перечисления имя1 [ , имя2 , ... ] ; Первый оператор определяет набор именованных констант, перечисленных в {}. Если какое-либо значение опущено, то оно больше предыдущей константы на 1, если опущено первое значение, то оно равно 0. Константы типа enum могут использоваться как обычные величины типа int. Второй оператор определяет переменные перечислимого типа, такие переменные могут принимать значения только из своего перечисления. Оба действия можно объединить в одном операторе. Тип enum очень похож на тип “перечисление” в языке PASCAL, но в С данные такого типа могут использоваться как обычные числа. Функция main может иметь два параметра : количество аргументов (типа int) и массив строк-аргументов (типа char*[] ), которые позволяют ей считывать информацию из командной строки. Эти параметры обеспечивают те же возможности, что и функции ParamCount и ParamStr в языке PASCAL. Для того чтобы в программе получить аргументы, переданные через командную строку, достаточно записать список параметров функции main, например, так: void main(int NumberOfArgs, char *Args[]) Выведем на экран аргументы, полученные программой : for(i=0;i
39
va_end(va_list указатель ); - закрывает список неименованных параметров, его использование в функции обязательно. Функции для организации интерфейса с пользователем (аналогичные средствам модуля Crt в языке PASCAL) описаны в файле conio.h : 1) void window(int left, int top, int right, int bottom) - распределяет текстовое окно; 2) void textbackground(int color) - устанавливает цвет фона; 3) void textcolor(int color) - устанавливает цвет символов; 4) void textattr(int attr) - устанавливает цветовой атрибут; 5) void clrscr(void) - очищает окно; 6) void gotoxy(int x, int y) - перемещает курсор; 7) void _setcursortype(int cur_type) - устанавливает тип курсора, тип принимает значения: _NOCURSOR, _SOLIDCURSOR и _NORMALCURSOR 8) int kbhit(void) - проверяет, нажата ли какая-нибудь клавиша; 9) int putch(int ch) - выводит символ на экран; 10) int cprintf(char * формат [ , список вывода ] ) - осуществляет форматный вывод на экран; 11) int cputs(char *str) - выводит строку на экран; 12) int getch(void) int getche(void) - читают символ с клавиатуры; 13) char *cgets(char *str) - читает строку с клавиатуры, str[0] должен содержать максимально допустимую длину вводимой строки, через str[1] функция возвращает длину введенной строки, сама строка начинается с str[2] , функция возвращает &str[2] ; 14) int cscanf(char * формат [ , список ввода ] ) - осуществляет форматный ввод с клавиатуры. Функции, предназначенные для работы с временем, описаны в файле time.h : 1) clock_t clock(void) - возвращает процессорное время в тактах, прошедшее с начала выполнения программы; clock_t - псевдоним типа long; 2) CLK_TCK - вещественная символическая константа, равная количеству тактов процессора в секунду; 3) time_t time(time_t* timer) - возвращает время в секундах, прошедшее с 0 часов 1 января 1970 года; time_t - псевдоним типа long; 4) struct tm* localtime(time_t* timer) - возвращает структуру, содержащую дату и время, соответствующие значению переменной timer; структурный шаблон tm описан в файле time.h ; 5) char* asctime(struct tm* dt) - преобразует дату и время в символьную строку и возвращает указатель на нее. 40
Функции для получения случайных чисел описаны в файле stdlib.h : 1) void randomize(void) - инициализирует генератор случайных чисел, используя функцию time ; 2) void srand(unsigned seed) - инициализирует генератор случайных чисел, используя значение параметра seed ; 3) int rand(void) - возвращает случайное число в диапазоне от 0 до 32767; 4) int random(int range) - возвращает случайное число в диапазоне от 0 до range-1.
41
Глава 2. ОПИСАНИЕ ЯЗЫКА FORTRAN В этой главе содержится краткое описание языка FORTRAN 77, соответствующее компилятору Microsoft FORTRAN Optimizing Compiler Version 5.00. 2.1. Структура FORTRAN-программы FORTRAN - позиционный язык; это означает, во-первых, что операторы языка должны располагаться в строго определенных позициях, и, вовторых, что значение символа может зависеть от его местонахождения. Каждая строка FORTRAN-программы может содержать не более одного оператора. Операторы записываются в позициях с 7-й по 72-ю. Если оператор не умещается на одной строке, его можно продолжить на следующей строке, записав в 6-й позиции любой символ, кроме пробела и символа 0. В 6-й позиции начальной строки допускается символ 0. Позиции с 1-й по 5-ю могут содержать метки. Метка в языке FORTRAN - это натуральное число (можно с ведущими нулями). Пустые строки допускаются. Комментарий занимает целую строку, в первой позиции строки комментария должен стоять один из символов *, C или c. Второй тип комментария - комментарий в остатке строки, он отделяется от оператора символом “ ! ”. И наконец, в любой строке позиции с 73-й и до конца строки есть позиции комментария. Пробелы допускаются в любом месте оператора, компилятор считает пробелы значащими только внутри символьных констант, в других местах они игнорируются. Это означает, в частности, что можно вставлять пробелы в идентификаторы : MYNUMBER и MY NUMBER и M Y N U M B E R это корректная запись одного и того же идентификатора. Компилятор не различает в идентификаторах строчные и прописные буквы. Длина идентификатора может быть любой, но учитываются только шесть первых символов. Ключевые слова языка FORTRAN не резервируются никогда, их можно использовать в любых целях. Последним оператором программы должен быть оператор END , первым оператором может быть (но не обязательно) оператор PROGRAM имя программы Кроме операторов текст программы может содержать так называемые метакоманды, которые дают возможность устанавливать и менять режимы компиляции. Метакоманда начинается с символа $, который записывается в первой позиции. Одна из метакоманд - $INCLUDE - включает в текст программы внешний файл, она записывается в виде $INCLUDE:'имя файла' 2.2. Типы данных. Константы 42
В языке FORTRAN есть арифметические, логические и символьные данные. Арифметические типы данных: INTEGER*1 - аналогичен char в языке C и ShortInt в языке PASCAL; INTEGER*2 - аналогичен int в языке C и Integer в языке PASCAL; INTEGER*4 - аналогичен long в языке C и LongInt в языке PASCAL; INTEGER - соответствует либо INTEGER*2, либо INTEGER*4 - в зависимости от настройки компилятора, по умолчанию это INTEGER*4. Явно задать длину данных типа INTEGER можно с помощью метакоманды $STORAGE:2 или $STORAGE:4 REAL*4 (или REAL) - аналогичен float в языке C и Single в языке PASCAL; REAL*8 (или DOUBLE PRECISION) - аналогичен double в языке C и Double в языке PASCAL; COMPLEX*8 (или COMPLEX) - комплексные числа, действительная и мнимая части которых имеют тип REAL*4 COMPLEX*16 - комплексные числа, действительная и мнимая части которых имеют тип REAL*8 Логические типы данных: LOGICAL*1 LOGICAL*2 LOGICAL*4 LOGICAL - соответствует либо LOGICAL*2, либо LOGICAL*4, в зависимости от настройки компилятора, по умолчанию это LOGICAL*4. Длина типа LOGICAL также определяется метакомандой $STORAGE. Символьные типы данных: CHARACTER - символ, CHARACTER*длина - строка символов. Вообще говоря, различие между символом и символьной строкой в языке FORTRAN не проводится, CHARACTER или CHARACTER*1 - это строка из одного символа. Целые константы в языке FORTRAN могут иметь основание от 2 до 36 и записываются в виде ± [ [ основание ] # ] константа . Если опущены основание и символ #, то константа считается десятичной, если опущено только основание, то константа 16-ричная. Например: 2#11111111, 3#100110, 255, #FF, 19#D8. В целых константах с основанием, большим 10, в качестве дополнительных цифр используются латинские буквы A=10, B=11, ... ,Z=35. Все целые константы имеют тип INTEGER.
43
Вещественные константы записываются с фиксированной или плавающей точкой и имеют по умолчанию тип REAL : 2. , -.05 , 1E0 , -.3E-7 . В константах двойной точности вместо буквы E пишется D : 1D0 , -67.3D-7 . Логические константы записываются в виде .FALSE. или .TRUE. Строковые константы записываются либо в виде 'символы' , либо в виде 'символы' C. Последняя запись называется C-строкой (т.е. это строка, организованная так же, как в языке C), она всегда заканчивается 0символом (который явно не записывается) и может содержать специальные символы \n, \t, \v, \b, \r, \f, \a, \', \”, \\, \8-ричный номер , \x16-ричный номер. Например, 'это - FORTRAN-строка' или 'это - \"C-строка\"\n\a'C . Комплексные константы записываются в виде: ( действительная часть , мнимая часть ) Для записи действительной и мнимой частей можно использовать любые константы, в том числе и целые : (-1,0.5) , (2D0,-3.5) . 2.3. Описание переменных, правила умолчания, операторы IMPLICIT, DATA, PARAMETER Переменные в языке FORTRAN описываются в виде: тип имя1 , имя2 , ... Язык FORTRAN допускает использование нигде не описанных переменных, в этом случае их тип определяется правилами умолчания: идентификаторы, начинающиеся с I, J, K, L, M, N, имеют тип INTEGER, все остальные - тип REAL. Стандартные правила умолчания для неописанных переменных можно изменить с помощью оператора IMPLICIT: IMPLICIT тип ( буквы ) , ... Здесь тип - описание типа, буквы - список латинских букв. В списке букв можно использовать конструкцию буква - буква , задающую диапазон, например: IMPLICIT REAL*8 (D,F-H,W-Z,A), INTEGER*1 (B,C,I-K) Явное описание типа переменной во всех случаях отменяет правило умолчания для этой переменной. Правила умолчания можно полностью отменить оператором IMPLICIT NONE. Если в программе есть такой оператор, то каждая переменная должна быть явно описана. Инициализировать переменные, т.е. присвоить им начальные значения можно в операторе DATA : DATA список имен / список констант / список имен - это имена переменных, разделенные запятыми, список констант - константы, разделенные запятыми. Несколько повторяющихся 44
значений можно записать в виде повторитель * значение , где повторитель - целая константа, например: DATA a,b,i,j,k,c /1.2,3.141,2*5,-100,0/,x,y/2*0/ Операторы DATA должны располагаться в тексте программы после всех операторов описания и до первого исполняемого оператора. Переменные также могут инициализироваться непосредственно в операторе описания, например: REAL a/1.2/, b/3.141/, c/0/, x/0/, y/0/ INTEGER i/5/, j/5/, k/-100/ но в этом случае нельзя инициализировать список имен, каждая переменная должна быть инициализирована отдельно (но повторители при инициализации массивов использовать можно). Для определения именованных констант служит оператор PARAMETER, аналогичный оператору CONST в языке PASCAL: PARAMETER ( имя = константное выражение , ... ) Можно предварительно описать тип константы в операторе описания типа, например: LOGICAL*1 TRUE REAL*4 b,c PARAMETER(TRUE=.TRUE.,b=-1.5353,c=2) PARAMETER(Number=#FF) Именованные константы могут использоваться точно так же, как и неименованные, в частности, при инициализации переменных в операторах DATA или операторах описания. 2.4. Оператор присваивания. Операции Оператор присваивания записывается в виде: имя = выражение Типы переменной в левой части и выражения в правой части должны быть или оба арифметическими, или оба логическими, или оба символьными. Причем в арифметическом операторе присваивания нет больше никаких ограничений, например, целой переменной можно присвоить комплексное выражение, которое автоматически будет преобразовано к целому типу. В языке FORTRAN существует пять арифметических операций: сложение + , вычитание - , умножение * , деление / , возведение в степень ** . Операция ** имеет наивысший приоритет и выполняется в последовательности справа налево, остальные операции выполняются слева направо. Операция деления / для вещественных операндов означает вещественное деление, для целых операндов - деление нацело (точно так же, как в языке 45
C). Если операнды арифметической операции имеют разные типы, то они приводятся к одному типу согласно правилу старшинства типов : INTEGER*1 < INTEGER*2 < INTEGER*4 < REAL*4 < REAL*8 < COMPLEX*8 < COMPLEX*16 . Если один операнд имеет тип REAL*8, а второй - COMPLEX*8, то оба они приводятся к типу COMPLEX*16. Кроме того, компилятор следит за корректностью выполнения целочисленных вычислений, поэтому результат арифметической операции над операндами INTEGER*1 будет иметь тип INTEGER*2, а результат арифметической операции над операндами INTEGER*2 будет иметь тип INTEGER*4. Операции сравнения записываются в виде .LT. - меньше, .LE. - меньше либо равно, .GT. - больше, .GE. - больше либо равно, .EQ. - равно, .NE. - не равно. Они применимы к арифметическим и символьным операндам. Для комплексных операндов определены лишь операции .EQ. и .NE. Результат операции сравнения имеет тип LOGICAL. Логические операции применимы к логическим операндам и записываются в виде .NOT. - логическое отрицание, .AND. - логическое “и”, .OR. - логическое “или” , .EQV. - эквивалентно, .NEQV. - не эквивалентно; обратите внимание, что ни одна операция сравнения, в том числе .EQ. и .NE., к логическим операндам неприменима. Для символьных операндов определена только одна операция - операция сцепления // . Приоритеты операций в языке FORTRAN определены следующим образом : ** > { * , / } > { + , - } > // > {.EQ. , .NE. , .LT. , .LE. , .GT. , .GE.} > .NOT. > .AND. > .OR. > .EQV. > .NEQV. 2.5. Стандартные математические функции В языке FORTRAN есть большое количество стандартных арифметических функций, причем почти каждая из них имеет несколько разновидностей, отличающихся типом аргумента и типом возвращаемого значения. Здесь приводится лишь часть из них. Запись вида INT(integer/real/complex) INTEGER означает, что функция INT может иметь аргумент любого целого, любого вещественного и любого комплексного типа, а результат, возвращаемый этой функцией, имеет тип INTEGER. Запись вида ABS(integer/real/complex) integer/real означает, что функция ABS может иметь аргумент любого числового типа и возвращает значение того же типа для целых и вещественных аргументов, значение типа REAL*4 для аргумента COMPLEX*8 и значение типа 46
REAL*8 для аргумента COMPLEX*16. Во всех случаях имя типа, записанное большими буквами, означает только этот конкретный тип, а имя типа, записанное малыми буквами, - любой из подходящих типов: а) преобразование к целому типу: 1. INT(integer/real/complex) INTEGER 2. INT1(integer/real/complex) INTEGER*1 3. INT2(integer/real/complex) INTEGER*2 4. INT4(integer/real/complex) INTEGER*4 б) преобразование к вещественному типу: 5. REAL(integer/real/complex) REAL*4 6. DREAL(integer/real/complex) REAL*8 в) преобразования символа в число: 7. ICHAR(character) INTEGER*1 г) преобразование числа в символ: 8. CHAR(integer) CHARACTER д) целая часть числа: 9. AINT(real) real 10. DINT(real) REAL*8 е) округление: 11. ANINT(real) real 12. DNINT(real) REAL*8 13. NINT(real) INTEGER ж) абсолютная величина числа: 14. ABS(integer/real/complex) integer/real 15. IABS(integer/real/complex) integer 16. DABS(integer/real/complex) REAL*8 з) знак числа (функции возвращают абсолютную величину первого аргумента, умноженную на знак второго аргумента): 17. SIGN(integer/real,integer/real) integer/real 18. ISIGN(integer/real,integer/real) INTEGER 19. DSIGN(integer/real,integer/real) REAL*8 и) остаток от деления: 20. MOD(integer/real,integer/real) integer/real 21. AMOD(integer/real,integer/real) REAL*4 22. DMOD(integer/real,integer/real) REAL*8 к) положительная разность (функции возвращают разность первого и второго аргумента, если она положительна, или 0): 23. DIM(integer/real,integer/real) integer/real 24. IDIM(integer/real,integer/real) INTEGER 47
25. DDIM(integer/real,integer/real) REAL*8 л) максимальное и минимальное значение (функции допускают любое количество аргументов): 26. MAX(integer/real,...) integer/real 27. MAX1(integer/real,...) INTEGER 28. AMAX1(integer/real,...) REAL*4 29. DMAX1(integer/real,...) REAL*8 30. MIN(integer/real,...) integer/real 31. MIN1(integer/real,...) INTEGER 32. AMIN1(integer/real,...) REAL*4 33. DMIN1(integer/real,...) REAL*8 м) произведение с двойной точностью: 34. DPROD(integer/real,integer/real) REAL*8 н) мнимая часть и комплексно сопряженное число: 35. IMAG(complex) real 36. DIMAG(complex) REAL*8 37. CONJG(complex) complex 38. DCONJG(complex) COMPLEX*16 о) квадратный корень: 39. SQRT(integer/real/complex) real/complex 40. DSQRT(integer/real) REAL*8 41. CSQRT(integer/real/complex) COMPLEX*8 42. CDSQRT(integer/real/complex) COMPLEX*16 п) экспонента: 43. EXP(integer/real/complex) real/complex 44. DEXP(integer/real) REAL*8 45. CEXP(integer/real/complex) COMPLEX*8 46. CDEXP(integer/real/complex) COMPLEX*16 р) натуральный логарифм: 47. LOG(integer/real/complex) real/complex 48. ALOG(integer/real) REAL*4 49. DLOG(integer/real) REAL*8 50. CLOG(integer/real/complex) COMPLEX*8 51. CDLOG(integer/real/complex) COMPLEX*16 с) десятичный логарифм: 52. LOG10(real) real 53. ALOG10(integer/real) REAL*4 54. DLOG10(integer/real) REAL*8 т) тригонометрические функции: 48
55. SIN(integer/real/complex) integer/real/complex 56. DSIN(integer/real) REAL*8 57. CSIN(integer/real/complex) COMPLEX*8 58. CDSIN(integer/real/complex) COMPLEX*16 59. COS(integer/real/complex) integer/real/complex 60. DCOS(integer/real) REAL*8 61. CCOS(integer/real/complex) COMPLEX*8 62. CDCOS(integer/real/complex) COMPLEX*16 63. TAN(real) real 64. DTAN(integer/real) REAL*8 65. COTAN(real) real 66. DCOTAN(integer/real) REAL*8 у) обратные тригонометрические функции: 67. ASIN(real) real 68. DASIN(integer/real) REAL*8 69. ACOS(real) real 70. DACOS(integer/real) REAL*8 71. ATAN(real) real 72. DATAN(integer/real) REAL*8 73. ATAN2(real,real) real 74. DATAN2(integer/real,integer/real) REAL*8 ф) гиперболические функции: 75. SINH(real/complex) real 76. DSINH(integer/real/complex) REAL*8 77. COSH(real/complex) real 78. DCOSH(integer/real/complex) REAL*8 79. TANH(real/complex) real 80. DTANH(integer/real/complex) REAL*8 х) битовые функции: 81. NOT(integer) integer - битовое отрицание 82. IAND(integer,integer) integer - битовое “и” 83. IOR(integer,integer) integer - битовое “или” 84. IEOR(integer,integer) integer - битовое исключающее “или” 85. ISHL(integer,integer) integer - сдвиг влево, если второй аргумент положителен, или вправо, если второй аргумент отрицателен. Функция полностью аналогична операциям ShL и ShR в языке PASCAL и операциям << и >> в языке C
49
86. ISHA(integer,integer) integer - “арифметический” сдвиг, в отличие от ISHL при сдвиге вправо старший - знаковый бит заполняет все освободившиеся позиции 87. ISHC(integer,integer) integer - циклический сдвиг, осуществляет циклическую перестановку битов, ни один бит при этом не теряется 88. BTEST(integer,integer) LOGICAL - возвращает .TRUE., если бит первого аргумента с номером, заданным вторым аргументом, единичный. Биты нумеруются от младшего к старшему, номер самого младшего бита равен 0. 89. IBCLR(integer,integer) integer - зануляет бит первого аргумента с номером, равным второму аргументу 90. IBSET(integer,integer)integer заединичивает бит первого аргумента с номером, равным второму аргументу 91. IBCHNG(integer,integer) integer - меняет значение бита на противоположное 2.6. Ввод-вывод Язык FORTRAN предоставляет средства для бесформатного и форматного ввода-вывода данных. Вывод на экран осуществляется оператором PRINT формат , список вывода формат - это или строка формата, или метка формата, или символ *, что означает вывод без форматирования; список вывода - это любое количество произвольных выражений, разделенных запятыми. Логические данные при выводе изображаются буквами T и F. Для ввода с клавиатуры служит оператор READ формат , список ввода В списке ввода могут быть только имена переменных. Вводимые с клавиатуры данные могут разделяться пробелами и (или) запятыми. Вводимые логические значения обозначаются буквами T и F (любой последовательностью символов, начинающейся с T или F). Метка формата - это метка специального оператора FORMAT : метка FORMAT( список форматов ) Этот оператор является невыполняемым и может стоять в любом месте программы. Список форматов есть последовательность элементов формата (называемых также для краткости форматами), разделенных запятыми. Список форматов очень похож по своей структуре на строку формата функции printf в языке C. Он может содержать форматы данных и управляющие элементы формата. Существуют следующие форматы данных: 50
Формат I F E D G A L Z
Тип данных целые вещественные вещественные REAL*8 вещественные символьные логические любые данные
Синтаксис In / In.c Fn.d En.d / En.d.e Dn.d Gn.d / Gd.e A / An Ln Z / Zn
Здесь n - размер поля вывода, т.е. количество позиций, отведенных для вывода значения, c - количество выводимых цифр числа, d - количество дробных цифр, e - количество цифр порядка. Если в формате I задано количество цифр и оно больше, чем количество цифр в выводимом значении, то число дополняется слева нулями, если оно меньше, чем необходимо, то данный параметр игнорируется. Если при выводе по форматам I,F,E,D,G задан слишком маленький размер поля, то выводятся только символы *. Вещественные числа с плавающей точкой представляются при выводе в виде: знак. мантисса E знак порядка порядок . По формату Z все данные выводятся в их внутреннем представлении и изображаются 16-ричным числом. Форматов в списке может быть меньше или больше, чем выводимых значений. Если форматов больше, то неиспользованные форматы игнорируются. Если форматов меньше, то список форматов используется многократно, но каждое исчерпание списка форматов означает переход на новую строку. Любой формат данных может иметь повторитель - целую константу, которая записывается непосредственно перед форматом, например, 5F12.7. Можно записывать повторители для групп форматов, тогда форматы, входящие в группу, заключаются в круглые скобки, такие скобочные конструкции могут быть вложенными, например, 3(2F10.2,E12.3E1,4(I6.5)) Существуют следующие управляющие форматы : ‘строка’ - “строка” - вывод строки, nHсимволы - “литерал” - вывод n символов, / - переход на следующую строку, nX - вывод n пробелов, Tn - переход на n-ю позицию, TLn - смещение влево на n позиций, TRn - смещение вправо на n позиций, SP - включение режима вывода знака + для чисел, SS - выключение режима вывода знака + для чисел. 51
Символ / сам служит разделителем, поэтому в списке форматов его можно не отделять запятыми. Кроме того, первый символ выводимой строки всегда считается управляющим и не изображается на экране. Значение управляющего символа таково: 0 - пропуск строки, + - вывод на ту же строку, остальные - переход на новую строку. Каждый новый оператор PRINT всегда начинает вывод с начала новой строки. Форматный ввод с клавиатуры не очень удобен и используется редко (за исключением формата A), главным образом форматный ввод применяется при чтении файлов. При вводе запрещены управляющие форматы “строка” и “литерал”, управляющие форматы SP и SS игнорируются. Форматы Tn, TLn, TRn, / действуют точно так же, как и при выводе, формат nX пропускает n позиций во входном потоке. При вводе чисел можно использовать управляющие форматы: BN - игнорирование пробелов внутри числа (действует по умолчанию), BZ - интерпретация пробелов внутри числа как нулей, kP - умножение введенного значения на 10-k. Формат kP также используется при выводе чисел по формату F, в этом случае он означает умножение выводимого значения на 10k. Действие форматов данных при вводе таково: - формат In (второй параметр игнорируется) - целое число вводится из очередных n позиций; - форматы Fn.d , En.d , Dn.d , Gn.d (все форматы при вводе идентичны, параметр e игнорируется) - вещественное число вводится из очередных n позиций, если в константе нет десятичной точки, то последние d цифр интерпретируются как дробные; - формат A - значение символьной переменной (без апострофов) считывается до конца строки, при необходимости усекается справа; - формат An - значение символьной переменной (без апострофов) считывается из очередных n позиций, при необходимости усекается слева; - формат Ln - логическое значение считывается из очередных n позиций, оно может представляться любой последовательностью символов, начинающейся буквой T(t) или F(f), перед этой буквой может быть символ “.” и любое количество пробелов. - форматы Z , Zn - 16-ричное значение вводится до конца строки или из очередных n позиций и передается в память, занимаемую переменной; так же, как для формата A, если n не задано, то вводимое значение усекается справа, если задано, то слева. Если, напротив, вводимое значение слишком короткое, то оно дополняется слева нулями. 52
Бесформатный ввод осуществляется почти так же, как в языке PASCAL: данные во входном потоке разделяются любым количеством пробелов или запятыми, числовые константы представляются в любом корректном виде, но символьные константы нужно заключать в апострофы. Каждый оператор READ всегда начинает ввод с новой строки. Формат можно задать и непосредственно в операторах PRINT и READ, для этого вместо метки формата записывается строка ‘(список форматов)’, список форматов - точно такой же, как в операторе FORMAT, круглые скобки и апострофы обязательны. 2.7. Оператор СОNTINUE. Операторы перехода. Условные операторы Оператор CONTINUE - пустой оператор, он не выполняет никаких действий и используется, как правило, для улучшения структуры программы. Он практически всегда имеет метку. Оператор перехода в языке FORTRAN имеет три разновидности: безусловный, вычисляемый и по предписанию. Безусловный оператор перехода записывается в виде: GOTO метка Вычисляемый оператор перехода: GOTO (список меток)целое выражение передает управление на метку из списка меток с порядковым номером, равным значению целого выражения. Оператор перехода по предписанию имеет вид GOTO целая переменная Этой целой переменной предварительно должна быть присвоена одна из меток программы специальным оператором: ASSIGN метка TO переменная Целочисленная переменная, которой оператором ASSIGN была присвоена метка, допускает еще одно применение - ее можно использовать в операторах READ и PRINT вместо метки формата. Обратите внимание, что обычный оператор присваивания переменная=метка в этом случае не годится. Условный оператор IF в языке FORTRAN также имеет три формы: логический IF, арифметический IF и блочный IF. Логический оператор IF записывается в виде: IF (логическое выражение)оператор Здесь оператор - это любой выполняемый оператор, кроме логического и блочного IF. Логический IF содержит только один исполняемый оператор и не имеет ELSE-конструкции, его используют в самых простых условных 53
алгоритмах. Вторая форма условного оператора - арифметический оператор IF : IF (арифметическое выражение)метка1,метка2,метка3 Он выполняется следующим образом: вычисляется арифметическое выражение (оно не должно быть комплексным), если его значение отрицательно, то осуществляется переход на метку метка1, если оно равно 0, то осуществляется переход на метку метка2, и если оно положительно - на метку метка3. Блочный оператор IF является обобщением логического условного оператора и соответствует по своим возможностям и структуре условным операторам в языках PASCAL и C : IF(логическое выражение)THEN операторы ELSEIF(логическое выражение)THEN операторы ................................... ELSE операторы ENDIF Формально ELSEIF, ELSE и ENDIF являются отдельными операторами и обязательно должны записываться в отдельной строке. Последовательности операторов после IF, ELSEIF, ELSE называют соответственно IFблоком, ELSEIF-блоком и ELSE-блоком. В любом из блоков можно записать любое количество операторов. Операторы ELSEIF и ELSE могут отсутствовать, но оператор ENDIF обязателен. Операторы, входящие в блоки, сами могут быть условными, что позволяет записывать сколь угодно сложные условные конструкции. Оператор выбора SELECT CASE аналогичен оператору CASE в языке PASCAL: SELECT CASE(выражение) CASE(список значений) операторы ........................ CASE DEFAULT операторы END SELECT Выражение должно иметь целый, логический тип или тип CHARACTER*1. Список значений состоит из константных выражений соответствующего типа, разделенных запятыми или двоеточиями, например: SELECT CASE (i**2+3-m) 54
CASE (-10:1,3,12,22:40) PRINT*,'A' CASE (-30:-15,8:10,19,-12) GOTO 111 CASE DEFAULT GOTO 222 END SELECT В любом месте программы можно использовать оператор STOP [параметр] который прекращает выполнение программы. Здесь параметр - либо пусто, либо строка символов, либо целая константа в диапазоне от 0 до 99999. Если оператор используется без параметра, то на экран выдается сообщение STOP - Program terminated. Если параметр задан строкой символов, то на экран выводится только эта строка. Если параметр задан целой константой, то выводится сообщение Return code константа. Оператор PAUSE служит для временной приостановки выполнения программы и имеет точно такой же синтаксис, как и оператор STOP. Если оператор выполнен без параметра, то на экран выводится сообщение Pause - Please enter a blank line (to continue) or a DOS command . Если задан параметр - символьная строка, то выводится эта строка, если задан параметр константа, то выводится сообщение Pause - константа. После этого пользователь может совершить одно из трех действий: ввести пустую строку, ввести любую команду DOS или ввести слово COMMAND, затем любое количество команд DOS, а затем команду EXIT. После выполнения этих операций выполнение программы продолжается обычным образом. 2.8. Операторы цикла Оператор цикла DO (аналогичный оператору цикла FOR в языке PASCAL) записывается в виде: DO[метка]переменная=начальное значение,конечное значение[,приращение] Если в операторе задана метка, то последним оператором тела цикла является оператор с такой меткой, если метка не задана, то тело цикла должно заканчиваться специальным оператором END DO. Переменная цикла может иметь любой арифметический тип, кроме комплексного. Начальное, конечное значение и приращение - любые арифметические выражения, кроме комплексных. Если приращение не задано, оно считается равным 1. Последний оператор тела цикла не может быть: безусловным GOTO, GOTO по предписанию, арифметическим IF, блочным IF, оператором DO. 55
Во всех случаях можно последним оператором цикла записывать оператор CONTINUE. Допускаются вложенные циклы, они могут заканчиваться одним и тем же оператором, т.е. иметь одну и ту же метку цикла. Запрещается изменять значение переменной цикла в теле цикла, такая попытка рассматривается как синтаксическая ошибка. Оператор цикла DO WHILE полностью аналогичен по своему действию оператору While в языке PASCAL : DO [метка] WHILE(логическое выражение) Такой цикл может заканчиваться либо меткой, либо оператором END DO. Для выхода из любого типа цикла служит оператор EXIT, выполняющий те же действия, что оператор break в языках C и PASCAL. Оператор CYCLE завершает текущий шаг цикла и передает управление оператору DO или DO WHILE, он аналогичен оператору continue в языках C и PASCAL. 2.9. Функции LOC, LOCFAR, LOCNEAR Язык FORTRAN не дает возможности непосредственно работать с адресами переменных, как в языке PASCAL и, особенно, C. Единственные средства языка, связанные с адресами - это встроенные функции LOC, LOCFAR и LOCNEAR. Они возвращают адрес аргумента (но это может быть не только переменная). Функция LOCFAR возвращает дальний адрес (тип INTEGER*4), функция LOCNEAR - ближний адрес (тип INTEGER*2), а функция LOC эквивалентна в дальней модели памяти LOCFAR, а в ближней модели - LOCNEAR. 2.10. Массивы Существуют два способа описания массива - описание измерений массива в операторе DIMENSION плюс описание типа элементов массива, или описание измерений массива непосредственно в операторе описания типа. Оператор DIMENSION записывается следующим образом : DIMENSION имя массива([нижняя граница:]верхняя граница[,...]) Нижняя граница измерения массива по умолчанию равна 1. Оператор DIMENSION никак не определяет тип элемента массива, но только количество и длины его измерений. Границы измерений могут задаваться любыми арифметическими константными выражениями. Если какая-либо граница задана вещественным значением, оно преобразуется к целому отбрасыванием дробной части. Язык FORTRAN не задает никаких ограниче56
ний на размер массива и суммарный размер данных программы. Если тип элемента массива должен отличаться от типа, определенного по умолчанию, необходимо явно определить этот тип обычным образом: тип имя массива Можно не пользоваться оператором DIMENSION для описания массива, одновременно описывая тип элементов и измерения массива: тип имя массива([нижняя граница:]верхняя граница[,...]) Инициализировать массивы можно в операторе DATA, так же, как и простые переменные (можно использовать повторители). Элементы многомерных массивов располагаются в памяти не так, как в языках Паскаль и C: первым меняется самый левый индекс. Пусть массив описан как DIMENSION x(3,3), тогда его элементы будут записаны в памяти в таком порядке : x11, x21, x31, x12, x22, x32, x13, x23, x33. Для сравнения такой же массив в программе на Паскале или C : x11, x12, x13, x21, x22, x23, x31, x32, x33. Элементы массива в программе можно использовать так же, как простые переменные соответствующего типа (кроме оператора DO),обращение к элементу массива имеет вид: имя массива(индексное выражение,...) Индексное выражение может быть целого или вещественного типа. Массивы можно целиком вводить оператором READ и выводить оператором PRINT. Массиву можно присвоить массив с таким же описателем измерений, тип элементов при этом может быть разным. Массиву можно присвоить скалярное выражение - это присваивание будет выполнено для каждого элемента массива. Массивы могут быть операндами выражений, например, запись PRINT*,SIN(a) вполне корректна. Операция над массивом означает последовательное выполнение этой операции над каждым элементом массива, результат выражения есть массив такой же структуры с элементами соответствующего типа. При вводе и выводе массивов (и не только массивов) можно задавать так называемые неявные циклы. Неявный цикл ввода-вывода записывается в виде: (список ввода-вывода,переменная цикла=начало,конец[,приращение]) Переменная цикла, параметры начало, конец и приращение имеют тот же смысл, что и в операторе DO. Список ввода-вывода вводится или выводится столько раз, сколько раз выполняется неявный цикл. Если переменная цикла каким-то образом входит в список ввода-вывода, ее значение изменяется на каждом шаге цикла. Сам список ввода-вывода, в свою очередь, может включать неявные циклы. 2.11. Подпрограммы SUBROUTINE, FUNCTION и INTERFACE
57
В языке FORTRAN так же, как в языке PASCAL, есть подпрограммыпроцедуры (SUBROUTINE) и подпрограммы-функции (FUNCTION). Оператор заголовка процедуры имеет вид: SUBROUTINE имя процедуры(параметр[[атрибуты]][,...]) параметр - это или имя параметра (допускаются параметры - переменные, массивы и подпрограммы), или символ *. Тип параметра в операторе SUBROUTINE не указывается; если это необходимо, параметры описываются внутри процедуры. Процедура заканчивается оператором END и располагается вне любой другой подпрограммы и главной программы так же, как в языке C. Подпрограммы могут находиться в том же файле, что и главная программа, или в других файлах. Процедура вызывается в главной программе или в другой подпрограмме специальным оператором вызова CALL: CALL имя(список аргументов) Рекурсия в языке FORTRAN запрещена. Количество и типы аргументов в операторе CALL должны точно соответствовать количеству и типам параметров в операторе SUBROUTINE. Все параметры по умолчанию передаются в процедуры по адресу, это означает, что все действия над параметрами в процедуре фактически осуществляются над аргументами. Чтобы передать аргумент по значению, необходимо в операторе SUBROUTINE для соответствующего параметра задать атрибут VALUE. Атрибуты задаются в квадратных скобках после имени параметра. Параметр процедуры может быть массивом любой размерности. Такой параметр должен быть обязательно описан внутри процедуры. В описании измерений массива-параметра можно использовать не только константы, но и параметрыпеременные данной процедуры, что позволяет записывать процедуры, которые могут обрабатывать массивы переменного размера. Кроме того, разрешается верхнюю границу последнего измерения параметра-массива задавать символом *. Если предполагается, что нижняя граница индекса всегда равна 1, то одномерный массив-параметр описывается в виде имя(*). Параметры-массивы также можно объявлять с атрибутом VALUE, но в этом случае они не могут иметь переменный размер. Параметрами процедур могут быть подпрограммы (процедуры и функции). Внутри процедуры такой параметр не нужно как-либо описывать, но в вызывающей программной единице соответствующий аргумент должен быть описан специальным образом. Если аргумент является именем стандартной функции, то он описывается в операторе INTRINSIC список имен если это любая другая функция или процедура, то следует использовать оператор 58
EXTERNAL список имен Некоторые стандартные функции запрещено использовать в качестве аргументов. Формальным параметром процедуры может быть *. Аргументом, соответствующим такому параметру, должна быть конструкция *метка. Параметры такого типа используются следующим образом : внутри процедуры можно записывать любое количество операторов RETURN или RETURN целое выражение Первый оператор приводит к завершению процедуры и передаче управления оператору, следующему за оператором CALL в вызывающей программной единице. Второй оператор также завершает выполнение процедуры, но управление передается оператору с меткой, равной значению одного из аргументов. Значение целочисленного выражения после RETURN определяет порядковый номер метки среди аргументов-меток. Например, программа SUBROUTINE A ( *,*,x,*,* ) INTEGER x IF(x.LT.0) RETURN 1 IF(x.EQ.0) RETURN 2 IF(x.LE.10) RETURN 3 RETURN 4 END CALL A( *100,*200,5,*300,*400) STOP '0' 100 STOP '1' 200 STOP '2' 300 STOP '3' 400 STOP '4' END выведет на экран символ 3. Оператор заголовка подпрограммы-функции записывается в виде: [тип] FUNCTION имя(список параметров) Если тип функции явно не указан, он определяется правилами умолчания. Список параметров функции аналогичен списку параметров процедуры, но не допускаются параметры - метки. Тип функции может быть целым, вещественным, комплексным, логическим и символьным. Если тип функции отличается от предполагаемого по умолчанию, то его нужно описать в вызывающей программной единице. Список параметров функции может быть 59
пустым, в этом случае в заголовке функции можно не записывать и ( ), но при вызове функции даже с пустым списком параметров список аргументов задается обязательно в виде ( ). Внутри функции необходимо присвоить имени функции возвращаемое значение, точно так же, как в языке PASCAL. Можно использовать оператор RETURN, но без параметра (так как запрещены параметры-метки). Подпрограммы SUBROUTINE и FUNCTION, находящиеся в других файлах или в библиотеках, можно описать в специальных подпрограммах INTERFACE. Такое описание дает возможность компилятору проверить соответствие типов аргументов и параметров при вызове подпрограмм. Подпрограмма INTERFACE имеет следующую структуру : INTERFACE TO заголовок процедуры или функции описания параметров END Подпрограмма INTERFACE должна находиться в том же файле, что и вызывающая программная единица. 2.12. Общие области, подпрограмма BLOCK DATA. Оператор EQUIVALENCE В языке FORTRAN отсутствует возможность описания глобальных имен, все имена переменных и массивов являются локальными и определены только в той подпрограмме, где они описаны. Взамен FORTRAN предоставляет аппарат общих областей. Общая область описывается в виде: COMMON [/имя/] список где имя - имя общей области, которое может быть опущено (но в программе может быть только одна неименованная общая область), список - это разделенные запятыми имена переменных и массивов. Для массивов можно задавать описатели измерений. Общие области используются в программе для передачи информации между подпрограммами, минуя списки параметров. Общая область имеет смысл, если она описана более чем в одной подпрограмме, при этом имена переменных, входящих в общую область, совпадать не обязаны. Любая именованная общая область должна иметь одинаковый размер во всех подпрограммах, для неименованной общей области это необязательно. Для инициализации переменных из именованных общих областей служит специальный тип подпрограммы - BLOCK DATA. Подпрограмма BLOCK DATA имеет такую структуру: 60
BLOCK DATA [имя] описания общих областей операторы DATA END К подпрограммам BLOCK DATA не нужно специально обращаться, они выполняются автоматически. В программе может быть сколько угодно подпрограмм BLOCK DATA, но только одна неименованная. Оператор EQUIVALENCE позволяет совмещать по памяти различные переменные и массивы, в том числе и данные разных типов. Оператор записывается в виде: EQUIVALENCE (список объектов)[,...] Здесь список объектов - это разделенные запятыми имена переменных, имена массивов и элементы массивов. Количество совмещаемых объектов не ограничено. Существует несколько ограничений на использование оператора EQUIVALENCE : 1) совмещения не должны противоречить друг другу; 2) совмещаемые переменные и массивы нельзя инициализировать в операторе описания типа, но только в операторе DATA; 3) нельзя совмещать два объекта, если оба они входят в общие области; 4) не рекомендуется совмещать символьные и несимвольные объекты. 2.13. Символьные переменные Символьные, или строковые, переменные описываются в виде: CHARACTER[*общая длина] имя[*длина] , ... Если задан описатель длина для какой-либо переменной, то он отменяет для этой переменной действие описателя общая длина. Если длина вообще не задана, она считается равной единице. Наибольшая возможная длина строки равна 32767. Символьные переменные можно инициализировать в операторе DATA и операторе присваивания. Если присваиваемое значение длиннее, чем переменная, то оно усекается справа, если короче, то дополняется справа пробелами. Таким образом, символьные переменные в языке FORTRAN имеют фиксированную длину. Неинициализированные символьные переменные заполнены 0-символами. Допускаются символьные массивы любой размерности. Именованные символьные константы задаются обычным образом в операторе PARAMETER, но такие константы должны быть дополнительно описаны в операторе CHARACTER. Символьные переменные можно выводить оператором PRINT* и вводить оператором READ*, но при вводе
61
символьные значения следует задавать в апострофах. При форматном вводе-выводе используется формат A. Существует только одна символьная операция - // - конкатенация или сцепление строк. Кроме того, к символьным данным применимы операции сравнения .EQ., .NE., .LT., .LE., .GT., .GE. Имеется возможность непосредственного обращения к любой подстроке символьной переменной, для этого используется псевдопеременная “подстрока”: имя строки([начало]:[конец]) где начало и конец - целочисленные выражения, задающие номера первого и последнего символов, образующих подстроку. Если не задано начало, то подстрока начинается с первого символа строки, если не задан конец, то подстрока включает весь остаток строки ( но символ : обязательно должен быть записан). Точно так же можно обратиться к подстроке элемента символьного массива. Подстрока может не только быть операндом выражения, но также стоять в левой части оператора присваивания, вводиться оператором READ и инициализироваться в операторе DATA, т.е. подстрока может использоваться точно так же, как и настоящая символьная переменная. Для обработки символьных данных в языке FORTRAN есть две встроенные функции: LEN(character) INTEGER - возвращает длину строки, но, поскольку строки в языке FORTRAN имеют фиксированную длину, возвращаемое значение всегда есть максимальная длина строки или длина строки по описанию. INDEX(character,character) INTEGER - возвращает номер символа в первом аргументе, начиная с которого второй аргумент входит в первый или 0. Параметрами подпрограмм могут быть символьные переменные или символьные массивы. Они должны быть соответствующим образом описаны в процедуре. Длину строки можно задавать константой, переменной или использовать описатель неопределенной длины (*). Отметим, что именно для таким образом описанных строк-параметров и используется функция LEN.
2.14. Операторные функции 62
Операторная функция записывается в виде: имя(список параметров)=выражение Она аналогична по своему назначению макроопределению в языке C. Операторную функцию можно рассматривать как подпрограмму-функцию, состоящую из одного оператора. Тип значения, вычисляемого операторной функцией, можно определить в операторе описания, сама операторная функция должна размещаться после операторов описания и перед выполняемыми операторами. Типы параметров операторной функции никак не определяются, что придает этому средству дополнительную гибкость. 2.15. Внешние файлы Для обработки внешних файлов в языке FORTRAN существуют операторы: OPEN - открытие файла; CLOSE - закрытие файла; READ - чтение из файла; WRITE - запись в файл; INQUIRE - опрос файла; BACKSPACE - переход к предыдущей записи; REWIND - переход в начало файла; ENDFILE - запись признака конца файла и встроенная функция EOF. Синтаксис оператора OPEN таков: OPEN([UNIT=]устройство [,FILE=имя файла] [,MODE=режим] [,ACCESS=способ доступа] [,STATUS=статус файла] [,FORM=тип записей] [,RECL=длина записи] [,BLANK=интерпретация пробелов] [,BLOCKSIZE=размер буфера][,ERR=метка] [,IOSTAT=код ошибки]) устройство- целая константа от -32767 до 32767, или целое выражение, или *. Задает номер устройства, к которому присоединяется файл. Устройство * всегда связано с клавиатурой и экраном, его переопределить нельзя. Устройство 0 по умолчанию связано с клавиатурой и экраном, 5 - с клавиатурой, 6 - с экраном, но эти устройства можно использовать и для других целей; имя файла - символьное выражение, можно задать ‘’, тогда программа во время выполнения самостоятельно попросит ввести имя файла; режим - ‘READ’, ‘WRITE’ или ‘READWRITE’ (по умолчанию); способ доступа - ‘DIRECT’ или ‘SEQUENTIAL’ (по умолчанию);
63
статус файла - ‘OLD’, ‘NEW’, ‘UNKNOWN’ (по умолчанию) или ‘SCRATCH’. ‘SCRATH’ определяет временный файл, который будет уничтожен перед окончанием программы, такой файл не должен иметь имени; тип записей - ‘FORMATTED’ (по умолчанию для ‘SEQUENTIAL’), ‘UNFORMATTED’ (по умолчанию для ‘DIRECT’) или ‘BINARY’; длина записи - целое выражение, игнорируется для ‘SEQUENTIAL’; интерпретация пробелов - ‘ZERO’ или ‘NULL’ (по умолчанию); размер буфера - целое выражение, автоматически округляется до числа, кратного 512; метка - метка оператора, которому передается управление при возникновении ошибки. Если этот параметр не задан, то ошибка открытия файла приводит к аварийному завершению программы; код ошибки - целая переменная, которая возвращает 0, если операция завершилась успешно. Оператор CLOSE записывается так: CLOSE([UNIT=]устройство[,STATUS=статус файла] [,ERR=метка] [,IOSTAT=код ошибки]) статус файла - ‘DELETE’ (по умолчанию для временных файлов) или ‘KEEP’(по умолчанию для всех остальных); метка - метка оператора, которому передается управление при возникновении ошибки; код ошибки - целая переменная, которая возвращает 0, если операция завершилась успешно. Оператор READ для ввода из внешнего файла: READ([UNIT=]устройство[,[FMT=]формат][,REC=номер записи] [,END=метка-1][,ERR=метка-2][,IOSTAT=код ошибки]) номер записи - целое выражение, задается только для файлов прямого доступа, записи нумеруются начиная с 1; код ошибки - возвращает отрицательное значение, если достигнут конец файла, положительное значение, если произошла ошибка чтения, и нулевое значение при успешном завершении операции. Оператор WRITE записывается в виде : WRITE([UNIT=]устройство [,[FMT=]формат] [,REC=номер записи] [,ERR=метка] [,IOSTAT=код ошибки]) В файл прямого доступа можно записывать в любое место, даже если это вновь созданный файл; таким образом, некоторые записи в файле могут физически существовать, но быть неопределенными. Переменная код ошибки возвращает нулевое значение при успешном завершении операции.
64
Оператор INQUIRE позволяет получить все характеристики файла или устройства. Он записывается в форме запроса об устройстве: INQUIRE([UNIT=]устройство [,NAME=name] [,NUMBER=number] [,EXIST=exist] [,NAMED=named] [,OPENED=opened] [,MODE=mode] [,ACCESS=access] [,DIRECT=direct] [,SEQUENTIAL=sequential] [,FORM=form] [,FORMATTED=formatted] [,UNFORMATTED=unformatted] [,BINARY=binary] [,RECL=recl] [,BLANK=blank] [,BLOCKSIZE=blocksize] [,ERR=метка] [,IOSTAT=код ошибки]) или в форме запроса о файле: INQUIRE(FILE=имя файла ... ) Здесь параметры устройство, имя файла, метка и код ошибки имеют тот же смысл, что и раньше. Все остальные параметры - это переменные целого, логического и символьного типа, которые возвращают характеристики устройства или файла. Они возвращают следующие значения: name - имя файла, number - номер устройства, exist - .TRUE. , если файл существует, named - .TRUE. , если файл имеет имя, opened - .TRUE. , если файл присоединен к устройству, mode - ‘READ’, ‘WRITE’, ‘READWRITE’ или ‘UNKNOWN’ access - ‘SEQUENTIAL’, ‘DIRECT’ или ‘UNKNOWN’ , direct - ‘YES’, ‘NO’ или ‘UNKNOWN’ , sequential - ‘YES’, ‘NO’ или ‘UNKNOWN’ , form - ‘FORMATTED’, ‘UNFORMATTED’ или ‘UNKNOWN’ , formatted - ‘YES’, ‘NO’ или ‘UNKNOWN’ , unformatted - ‘YES’, ‘NO’ или ‘UNKNOWN’ , binary - ‘YES’, ‘NO’ или ‘UNKNOWN’ , recl - длина записи в байтах, blank - ‘NULL’, ‘ZERO’ или ‘UNKNOWN’ , blocksize - размер буфера в байтах. Оператор BACKSPACE устанавливает файловый указатель на начало предыдущей записи и может использоваться в двух формах : BACKSPACE устройство или BACKSPACE([UNIT=]устройство[,ERR=метка] [,IOSTAT=код ошибки]) Оператор REWIND устанавливает файловый указатель на начало файла: 65
REWIND устройство или REWIND([UNIT=]устройство[,ERR=метка][,IOSTAT=код ошибки]) Оператор ENDFILE записывает в файл признак конца файла, усекая таким образом файл: ENDFILE устройство или ENDFILE([UNIT=]устройство[,ERR=метка][,IOSTAT=код ошибки]) Эти три оператора работают и для текстовых файлов. В языке FORTRAN есть всего одна встроенная функция для обработки файлов EOF(integer) Функция возвращает истинное значение, если при вводе достигнут конец файла. Из текстового файла можно читать числа в свободном формате, т.е. READ(номер устройства,*, ... ) ... Но если числа записаны в файле совершенно произвольным образом, то прочесть их не так просто, как в языках PASCAL и С, потому что каждое выполнение оператора READ - это чтение новой строки. Кроме внешних устройств, которые определяются своим номером и связываются с каким-либо внешним файлом, можно использовать так называемые внутренние устройства - символьные переменные или массивы. Открывать и закрывать такие устройства не нужно, а операторы READ и WRITE записываются в виде READ/WRITE(UNIT=имя,FMT=формат)список ввода/вывода Внутренние устройства используются для преобразования числа в строку и наоборот. Если в качестве устройства используется символьный массив, то каждый элемент массива считается записью. Можно использовать подстроку в качестве внутреннего устройства. 2.16. Структуры Структуры в языке FORTRAN аналогичны записям в языке PASCAL и структурам в языке C. Структурные переменные описываются в программе в два этапа: сначала описывается структурный тип в операторе STRUCTURE : STRUCTURE /имя типа/ описания элементов END STRUCTURE а затем описываются переменные в специальном операторе RECORD : 66
RECORD/имя типа/ имя переменной[описатель измерений]... Допустимы вложенные структуры и массивы структур, структуры могут быть параметрами подпрограмм. Элемент структуры записывается в виде: имя структуры.имя элемента точно так же, как в языках PASCAL и C. Целые структуры нельзя вводить или выводить, подобно массивам; для них не работает оператор DATA. 2.17. Динамические массивы В языке FORTRAN отсутствуют средства для произвольных операций с динамической памятью, как в языках PASCAL и C. Вместо них можно использовать динамически распределяемые (или просто динамические) массивы. Память под такие массивы отводится не при компиляции программы, а по мере необходимости во время выполнения, а после использования может быть освобождена. Динамические массивы описываются со специальным атрибутом ALLOCATABLE : имя[ALLOCATABLE] описатель измерений Причем описатель измерений динамического массива имеет вид ( : , : , ... , : ) и задает только размерность массива, которая равна количеству двоеточий (двоеточия разделяются запятыми). Выделяется память для динамического массива оператором ALLOCATE : ALLOCATE (имя описатель измерений [,STAT=код ошибки]) Описатель измерений в операторе ALLOCATE записывается уже полностью, но, в отличие от обычных массивов, границы измерений могут задаваться любыми выражениями. Существует одно ограничение на использование динамических массивов - их можно использовать как скалярные переменные только в списках ввода-вывода, но не в операторе присваивания и не в арифметических выражениях. Память, отведенная под динамический массив, освобождается оператором DEALLOCATE : DEALLOCATE (имя[,STAT=код ошибки]) 2.18. Графика Для использования в программе графической библиотеки необходимо включить в программу операторы: INCLUDE ‘FGRAPH.FI’ INCLUDE ‘FGRAPH.FD‘ или метакоманды $INCLUDE:’FGRAPH.FI’ 67
$INCLUDE:’FGRAPH.FD’ для подключения файлов, содержащих описания графических процедур и функций (подпрограммы INTERFACE), а также для описания констант и переменных. Графический режим устанавливается функцией INTEGER*2 FUNCTION SetVideoMode (mode) INTEGER*2 mode Аргумент mode может быть, например, одной из констант: $MAXRESMODE =-3 - графический режим максимального разрешения; $MAXCOLORMODE =-2 - графический режим максимальной цветности; $DEFAULTMODE =-1 - возврат к оригинальному режиму. Функция возвращает 0 в случае неудачи. Аналогична InitGraph в языках PASCAL и C. Получить информацию о текущем экранном режиме можно с помощью процедуры: SUBROUTINE GetVideoConfig(s) STRUCTURE/videoconfig/ INTEGER*2 numxpixels ! число пикселов по X INTEGER*2 numypixels ! число пикселов по Y INTEGER*2 numtextcols ! число текстовых колонок INTEGER*2 numtextrows ! число текстовых строк INTEGER*2 numcolors ! число цветов INTEGER*2 bitsperpixel ! число битов на пиксел INTEGER*2 numvideopages ! число доступных видеостраниц INTEGER*2 mode ! текущий видеорежим INTEGER*2 adapter ! активный адаптер дисплея INTEGER*2 monitor ! активный монитор INTEGER*2 memory ! видеопамять адаптера в килобайтах END STRUCTURE RECORD/videoconfig/s Процедура возвращает текущую графическую конфигурацию через структуру s. Поле mode возвращает установленный графический режим. Поле adapter возвращает тип графического адаптера, например, $VGA = 8 . Поле monitor возвращает тип монитора, например, $COLOR или $ENHCOLOR .Фоновый цвет экрана устанавливается функцией INTEGER*4 FUNCTION SetBkColor (color) INTEGER*4 color Подпрограмма SUBROUTINE ClearScreen(area) INTEGER*2 area
68
очищает некоторую область экрана, заполняя ее текущим цветом фона. Параметр area может быть одной из следующих констант: $GCLEARSCREEN - весь экран, $GVIEWPORT - текущий видеопорт, $GWINDOW - текущее текстовое окно. Текущий цвет для линий и закраски устанавливает функция INTEGER*2 FUNCTION SetColor(color) INTEGER*2 color Вывод пиксела на экран (с использованием текущего цвета) выполняется функцией INTEGER*2 FUNCTION SetPixel (x,y) INTEGER*2 x,y Отрезок прямой от текущего положения графического курсора до точки с координатами x,y рисует функция INTEGER*2 FUNCTION LineTo (x,y) INTEGER*2 x,y Графический курсор перемещается процедурой SUBROUTINE MoveTo(x,y,s) INTEGER*2 x,y STRUCTURE/xycoord/ INTEGER*2 xcoord INTEGER*2 ycoord END STRUCTURE RECORD/xycoord/s Процедура возвращает через параметр s предыдущее положение графического курсора. Шаблон линии задается процедурой SUBROUTINE SetLineStyle (mask) INTEGER*2 mask Эллиптическая дуга рисуется функцией INTEGER*2 FUNCTION Arc(x1,y1,x2,y2,x3,y3,x4,y4) INTEGER*2 x1,y1,x2,y2,x3,y3,x4,y4 Центром эллипса является центр прямоугольника, который определяется точками (x1,y1) и (x2,y2), дуга начинается в точке, (x3,y3), и заканчивается в точке (x4,y4). Эллипс рисуется функцией INTEGER*2 FUNCTION Ellipse(control,x1,y1,x2,y2) INTEGER*2 control,x1,y1,x2,y2 Центром эллипса является центр ограничивающего прямоугольника, определяемого точками (x1,y1) и (x2,y2). Аргумент control может быть одной из двух констант: $GFILLINTERIOR - эллипс закрашивается, $GBORDER - эллипс не закрашивается. 69
Прямоугольник рисуется функцией INTEGER*2 FUNCTION Rectangle (control,x1,y1,x2,y2) INTEGER*2 control,x1,y1,x2,y2 Замкнутая область закрашивается функцией INTEGER*2 FUNCTION FloodFill(x,y,boundary) INTEGER*2 x,y,boundary Функция аналогична FloodFill в языках PASCAL и C. Способ закрашивания устанавливается процедурой SUBROUTINE SetFillMask(mask) INTEGER*1 mask где mask - массив из 8 элементов типа INTEGER*1, который определяет шаблон закраски области 8 на 8 пикселов. Если маска заполнения не установлена или массив нулевой, то для заполнения используется текущий цвет. Текст на графический экран выводится процедурой SUBROUTINE OutGText(text) CHARACTER*(*) text Текст выводится с использованием текущего шрифта, текущей графической позиции и текущего цвета. После вывода текста сохраняется текущая графическая позиция. Текст позиционируется по левому верхнему краю. Кроме того язык FORTRAN позволяет пользоваться в графическом режиме и обычными операторами PRINT и WRITE. Текущее положение графического курсора возвращает процедура SUBROUTINE GetCurrentPosition (s) RECORD/xycoord/s Ширину текста в пикселах возвращает функция INTEGER*2 FUNCTION GetGTextExtent(text) CHARACTER *(*) text Подключение графических шрифтов осуществляет функция INTEGER*2 FUNCTION RegisterFonts (PathToFiles) CHARACTER*(*) PathToFiles Файлы с графическими шрифтами имеют расширение fon и должны быть доступны программе. Функция возвращает положительное число, равное номеру последнего зарегистрированного шрифта при успешной регистрации. Отрицательное число означает : -1 - файл не найден; -2,-3 один или несколько файлов ошибочны. Графические шрифты отключаются процедурой SUBROUTINE UnRegisterFonts() Текущий шрифт устанавливается функцией INTEGER*2 FUNCTION SetFont (options) CHARACTER *(*) options 70
Она устанавливает один из зарегистрированных шрифтов, который характеризуется параметром options. Строка options может содержать подстроки: t’fontname’ - имя шрифта; hx - высота символа в пикселах для масштабируемых шрифтов; wx - ширина символа в пикселах для масштабируемых шрифтов; nx - номер шрифта. 2.19. Дополнительные возможности языка: дополнительные точки входа, свободный формат, строки отладки, средства консольного ввода-вывода, системное время, случайные числа В подпрограммах SUBROUTINE и FUNCTION могут быть дополнительные точки входа, которые задаются оператором ENTRY : ENTRY имя точки входа(список параметров) Операторы ENTRY могут записываться в любом месте подпрограммы, но не внутри цикла и не внутри условного оператора. Список параметров дополнительной точки входа может не совпадать со списком параметров в заголовке подпрограммы. Тип вычисляемого значения (если это функция) также может отличаться от типа, указанного в заголовке. Но в самом операторе ENTRY задать вычисляемый тип нельзя, это нужно сделать в операторе описания в начале подпрограммы. Подпрограмму, имеющую дополнительные точки входа, можно вызвать через любую из этих точек, точно так же, как и по основному имени. Но дополнительные точки входа нельзя передавать другим подпрограммам как аргументы. Метакоманда $FREEFORM позволяет записывать текст программы в так называемом свободном формате. При этом операторы можно размещать в позициях с 1-й по 80-ю; метки записываются начиная с 1-й позиции и могут не отделяться от оператора пробелом; если последний символ строки “-”, то следующая строка считается строкой продолжения; признак комментария -символ “ в 1-й позиции. Строки отладки позволяют осуществлять так называемую условную компиляцию, т.е. компилировать не все операторы программы, а лишь некоторое их подмножество. Строка отладки - это любая строка программы, в первой позиции которой записана какая-нибудь латинская буква (кроме буквы C). Для включения режима условной компиляции используется метакоманда $DEBUG. Она может быть задана (в любом месте программы) либо без параметров : $DEBUG , либо с параметром - строкой, состоящей из латинских букв : $DEBUG:’буквы’. В первом случае метакоманда 71
включает расширенную диагностику ошибок времени выполнения (целочисленное переполнение, проверка границ подстрок, проверка индексных выражений элементов массивов и т.д.). Во втором случае метакоманда включает режим условной компиляции - компилируются только те из строк отладки, которые начинаются буквой, входящей в строку, заданную в $DEBUG, остальные строки отладки считаются комментариями. Отменить действие метакоманды $DEBUG можно либо другой метакомандой $DEBUG, либо метакомандой $NODEBUG. В языке FORTRAN есть некоторые средства для работы с текстовыми окнами на экране. Они находятся в той же библиотеке, что и графические процедуры и функции, поэтому для их использования необходимо подключить к программе файлы fgraph.fi и fgraph.fd. Процедура SetTextWindow (y1,x1,y2,x2) распределяет текстовое окно на экране, y1,y2 - номера строк, x1,x2 - номера позиций. Для очистки текстового окна используются функция SetBkColor и процедура ClearScreen, которые работают как в графическом, так и в текстовом режиме. Положение курсора устанавливает процедура SetTextPosition(y,x,OlpPos), где y,x - номер строки и номер позиции (координаты отсчитываются от левого верхнего угла окна), OldPos - структурная переменная, через которую возвращаются предыдущие координаты курсора, она должна быть описана как RECORD/rccoord/TextPos. Цвет текста устанавливает функция SetTextColor(Color). Вывод в текстовое окно осуществляется специальной процедурой OutText(text). Процедура выводит только символьные значения, числа должны быть предварительно преобразованы в строку. Не следует использовать совместно с процедурой OutText операторы PRINT и WRITE. Форму курсора можно установить функцией SetTextCursor. Она имеет один параметр типа INTEGER*2, значение #0707 задает тонкий курсор, #0007 - максимально большой (“блочный”) курсор, #0607 - нормальный курсор, #2000 - невидимый курсор. При работе с текстовыми окнами нежелательно использование оператора PAUSE, вместо него можно использовать оператор READ(*,*), который ожидает нажатия клавиши Enter (как ReadLn в языке PASCAL). Этот оператор работает и во всех других случаях. Для получения системного времени служит стандартная подпрограммапроцедура SUBROUTINE GetTim(h,m,s,s100) INTEGER*2 h,m,s,s100 где h-часы, m-минуты, s-секунды и s100-сотые доли секунды. Случайное вещественное число из отрезка (0,1) возвращает подпрограмма-процедура 72
SUBROUTINE Random(x) REAL*4 x Генератор случайных чисел можно инициализировать с помощью подпрограммы-процедуры SUBROUTINE Seed(Init) INTEGER*2 Init При возрастании параметра Init от 0 до примерно 10000 первое значение, возвращаемое генератором случайных чисел, растет от почти нуля до почти единицы, а для больших значений Init этот цикл повторяется. В качестве хорошего инициализатора можно брать величину s100*100, где s100 - сотые доли секунды текущего системного времени.
73
Глава 3. РЕШЕНИЕ ЗАДАЧ НА ЯЗЫКАХ C И FORTRAN В этой главе приведены тексты нескольких программ на языках C, FORTRAN и PASCAL. Составляя эту подборку программ авторы ставили перед собой задачу максимально полно проиллюстрировать возможности языков программирования, поэтому главным критерием при написании программ была “прозрачность”, возможно, в ряде случаев в ущерб компактности и эффективности. Каждая задача включает в себя исчерпывающую постановку, спецификацию исходных данных и три текста программы. Задача 1. Вывод натурального числа Ввести натуральное число, не превосходящее 1000000, и вывести его в десятичном, двоичном, 8-ричном и 16-ричном виде. { Решение Задачи 1 на языке PASCAL } VAR N,Divider : LongInt; Shift,Num : Byte; BEGIN Write(#10,#13,'Введите число '); Read(N); WriteLn('Число в десятичном виде : ',N); Write('Число в двоичном виде : '); FOR Shift:=19 DOWNTO 0 DO Write(N AND(LongInt(1) ShL Shift)ShR Shift); WriteLn(#10,#13,'Число в 8-ричном виде : ',N AND $C0000 ShR 18, N AND $38000 ShR 15,N AND $07000 ShR 12,N AND $00E00 ShR 9, N AND $001C0 ShR 6, N AND $00038 ShR 3,N AND $00007); Write('Число в 16-ричном виде : '); Divider:=$F0000; FOR Shift:=4 DOWNTO 0 DO BEGIN Num:=N AND Divider ShR (Shift*4); CASE Num OF 0..9 : Write(Num); ELSE Write(Chr(Ord('A')+Num-10)); END; Divider:=Divider ShR 4; END; END. /* Решение Задачи 1 на языке C */ #include<stdio.h> void main(void) { long n; int shift; printf("\nВведите число "); scanf("%li",&n); printf("\nЧисло в десятичном виде : %ld",n); printf("\nЧисло в двоичном виде : "); 74
for(shift=19;shift>=0;shift--)printf("%ld",(n&(1L<<shift))>>shift); printf("\nЧисло в 8-ричном виде : %lo",n); printf("\nЧисло в 16-ричном виде : %lX",n); } C Решение Задачи 1 на языке FORTRAN $STORAGE:4 $DEBUG INTEGER*1 SHIFT PRINT *,'Введите число ' READ*,N PRINT'(1X,A,I7)','Число в десятичном виде : ',N 0PRINT'(1X,A,20I1)','Число в двоичном виде : ', 1(ISHL(IAND(N,INT4(2)**SHIFT),-SHIFT),SHIFT=19,0,-1) 0PRINT'(1X,A,7I1)','Число в 8-ричном виде : ', 1ISHL(IAND(N,8#7000000),-18),ISHL(IAND(N,8#0700000),-15), 2ISHL(IAND(N,8#0070000),-12),ISHL(IAND(N,8#0007000),-9), 3ISHL(IAND(N,8#0000700),-6),ISHL(IAND(N,8#0000070),-3), 4IAND(N,8#0000007) PRINT'(1X,A,Z5)','Число в 16-ричном виде : ',N END
Задача 2. Сумма ряда Ввести вещественное число x из отрезка [0,10]. Вычислить еx двумя способами: разложением в ряд и стандартной функцией. Сравнить два вычисленных значения. { Решение задачи 2 на языке PASCAL } CONST Xmin=0; Xmax=10; CONST S2:Real=1; a:Real=1; n:LongInt=1; VAR x,S1 : Real; BEGIN Write('Введите вещественное число из отрезка [',Xmin,',',Xmax,'] '); Read(x); IF (x<Xmin)OR(x>Xmax) THEN BEGIN WriteLn('Неверное число'); Halt; END; REPEAT S1:=S2; a:=a*x/n; S2:=S1+a; Inc(n); UNTIL S1=S2; WriteLn('Exp(',x:0:2,'), вычисленная разложением в ряд : ',S2:0:3, #10,#13,'Exp(',x:0:2,'), вычисленная стандартной функцией :' , Exp(x):0:3,#10,#13,'Погрешность : ',S2-Exp(x)); END. /*Решение задачи 2 на языке C*/ #include<stdio.h> #include<math.h> 75
#define XMIN 0. #define XMAX 10. void main(void) { double S2=1,a=1,x,S1; long n=1; printf("\nВведите вещественное число из отрезка [%g,%g] ",XMIN,XMAX); scanf("%lg",&x); if(x<XMIN||x>XMAX){printf("\nНеверное число"); exit(1);} do{S1=S2; a*=x/n++; S2=S1+a;}while(S1-S2); printf("Exp(%g), вычисленная разложением в ряд : %g\n" "Exp(%g), вычисленная стандартной функцией : %g\n" "Погрешность : %g\n",x,S2,x,exp(x),S2-exp(x));} C Решение задачи 2 на языке FORTRAN PARAMETER(Xmin=0,Xmax=10) REAL*8 x,S1/0/,S2/1/,a/1/ INTEGER*4 n/1/ PRINT'(1X,A,F4.1,A,F4.1,A)','Введите вещественное число из отрезка * [',Xmin,',',Xmax,']' READ*,x IF (x.LT.Xmin.OR.x.GT.Xmax) STOP'Неверное число' DO WHILE(S1.NE.S2) S1=S2 a=a*x/n S2=S1+a n=n+1 END DO PRINT'(2(1X,A,F4.1,A,F9.3/),A,E12.5)','Exp(', x, '), вычисленная р *азложением в ряд : ', S2,'Exp(', x, '), вычисленная стандартной * функцией : ', Exp(x),' Погрешность : ', S2-Exp(x) END
Задача 3. Медленная сортировка Ввести последовательность вещественных чисел длиной не более 100 чисел. Упорядочить последовательность по неубыванию, используя алгоритм сортировки выбором. { Решение задачи 3 на языке PASCAL } CONST Nmax=100; TYPE TArray=ARRAY[1..Nmax] OF Real; VAR A : TArray; n,i,j,Imin : Byte; Min : Real; BEGIN Write('Введите длину последовательности '); Read(n); IF (n=0)OR(n>Nmax) THEN BEGIN WriteLn('Неверная длина'); Halt; END; 76
WriteLn('Введите последовательность'); FOR i:=1 TO n DO Read(A[i]); FOR i:=1 TO n-1 DO BEGIN Imin:=i; Min:=A[i]; FOR j:=i+1 TO n DO IF A[j]<Min THEN BEGIN Min:=A[j]; Imin:=j; END; IF Imin<>i THEN BEGIN A[Imin]:=A[i]; A[i]:=Min; END; END; WriteLn('Упорядоченная последовательность :'); FOR i:=1 TO n DO Write(A[i]:16); WriteLn; END. /* Решение задачи 3 на языке C */ #define NMAX 100 typedef double TArray[NMAX]; void main(void) {TArray a; char n,i,j,Imin; double Min; printf("\nВведите длину последовательности "); scanf("%i",&n); if(n<=0||n>NMAX){printf("\nНеверная длина"); exit(1);} printf("\nВведите последовательность\n"); for(i=0;i
1 99
IF(Imin.NE.i) THEN A(Imin)=A(i) A(i)=Min END IF CONTINUE PRINT*,'Упорядоченная последовательность :' PRINT 99,(a(i),i=1,n) FORMAT(5G16.3) END
Задача 4. Быстрая сортировка Ввести натуральное число N, не превосходящее 10000. Получить N случайных натуральных чисел, не превосходящих 999. Упорядочить числа по неубыванию, используя алгоритм сортировки бинарными вставками. { Решение задачи 4 на языке PASCAL } {$X+} USES Crt; CONST Nmax=10000; Range=1000; TYPE TArray=ARRAY[1..Nmax] OF Word; PROCEDURE Print(n:Word; VAR A:TArray); VAR i : Word; BEGIN i:=1; WHILE i<=n DO BEGIN Write(A[i]:4); IF i MOD 480=0 THEN BEGIN Write('Нажмите клавишу'); ReadKey; WriteLn; END; Inc(i); END; IF i MOD 480<>0 THEN BEGIN Write(#10,#13,'Нажмите клавишу'); ReadKey; WriteLn; END; END; PROCEDURE BinarySort(n:Word; VAR a:TArray); VAR i,Nsort,as,bs,xs,Tmp : Word; BEGIN Nsort:=1; WHILE Nsort=a[Nsort] THEN BEGIN Inc(Nsort); Continue; END; IF Tmp<=a[1] THEN BEGIN FOR i:=Nsort+1 DOWNTO 2 DO a[i]:=a[i-1]; a[1]:=Tmp; Inc(Nsort); Continue; END; as:=1; bs:=Nsort; xs:=(as+bs)DIV 2; WHILE (xs<>as)AND(xs<>bs) DO BEGIN 78
IF Tmp=a[xs] THEN BEGIN bs:=xs+1; Break; END; IF Tmp #include #include #include<stdlib.h> #define NMAX 10000 #define RANGE 1000 void Print(unsigned n,unsigned a[]){ unsigned i; i=1; while(i<=n){printf("%4u",a[i]); if(!(i%480)){printf("Нажмите клавишу"); getch(); printf("\n");}i++;} if(i%480){printf("\nНажмите клавишу"); getch(); printf("\n");}} void BinarySort(unsigned n,unsigned a[]){ unsigned i,Nsort=0,as,bs,xs,Tmp; while(++Nsort=a[Nsort])continue; if(Tmp<=a[1]){for(i=Nsort+1;i>=2;i--)a[i]=a[i-1];a[1]=Tmp;continue;} as=1; bs=Nsort; xs=(as+bs)/2; while(xs!=as&&xs!=bs){if(Tmp==a[xs]){bs=xs+1;break;} if(Tmpbs;i--)a[i]=a[i-1]; a[bs]=Tmp;}} void main(void) { unsigned A[NMAX+1],n,i; printf("\nВведите длину последовательности "); scanf("%i",&n); randomize(); for(i=1;i<=n;i++)A[i]=random(RANGE); Print(n,A); printf("Упорядоченная последовательность :\n"); BinarySort(n,A); Print(n,A);} C Решение Задачи 4 на языке FORTRAN SUBROUTINE $Print(n,A) INTEGER*2 A(n) 79
1
999
1 2 100 3
101 99
80
nn=n/460 DO 1 i1=1,nn PRINT 999,(A(i),i=(i1-1)*460+1,i1*460) PAUSE'Нажмите ENTER' IF(MOD(n,460).GT.0) THEN PRINT 999,(A(i),i=nn*460+1,n) PAUSE'Нажмите ENTER' ENDIF FORMAT(20I4) END SUBROUTINE BinarySort(n,a) INTEGER*2 a(n),as,bs,xs,Tmp Nsort=1 DO 99 WHILE(Nsort.LT.n) Tmp=a(Nsort+1) IF(Tmp-a(Nsort))1,99,99 IF(Tmp-a(1))2,2,3 DO 100 i=Nsort+1,2,-1 a(i)=a(i-1) a(1)=Tmp GOTO 99 as=1 bs=Nsort xs=(as+bs)/2 DO WHILE(xs.NE.as.AND.xs.NE.bs) IF (Tmp.EQ.a(xs)) THEN bs=xs+1 EXIT ENDIF IF(Tmp.LT.a(xs)) THEN bs=xs ELSE as=xs ENDIF xs=(as+bs)/2 END DO DO 101 i=Nsort+1,bs+1,-1 a(i)=a(i-1) a(bs)=Tmp Nsort=Nsort+1 END PARAMETER(Nmax=10000,Range=1000) INTEGER*2 A(Nmax) PRINT *,'Введите длину последовательности '
1
READ*,n CALL GetTim(ih,im,is,is100) CALL Seed(is*100) DO 1 i=1,n CALL Random(x) A(i)=x*Range CALL $Print(n,A) PRINT*,'Упорядоченная последовательность :' CALL BinarySort(n,A) CALL $Print(n,A) END
Задача 5. Слова В файле INPUT.TXT записан некоторый русский текст. Переписать все слова текста, содержащие наиболее часто встречающуюся в тексте букву, в файл OUTPUT.TXT и найти самое длинное из них. { Решение задачи 5 на языке PASCAL } FUNCTION RusUpCase(c:Char):Char; BEGIN IF c IN ['а'..'п'] THEN RusUpCase:=Chr(Ord(c)-Ord('а')+Ord('А')) ELSE IF c IN ['р'..'я'] THEN RusUpCase:=Chr(Ord(c)-Ord('р')+Ord('Р')) ELSE RusUpCase:=c; END; VAR f1,f2 : Text; c,m : Char; s,ss : String; Flag : Boolean; L : Array['А'..'Я'] Of LongInt; CONST Letters=['а'..'п','р'..'я','А'..'Я']; BEGIN Assign(f1,'input.txt'); {$I-} Reset(f1); IF IOResult<>0 THEN BEGIN WriteLn('Файл input.txt не найден'); Halt; END; {$I+} Assign(f2,'output.txt'); Rewrite(f2); FillChar(L,SizeOf(L),0); WHILE NOT EOF(f1) DO BEGIN Read(f1,c); IF c IN Letters THEN Inc(L[RusUpCase(c)]); END; m:='А'; FOR c:='Б' TO 'Я' DO IF L[c]>L[m] THEN m:=c; 81
WriteLn('наиболее часто встречается в тексте буква "',m,'"'); Reset(f1); WHILE NOT EOF(f1) DO BEGIN Read(f1,c); IF c IN Letters THEN BEGIN s:=''; Flag:=FALSE; WHILE c IN Letters DO BEGIN IF RusUpCase(c)=m THEN Flag:=TRUE; s:=s+c; Read(f1,c); END; IF Flag THEN WriteLn(f2,s); END; END; Close(f2); Writeln('слова текста, содержащие букву "',m,'", записаны в файл output.txt'); Reset(f2); ss:=''; WHILE NOT EOF(f2) DO BEGIN ReadLn(f2,s); IF Length(s)>Length(ss) THEN ss:=s; END; Writeln('самое длинное из таких слов :"',ss,'"'); END. /* Решение задачи 5 на языке C */ #include<stdio.h> #include<stdlib.h> #include<string.h> char *LowRussian="абвгдежзийклмнопрстуфхцчшщъыьэюя"; char *UpRussian ="АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"; int RusUpCase(char c){char*p; if(p=strchr(LowRussian,c))return *(UpRussian+(p-LowRussian)); return c;} int Letter(char c){return strchr(LowRussian,c)||strchr(UpRussian,c);} void main(void) { FILE*f1,*f2; char c,m,flag,i; long L[]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; char*s=malloc(81),*ss=malloc(81); if((f1=fopen("input.txt","r"))==NULL){ printf("\nФайл input.txt не найден\n"); exit(7);} f2=fopen("output.txt","w+"); do{c=fgetc(f1);if(Letter(c))L[RusUpCase(c)-'А']++;}while(c!=EOF); m=0;for(c=1;c<=31;c++)if(L[c]>L[m])m=c; m+='А'; printf("\nнаиболее часто встречается в тексте буква %c",m); fseek(f1,0,SEEK_SET); 82
while(!feof(f1)){c=fgetc(f1); if(Letter(c)){i=-1;flag=0; while(Letter(c)){if(RusUpCase(c)==m)flag=1;s[++i]=c;c=fgetc(f1);} s[++i]='\0';if(flag)fprintf(f2,"%s\n",s);}} printf("\nслова текста, содержащие букву \"%c\", записаны в файл output.txt",m); fseek(f2,0,SEEK_SET); strcpy(ss,""); do{i=fscanf(f2,"%s",s);if(strlen(s)>strlen(ss))strcpy(ss,s);}while(i!=EOF); printf("\nсамое длинное из таких слов :\"%s\"",ss); fclose(f1); fclose(f2);} C Решение задачи 5 на языке FORTRAN BLOCK DATA CHARACTER*32 Low,Up COMMON/Russian/Low,Up DATA Low,Up/'абвгдежзийклмнопрстуфхцчшщъыьэюя', * 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'/ END INTEGER FUNCTION Number(c) COMMON/Russian/Low,Up CHARACTER c,Low*32,Up*32 i=INDEX(Low,c) IF(i)2,2,1 1 Number=i RETURN 2 Number=INDEX(Up,c) END LOGICAL FUNCTION Letter(c) COMMON/Russian/Low,Up CHARACTER c,Low*32,Up*32 Letter=INDEX(Low,c).GT.0.OR.INDEX(Up,c).GT.0 END INTEGER FUNCTION Length(s) CHARACTER*(*) s L=LEN(s) DO WHILE(L.GT.0.AND.(s(L:L).EQ.' '.OR.s(L:L).EQ.CHAR(0))) L=L-1 ENDDO Length=L END LOGICAL Flag,Letter INTEGER*4 L(32)/32*0/ CHARACTER*79 s/' '/,ss,Low*32,Up*32 COMMON/Russian/Low,Up 83
101 102 103
104 105 106 107
108
109
84
OPEN(1,FILE='input.txt',MODE='READ',STATUS='OLD',ERR=101) GOTO 102 STOP'файл input.txt не найден' OPEN(2,FILE='output.txt',MODE='READWRITE',STATUS='UNKNOWN') READ(1,FMT='(A)',END=105)s DO 104 i=1,Length(s) IF(Letter(s(i:i))) THEN ip=Number(s(i:i)) L(ip)=L(ip)+1 ENDIF CONTINUE GOTO 103 m=1 DO 106 i=2,32 IF(L(i).GT.L(m))m=i CONTINUE PRINT*,'наиболее часто встречается в тексте буква "',Up(m:m),'"' REWIND 1 READ(1,FMT='(A)',END=108)s i=1 DO WHILE(i.LE.Length(s)) IF(Letter(s(i:i))) THEN Flag=.FALSE. j=0 ss=' ' DO WHILE(i.LE.Length(s).AND.Letter(s(i:i))) IF(Number(s(i:i)).EQ.m)Flag=.TRUE. j=j+1 ss(j:j)=s(i:i) i=i+1 ENDDO IF(Flag)WRITE(2,FMT='(79A1)')(ss(j:j),j=1,Length(ss)) ELSE i=i+1 ENDIF ENDDO GOTO 107 PRINT*,'слова текста, содержащие букву "',Up(m:m),'", записаны в ф *айл output.txt' REWIND 2 ss=' ' READ(2,FMT='(A)',END=110)s IF(Length(s).GT.Length(ss))ss=s GOTO 109
110
PRINT*,'самое длинное из таких слов :"',(ss(i:i),i=1,Length(ss)), *'"' CLOSE(1) CLOSE(2) END
Задача 6. График Нарисовать на графическом экране график функции y=sin2(2x)+ +√x·cos2(x/2), 1≤x≤10. { Решение задачи 6 на языке PASCAL } {$X+} USES Graph,Crt; CONST x1=1.0; x2=10.0; FUNCTION f(x:Real):Real; BEGIN f:=Sqr(Sin(2*x))+Sqrt(x*Sqr(Cos(x/2))); END; CONST X0=100; Y0=400; Lx=400; Ly=300; Blank=40; TicX=9; TicY=10; TicSize=5; ScreenColor=9; FieldColor=7; AxisColor=15; LineColor=14; TextColor=9; TextStyle=1; TextSize=4; N=100; h=(x2-x1)/N; VAR GraphDriver,GraphMode,i : Integer; x,y,Ymin,Ymax,Mx,My : Real; s : String; BEGIN Ymin:=+1.7E38; Ymax:=-1.7E38; FOR i:=0 TO N DO BEGIN y:=f(x1+i*h); IF yYmax THEN Ymax:=y; END; Mx:=Lx/(x2-x1); My:=Ly/(Ymax-Ymin); GraphDriver:=Detect; InitGraph(GraphDriver,GraphMode,''); SetFillStyle(8,ScreenColor); Bar(0,0,GetMaxX,GetMaxY); SetFillStyle(SolidFill,FieldColor); Bar(Blank,Blank,GetMaxX-Blank,GetMaxY-Blank); SetColor(AxisColor); SetTextStyle(TextStyle,0,TextSize); MoveTo(X0,Y0-Ly-TicSize); SetTextJustify(CenterText,BottomText); OutText('Y'); MoveTo(X0,Y0-Ly); LineRel(0,Ly); LineRel(Lx,0); SetTextJustify(LeftText,BottomText); MoveRel(TicSize,0); OutText('X'); SetTextStyle(TextStyle,0,1); SetTextJustify(CenterText,TopText); FOR i:=1 TO TicX DO BEGIN x:=x1+i*(x2-x1)/TicX; Str(x:0:1,s); MoveTo(X0+Round((x-x1)*Mx),Y0); LineRel(0,TicSize); OutText(s); 85
END; SetTextJustify(RightText,CenterText); FOR i:=1 TO TicY DO BEGIN y:=Ymin+i*(Ymax-Ymin)/TicY; Str(y:0:2,s); MoveTo(X0,Y0-Round((y-Ymin)*My)); LineRel(-TicSize,0); OutText(s); END; SetColor(TextColor); SetTextStyle(TextStyle,0,TextSize); SetTextJustify(CenterText,BottomText); OutTextXY(X0+Lx DIV 2,Y0-Ly-TextHeight(''),'y=sin2(2x)+√x⋅ cos2(x/2)'); SetColor(LineColor); SetLineStyle(0,0,3); SetViewPort(X0,Y0,GetMaxX,GetMaxY,FALSE); MoveTo(0,-Round((f(x1)-Ymin)*My)); FOR i:=1 TO N DO BEGIN x:=x1+i*h; LineTo(Round((x-x1)*Mx),-Round((f(x)-Ymin)*My)); END; ReadKey; CloseGraph; END. /* Решение задачи 6 на языке C */ #include<stdio.h> #include #include #include<math.h> #define x1 1.0 #define x2 10.0 #define F(x) (pow(sin(2*(x)),2)+sqrt((x)*pow(cos((x)/2),2))) #define ROUND(x) (floor((x)+.5)) void main(void){ int X0=100,Y0=400,Lx=400,Ly=300,Blank=40,TicX=9,TicY=10,TicSize=5, ScreenColor=9,FieldColor=7,AxisColor=15,LineColor=14,TextColor=9, TextStyle=1,TextSize=4,N=100,GraphDriver,GraphMode,i; float h=(x2-x1)/N,x,y,Ymin=+1.7E38,Ymax=-1.7E38,Mx,My; char*s=(char*)malloc(10); for(i=0;i<=N;i++){y=F(x1+i*h);Ymin=(yYmax)?y:Ymax;} Mx=Lx/(x2-x1); My=Ly/(Ymax-Ymin); GraphDriver=DETECT; initgraph(&GraphDriver,&GraphMode,""); setfillstyle(8,ScreenColor); bar(0,0,getmaxx(),getmaxy()); setfillstyle(SOLID_FILL,FieldColor); bar(Blank,Blank,getmaxx()-Blank,getmaxy()-Blank); setcolor(AxisColor); settextstyle(TextStyle,0,TextSize); moveto(X0,Y0-Ly-TicSize); settextjustify(CENTER_TEXT,BOTTOM_TEXT); outtext("Y"); moveto(X0,Y0-Ly); linerel(0,Ly); linerel(Lx,0); 86
settextjustify(LEFT_TEXT,BOTTOM_TEXT); moverel(TicSize,0); outtext("X"); settextstyle(TextStyle,0,1); settextjustify(CENTER_TEXT,TOP_TEXT); for(i=1;i<=TicX;i++){x=x1+i*(x2-x1)/TicX;sprintf(s,"%.1f",x); moveto(X0+ROUND((x-x1)*Mx),Y0);linerel(0,TicSize);outtext(s);} settextjustify(RIGHT_TEXT,CENTER_TEXT); for(i=1;i<=TicY;i++){y=Ymin+i*(Ymax-Ymin)/TicY;sprintf(s,"%.2f",y); moveto(X0,Y0-ROUND((y-Ymin)*My));linerel(-TicSize,0);outtext(s);} setcolor(TextColor); settextstyle(TextStyle,0,TextSize); settextjustify(CENTER_TEXT,BOTTOM_TEXT); outtextxy(X0+Lx/2,Y0-Ly-textheight(""),"y=sin2(2x)+√x⋅ cos2(x/2)"); setcolor(LineColor); setlinestyle(0,0,3); setviewport(X0,Y0,getmaxx(),getmaxy(),0); moveto(0,-ROUND((F(x1)-Ymin)*My)); for(i=1;i<=N;i++) { x=x1+i*h; lineto(ROUND((x-x1)*Mx),-ROUND((F(x)-Ymin)*My));} getch(); closegraph();} C Решение задачи 6 на языке FORTRAN $INCLUDE:'FGRAPH.FI' $INCLUDE:'FGRAPH.FD' PARAMETER(X1=1.,X2=10.,N=100,X0=100,Lx=400,Y0=400,Ly=300, * Blank=40,TicX=9,TicY=10,TicSize=5,ScreenColor=9, * FieldColor=7,AxisColor=15,LineColor=14,TextColor=9, * LineStyle=#FFFF) RECORD /XYCoord/ xy CHARACTER*4 s,Font/'n4'/,FontXY/'n2'/,Path*80/'*.fon'/ RECORD /FontInfo/ FInf RECORD/videoconfig/video REAL mx,my INTEGER*1 Mask0(8)/#FF,#FF,#FF,#FF,#FF,#FF,#FF,#FF/, * Mask(8) /#0F,#0F,#0F,#0F,#0F,#0F,#0F,#0F/ f(x)=SIN(2*x)**2+SQRT(x*Cos(x/2)**2) h=(X2-X1)/n Ymin=+1.7e38 Ymax=-1.7e38 DO 1 i=0,n x=X1+i*h y=f(x) IF(Ymin.GT.y) Ymin=y IF(Ymax.LT.y) Ymax=y 1 CONTINUE mx=Lx/(X2-X1) my=Ly/(Ymax-Ymin) 87
idummy=SetVideoMode($MAXRESMODE) IF(idummy.EQ.0) STOP 'Graphics error' CALL GetVideoConfig(video) idummy=SetColor(ScreenColor) CALL SetFillMask(Mask) idummy=Rectangle($GFILLINTERIOR,0,0, * video.NumXPixels-1,video.NumYPixels-1) CALL SetFillMask(Mask0) idummy=SetColor(FieldColor) idummy=Rectangle($GFILLINTERIOR,Blank,Blank, * video.NumXPixels-1-Blank,video.NumYPixels-1-Blank) NumFonts=RegisterFonts(Path) idummy=SetFont(Font) idummy=GetFontInfo(FInf) idummy=SetColor(AxisColor) CALL MoveTo(X0,Y0-LY,xy) idummy=LineTo(X0,Y0) idummy=LineTo(X0+LX,Y0) DO 11 i=1,TicX x=X1+i*(X2-X1)/TicX CALL MoveTo(X0+NINT(mx*(x-X1)),Y0,xy) idummy=LineTo(X0+NINT(mx*(x-X1)),Y0+TicSize) WRITE(s,1000) x CALL MoveTo(X0+NINT(mx*(x-X1))-GetGTextExtent(s)/2,Y0+TicSize, * xy) CALL OutGText(s) 11 CONTINUE DO 12 i=1,TicY y=Ymin+i*(Ymax-Ymin)/TicY CALL MoveTo(X0,Y0-NINT(my*(y-Ymin)),xy) idummy=LineTo(X0-TicSize,Y0-NINT(my*(y-Ymin))) WRITE(s,1000) y CALL MoveTo(X0-GetGTextExtent(s)-TicSize, * Y0-NINT(my*(y-Ymin))-FInf.PixHeight/2,xy) CALL OutGText(s) 12 CONTINUE 1000 FORMAT(F4.1) idummy=SetFont(FontXY) idummy=GetFontInfo(FInf) CALL MoveTo(X0,Y0-Ly-FInf.PixHeight,xy) CALL OutGText('Y') CALL MoveTo(X0+Lx,Y0-FInf.PixHeight,xy) CALL OutGText('X') CALL SetLineStyle(LineStyle) 88
2
idummy=SetColor(LineColor) CALL MoveTo(X0,Y0-NINT(my*(f(X1)-Ymin)),xy) DO 2 i=1,n x=X1+i*h y=f(x) idummy=LineTo(X0+NINT(mx*(x-X1)),Y0-NINT(my*(y-Ymin))) CONTINUE idummy=SetFont(FontXY) idummy=GetFontInfo(FInf) idummy=SetColor(TextColor) CALL MoveTo(X0-GetGTextExtent('y=sin2(2x)+√x⋅cos2(x/2)')/2+Lx/2, * Y0-Ly-FInf.PixHeight-TicSize,xy) CALL OutGText('y=sin2(2x)+√x⋅cos2(x/2)') PAUSE 'Press ENTER for exit' idummy=SetVideoMode($DEFAULTMODE) END
Задача 7. Кубическое уравнение Найти все корни (в том числе и комплексные) уравнения x3+ax2+bx+c= =0, где a, b, c - заданные вещественные коэффициенты. Использовать решение Кардано. { Решение задачи 7 на языке PASCAL } TYPE Real = Extended; Complex = RECORD Re,Im : Real; END; Troot = ARRAY[1..3] OF Complex; PROCEDURE ComplexRoot2(x:Real; VAR x1_2:Complex); BEGIN WITH x1_2 DO IF x>=0 THEN BEGIN Re:=Sqrt(x); Im:=0; END ELSE BEGIN Re:=0; Im:=Sqrt(Abs(x)); END; END; PROCEDURE ComplexAdd(x1,x2:Complex; VAR y:Complex); BEGIN WITH y DO BEGIN Re:=x1.Re+x2.Re; Im:=x1.Im+x2.Im; END; END; PROCEDURE ComplexRoot3(x:Complex; VAR x1_3:Complex); VAR z,f : Real; BEGIN WITH x DO BEGIN z:=Sqrt(Sqr(Re)+Sqr(Im)); IF Re=0 THEN BEGIN IF Im>=0 THEN f:=0.5*Pi ELSE f:=1.5*Pi END ELSE BEGIN f:=Arctan(Im/Re); IF Re<0 THEN f:=Pi+f ELSE IF Im<0 THEN f:=2*Pi+f; END; 89
END; IF z>0 THEN z:=Exp(Ln(z)/3); WITH x1_3 DO BEGIN Re:=z*Cos(f/3); x1_3.Im:=z*Sin(f/3); END; END; PROCEDURE ComplexMult(x1,x2:Complex; VAR y:Complex); BEGIN WITH y DO BEGIN Re:=x1.Re*x2.Re-x1.Im*x2.Im; Im:=x1.Re*x2.Im+x1.Im*x2.Re; END; END; PROCEDURE PrintComplex(x:Complex); BEGIN Write(x.Re:10:5); IF x.Im>=0 THEN Write('+',x.Im:8:5) ELSE Write(x.Im:9:5); Write('i'); END; FUNCTION Min3(x1,x2,x3:Real):Byte; BEGIN IF x1<x2 THEN IF x1<x3 THEN Min3:=1 ELSE Min3:=3 ELSE IF x2<x3 THEN Min3:=2 ELSE Min3:=3; END; VAR a,b,c,p,q,QQ : Real; AA,BB,QQ1_2,Tmp,Tmp1,Tmp2,d,One_1_3_p,One_1_3_m : Complex; y,x : TRoot; i,j : Byte; BEGIN WITH One_1_3_p DO BEGIN Re:=-0.5; Im:= Sqrt(3)/2; END; WITH One_1_3_m DO BEGIN Re:=-0.5; Im:=-Sqrt(3)/2; END; Write('Введите коэффициенты a,b,c '); ReadLn(a,b,c); p:=-Sqr(a)/3+b; q:=2*Sqr(a/3)*(a/3)-a*b/3+c; QQ:=Sqr(p/3)*(p/3)+Sqr(q/2); ComplexRoot2(QQ,QQ1_2); WITH AA DO BEGIN Re:=QQ1_2.Re-q/2; Im:=QQ1_2.Im; END; WITH BB DO BEGIN Re:=-QQ1_2.Re-q/2; Im:=-QQ1_2.Im; END; ComplexRoot3(AA,AA); ComplexRoot3(BB,BB); ComplexMult(AA,BB,Tmp); CASE Min3(Abs(p/3+Tmp.Re),Abs(p/3-Tmp.Re/2-Sqrt(3)*Tmp.Im/2), Abs(p/3-Tmp.Re/2+Sqrt(3)*Tmp.Im/2)) OF 2:ComplexMult(AA,One_1_3_p,AA); 3:ComplexMult(AA,One_1_3_m,AA); END; ComplexAdd(AA,BB,y[1]); With Tmp1 DO BEGIN Re:=(AA.Re+BB.Re)/2; Im:=(AA.Im+BB.Im)/2; END; With Tmp2 DO BEGIN Re:=AA.Re-BB.Re; Im:=AA.Im-BB.Im; END; With Tmp DO BEGIN Re:=0; Im:=Sqrt(3)/2; END; ComplexMult(Tmp,Tmp2,Tmp2); With y[2] DO BEGIN Re:=-Tmp1.Re+Tmp2.Re; Im:=-Tmp1.Im+Tmp2.Im; END; 90
With y[3] DO BEGIN Re:=-Tmp1.Re-Tmp2.Re; Im:=-Tmp1.Im-Tmp2.Im; END; WriteLn(' Корни уравнения и контроль :'); FOR i:=1 TO 3 DO BEGIN x[i].Re:=y[i].Re-a/3; x[i].Im:=y[i].Im; ComplexMult(x[i],x[i],Tmp1); ComplexMult(x[i],Tmp1,Tmp2); d.Re:=Tmp2.Re+a*Tmp1.Re+b*x[i].Re+c; d.Im:=Tmp2.Im+a*Tmp1.Im+b*x[i].Im; PrintComplex(x[i]); Write(' ----> '); PrintComplex(d);WriteLn(' = 0'); END; WriteLn('Нажмите Enter'); ReadLn; END. /*Решение задачи 7 на языке С*/ #include<math.h> #include<stdio.h> #include struct s_complex{double Re,Im;}; typedef struct s_complex complex; complex ComplexRoot2(double x) { complex x_1_2; if(x>=0){x_1_2.Re=sqrt(x); x_1_2.Im=0;} else{x_1_2.Re=0; x_1_2.Im=sqrt(fabs(x));}return x_1_2;} complex ComplexAdd(complex x1,complex x2) {complex y; y.Re=x1.Re+x2.Re; y.Im=x1.Im+x2.Im; return y;} complex ComplexRoot3(complex x){complex x1_3; double z,f; z=sqrt(pow(x.Re,2)+pow(x.Im,2)); f=atan2(x.Im,x.Re); if(z) z=pow(z,1./3); x1_3.Re=z*cos(f/3); x1_3.Im=z*sin(f/3); return x1_3;} complex ComplexMult(complex x1,complex x2){complex y; y.Re=x1.Re*x2.Re-x1.Im*x2.Im; y.Im=x1.Re*x2.Im+x1.Im*x2.Re; return y;} int Min3(double x1,double x2,double x3) {if(x1<x2)if(x1<x3)return 1;else return 3; else if(x2<x3)return 2;else return 3;} void PrintComplex(complex x){ printf("%10.5f",x.Re); if(x.Im>0)printf("+%7.5fi",x.Im);else printf("%8.5fi",x.Im);} void main(void) { double a,b,c,p,q,Q; int i,j; complex A,B,Q1_2,Tmp,Tmp1, Tmp2,y[3],x[3],d,One_1_3_p={-.5,.5},One_1_3_m={-.5,-.5}; One_1_3_p.Im*=sqrt(3); One_1_3_m.Im*=sqrt(3); printf("\nВведите коэффициенты a,b,c "); scanf("%lg%lg%lg",&a,&b,&c); p=-a*a/3+b; q=2*pow(a/3,3)-a*b/3+c; Q=pow(p/3,3)+pow(q/2,2); Q1_2=ComplexRoot2(Q); A.Re=Q1_2.Re-q/2; A.Im=Q1_2.Im; B.Re=-Q1_2.Re-q/2; B.Im=-Q1_2.Im; A=ComplexRoot3(A); B=ComplexRoot3(B); Tmp=ComplexMult(A,B); switch(Min3(fabs(p/3+Tmp.Re), fabs(p/3-Tmp.Re/2-sqrt(3)*Tmp.Im/2), 91
fabs(p/3-Tmp.Re/2+sqrt(3)*Tmp.Im/2))){ case 2:A=ComplexMult(A,One_1_3_p); break; case 3:A=ComplexMult(A,One_1_3_m);} y[0]=ComplexAdd(A,B); Tmp1.Re=(A.Re+B.Re)/2; Tmp1.Im=(A.Im+B.Im)/2; Tmp2.Re=A.Re-B.Re; Tmp2.Im=A.Im-B.Im; Tmp.Re=0; Tmp.Im=sqrt(3)/2; Tmp2=ComplexMult(Tmp,Tmp2); y[1].Re=-Tmp1.Re+Tmp2.Re; y[1].Im=-Tmp1.Im+Tmp2.Im; y[2].Re=-Tmp1.Re-Tmp2.Re; y[2].Im=-Tmp1.Im-Tmp2.Im; printf("\nКорни уравнения и контроль :\n"); for(i=0;i<3;i++){x[i].Re=y[i].Re-a/3; x[i].Im=y[i].Im; Tmp1=ComplexMult(x[i],x[i]); Tmp2=ComplexMult(x[i],Tmp1); d.Re=Tmp2.Re+a*Tmp1.Re+b*x[i].Re+c; d.Im=Tmp2.Im+a*Tmp1.Im+b*x[i].Im; PrintComplex(x[i]);printf(" ----> ");PrintComplex(d);printf(" == 0\n");} printf("\nНажмите любую клавишу\n"); getch();} // Решение задачи 7 на языке C++ #include<stdio.h> #include #include #include #include<math.h> #include void main(void){ double a,b,c,p,d1,d2,d3; int i,j; complex q,Q,tmp,A,B,y[3],x[3], One_1_3_p=complex(-.5,.5*sqrt(3)),One_1_3_m=complex(-.5,-.5*sqrt(3)); cout<<setfill(' ' )<<setprecision(5)<< setiosflags(ios::showpoint | ios::fixed | ios::right); cout<<"Введите коэффициенты a,b,c "; cin>>a>>b>>c; p=-a*a/3+b; q=2*pow(a/3,3)-a*b/3+c; Q=pow(p/3,3)+pow(q/2,2); A=pow(-q/2+sqrt(Q),complex(1./3,0)); B=pow(-q/2-sqrt(Q),complex(1./3,0)); tmp=A*B; d1=fabs(p/3+real(tmp)); d2=fabs(p/3-real(tmp)/2-sqrt(3)*imag(tmp)/2); d3=fabs(p/3-real(tmp)/2+sqrt(3)*imag(tmp)/2); if(d2
cout<<setw(10)<"<<setw(10) <',2G12.6,'i = 0') PAUSE 'Нажмите ENTER' END
Задача 8. Собственное число Найти наибольшее собственное число заданной квадратной матрицы. { Решение задачи 8 на языке PASCAL } CONST N=3; TYPE TVector=ARRAY[1..N] OF Real; TMatrix=ARRAY[1..N] OF TVector; CONST a : TMatrix = ((11,-6,2),(-6,10,-4),(2,-4,6)); x : TVector = (1,1,1); Error = 1E-10; 93
Limit = 100; VAR Iteration : Word; L,L0,s : Real; i,j : Byte; y : TVector; BEGIN Iteration:=0; L:=0; REPEAT Inc(Iteration); L0:=L; L:=0; FOR i:= 1 TO N DO BEGIN s:=0; FOR j:=1 TO N DO s:=s+a[i,j]*x[j]; IF Abs(s)>Abs(L) THEN L:=s; y[i]:=s; END; FOR i:=1 TO N DO x[i]:=y[i]/L; UNTIL (Iteration=Limit)OR(Abs(1-L0/L)<Error); WriteLn('Наибольшее собственное значение матрицы=',L,#10,#13, 'Достигнута относительная погрешность ',Abs(1-L0/L):7, ' за ',Iteration:3,' итераций',#10,#13, 'Соответствующий собственный вектор :'); FOR i:=1 TO N DO WriteLn(' x[',i:2,']=',x[i]); END. // Решение задачи 8 на языке C #include<stdio.h> #include<math.h> #define N 3 void main(void){ double a[][N]={11,-6,2,-6,10,-4,2,-4,6}, x[]={1,1,1}, Error=1E-10,L=0,L0,s,y[N]; int Limit=100,Iteration=0,i,j; do{ for(L0=L,L=0,i=0;ifabs(L))L=s;y[i]=s;} for(i=0;i=Error); printf("\nНаибольшее собственное значение матрицы=%g\n" "Достигнута относительная погрешность %g за %d итераций\n" "Соответствующий собственный вектор :\n",L,fabs(1L0/L),Iteration); for(i=0;i
1
3 2
998
999
& L/0/,L0,s,y(N) INTEGER Limit/100/ Iteration=0 Iteration=Iteration+1 L0=L L=0 DO 2 i=1,N s=0 DO 3 j=1,N s=s+a(i,j)*x(j) IF(ABS(s).GT.ABS(L))L=s y(i)=s x=y/L IF(Iteration.LT.Limit.AND.ABS(1-L0/L).GE.Error)GOTO 1 PRINT 998,L,ABS(1-L0/L),Iteration 0FORMAT(' Наибольшее собственное значение матрицы='G8.2/ 1 ' Достигнута относительная погрешность 'G7.1' за 'I3 2 ' итераций'/' Соответствующий собственный вектор :') PRINT 999,(i,x(i),i=1,N) FORMAT(' x['I2']='G10.2) END
Комментарии к задачам Задача 1. Вывод натурального числа Вывод числа в десятичном виде очевиден. При выводе числа в двоичном виде используются битовые операции AND,ShL,ShR (Pascal), &,<<,>> (C), или битовые функции IAND,ISHL (FORTRAN). Очевидно, что число содержит не более 20 значащих двоичных цифр. Восьмеричное представление числа получено в Pascal-программе и Fortran-программе точно так же, а в языке C есть специальный восьмеричный формат. Шестнадцатеричное представление числа получено в C-программе и FORTRAN-программе средствами формата, а на языке Pascal для этого пришлось записать специальный алгоритм. Задача 2. Сумма ряда Решение задачи записывается практически идентично на трех языках, результат вычисляется с абсолютной компьютерной точностью. Задача 3. Медленная сортировка
95
Алгоритм сортировки очевиден. Программы иллюстрируют обработку одномерных массивов. Задача 4. Быстрая сортировка Алгоритм сортировки бинарными вставками состоит в следующем: первый элемент массива есть упорядоченный массив длиной 1. Возьмем первый элемент неупорядоченного массива (второй элемент исходного массива) и вставим его в упорядоченный массив, не нарушая упорядоченности. Получим упорядоченный массив длиной 2 и неупорядоченный массив длиной n-2. Будем повторять эту операцию до тех пор, пока длина неупорядоченного массива не станет равной нулю. Место вставки нового элемента в упорядоченный массив ищется методом бисекции, это позволяет понизить сложность всего алгоритма до n⋅ln(n). Программы иллюстрируют использование подпрограмм и встроенного генератора случайных чисел. Задача 5. Слова Программы иллюстрируют способы обработки текстовых файлов и работу с символьными строками. В этих программах заметны отличия в представлении символьных данных в языках Pascal, C и Fortran. В Fortranпрограмме пришлось записать функцию Length для вычисления фактической длины строки, так ка язык не предоставляет таких средств. Обратите внимание, что остаток строки может быть заполнен пробелами или нульсимволами. Pascal-программа очень продуктивно использует возможность описания массива с символьными индексами и переменные типа “множество”. Задача 6. График Pascal-программа и C-программа практически идентичны, так как используют один и тот же графический интерфейс, графические средства языка Fortran существенно отличаются. В C-программе использовано макроопределение F(x), а в Fortran-программе - оператор-функция F(x). Все три программы предполагают, что в текущем каталоге есть все необходимые файлы поддержки (графический драйвер для Pascal-программы и Cпрограммы и шрифтовые файлы для всех трех программ). Задача 7. Кубическое уравнение Программы реализуют решение Кардано кубического уравнения с вещественными коэффициентами (см.: Корн Г., Корн Т. Справочник по математике для научных работников и инженеров. М.: Наука, 1970. С.43). В 96
виде исключения приведено четыре решения - на языках Pascal, C, Fortran и C++. Языки Fortran и C++ поддерживают комплексный тип данных, поэтому алгоритм записывается в этих двух программах просто и естественно. В Pascal-программе и C-программе пришлось моделировать комплексные числа записями (структурами) и написать несколько функций для обработки таких данных. Задача 8. Собственное число Использованный в программах итерационный метод вычисления наибольшего собственного числа L и соответствующего собственного вектора X квадратной матрицы А заключается в следующем : задается некоторый достаточно произвольный вектор X0. Вычисляется вектор AX0 и его норма (максимум из абсолютных величин компонентов), которая дает первое приближение L1 для собственного числа, а первое приближение для собственного вектора X1 получается нормировкой вектора AX0. Вторая и все остальные итерации выполняются аналогично. Итерационный процесс заканчивается, когда либо относительная погрешность собственного числа станет меньше заданной предельно допустимой ошибки, либо количество итераций достигнет заданного предельного значения.
97