ПРИМЕНЕНИЕ ЭВМ
(Программирование)
КУРС ЛЕКЦИЙ
СОДЕРЖАНИЕ
1. |
ВВЕДЕНИЕ |
3 |
2. |
ПРОГРАММНОЕ УПРАВЛЕНИЕ КОМПЬЮТЕРОМ |
11 |
3. |
ЭТАПЫ РАЗРАБОТКИ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ. ЯЗЫКИ ПРОГРАММИРОВАНИЯ |
15 |
4. |
АЛГОРИТМЫ И СТРУКТУРНОЕ ПРОГРАММИРОВАНИЕ |
29 |
5. |
МЕТОДЫ РАЗРАБОТКИ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ |
35 |
5.1. Методы процедурного программирования |
35 |
|
5.2. Структуры данных |
36 |
|
5.3. Поиск и сортировка |
38 |
|
5.4. Объектно-ориентированное программирование |
42 |
|
6. |
БАЗОВЫЕ ЭЛЕМЕНТЫ ЯЗЫКА ТУРБО ПАСКАЛЬ (ТР) |
45 |
6.1. Алфавит |
45 |
|
6.2. Константы и переменные |
46 |
|
6.3. Выражения и операции |
47 |
|
6.4. Структура программы |
50 |
|
7. |
ТИПЫ ДАННЫХ ЯЗЫКА ТР |
53 |
8. |
УПРАВЛЯЮЩИЕ СТРУКТУРЫ ЯЗЫКА ТР |
67 |
8.1. Операторы |
67 |
|
8.2. Процедуры и функции |
72 |
|
8.3. Модули |
79 |
|
9. |
ВВОД-ВЫВОД ДАННЫХ И ФАЙЛОВАЯ СТРУКТУРА В ЯЗЫКЕ ТР |
83 |
10. |
УКАЗАТЕЛИ И ДИНАМИЧЕСКАЯ ПАМЯТЬ В ЯЗЫКЕ ТР |
87 |
11. |
ОБЪЕКТЫ В ЯЗЫКЕ ТР |
91 |
1. ВВЕДЕНИЕ
Программное обеспечение для управления полетом "космического челнока" серии "Шаттл" состоит из нескольких самостоятельных программ, которые вместе содержат около миллиона отдельных команд, контролирующих и регулирующих все процессы - от расчета траектории и управления полетом до поддержания систем жизнеобеспечения. Подобные программные комплексы - одно из достижений современного мира. Но, несмотря на всю сложность, такие программы принципиально не отличаются от тех, которыми пользуется бухгалтер при финансовых расчетах или человек, играющий в компьютерные игры. Без программ компьютер любого размера - не более чем простое переплетение бесполезных электронных схем.
Термин "программное обеспечение" (software – мягкие изделия) широко вошел в обиход с начала 60-х годов. Это было связано с необходимостью провести четкую грань между командами, управляющими компьютером, и его физическими компонентами, или "аппаратным обеспечением" (hardware), которое, собственно, и составляет саму машину. В современных компьютерах команды, записанные в виде электромагнитных импульсов, хранятся, например, на магнитных дисках. Специальный блок компьютера считывает информацию с магнитных носителей и преобразует ее в специальную форму, пригодную для запоминания во внутренней памяти и последующего исполнения. Поэтому переключение компьютера с одной задачи на другую, например, с построения диаграммы на написание текста или - в случае более мощной машины - с разработки архитектурного проекта на создание карты погоды земного шара осуществляется простым изменением последовательности команд, управляющих ее работой.
Однако перенастройка компьютера не всегда была столь простым делом. Для программирования "монстров", которые возвестили своим появлением в 40-50-е годы начало компьютерной эры, требовались не только математические способности, но и в не меньшей степени физическая выносливость. Первым универсальным электронным компьютером считается машина "ЭНИАК"
(ENIAC - Electronic Numerical Integrator and Computer), созданная в 1945 г. в Высшем техническом училище Пенсильванского университета. Она предназначалась для вычисления баллистических таблиц, в которых остро нуждалась артиллерия американской армии. Эта машина представляла собой конгломерат 17468 электронных ламп и соединительных проводов, смонтированных на 40 панелях, расположенных в форме подковы вдоль стен большой комнаты. Ее возможности были таковы, что за секунду можно было сложить 5 тыс. десятиразрядных чисел. Однако каждое изменение программы "ЭНИАКа" требовало переключения сотен кабелей и установку в нужное положение приблизительно 6 тыс. переключателей. Поэтому в среднем на подготовку машины к вычислению одной таблицы уходило два дня кропотливой ручной работы. Несмотря на низкую надежность "ЭНИАКа" и его послевоенных преемников, именно с помощью этих первых машин были заложены основы программирования.
Вычислительные машины военного времени в конструктивном плане отчасти строились на идеях 19 века. Одна из таких идей заключается в использовании для программирования перфокарт. Перфокарточный метод управления механизмом связан с именем Жозефа Мари Жаккара. В 1804 году он создал ткацкий станок ("машина Жаккара"), для управления которым применялись перфокарты, соединенные друг с другом в виде ленты. Движением челнока управляли деревянные шпильки "читающего устройства" станка, которые по расположению отверстий в перфокарте определяли, какие нити следует поднять, а какие опустить для создания нужного узора.
Примерно 30 лет спустя английский математик Чарлз Бэббидж использовал идею Жаккара при разработке устройства, которое он назвал Аналитической машиной. Он разработал и спроектировал универсальный программируемый компьютер для решения широкого класса задач, где, в частности, предусматривалось специальное устройство для хранения обрабатываемых чисел - аналог оперативного запоминающего устройства современного компьютера. Предполагалось, что машина будет производить вычисления и запоминать результаты с помощью набора валов и шестерен. Управление машиной предполагалось осуществять с помощью массивных перфокарт. Однако требуемая для создания машины точность обработки намного превосходила технологические возможности того времени и поэтому она так и не была построена.
Одна из немногих современников Бэббиджа, кто сумел оценить достоинства Аналитической машины, была Ада Лавлейс (дочь лорда Байрона) - есть язык АДА. Иногда ее называют первым в мире программистом. Она разработала теоретически некоторые приемы управления последовательностью вычислений, которые используются и по сей день. Например, она описала команды, обеспечивающие повторение определенной последовательности шагов до тех пор, пока не выполнено заданное условие (такая конструкция называется циклом).
Создатели машины "ЭНИАК" (Джон Мочли и Преспер Экерт) прекрасно понимали, что основное преимущество электронных компьютеров (по сравнению с электромеханическими) состоит в их потенциальной способности хранить большое количество информации. Однако сама "ЭНИАК" имела весьма ограниченную внутреннюю память. Хотя она умела достаточно быстро выполнять сложные программы, ее сумматор (внутреннее запоминающее устройство на электронных лампах, предназначенное для хранения обрабатываемых данных) мог запомнить лишь 20 10-значных чисел. В своей новой машине, которая называлась "ЭДВАК" (EDVAC - Electronic Discrete Variable Automatic Computer), Мочли и Экерт в качестве внутренней памяти
предложили использовать ртутные линии задержки, в которых можно было "консервировать электрические импульсы", в затем в нужное время извлекать их оттуда. Еще одна особенность этой машины состояла в ориентации на работу не с десятичными, а с двоичными числами
, что позволяло упростить конструкцию арифметического устройства.
Другой принцип организации внутренней памяти был применен при создании компьютера "Марк 1", созданного в 1950 году в Манчестерском университете (имя "Марк 1" имел также более ранний компьютер, который был изготовлен в Гарвардском университете с участитем сотрудников фирмы IBM - основа - электромеханические реле). Внутренняя память его была построена на 6 ЭЛТ, в которых электронная пушка создавала заряженные точки на покрытом люминофором экране. Включаясь и выключаясь, электронный луч генерировал точки, которые соответствовали нулям и единицам. Для обнаружения (считывания) заряженных точек использовалась система специальных электродов, расположенных снаружи трубки. Точки мог видеть и человек, что давало возможность проконтролировать процесс запоминания и считывания.
Появление компьютеров, снабженных внутренней памятью и схемами обработки двоичных данных, вплотную подвело вычислительную науку к осознанию огромных возможностей универсальных вычислительных систем. Однако специалисты понимали, что общение с компьютером на языке цепочек нулей и единиц
(т.е. с помощью т.н. машинного кода, который является базовым языком компьютера) окажется достаточно трудным делом.
Одни из первых попыток создания кодов, понятных человеку, были предприняты Мочли и Экертом при создании новой машины "ЮНИВАК-1" (1949 г.) (UNIVAC, Universal Automatic Computer), предназначенного для Национального бюро переписи населения США. Этот компьютер был оснащен программой-компоновщиком
, который использовался как вспомогательное средство для создания других программ (в разработке принимала участие Грейс Хоппер). По заданному идентификатору, т.е. условному коду, он осуществлял выборку нужной подпрограммы из специальной библиотеки (хранилась на магнитной ленте), считывание и запись ее в отведенное место оперативной памяти. Т.о. компоновщик, извлекая несколько различных подпрограмм, формировал из них единую программу. Хоппер назвала эту транслирующую программу компилятором (compiler - компоновщик). Компоновщик, поставляемый вместе с компьютером "ЮНИВАК" (они поступили в продажу в конце 50-х годов), позволял создавать т.н. автоматические программы. Автоматическими они назывались потому, что большую часть утомительной работы по компоновке программы из отдельных машинных команд брал на себя сам компьютер.
Компоновщик и другие вспомогательные средства способствовали коммерческому успеху компьютера "ЮНИВАК" - первого компьютера массового выпуска и спроса. Другие предприниматели, заметив этот успех, быстро включились в компьютерный бизнес - так началась технологическая революция
. У нее было два движущих фактора: прогресс в области разработки быстродействующей электроники
и непрерывное совершенствование языка
общения человека с машиной.
С середины 50-х годов, когда вычислительная техника прочно укоренилась в университетах и научно-исследовательских лабораториях США Европы, наступило время стремительного прогресса в области программирования. Однако новые разработки не отрицали всего того, что было сделано раньше. Напротив, они опирались на уже построенный фундамент. Компиляторы и интерпретаторы для т.н. языков ассемблера
(эти языки требуют от программиста глубокого знания аппаратуры) остались важным средством программирования. Однако хотя эти средства и продолжали использоваться, их роль постепенно снижалась. Посредниками между программистами и машинами становились языки программирования высокого уровня
, которые давали возможность больше времени уделять решению конкретной задачи, не отвлекаясь на тонкие вопросы организации самого процесса выполнения задания на машине. Языки программирования - это тщательно составленные последовательности слов, букв, чисел и мнемонических сокращений, используемые для общения с компьютером. Языки программирования, имитирующие естественные языки и способные на основании одного предложения строить несколько команд компьютера, принято считать "языками высокого уровня".
Двумя яркими представителями этого этапа были сотрудники Дартмутского колледжа Томас Курц и Джон Кемени. Долгое время им приходилось работать на ближайшем к ним компьютере марки IBM-700 (объем памяти - 8192 слова, каждое длиной 36 разрядов), который находился в 215 км от их колледжа в Массачусетском технологическом интституте. Программисту требовалось подготовить задание на перфокартах и передать его в вычислительный центр специальному оператору. Этот посредник вводил карты в машину в большом пакете, который часто содержал сотни отдельных программ, принадлежавшим многим пользователям. Чтобы получить ответ, приходилось ждать, пока компьютер обработает весь пакет, но что уходило около суток. Затем исправление, вновь ожидание. Помимо этого первые программы для IBM-700 приходилось писать на языке ассемблера, программирование на котором мало чем отличалось от программирования в двоичном коде.
Сотрудники Дартмутского колледжа Томас Курц и Джон Кемени задались целью создать язык высокого уровня, который бы был бы прост и доступен студентам-гуманитариям, которые обучались в Дартмутском колледже.
Надо заметить, что к тому времени уже существовали несколько языков высокого уровня. Первым
и одним из наиболее распространенным был Фортран
(FORTRAN -Formula Translator), который разработала в конце 50-х годов группа инженеров фирмы IBM под руководством Джона Бекуса. Фортран был предназначен для работы с формулами, используемыми в математике и других научно-технических дисциплинах.
Курц и Кемени предполагали, что по синтаксису операторы нового языка будут похожи на операторы Фортрана, однако отличаться большей простотой. Кроме того, предполагалось, что студенты сами будут набирать программы на терминале (а не передавать их в виде колоды перфокарт оператору вычислительного центра), т.е. язык был ориентирован на интерактивное взаимодействие. В результате был создан универсальный символический код для начинающих (BASIC
- Beginners All-Purpose Symbolic Code). В последствии Бейсик стал поистине массовым языком программирования. В значительной степени этому способствовало то, что Бейсик практически повсеместно стали использовать как встроенный язык микрокомпьютеров, получивших широкое распространение в конце 70-х годов.
Появление и широкое распространение в 1957 г. Фортрана послужило толчком к разработке еще одного языка АЛГОЛА
(ALGOrithmic Language - алгоритмический язык), который создавался как единый язык для научного программирования
и в США и в Европе. Он был создан на совещании в Цюрихе в мае 1958 г. с участием создателя Фортрана Джона Бекуса и унаследовал многое от Фортрана. Но в тоже время основные понятия в нем были собраны в более логическую структуру. В дополненном варианте этот язык появился в январе 1960 г. (АЛГОЛ-60). Многие языки, возникшие позднее, базировались (по-крайней мере, частично) на Алголе. Это связано с несколькими его идеями. Одна из наиболее важных - блочная структура
, позволяющая делить программу на замкнутые единицы, разрабатываемые независимо. Другая идея - рекурсия
, т.е. способность программ повторно обращаться к себе. Третья идея - строго формализованное определение синтаксиса
, т.е. способа размещения слов в языке.
Модернизация АЛГОЛА продолжалась и в 60-е годы. В 1968 году появилась версия АЛГОЛ-68. Однако она оказалась слишком громоздкой и неудобной, и не получила широкого признания в Европе. Одним их критиков этой версии был швейцарский специалист по информатике Никлаус Вирт. В этом же 1968 году он начал создавать новый язык Паскаль
, который назвал в честь французского философа и математика XVII века. Он подошел к разработке языка, как инженер к проектированию машины. "Искусство инженера состоит в том, чтобы делать сложные задачи простыми". Язык Паскаль требует от программиста определения всех переменных в отдельной секции, расположенной в начале программы. Так как эти определения задаются явным образом, то в Паскаль-программах сравнительно немного ошибок и их проще понять и исправить программисту, не являющемуся автором программы. Это делает Паскаль весьма подходящим для создания больших программ. Датой рождения Паскаля считается 1970 год. В 1973 году он был объявлен официальным языком программирования для учащихся средних школ, которые намерены специализироваться в области вычислительной техники и программирования в университетах США. Паскаль - это один из языков, основанный на принципах структурного программирования.
Важный вклад в теорию структурного программирования
внес голландский ученый Эдсгер Дийкстра (работа 1968 г "Заметки по структурному программированию"). Он доказывал, что большинство программ неоправданно сложны из-за отсутствия в них четкой математической структуры (в 1968 г проходила конференция, где возникшая ситуация со сложностью и множеством ошибок в программном обеспечении получила название "кризис программного обеспечения"). Одна из основных причин, по мнению Дийкстры, состояла в частом использовании в программах оператора безусловного перехода GOTO, который передает управление из одной точки в другую. Прерывая логическую последовательность алгоритма, операторы GOTO практически исключают возможность хорошо разобраться в структуре программы для всех, кроме ее автора. Дийкстра предложил использовать три типа управляющих структур: простую последовательность (т.е. группу операторов, выполняемых друг за другом), альтернативу (конструкцию, позволяющую выбрать один из двух или более возможных операторов) и повторение (конструкцию, позволяющую выполнять заданный оператор до тех пор, пока удовлетворяется некоторое условие).
Кроме того, важная роль в преодолении сложности программ стала отводиться стандартизации отдельных ее частей (аналогично тому, как это делается при создании библиотек программ). В этом случае разработчику уже не приходится создавать каждую программную систему с нуля, он может использовать уже имеющиеся программные модули.
Несколько позже Паскаля появился еще один широко распространенный в настоящее время язык высокого уровня - Си
. Язык Си разработал в 1972 г. Деннис Ричи, специалист по системному программированию из фирмы "Белл телефон лабораторис". При его создании он ориентировался на программирование с помощью Си новой операционной системы UNIX. Традиционно ради повышения скорости работы операционных систем они писались на языке низкого уровня - ассемблере. В этом плане Си наиболее близок к языкам низкого уровня, поскольку обеспечивает непосредственный доступ к аппаратуре. В конечном итоге на Си было написано более 90% всего кода центральной программы (ядра) системы UNIX. Windows написаны на Си?
В 1960 г. в США было около 5 тыс. компьютеров. В то время фирмы-производители в основном сами создавали программное обеспечение для собственных машин. Однако ситуация начала изменяться. На арену вышел новый вид бизнеса - независимая продажа программного обеспечения. Одна из первых компаний КСК (Computer Science Corporation, CSC) такого рода образовалась в 1959 г. с начальным капиталом 100 долларов (Рой Натт и Флетчер Джоунс). Первоначально основными клиентами были гигантские корпорации и правительственные учреждения наподобие НАСА. Компания совершила головокружительный взлет: в 1964 г КСК заняла первое место среди компаний - производителей программного обеспечения, включенных в список национальной фондовой биржи.
В середине 60-х годов наметился быстрый рост производства компьютеров. Этому способствовало появление нового компьютера PDP-8. Это был первый мини-компьютер, имевший коммерческий успех. При цене приблизительно 18 тыс. долларов он становился доступным для множества технических фирм. Это в свою очередь стимулировало создание большого количества новых разнообразных компонентов программного обеспечения, т.к. требовались программы для использования повсеместно во всех областях производства, проектирования, управления и т.д. К концу 60-х годов в мире уже использовалось около 100 тысяч вычислительных машин.
Новый этап в развитии программирования наступил с появлением персонального компьютера
, создание которого стало возможным благодаря изобретению микропроцессора. Отныне простой любитель, заплатив несколько сотен долларов, мог стать обладателем компьютера. Началом этого этапа считается публикация журнала Popular Electronics за 1975 г, в которой описывался первый набор для сборки мини-компьютера "АЛЬТАИР - 8800" (компания производитель - Micro Instrumentation and Telemetry System, MITS, цена - 397 долл., микропроцессор Intel (Integrated Electronics) i8080, n-MOS, 8-разрядный, 2 МГц). В сборке он представлял собой единственную микросхему, расположенную в небольшом ящике, на передней панели которого ряды переключателей и лампочек. Пользователю требовалось самому создавать двоичную программу и затем ввести ее в машину, орудуя переключателями.
Среди наиболее горячих энтузиастов этой новинки был Пол Аллен молодой программист из небольшой компьютерной фирмы "HONEYWELL". Он, вместе со своим другом детства Уильямом Гейтсом, студентом первого курса Гарвардского университета, решили, что новая машина нуждается в специально программном обеспечении. Следующий их шаг был таков: они позвонили в фирму-производитель компьютеров "АЛЬТАИР" и предложили ее владельцу программу, которая может переводить Бейсик в машинный код для этого компьютера. Несмотря на свою молодость, они уже имели определенный опыт программирования, который пригодился им при выполнении проекта для "АЛЬТАИРА", ибо они работали даже не видя эту машину. Все, чем они располагали - это руководство по системе команд микропроцессора "ИНТЕЛ 8080", на базе которого был построен "АЛЬТАИР". Имевшихся данных было достаточно для написания программы, прогоняя ее на большом компьютере, они отлаживали свой интерпретатор. Получив эту программу и проверив ее в работе, создатели "АЛЬТАИРА" были очень довольны, поскольку, по их словам, до этого не видели, чтобы их машина что-нибудь делала.
Стали появляться программное обеспечение для этого компьютера. В частности, для нее тогда еще студент первого курса Гарвардского университета Билл Гейтс со своим другом Полом Алленом первыми создали программу, которая могла бы переводить Бейсик в машинный код (интерпретатор). Вскоре после продажи этой программы разработчикам «Альтаира», Аллен и Гейтс организовали компанию, назвав ее “Microsoft”
.
Надо отметить, что к тому времени сложилось некоторое сообщество любителей, энтузиастов компьютерной техники, которые называли себя хакерами (hacker). Они свободно обменивались информацией, советами, частями компьютеров и программами, никакая информация при этом не скрывалась. Естественно они страстно желали получить, созданный Алленом и Гейтсом, интерпретатор Бейсика для "АЛЬТАИРА". Но компания МИТС продавала ленту с программой за 500 долларов, что делало ее недоступной для большей части компьютерных любителей. Но на семинаре в Пало-Альто в 1975 г, где группа инженеров из фирмы МИТС демонстрировала "АЛЬТАИР", один из хакеров прихватил запасную перфоленту, которая случайно оказалась неподалеку от работающего телетайпа. После чего она мгновенно разошлась множеством копий. Естественно Аллен и Гейтс были более чем удручены подобным развитием событий, поскольку получали определенный процент от каждого проданного экземпляра своей программы. Это было началом войны, продолжающейся и по сей день, между производителями программного обеспечения, продажа которого приносит им большие доходы, и нелегальными распространителями скопированных программ.
По мере расширения рынка персональных компьютеров, их усовершенствования и оснащения периферийными устройствами (монитор-телевизор, клавиатура, принтер), стали появляться программные продукты, предназначенные для довольно распространенной, но в тоже время конкретной цели. В первую очередь это были текстовые процессоры
("Электрический карандаш" (Майкл Шрейер) для "АЛЬТАИРА" и WorldStar (Джон Барнэби) - 1978 г.). В 1979 г. появилась программа VisiCalc (Visible Calculator) (Дэниел Бриклин), позволяющая быстро и удобно работать с большим количеством чисел - электронные таблица
. Изначально это программа была создана для персонального компьютера "Apple" (1977 г. - Стефан Возняк, Стивен Джобс), по-существу сыграв главную роль в успехе этой машины. VisiCalc подготовила почву для появления других видов программного обеспечения для бизнеса. Не последнее место в этом ряду принадлежит системе "dBASE II" (1981 г. - Уэйн Рэтлифф), которая относится к классу систем управления базами данных
. Вскоре ряды миллионеров, наживших состояния на программном обеспечении, пополнил Митчел Кэпор. Его программа "Лотос 1-2-3" завоевала широкое признание, поскольку соединяла в себе лучшие качества системы VisiCalc с графическими возможностями и средствами информационного поиска.
При разработке "Лотоса 1-2-3" Кэпор ориентировался не на 8-ми разрядные процессоры, которые в основном использовались в микрокомпьютерах того времени, а на 16-разрядные. Именно такой процессор предполагалось использовать в первом персональном компьютере IBM PC
(1981 г., микропроцессор Intel i8088, 29 т. транзисторов, 16-разрядный (работа с 16-разрядными словами), 8-разрядная шина, 4.77 МГц), который разрабатывалсся в фирме IBM (International Business Machines). Выход IBM на рынок персональных компьютеров стал знаменательным событием, поскольку одно присутствие на этом рынке гигантской компании-ветерана окончательно убедило многих сомневающихся потенциальных потребителей, что персональные компьютеры заслуживают серьезного отношения. Другим важным следствие вторжения IBM явился массовый переход производителей компьютеров на иную операционную систему, которая организует взаимодействие аппаратуры с программным обеспечением, обеспечивая выполнение поставленных задач. Программы, написанные для одной операционной системы, не могут работать под управлением другой.
В большинстве 8-разрядных компьютеров до появления IBM PC использовалась операционная система СР/М (Control Program for Microcomputers), которая была разработана в 1979 г. Гэрри Килдолом. Именно к нему сначала обратились специалисты IBM с предложением обсудить операционную систему для будущего компьютера. Однако (причины в интерпретации участников - разные) сотрудничество не состоялось. IBM заключило контракт с компанией "Microsoft", возглавляемой Биллом Гейтсом. Новая операционная система MS-DOS
(Microsoft Disk Operating System), получив благословление IBM, была хорошо принята многими производителями, которые стали выходить на рынок с моделями машин, совместимых с системой IBM PC.
В 70-х годах переживал громадный успех, созданный Никлаусом Виртом, сильно структурированный язык Паскаль. Но разработчики программного обеспечения, пытавшиеся приспособить Паскаль для микрокомпьютеров и использовать его в коммерческих целях, внесли в язык значительные изменения. Поскольку Вирт изначально разрабатывал Паскаль для обучения студентов программированию, язык почти не имел ввода-вывода и других средств, существенных для практического применения. По этой причине в компиляторах Паскаля появлялись все новые расширения, выводящие язык за пределы чисто академических приложений. Это породило множество диалектов Паскаля. Однако Вирт стремился к универсальности. В 1981 г. он разработал язык Модула-2, который должен был заменить Паскаль в универсальных приложениях и представлял собой его расширение в нескольких направлениях. Наиболее примечательным было введение средств написания больших и сложных программ, которые теперь можно было создавать в виде отдельных модулей, а затем собирать их вместе. Однако в начале 80-х годов, когда компании по разработке программного обеспечения трудились над дорогими и медленными компиляторами для Модулы-2, которые позволили бы использовать его на персональных компьютерах, появился компактный, дешевый и мгновенно срабатывающий компилятор Турбо-Паскаль
. Его разработчиком был бывший студент Вирта, переехавший в США, Филип Кан. Собрав у членов своей семьи небольшую сумму в 20 тыс. долларов, в марте 1984 года он основал фирму "Борланд Интернэшнл". Оценив одну копию Турбо-Паскаля в 50 долларов, за первые два года удалось продать около 300 тысяч копий, что превзошло объем продаж всех прочих языков для персональных компьютеров.
Совершенствуя Turbo-Pascal, фирма Borland разрабатывала новые версии пакета. Уже первая версия Turbo-Pascal содержала расширения языка, хотя и небольшие. В последующих версиях расширений становилось все больше и больше: встроенная графика (версия 3.0), от которой потом отказались, модули (4.0), средства ООП (5.5) и т.д. и т.д. Начиная с версии 7.0, язык стал называться Борланд Паскаль. Со временем в Turbo Pascal появились дополнительные средства, обеспечивающие поддержку концепции объектно-ориентированного программирования, и язык программирования Turbo Pascal стал именоваться Object Pascal. На базе Object Pascal в фирме Borland был создан принципиально новый программный продукт, который получил название Delphi. Delphi - это среда разработки программ, ориентированных на работу в Windows - новой операционной системы фирмы Microsoft. В основе идеологии Delphi лежит технология визуального проектирования и методология объектно-ориентированного программирования.
На основе Паскаля был создан еще один довольно распространенный в настоящее время язык Ада. Толчком к его созданию послужило создание в военном ведомстве США в 1975 г. комитета, которому было предписано найти или разработать язык, способный объединить достоинства существующих в то время языков и таким образом навести определенный порядок в военном ведомстве, где в то время использовались большое количество различных языков. Победителем в соревновании, результаты которого были объявлены в мае 1979 г., стал Жан Ихбиа (родился в Париже, в США кандидатская диссертация по оптимальному размещению систем метро) с языком Ада, названным в честь Огасты Ады Лавлейс, математика и писателя XIX века. Наиболее характерная черта языка Ада – главный акцент на структурное программирование. Язык позволяет писать программы в виде пакетов, т.е. самостоятельных модулей, которые разрабатываются отдельными программистами, а затем собираются вместе. После довольно длительного времени, появились удачные компиляторы для этого языка, и в 1983 г. министерство обороны распорядилось, чтобы все новые программы для «критических» приложений писались на Аде (под «критическими» понимались компьютеризированные системы связи и системы вооружений, например, программы, используемые в системе СОИ).
Microsoft осталась верна Бейсику. QuickBasic, VisualBasic - входит в поставку Windows.
В начале 80-х годов появилась, разработанная сотрудником «Белл телефон лабораторис» Бъярном Строуструпом, новая версия языка Си Си++ (++ это обозначение инкремента в Си). В него были внесены ряд добавлений (возможность работы с абстрактными данными и др.), главным из которых были средства ООП. При этом осталась ориентация на системное программирование. До последнего времени самый модный язык. Сейчас принято считать, что Си++ сложноват и не слишком надежен. В 1998 г. – стандарт С++.
Показано, что наиболее удобными для реализации программных систем, разработанных в рамках объектно-ориентированного подхода, являются объектно-ориентированные языки программирования, хотя возможна реализация и на обычных (не объектно-ориентированных) языках (например, на языке C и на языке Fortran). Первый объектно-ориентированный язык программирования Simula 67 был разработан в конце 60-х годов в Норвегии. Авторы этого языка очень точно угадали перспективы развития программирования: их язык намного опередил свое время. Однако современники (программисты 60-х годов) оказались не готовы воспринять ценности языка Simula 67, и он не выдержал конкуренции с другими языками программирования (прежде всего, с языком Fortran). Прохладному отношению к языку Simula 67 способствовало и то обстоятельство, что он был реализован как интерпретируемый (а не компилируемый) язык, что было совершенно неприемлемым в 60-е годы, так как интерпретация связана со снижением эффективности (скорости выполнения) программ. Но достоинства языка Simula 67 были замечены некоторыми программистами, и в 70-е годы было разработано большое число экспериментальных объектно-ориентированных языков программирования: например, языки CLU, Alphard, Concurrent Pascal и др. Эти языки так и остались экспериментальными, но в результате их исследования были разработаны современные объектно-ориентированные языки программирования: C++, Smalltalk, Eiffel и др. Наиболее распространенным объектно-ориентированным языком программирования безусловно является C++. Свободно распространяемые коммерческие системы программирования C++ существуют практически на любой платформе. Широко известна свободно распространяемая система программирования G++, которая дает возможность всем желающим разобрать достаточно хорошо и подробно прокомментированный исходный текст одного из образцовых компиляторов языка C++. Разработка новых объектно-ориентированных языков программирования продолжается.
С 1995 года стал широко распространяться новый объектно-ориентированный язык программирования Java, ориентированный на сети компьютеров и, прежде всего, на Internet. Синтаксис этого языка напоминает синтаксис языка C++, однако эти языки имеют мало общего. Java интерпретируемый язык: для него определены внутреннее представление (bytecode) и интерпретатор этого представления, которые уже сейчас реализованы на большинстве платформ. Интерпретатор упрощает отладку программ, написанных на языке Java, обеспечивает их переносимость на новые платформы и адаптируемость к новым окружениям. Он позволяет исключить влияние программ, написанных на языке Java, на другие программы и файлы, имеющиеся на новой платформе, и тем самым обеспечить безопасность при выполнении этих программ. Эти свойства языка Java позволяют использовать его как основной язык программирования для программ, распространяемых по сетям (в частности, по сети Internet).
2. Программное управление компьютером
Современные достижения в области минитюаризации
электронных схем позволили создать устройства, которые размещаются на кремниевой пластине размером несколько миллиметров, но по вычислительной мощности превосходят гигантские машины 50-х годов. Это сделало компьютеры доступными практически любому человеку. Они позволяют решать задачи, гораздо более сложные, чем во времена компьютеров на электронных лампах и перфокартах, и самые разнообразные. Такая универсальность
обусловлена программным обеспечением, которое позволяет использовать одну и ту же машину для решения огромного множества самых разнообразных задач.
Однако по существу команды компьютера заметных изменений по сравнению с ранними годами развития современной вычислительной техники не претерпели. Любой компьютер должен разложить задание на последовательность логических операций, а затем выполнить их одну за другой.
Можно выделить несколько основных типов программ. Центральное место занимает специальная совокупность программ, называемая операционной системой
, которая координирует работу компьютера и управляет размещением программ и данных в оперативной памяти. Она дает указания компьютеру, как интерпретировать команды и данные, как распределять аппаратные ресурсы для выполнения задания и как управлять периферийными устройствами (монитор, принтер и т.д.). Она также обеспечивает возможность непосредственного взаимодействия человека и компьютера, выполняя такие действия, как хранение программ и файлов данных.
Если операционная система – «режиссер», то прикладные программы
– «артисты». Именно благодаря таким программам (текстовый редактор, электронные таблицы, игры и т.д.) компьютер приобретает свою разносторонность. Предназначены для решения специальных задач.
В состав программного обеспечения входят также утилиты и компиляторы
(utility – польза). Это вспомогательные программы, позволяющие оптимизировать работу компьютера, например, произвести сортировку и перемещение файлов (утилиты) или преобразовать созданную на языке высокого уровня программу в машинный код (компиляторы).
Вычислительный процесс
состоит из нескольких последовательных этапов: ввод
информации в компьютер, ее специализированная обработка
и вывод
результатов. Такие устройства ввода, как клавиатура и «мышь», служат для задания компьютеру простых команд и данных
(рис.1). Они поступают в центральный процессор (ЦП) – сердце аппаратной части вычислительной системы. ЦП координирует движение потоков информации и
выполняет вычисления
. ЦП понимает определенную систему команд, т.е. те коды, которые предписывают ему выполнение определенных операций
. Любая программа представляется в виде последовательности таких кодов. В период работы программы ЦП в каждый момент времени выполняет только одну из ее команд.
Процессор обменивается информацией с оперативным запоминающим устройством (ОЗУ) (оперативная память), которое хранит данные и программы
во время их обработки. Большие и сложные программы
обычно загружаются в оперативную память с устройств внешней памяти (жесткие и гибкие диски, магнитная лента), которые обеспечивают долговременное хранение программных и информационных файлов. Эти устройства совмещают в себе функции ввода и вывода. Полученные результаты компьютер может записать обратно на диск или ленту. Имеется также постоянное запоминающее устройство (ПЗУ), в котором хранятся программы, обеспечивающие начальную активизацию компьютера при его включении. Эти программы должны отыскать на диске операционную систему и загрузить ее в оперативную память. Устройствами вывода информации являются видеотерминал-монитор, принтер, а также звуковые колонки и др.
Телетайп (как и монитор) предназначен для ввода и вывода информации в диалоговом режиме. Ввод осуществляется посредством клавиатуры. При этом вводимые символы одновременно печатаются на бумажной ленте. Вывод результатов также осуществляется на бумажную ленту. Там же печатаются сообщения системы. Достоинство - непрерывное документирование. Недостатки - отсутствие простых способов корректировки прогираммы и низкая скорость вывода.
Перфокарты содержит 80 колонок и 12 строк. Каждый символ в одной колонке и кодируется одним, двуми или тремя отверстиями (кодируется перфоратором). Фотоэлектрический способ считывания. Основными характеристиками вычислительных машин являются ее быстродействие (т.е. количество операций, выполняемых в единицу времени) и объем оперативной памяти.
Быстрый и точный доступ к обрабатываемой информации и программам, управляющим работой ЦП, возможен благодаря тому, что программы и данные кодируются
в виде последовательности электронных импульсов
, каждый из которых соответствует одной двоичной цифре: 1 или 0. Импульсы записываются в микроэлектронных элементах - переключателях, из которых состоит оперативная память. Каждый элемент хранит одну двоичную цифру, или, как иначе говорят бит информации (binary digit) (рис.2). Эти элементы группируются друг с другом, формируя, таким образом, более крупную единицу хранения информации, называемой байтом, которая состоит из 8-ми бит. Производными являются Кбайт (103
байт), Мбайт, Гбайт.
Иногда используют обозначение 1К = 210
бит (1024 бита).
I80286 – на кристалле 130 т. транзисторов, Win3.0, 16-разрядный процессор, 16 Мбайт памяти, 8 МГц. I80386DX (1985)– 32-разрядный процессор и 32-разрядная шина данных, 386SX – 16-разрядную внешнюю шину данных и 24-разрядную адресную. 486DX – 1.2 млн. транзисторов, 32-разряда. Pentium (1993) – 66 МГц – частота процессора (затем умножение), 3.1 млн. транзисторов, 32-разр.адресную (процессор) и 64-разрядную внешнюю шину данных, обмен данными с системной платой на частоте 528 Мбайт/с.
Есть разрядность – внутренних регистров, шины данных, шины адреса.
Системная шина – передача информации между процессором и другими электронными компонентами. IBM PC/XT – 8 разрядов, т.к процессор 8088 имел 8 линий связи. ISA (Industry Standard Architecture) – в РС АТ с 286 процессором, имела 16 разрядов данных. EISA (Extended) расширение ISA до 32 бит. Локальные шины (VLB, PCI) обеспечивают подключение периферии к скоростной шине собственно процессора. Обе 32/64 бита.
Разрядность ОЗУ определяется разрядностью шины данных процессора.
MS-DOS и Win3.1 – 16-разрядные операционные системы, Win95 – 32-разрядная.
Каждый байт памяти имеет свой адрес
. ЦП может прочитать содержимое того или иного байта, послав сигнал по соответствующему адресу. Кроме того, процессор может передать информацию по тому или иному адресу, при этом происходит замена хранившейся там прежде информации.
ЦП генерирует последовательность импульсов, определяющих конкретный адрес в ОЗУ. Информация, найденная по этому адресу, поступает в процессор для обработки. Коды адресов передаются по параллельным проводящим линиям, образующим адресную шину
. Информация передается в ЦП по шине данных.
Разрядность этих шин - максимальное число бит информации, содержащихся в адресе или в элементе данных. Есть шина управления
, по которой движутся управляющие сигналы (например, запрос на прерывание). Разрядность адресной шины определяет максимальное число адресов, и, следовательно, максимальный объем памяти (32-разрядная - 232
= 4*109
адресов (4 Гбайта)).
Байты могут объединяться в более крупные единицы памяти (ячейки памяти
- слово). Для каждого компьютера определена характерная для нее длина (число байт) ячейки памяти.
Каждый байт позволяет хранить 256 (28
) различных комбинаций 0 и 1, которое можно интерпретировать как некоторое множество символов, зависящее от программного обеспечения компьютера. Например, с помощью одного байта можно закодировать целые десятичные числа в интервале от –127 до +127. С помощью 0 и 1 можно также закодировать и нечисловую информацию. Так, например, в специальном коде ASCII (Американский стандартный код для обмена информацией) для представления букв английского алфавита, цифр от 0 до 9 и набора знаков препинания используются десятичные числа от 0 до 127 (7 бит информации, 27
символов, букве А соответствует число 65 - 100001). Для того чтобы представить помимо английского алфавита еще и русский, требуется 8 бит информации.
Поскольку в современных вычислительных системах для представления информации используют лишь два символа 0 и 1, естественным оказалось использовать для представления чисел двоичную систему счисления
. В этой системе существуют только две цифры 0 и 1. Для представления десятичной цифры 2 необходимо использовать следующий разряд (второй), она записывается как (10)2
(2 внизу - основание системы счисления). Двоичная система (как и десятичная) является позиционной
, т.е. вес цифры определяется ее положением в записи числа: младшая цифра имеет вес 20
, вторая справа 21
, третья - 22
, и т.д. Например: число с четырьмя разрядами (1101)2
= 1*20
+ 0*21
+ 1*22
+ 1*23
= (13)10
. Обратный перевод из десятичной системы в двоичную осуществляется последовательным делением на 2. В качестве остатка от деления получается очередная цифра двоичного числа, начиная с младшей. 13 делим на 2 равно 6, остаток 1 - младшая цифра, 6 делим на 2 равно 3, остаток 0, 3 делим на 2 равно 1, остаток 1, 1 делим на 2 равно 0, остаток 1.
Перевод дробной части из двоичной системы: (0.1011)2
= 1*2-1
+ 0*2-2
+ 1*2-3
+ 1*
2-4
= 1/2 + 1/8 + 1/16 = 0.5 + 0.125 + 0.0625 = (0.6875)10
.
Перевод дробной части из десятичной системы осуществляется умножением на 2 (целая часть полученного числа - очередная цифра двоичного начиная с первой цифры после запятой): 0.6875*2 = 1.375 (первая цифра - 1), 0.375*2 = 0.75 (0), 0.75*2 = 1.5 (1), 0.5*2 = 1.0 (1). Получается (0.1011)2
.
Точное двоичное представление возможно не для всех десятичных дробей. Так (0.4)10
= (0.0110011001100…)2
. Для представления в ячейке памяти, имеющей ограниченное количество двоичных разрядов, эта дробь будет оборвана, т.е. число 0.4 будет представлено приближенно.
В области программирования используются также восьмеричная (основание - 8) и шестнадцатеричная (основание - 16) системы счисления. Смысл использования - в удобстве перехода к двоичной и наоборот.
Рассмотрим, как представляются числа в памяти компьютера
. Т.к. числа различных типов могут принимать различные значения, то для их хранения требуется разное количество памяти. Память выделяется целым числом байтов
. Например, для хранения целых чисел от -32768 до 32767 (215
-1) требуется 2 байта (16 бит). При этом один разряд в двоичном представлении отдается на знак числа. Переменные, которые могут принимать такие значения, называются integer. Чем больше диапазон значений типа, тем больше требуется памяти (табл. для языка Паскаль).
Тип переменной |
Занимаемая память (байт) |
Диапазон значений |
Char |
1 |
Любой символ |
String |
256 |
Строка из 256 символов |
String[n] |
1*n |
Строка из n символов |
Byte |
1 |
0..255 |
Word |
2 |
0..65 535 |
Integer |
2 |
-32 768..32 767 |
Longint |
4 |
-2 147 483 648..2 147 483 647 |
Real |
6 |
2.9*10-39
|
Single |
4 |
1.5*10-45
|
Double |
8 |
5.0*10-324
|
Extended |
10 |
3.4*10-4932
|
Например, под число типа "Single" отводится 4 байта (32 бита) - 32 разрядное двоичное представление. Число имеют представление с плавающей точкой. Самый старший разряд отводится под знак числа, следующие 7 - под порядок (1 - под знак порядка, 6 под значение, 26
= 64), 24 разряда - под мантиссу (224
= 1.6*107
)
Вещественные числа представляются в памяти компьютера в общем случае приближенно. Арифметические операции над такими числами также дают результат, который приходится округлять. Эта особенность вычислительных машин часто порождает трудности при решении практических задач, т.к. ошибки округления способны влиять на точность окончательного результата, а оценка влияния этих ошибок представляет собой довольно сложную задачу.
При выполнении арифметических операций могут возникнуть ситуации, когда их результат не удовлетворяет ограничениям на диапазон (или форму) представляемых чисел. Возникает переполнение. Выполнение программы прекращается и выдается соответствующее сообщение. Если резельтат имеет порядок, меньший допустимого, то возникает "исчезновение порядка". Результат преобразуется к форме машинного нуля, а программа продолжает работать.
3. ЭТАПЫ РазработкИ программного обеспечения. ЯЗЫКИ ПРОГРАММИРОВАНИЯ.
Вычислительные машины предназначены для автоматической обработки информации. Предварительно весь процесс обработки должен быть расчленен
на простые действия и каждое из них записыно на специальном, понятном машине языке, т.е. составлена программа. Программа
представляет собой полное руководство для решения задачи с помощью вычислительной машины. Программа и данные, подлежащие обработке (исходная информация), помещается в компьютер, и далее программа выполняется автоматически без участия человека, перерабатывая исходные данные в результат.
Распространен термин программное обеспечение
(пакет программ), который можно определить как группу взаимодействующих друг с другом программ. Из этого определения не видно четкой границы между программным обеспечением и большой программой, которая также может состоять из множества отдельных модулей. Поэтому можно ввести уточнение, что ПО – это большая группа взаимосвязанных и взаимодействующих программ, предназначенных для решения любой задачи из конкретной области (например, для моделирования технологических процессов, для выполнения графических работ). Программы какого-либо пакета рассчитаны на совместное использование в различных комбинациях друг с другом.
Любая вычислительная машина общего назначения может выполнить все, что только может быть описано с помощью алгоритма.
Можно ввести этапы разработки
программного обеспечения («цикл жизни»):
- анализ требований, предъявляемых к системе и определение спецификаций 6%
- проектирование 5%
- кодирование 7%
- тестирование (автономное 8%, комплексное 7%)
- эксплуатация и сопровождение 67%
Естественно, что для разных программ соотношение временных затрат на реализацию отдельных этапов будут различными. Тем не менее, если говорить о программном обеспечении относительно больших систем, то усредненное временное соотношение будет следующим.
На первом этапе
определяются требования, выполнение которых позволяет получить приемлемое решение проблемы. Необходимо рассмотреть ряд основных требований, таких как время обработки информации,
стоимость обработки
, вероятность ошибки
и различного рода преднамеренных действий
. Необходимо делать различие между жесткими требованиями и требованиями, выполнение которых не является строго обязательным. Следует определить пространственно-временные ограничения
, налагаемые на систему, средства системы, которые в будущем могут претерпеть изменения
, а также средства, используемые в различных версиях системы
для разных применений. Важным является также определение ресурсов
, требуемых для реализации системы. При определении спецификаций
(также первый этап) осуществляется точное описание функций, реализуемых с томощью вычислительной машины. Задается структура входных и выходных данных
(например, каков должен быть формат записи в файлах, требуется ли предусмотреть распечатку данных, следует ли сохранять промежуточную информацию и т.д.). Если разрабатываемая система связана с обработкой значительного количества информации, центральной проблемой выступает организация базы данных
. Решается комплекс вопросов, имеющих отношение к структуре файлов, организации доступа к данным, обновлению и изъятию данных. Результатом являются функциональные спецификации, которые представляют собой документ, отображающий предполагаемую реализацию системы с помощью вычислительной машины. Данные для тестирования
должны определяться на ранних этапах разработки, поскольку в этом случае на данные не будем оказывать влияние конкретная реализация системы. Эти спецификации можно использовать для начальных оценок временных затрат, числа специалистов и других ресурсов, необходимых для проведения работ.
На этапе проектирования
разрабатываются алгоритмы
, задаваемые спецификациями, и формируется общая структура
вычислительной системы. Необходимо представить процесс преобразования исходных данных в результат в виде последовательности более простых этапов. При этом некоторые этапы могут представлять собой менее сложную, но самостоятельную задачу, для которой необходимо повторить указанную процедуру, т.е. представить ее решение в виде последовательности более простых этапов. Этот метод иначе называется методом пошаговой детализации
. Детализация заканчивается, когда каждый отдельный этап может быть записан на выбранном языке программирования, или представляет собой известную задачу, для которой уже имеется готовая программа. Результатом этого этапа является разработанный алгоритм решения задачи, который можно фиксировать, например, на языке схем (подробнее - в следующем разделе).
Кодирование
обычно является наиболее простым этапом, а его реализация облегчается при использовании алгоритмических языков высокого уровня и методов структурного программирования. Было показано (B.Boehm, 1975), что в среднем приблизительно 64% всех ошибок вносится на этапе проектирования и лишь 36% - на этапе кодирования.
Этап тестирования
может повлечь затраты, которые составят половину общих расходов на создание системы. В процессе тестирования используются данные, характерные для системы в рабочем состоянии, т.е. данные для тестирования нельзя выбирать случайным образом. План проведения испытаний должен быть составлен заранее
,
а большую часть тестовых данных следует определить на этапе проектирования системы. Тестирование подразделяется на стадии: автономное, комплексное и системное. При автономном тестировании
каждый модуль проверяется с помощью данных, подготовляемых программистом. При этом программная среда модуля имитируется с помощью программы управления тестированием, содержащей фиктивные программы вместо реальных подпрограмм, к которым имеется обращение из данного модуля.
Модуль, прошедший автономное тестирование, попадает под комплексное тестирование
, в процессе которого производится совместная проверка групп программных компонентов. Системное
(оценочное) тестирование предполагает испытание системы в целом с помощью независимых тестов. Для случая, когда сравниваются характеристики нескольких систем (например, изготовленных разними исполнителями), такая процедура известна, как сравнительное тестирование.
Подробнее вопросы тестирования и отладки будем рассматривать после изучения языка программирования.
На виды работ, которые рассматривали ранее (этап разработки ПО) приходится лишь от четверти до трети всех расходов, затрачиваемых в течение жизненного цикла системы. Остальное время уходит на эксплуатацию и сопровождение
.
Ни одна вычислительная система не остается неизменной
. Покольку заказчик обычно не может четко сформулировать свои требования, он редко бывает удовлетворен созданной системой и поэтому настаивает на внесении изменени
й в готовую систему. Кроме того, могут быть обнаружены ошибки, пропущенные при тестировании. Могут потребоваться специальные модификации
системы для частных условий функционирования, связанных с различным применением. Сопровождение многочисленных копий системы также представлят серьезную проблему, о которой необходимо помнить уже на ранних этапах разработки.
Прежде, чем рассматривать вопросы, относящиеся к алгоритмизации, остановимся на выборе языка программирования.
ЯЗЫКИ ПРОГРАММИРОВАНИЯ
Следует еще раз повторить, что компьютер может обрабатывать команды и данные только тогда, когда они представлены в машинном коде
, т.е. выражены на языке нулей и единиц, непосредственно связанном с электронной «начинкой» компьютера. Машинная команда состоит из кода операции и адресной части (рис.). Код операции указывает вид выполняемого действия (сложить, вычесть и т.д.). Адресная часть содержит адреса (номера) ячеек памяти, в которых расположены операнды, и адрес ячейки, куда следует поместить результат. Преимущества машинного кода – детализация управления, максимальная эффективность. Недостатки – трудность написания кода, его тестирования и последующего сопровождения.
Создание программ в более привычном человеку виде, нежели машинный код, связано с использованием машинно-ориентированных языков (языков ассемблера)
. Такие языки отличаются от машинного кода тем, что коды операций заменены буквенными обозначениями и вместо номеров ячеек используются символические адреса. Время появления языка ассемблера относится к началу 50-х годов, когда авторы машины «ЭДСАК» ввели систему мнемоники, где каждая машинная команда представлялась одной заглавной буквой (S – вычитание, I – прочитать следующий ряд дырочек во входной бумажной ленте, Т – передать информацию в память, Z – остановка машины). Они назвали эту систему assembly system (собирающая система), отсюда название ассемблер (кроме мнемоники, также была введена библиотека стандартных подпрограмм).
Программа называется программой ассемблера, или, кратко ассемблером
, если она преобразует мнемонику языка ассемблера непосредственно в двоичные представления машинных команд.
Языки ассемлера используются и в настоящее время, поскольку тесно связаны с машиной; дествительно, язык ассемлера – это машинно-зависимый язык
, спроектированный так, чтобы он соответствовал набору машинных команд, заложенных в центральный процессор конкретного компьютера. Язык ассемлера предпочитают те программисты, которые стремяться сжать свои программы до минимального размера, что позволяет выполнить их максимально быстро и эффективно
. Однако ассемблер имеет и свои недостатки. Поскольку он ближе к языку машины, чем к естественному языку, то весьма труден для работы
. Тот кто пользуется языком ассемлера, должен быть близко знаком с работой компьютера – например, должен знать шаги, которые выполняет компьютер при том или ином действии. Более того, поскольку ассемблер машинно-зависим, программа, написанная на ассемблере одного компьютера, абсолютно непонятна компьютеру другого типа
. Языки ассемблера – это языки низкого уровня. 8088 (ХТ) - ассемблер работает только с целыми числами. 8087 - сопроцессор позволяет работать и с вещественными.
Языки программирования, имиттирующие естественные языки и способные на основании одного предложения строить несколько команд компьютера, принято считать языками высокого уровня
. Но по сравнению с естественными языками, они, как правило, более строги и точны, в них остутствует многозначность слов, употребление их в переносном смысле, игра слов и т.д. В настоящее время насчитывается несколько сотен таких языков; если же учитывать все их варианты, называемые диалектами то, возможно, и более тысячи.
Варианты языков – подмножества, расширения, диалекты.
Подмножество
– это версия языка, включающая только часть возможностей полного языка. Расширение
– это расширенная версия, дополненная новыми свойствами, делающими язык более разносторонним. Диалекты
содержат незначительные изменения, которые способствуют либо настройке языка на специальное применение, либо выявлению сильных сторон конкретного компьютера. Диалекты несовместимы как с исходным языком, так и с другими диалектами того же языка (программа, написанная на одном диалекте, не может транслироваться компилятором, разработанным для другого диалекта).
Языки программирования служат самым разнообразным целям
- от решения сложных математических задач и проведения экономико-математических расчетов до создания музыкальной партитуры и машинной графики. Не существет языка, который бы в равной мере годился для всех случаев. Выбор языка обычно определяется одним или, возможно, несколькими из трех факторов: язык должен быть удобен для программиста, пригоден для данного компьютера и для решения данной задачи
.
Самого лучшего языка программирования не существует, как нет самого лучшего естественного языка.
Теоретически для решения большинства задач можно использовать любой язык
. Однако на практике оказывается, что написать программу для конкретной задачи гораздо легче на одних языках, чем на других. Это связано с тем, что в языках программирования способ выражения те или иных понятий приспособлен к потребностям конкретного образа мыслей.
Каждый язык имеет свой собственный набор
так называемых ключевых слов, букв, чисел или других символов. Ключевые слова соответствуют конкретным операциям или последовательностям операций, которые должен произвести компьютер. Одни выполняют функцию глаголов, другие - существительных, определений или знаков препинания. Они связываются друг с другом в соответствии с синтаксическими правилами, образуя предложения языка программирования.
При выборе наиболее подходящего языка для решения той или иной задачи следует учитывать ряд факторов, в том числе, степень квалификации программиста и размер будущей программы.
БЕЙСИК (BASIC). Сравнительно несложен для изучения и хорошо подходит для разработки коротких и простых программ. В то же время большие программы на этом языке могут оказаться неуклюжими и плохо организованными. В свое время завоевал признание своей компактностью и пригодностью для первых персональных компьютеров с их ограниченным объемом памяти. VB хорош для маленьких приложений, которые должны быть написаны быстро и относительно неопытными программистами. Недостаток VB – не поддерживает некотороые важные конструкции: указатели или числовые типы данных без знака (абстрактные переменные?).
Языки сценариев предназначаются для иных задач, нежели языки программирования систем, а потому фундаментально отличаются от них. Последние проектировались с расчетом на построение структур данных и алгоритмов, начиная с самых примитивных компьютерных компонентов, таких как слова памяти. Языки сценариев создавались для "склеивания" мощных готовых компонентов в предположении, что большинство из них уже существует и надо лишь связать их между собой. Языки программирования систем, как правило, сильно типизированы, что помогает справиться со сложностью, в то время как языки сценариев являются бестиповыми, что упрощает связи между компонентами и обеспечивает быструю разработку приложений. Сами по себе современные компьютеры принципиально бестиповые. Современные языки программирования систем, напротив, сильно типизированы. Типизация обеспечивает ряд преимуществ. Во-первых, она делает большие программы более управляемыми благодаря точному определению используемых сущностей и их отличий от других. Во-вторых, компиляторы используют информацию о типах для обнаружения определенных видов ошибок, таких как попытка задействовать величину с плавающей точкой как указатель. В-третьих, типизация повышает эффективность исполнения, позволяя компилятору генерировать специализированный код.
Языки Perl [4], Python [5], Rexx [6], Tcl [7], Visual Basic и оболочки Unix (shell) представляют стиль программирования, отличный от присущего языкам программирования систем. Этот стиль предполагает, что набор полезных в контексте решаемой задачи компонентов уже существует и остается должным образом их скомбинировать. Например, Tcl и Visual Basic могут служить для размещения на экране набора управляющих элементов пользовательского интерфейса, а написанные на shell командные файлы позволяют собрать программы-фильтры в конвейеры. Хотя языки сценариев часто используются для расширения свойств компонентов, они мало пригодны для программирования сложных алгоритмов и структур данных, обычно как раз и обеспечиваемых компонентами. Вот почему языки сценариев часто называют склеивающими языками (glue languages) или языками интеграции систем (system integration languages).
ПАСКАЛЬ (PASCAL). Обеспечивает возможность создания больших программ, поддерживая их строгую логическую структуру. Это ценно для начинающих программистов, создающих серьезные программы, т.к. приучает их к определенной дисциплине. Для коротких программ может оказаться слишком громоздким. Считается важнейшим инструментом для обучения методам структурного программирования. Компилятор Турбо Паскаль был выпущен в продажу фирмой Борланд в 1983 году. Начиная с версии 7.0, язык стал называться Борланд Паскаль, а с появлением системы Delphi был переименован в Объектный Паскаль. Сейчас входной язык Delphi содержит уже очень много синтаксических расширений по сравнению со стандартным Паскалем. Существует даже мнение, что именно отсутствие необходимости придерживаться стандарта языка объясняет тот факт, что визуальная среда программирования была создана компанией Борланд сначала для Паскаля, а уж потом для Си. Но думаю также, что не менее, а скорее более важной причиной этого было то, что разработкой Delphi занимался выдающийся программист A.Хейльсберг.
Компилятор, встроенный в Delphi, обеспечивает высокую производительность, необходимую для построения приложений в архитектуре "клиент-сервер". Этот компилятор в настоящее время является самым быстрым в мире, его скорость компиляции составляет свыше 120 тысяч строк в минуту на компьютере 486DX33. Он предлагает легкость разработки и быстрое время проверки готового программного блока, характерного для языков четвертого поколения (4GL) и в то же время обеспечивает качество кода, характерного для компилятора 3GL. Кроме того, Delphi обеспечивает быструю разработку без необходимости писать вставки на Си или ручного написания кода (хотя это возможно). Еще до компиляции он видит результаты своей работы - после подключения к источнику данных их можно видеть отображенными на форме, можно перемещаться по данным, представлять их в том или ином виде. В этом смысле проектирование в Delphi мало чем отличается от проектирования в интерпретирующей среде, однако после выполнения компиляции мы получаем код, который исполняется в 10-20 раз быстрее, чем то же самое, сделанное при помощи интерпретатора. Кроме того, компилятор компилятору рознь, в Delphi компиляция производится непосредственно в родной машинный код, в то время как существуют компиляторы, превращающие программу в так называемый p-код, который затем интерпретируется виртуальной p-машиной. Это не может не сказаться на фактическом быстродействии готового приложения. Cреда Delphi включает в себя полный набор визуальных инструментов для скоростной разработки приложений (RAD - rapid application development), поддерживающей разработку пользовательского интерфейса и подключение к корпоративным базам данных. VCL - библиотека визуальных компонент, включает в себя стандартные объекты построения пользовательского интерфейса, объекты управления данными, графические объекты, объекты мультимедиа, диалоги и объекты управления файлами, управление DDE и OLE. Единственное, что можно поставить в вину Delphi, это то, что готовых компонент, поставляемых Borland, могло бы быть и больше. Однако, разработки других фирм, а также свободно распространяемые программистами freeware-компоненты уже восполнили этот недостаток. Постойте, - скажете вы, ведь это уже было. Да, это было в Visual Basic. Соответствующий стандарт компонент назывался VBX. И этот стандарт так же поддерживается в Delphi. Однако, визуальные компоненты в Delphi обладают большей гибкостью. Вспомним, в чем была проблема в VB. Прикладной программист программировал, вообще говоря, в среде языка бэйсик. А компоненты в стандарте VBX готовили ему его коллеги-профессионалы на С++. VBX'ы приходили, "как есть", и ни исправить, ни добавить ничего было нельзя. А для изготовления VBX надо было осваивать "кухню" языка C++. В Delphi визуальные компоненты пишутся на объектном паскале, на том же паскале, на котором пишется алгоритмическая часть приложения. И визуальные компоненты Delphi получаются открытыми для надстройки и переписывания. Чувствуете разницу?
ФОРТРАН (FORTRAN). В основном используется для программ, выполняющих естественно-научные и математические расчеты. Существует мнение, что этот язык оказал долговременное влияние «в том смысле, что начиная с 1957 г. все исследования в области языков программирования имели своей целью преодоление недостатков, присущих Фортрану».
В 1995 г. Международная организация по стандартам (ISO) предложила проект нового стандарта языка — Fortran 95. Он был принят взамен Fortran 90 в октябре 1996 г. Однако Американский национальный институт по стандартам (ANSI), на решения которого ориентируются все разработчики США, утвердил этот стандарт лишь летом 1997 г. В стандарт Fortran 95 включено 17 новых функций, многие из них являются расширением существовавших операторов языка. Одновременно из него удалено пять старых конструкций. Кроме того, составлен список устаревающих операторов — кандидатов на удаление при следующей ревизии стандарта. В целом нововведения в Fortran 95 представляются не очень существенными, большинство из них уже было реализовано во многих компиляторах как неофициальные расширения языка. Во всяком случае, эти нововведения не идут ни в какое сравнение с революционными изменениями, произошедшими при переходе к Fortran 90.
В начале 1997 г. Microsoft объявила о прекращении своей деятельности по развитию средств разработки на базе языка Фортран. Данное решение тогда вызвало явную озабоченность программистов, пишущих на Фортране, который продолжает оставаться весьма популярной системой в сфере математических расчетов. Довольно значительная часть задач решается именно на ПК, а для них Фортран-инструменты Microsoft занимали лидирующие позиции. Следует напомнить, что речь шла о трех выпущенных в 90-х годах пакетах, которые довольно широко применяются и в настоящее время: 2 MS Fortran 5.1 (выпуск 1990 г.). Система для MS-DOS, позволяющая создавать 16-разрядные DOS-приложения, способные работать с оперативной памятью до 64 Мб; 2 MS Fortran PowerStation (FPS) 1.0 (1993 г.). Разработка и отладка программ ведется в среде Windows, в результате чего получаются 32-разрядные приложения для DOS, использующие до 4 Гб оперативной памяти; 2 MS Fortran PowerStation 4.0 (1995 г.). Создание 32-разрядных приложений под Windows 95 и NT. Полная поддержка стандарта Fortran-90. Однако уход Microsoft сопровождался заключением соглашения с корпорацией Digital Equipment, по которому последняя лицензировала среду MS Developer Studio для своего нового пакета Visual Fortran (DVF) 5.0 под Windows NT (Intel, Alpha) и Windows 95. В обращении к своим пользователям Microsoft рекомендовала для будущего обновления именно пакет DVF 5.0. Таким образом, Digital, имевшая многолетний опыт создания средств разработки для Фортрана (в основном для Unix-систем), получала отличную возможность продвинуться в сферу ПК. А Microsoft не только продавала свою среду разработки, но и обеспечивала тем самым высокую степень интеграции собственных инструментальных средств с новым продуктом Digital. Переходя к характеристике основных возможностей DVF 5.0, о выпуске которого было официально объявлено весной 1997 г., мы рассмотрим их с точки зрения трех главных компонентов — самого языка программирования, среды разработки и вариантов получения законченных приложений, обратив особое внимание на новшества DVF 5.0 по сравнению с FPS 4.0. Основные характеристики DVF 5.0 Компилятор DVF 5.0 (как и FPS 4.0) поддерживает три основных стандарта языка — FORTRAN 66, FORTRAN 77 и Fortran 90. Кроме того, в нем можно использовать большое число специфических расширений языка, имеющихся в FPS 4.0, а также в Digital Fortran, для других компьютерных платформ. DVF 5.0 включает также все новшества самого последнего стандарта языка — Fortran 95. Однако варианта компиляции в режиме Fortran 95 как такового в DVF 5.0 нет, поскольку к моменту выпуска продукта этот стандарт еще не был официально утвержден. В DVF 5.0 используется интегрированная среда разработки Developer Studio, которая применяется в качестве основы всего семейства средств разработки Microsoft (ее предыдущий вариант имелся и в FPS 4.0). Она представляет собой многооконный редактор текста с широкими возможностями настройки среды и включает целый ряд универсальных средств: отладчик кода, встроенную справочную систему, профайлер для изучения частоты обращения к отдельным частям программы, средство просмотра структуры программы и межпроцедурных ссылок, редактор для создания компонентов графического интерфейса пользователя и пр. В рамках одного проекта можно использовать исходные модули, написанные на разных языках, что существенно упрощает разработку приложений методом смешанного программирования, в том числе на VC++, VB, VJ++ и MASM. С помощью DVF можно получать библиотеки подпрограмм (статические OBJ и динамические DLL) и исполняемые модули. Для создания законченных приложений разработчик может выбрать один из следующих типов исполняемых модулей. 3 Console application. Такая программа вообще не содержит графики и реализует наиболее простую форму диалогового интерфейса — ввод данных в традиционном стиле MS-DOS. Это самый быстрый и наиболее легко переносимый на другие платформы тип рабочей программы. 3 Standard Graphics. Данный вариант исполняемого модуля имеет однооконный графический интерфейс. Его рекомендуется применять в тех случаях, когда есть необходимость графического вывода, но не нужен развитой интерфейс пользователя. 3 QuickWin Graphics Application. При создании модуля этого типа используется библиотека QuickWin, позволяющая применять сокращенный набор функций WinAPI 32, с помощью которого можно создать многооконный интерфейс. 3 Win32 Application. Этот модуль является полнофункциональным Windows-приложением, которому доступны все ресурсы WIN32 API. Все эти варианты Fortran-приложения были реализованы и в FPS 4.0. Новшеством DVF 5.0 является встроенное средство Fortran Module Wizard, которое обеспечивает генерацию исходных модулей на языке Fortran 90 для обращения к процедурам DLL-библиотек, методам ActiveX-объектов, а также функциям COM-серверов. Формирование текстов этих модулей производится в автоматическом режиме в процессе заполнения экранных форм, запрашивающих информацию об объекте. DVF 5.0 поставляется в двух редакциях — Standard и Professional. Последняя появилась в конце 1997 г. и дополнительно включает известную математическую библиотеку IMSL фирмы Visual Numerics, которая входила ранее и во все Фортран-инструменты Microsoft. Стоимость данных редакций DVF 5.0 составляет соответственно 599 и 799 долл., причем документацию и лицензию можно приобрести отдельно. Имеются льготные цены для пользователей Фортран-инструментов (причем не только от Digital и Microsoft). Научные бюджетные институты и вузы могут приобрести пакет с “академической” скидкой.
СИ (C). Стал очень популярен благодаря многим решениям, сделавшим запись программы на Си весьма компактной. Накладывает на программиста не слишком много ограничений. С++ - первоначальное название "Си с классами". В язык Си Бъярном Строуструпом введены средства ООП и ряд других добавлений. До последнего времени самый модный язык. Сейчас принято считать, что Си++ сложноват и не слишком надежен.
АДА (ADA). Официальный язык программирования американских военных. Происходит от Паскаля, но заметно сложнее его.
МОДУЛА-2 (MODULA-2). Язык, который должен был заменить Паскаль, устранив основное его ограничение – отсутствие модульности. Но этого в полной мере не произошло. Благодаря политике компании Борланд, которая в тот момент, когда нужно было обеспечить модульность в создаваемых ею системах программирования, решила, что выгоднее добавить новые элементы в Паскаль, а не переходить на Модулу-2. Тем не менее, известно, что Модула-2 использовалась и используется в проектах, где важнейшую роль играет надежность. Средства межмодульного контроля Модулы-2 заметно совершенней аналогичных возможностей Турбо Паскаля и Си.
Оберон (OBERON). Разработан Н.Виртом в 1987 году. Представляет собой существенно упрощенный вариант Модулы-2, в который добавлены расширяемые записи – основной механизм ООП. Язык необычайно прост. При этом сохраняет универсальность и в функциональном отношении не уступает другим языкам. В 1992 году были приняты расширения языка Оберон-2, предложенные молодым коллегой Н.Вирта Ханспетером Мёссенбёком. В язык введены так называемые связанные процедуры – аналог виртуальных методов в других языках.
ЯВА (JAVA). Самый молодой и самый обсуждаемый ныне язык. Является непосредственным наследником Си++. Отличается от него отсутствием некоторых потенциально ненадежных механизмов, а также тем, что устранены любые, не относящиеся к ООП, средства. Объекты и ничего кроме объектов. Говорят, что Ява простой язык. Но стоит заглянуть в его официальное описание (James Gosling, Bill Joy, Guy Steele. The Java Language Specification Version 1.0), как возникает повод усомниться. Тяжелый (в прямом и переносном смысле) 700-страничный документ, насыщенный многословными и громоздкими определениями.
Немалую роль играют пропаганда и мода. Сейчас, например, мы все являемся свидетелями массированной пропагандистской кампании, развернутой вокруг языка Ява. Впервые язык программирования выводится на рынок такими же маркетинговыми приемами, что и традиционные товары.
ЛИСП (LISP, LISt Processing - обработка списков). Используется в области искусственного интелекта - направления, которое занимается созданием ПО, имиттирующего человеческий разум. Создан в конце 50-х годов математиком из МТИ Джоном Маккарти. Лучше подходит для задач, связанных с манипулированием символами, чем для обработки обычных чисел.
КОБОЛ (COBOL, COmmon Business Oriented Language - общий язык, ориентированный на деловые задачи). Создан в Пентагоне в 1960 г. совместными усилиями федерального правительства и производителей компьютеров. Основной целью было создать язык, который могли бы легко понимать деловые люди, профессионально не связанные с программированием. Структура и словарь близки к обычному английскому языку. Является основным языком в США для обработки данных в таких учереждениях как банки и страховые компании.
ФОРТ (FORTH - четвертый, по мнению автора язык четверого поколения). Идея создания принадлежит Чарлзу Муру, который разработал его в конце 60-х - начале 70-х годов как персональное средство повышения производительности труда. Стал широко применять в задачах управления после того, как Мур использовал его для реализации программы, предназначенной для управления радиотелескопом Аризонской обсерватории.
Языки программирования (ЯП) по своим возможностям и времени создания принято делить на несколько поколений
(Generation Language, GL). (Пятое поколение -- языки программирования или прикладные системы? PC W
eek/RE Сергей Бобровский 1997).
Каждое из последующих поколений по своей функциональной мощности качественно отличается от предыдущего. К сегодняшнему дню насчитывается пять поколений ЯП.
В первое поколение
(1GL) входят языки, созданные в 40-50 гг., когда компьютеры только появились на свет. В то время программы писались в машинных кодах
, то есть каждая компьютерная команда вместе с ее операндами вводилась в ЭВМ в двоичном виде. Это требовало огромных усилий по набору цифровых текстов и приводило к множеству трудноуловимых ошибок. Конечно, ни о каких мало-мальски больших проектах речи идти не могло. Ситуация качественно изменилась в середине 50-х годов, когда был написан первый ассемблер , что само по себе можно считать подвигом, учитывая довольно сложную логику программы. Хотя этот ассемблер был неполноценным в сегодняшнем понимании, но он позволял задавать названия команд в символическом виде и указывать числа не только в двоичном, но и в десятичном или шестнадцатиричном формате, что существенно облегчало работу программистов. Языки первого поколения продолжают использоваться и сегодня, хотя в значительно меньшем объеме. Чаще всего приходится писать программы в машинных кодах для новых микропроцессоров, для которых еще не разработаны компиляторы, поддерживающие требуемый набор команд.
Расцвет второго поколения
языков программирования (2GL) пришелся на конец 50-х - начало 60-х годов. Был создан символический ассемблер, позволявший писать программы без привязки к конкретным адресам памяти. В него было введено понятие переменной
, и он по сути стал первым настоящим (хоть и машинно-ориентированным) языком программирования со своим компилятором. Скорость создания и эффективность программ резко возросли. Ассемблеры активно применяются в настоящее время, как правило, для создания программ, максимально использующих возможности аппаратуры -- различных драйверов, модулей состыковки с нестандартным оборудованием и т. д. В некоторых областях, например, в машинной графике, на ассемблере пишутся библиотеки, эффективно реализующие стандартные алгоритмы обработки изображений. Кроме того, среди программистов просто есть немало людей, предпочитающих использовать ассемблер в своей работе. Как правило, это специалисты, хорошо разбирающиеся в электронике.
Третье поколение ЯП (3GL)
принято относить к 60-м годам. В это время родились языки, которые называют универсальными языками высокого уровня, с их помощью можно решать задачи из любых областей. Это общеизвестные Фортран, Кобол, Алгол и др. Такие качества новых языков, как относительная простота, независимость от конкретного компьютера
и возможность использования мощных синтаксических конструкций позволили резко повысить производительность труда программистов. Кроме того, понятная большинству пользователей процедурная идеология этих языков
позволила привлечь к написанию небольших программ (как правило, расчетного или учетного характера), большое количество специалистов из некомпьютерных областей. Подавляющее большинство языков 3GL успешно применяется и сегодня. Современные компиляторы с интегрированными средами разработки предоставляют очень удобные средства поддержки процесса создания программ, легко осваиваемые студентами компьютерных специальностей первых курсов, благодаря чему простые в освоении языки 3-го поколения используются для разработки программ абсолютным большинством людей, зарабатывающих на жизнь программированием. Практически все современные коммерческие продукты, рассчитанные на массовый рынок, написаны на языках 3-го поколения.
С начала 70-х годов по настоящее время тянется период языков четвертого поколения (4GL)
. После первых восторгов по поводу безграничных способностей ЭВМ стали более ясны возможности существующих языков программирования. Несмотря на рождение новых технологий (ООП, визуальное программирование, CASE-методологии, системный анализ), процесс создания больших программных комплексов оказался очень трудоемкой задачей, так как для реализации крупных проектов требовался более глобальный подход к решаемым задачам, чем предлагали имевшиеся средства разработки. Языки 4GL частично снимали эту проблему. Целью их создания было в первую очередь стремление к увеличению скорости разработки проектов, снижение числа ошибок и повышение общей надежности работы больших программных комплексов
, возможность быстрого и легкого внесения изменений в готовые проекты, упрощение самих языков для конечного пользователя, активное внедрение технологий визуальной разработки и т. д. Все средства разработки 4-го поколения имеют мощные интегрированные оболочки и обладают простым и удобным пользовательским интерфейсом. Они чаще всего используются для проектирования баз данных и работы с ними (встроенные языки СУБД), что объясняется возможностью формализации всех понятий, используемых при построении реляционных баз данных. Языки 4GL активно применяются в различных специализированных областях, где высоких результатов можно добиться, используя не универсальные, а проблемно-ориентированные языки, оперирующие конкретными понятиями узкой предметной области. Как правило, в эти языки встраиваются мощные примитивы, позволяющие одним оператором описать такую функциональность, для реализации которой на языках младших поколений потребовалось бы написать тысячи строк кода.
Однако пользователям, использующим языки 4GL для создания законченных приложений, по прежнему необходимо кодировать программу вручную, используя обычный процесс последовательного ввода команд. При этом сохраняется главный недостаток языков предыдущих поколений. Он заключается в том, что все они в значительной степени ориентированы на чуждую человеческому мышлению чисто компьютерную идеологию
(работа с памятью, переменными, базами данных, последовательностями абстрактных операторов и т. п.), что требует от людей хорошего понимания принципов функционирования компьютера и операционных систем. Кроме того, парадигма функционального программирования по прежнему присутствует в языках 4GL во всей полноте, не позволяя перейти к более высокому уровню абстракций при разработке программных систем.
Рождение языков пятого поколения
относится к настоящему времени. Довольно неожиданно вокруг самого названия 5GL разгорелись жаркие споры. Возникло несколько программистских "школ", представители каждой из которых имеют свое мнение о том, какие средства разработки считать языками 5-го поколения, а какие нет. Например, на страницах лондонского журнала SURPRISE (Surveys and Presentations in Information Systems Engineering), выпускаемого при поддержке английских министерств по электронике и вычислительной технике, публикуются статьи известных специалистов в компьютерной области -- профессоров Лондонского научного колледжа, разработчиков популярных английских программ и др. Представители "английского" направления рассматривают средства разработки 5-го поколения в более широком аспекте, чем это принято делать в отношении обычных языков программирования. Они считают, что к системам 5GL можно отнести не только новые мощные языки, но и системы создания программ, ориентированные на непрограммиста. Подобные системы отличаются стремлением предоставить конечному пользователю-неспециалисту богатые возможности создания прикладных программ с помощью визуальных средств разработки без знания программирования. Главная идея, которая закладывается в эти системы 5GL -- возможность компьютерного интерактивного или полностью автоматического преобразования инструкций, вводимых в систему наиболее удобными человеку методами в максимально наглядном виде, в текст на универсальных языках программирования, описывающий готовую программу. Наличие подобного промежуточного этапа (получение не готового исполняемого модуля, а только исходных текстов, требующих дальнейшей обработки) объясняется низкой эффективностью автоматически генерируемого кода приложений, созданных с использованием подобных систем 5-го поколения, связанной с внутренней сложностью последних, и стремлением создавать независимые от платформы продукты. Исходные тексты обычно генерируются на языках более низкого уровня, как правило, третьего поколения. Благодаря автоматическому процессу получения текстов программы результирующий код получается хоть и неэффективным, но высоконадежным и не содержащим ошибок. Правда, при этом возникает проблема совместимости с имеющимися на рынке компиляторами. После генерации кода созданного приложения необходимо перевести его в машинное представление. Для этого требуется тесная интеграция с имеющимися коммерческими компиляторами, легкая настройка, ориентированная на пользователя- непрограммиста и соответствие получаемого кода требованиям конкретных средств разработки. В большинстве случаев из-за острой конкурентной борьбы решить проблему совместимости в целом не удается, поэтому системы разработки 5GL ориентируются обычно на определенные версии компиляторов.
Ряд современных языков 5GL созданы на основе успешно реализованных продуктов 4GL, и граница между этими поколениями сильно размыта. Пока системы разработки 5-го поколения только появляются на свет, и нередко аббревиатура 5GL используется больше для рекламы -- дескать смотрите, мы впереди конкурентов!
Таким образом, определенная часть компьютерных экспертов считают продукты последнего поколения уже не языками, а средствами разработки, прикладными пакетами, не имеющими к процессу создания программ с помощью ЯП никакого отношения. Проектирование программы происходит в специализированном визуальном редакторе, и работа с исходными текстами отсутствует.
Пока сложно сказать, насколько успешной окажется тенденция стремления к полной и недостижимой универсальности. На практике с помощью систем 5GL этого направления пока удавалось создать небольшие и логически простые приложения, которые при реализации на языках 3-го поколения потребовали бы не более 10 000 исходных строк кода. При попытках разработки сложных программ возникает проблема, типичная для более старых языков -- необходимость отладки, что требует от пользователя высокой квалификации.
Языки 5GL, ориентированные на конкретные области применения, уже в ближайшее время могут завоевать самую широкую популярность. Наиболее вероятно это для продуктов, позволяющих создавать приложения для работы с базами данных, области информатики, наиболее успешно поддающейся формализации. Наглядное подтверждение этому -- тенденции развития практически всех известных СУБД корпоративного уровня. Вслед за встроенными языками СУБД появляются и другие проблемные ЯП. Кроме того, универсальные языки логического программирования наподобие Пролога , основанные на мощных математических аппаратах, совсем не канули в Лету, как может иногда показаться на фоне мелькания сплошных Си и Яв читающим российскую компьютерную прессу. Эти языки продолжают более чем успешно развиваться, другое дело, что для их грамотного применения требуется высокая культура программирования (и проектирования) в сравнении с тем же Си++. И используются они для несколько других задач, чем создание коробочных бухгалтерий (хотя для этих целей они также подходят значительно лучше Си или Паскаля). Неудивительно, что в нашей стране мощными зарубежными средствами разработками, реализующими алгоритмы искусственного интеллекта или имеющими в своей основе уникальные математические теории, нередко наиболее активно интересуется ФАПСИ, что явствует хотя бы из открытых семинаров ведущих российских фирм-дистрибьютеров соответствующих продуктов. Хочется надеяться, что рано или поздно и коммерческие фирмы поймут выгоду использования хоть и дорогих, но очень мощных по своим возможностям систем создания приложений сверхвысокого уровня.
Какой язык проще, я какой сложнее? Было проведено исследование (PC Week/RE #42-43, 1998, Арифметика синтаксиса Сергей Свердлов)
, где в качестве меры сложности языка программирования количественные характеристики формализованного описания его синтаксиса (общее число лексем – подробности ниже).
Правила, записанные на РБНФ - расширенные формулы Бэкуса-Наура (РБНФ), (как и текст на языке программирования) состоят из отдельных элементов – лексем. Лексемами являются названия понятий, называемые в теории формальных языков нетерминальными символами или просто нетерминалами. Общее число лексем
в описании синтаксиса языка может служить обобщенной характеристикой размера этого описания. Число лексем использовать в качестве меры объема гораздо лучше, чем, скажем, число знаков в описании. В этом случае значение нашего критерия не будет зависеть от того, на каком языке (русском, английском) или какими конкретно словами названы нетерминалы – понятия языка. Число различных нетерминалов
– следующая характеристика, которую мы будем вычислять. Количество используемых для описания языка понятий – несомненно важнейшее свойство, от которого зависит легкость освоения этого языка. Можно заметить, что число нетерминалов должно быть равно числу правил
в описании синтаксиса, поскольку для каждого понятия обязано существовать ровно одно правило. Набор и количество различных терминальных символов
языка, упомянутых в синтаксических формулах, характеризуют лексику языка – набор знаков и специальных символов. Во всех обсуждаемых нами языках существуют служебные слова, которые могут употребляться только в строго определенном смысле. Их, программист, вообще-то, должен знать наизусть. Подсчет количества служебных слов
позволит оценить объем зубрежки.
Результаты
Элементы
|
A-60
|
Паскаль
|
ТП2
|
ТП5
|
ТП5.5
|
ТП6
|
ОП
|
М2
|
О
|
О2
|
Си
|
Си++
|
Java
|
Ада
|
Лексемы |
1085 |
1012 |
1184 |
1331 |
1410 |
1488 |
1825 |
887 |
765 |
726 |
917 |
1662 |
1771 |
2206 |
Нетерминалы |
119 |
110 |
124 |
127 |
135 |
143 |
180 |
70 |
62 |
43 |
917 |
126 |
174 |
226 |
Терминалы |
92 |
84 |
87 |
87 |
87 |
89 |
90 |
88 |
90 |
91 |
123 |
131 |
121 |
102 |
Служебные слова |
25 |
35 |
42 |
48 |
52 |
55 |
83 |
39 |
32 |
34 |
27 |
47 |
48 |
63 |
Результаты приведены на рис. 4. Линия Вирта, начинающаяся от Алгола-60 и образованная языками Паскаль, Модула-2, Оберон и Оберон-2, автором которых является Никлаус Вирт, и, что самое главное, линия, которой Вирт неуклонно придерживается: наращивание мощи языка без его усложнения. Паскаль намного богаче Алгола, но не сложнее его. Модула существенно мощнее и совершеннее Паскаля, но проще. Оберон обогатил Модулу средствами объектно-ориентированного программирования – расширяемыми записями, и при этом не только не стал более сложным, но заметно упрощен. Удивительным выглядит то, что Оберон-2 оказался проще Оберона, расширением которого является.
В отношении размера определения синтаксиса так оно и есть. Да и по существу нововведения Оберона-2 оформлены очень экономно. Кроме того авторы языка объединили отдельные правила для каждой разновидности операторов в одно правило для нетерминала "Оператор". То же сделано в отношении правил для типов. По-другому, более компактно, определен синтаксис некоторых конструкций. И хотя получившееся упрощение отчасти формальное, но экономия понятий – это именно то, к чему и следует стремиться, как заметил еще У.Оккам почти 700 лет назад. Кроме того, что остаются простыми формальные описания синтаксиса языков Н.Вирта, очень лаконичны и полные тексты спецификаций этих языков. Сравнивая авторское описание Паскаля, тоже очень небольшое, и описания языков Оберон и Оберон-2, можно увидеть как маэстро совершенствовал свое мастерство. Я очень рекомендую специалистам по Си++ и Яве почитать спецификацию Оберона, даже если вы не собираетесь использовать этот язык. Всего 20 страниц! И при том, это документ, необходимый и достаточный для реализации языка. Сравните с аналогичного назначения книгами Б. Строуструпа и Д. Гослинга по Си++ и Яве.
Линия Борланд берет начало от виртовского Паскаля, а дальше "все выше и выше и выше...". Версии Турбо - Борланд - Объектного Паскаля становятся сложнее и сложнее. По другому просто не может быть, поскольку, избрав однажды путь расширения старого языка с сохранением обратной совместимости, язык можно только усложнять
. Отказаться от чего-либо уже невозможно. Недаром по линии Борланд идет монотонный рост всех без исключения критериев.
Что же в результате. Из простого и изящного Паскаля получился язык, приближающийся по сложности к языку Ада. По большому счету Паскаль в версии Борланд (Inprise) уже не может считаться общемировым языком программирования. Это фирменный язык одной не очень большой американской компании. В этом смысле он ничем не отличается от Бейсика, языка другой, правда более крупной фирмы. Отсутствие переносимости даже делает не вполне правомерным сравнение этого языка с Си, Си++, Явой, Модулой, Обероном и Адой.
Одним из неожиданных результатов измерений стало то, что Си оказался не простым, а очень простым языком. По некоторым параметрам он даже проще Оберона. Недаром же он завоевал в свое время такую популярность. И в самом деле Си весьма гармоничен. И хотя поощряемый им стиль принимают не все, но в изяществе языку не откажешь. Ничего хорошего в смысле простоты нельзя сказать про Си++. Сейчас признание чрезмерной сложности этого языка стало общим местом. Так что неожиданностей наши измерения не дали. Разве что можно заявить о том, что Си++ сложнее Си практически вдвое. Вообще, если Си – конструкция весьма цельная, то Си++ представляется достаточно противоестественным соединением языка с незамаскированными понятиями низкого уровня и высокоуровневой концепции объектов. К тому же, мне кажется, что популярность Си возникла естественным путем., а Си++ – искусственно "раскрученный" язык.
Судя по вычисленным нами критериям Ява не только не может считаться простым, но является одним из самых сложных языков, более сложным чем Си++, и вдвое более сложным чем Оберон.
Трансляция
Перевод с языков высокого уровня в машинные коды выполняют программы, которые называются трансляторами
, которые существенно более сложны, чем ассемблеры. Различают два типа трансляторов.
Компилятор
переводит на машинный язык всю программу целиком. Результатом трансляции в этом случае является машинная программа, записанная в двоичном представлении. Достоинством такого метода является то, что полученная машинная программа может выполняться многократно без повторного обращения к транслятору. Недостаток - при каждом изменении программя ее необходимо транслировать заново.
Интерпретатор
транслирует и выполняет последовательно один оператор за другим. Этот метод требует значительно большего времени процессора. Внесение изменении значительно проще. На практике используют сочетание этих методов. Во время отладки - используют интерпретатор, после отладки - компилятор.
Схема работы компилятора приведена на рис.3.
Среди этапов транслирования можно выделить следующие.
Лексический анализ.
Задача этого этапа сделать программу понятной для других этапов. Литеры исходного кода читаются одна за другой, а сканер (лексический анализатор) в соответствии с грамматическими правилами языка объединяет литеры в группы, определяя их смысл. Для каждой смысловой группы генерируется символ - лексема
. Большинство лексем имеют фиксированный смысл
(ключевые слова указывают на действия, задаваемые синтаксисом языка - начало, если, конец,…, операции указывают на арифметические и др. действия, пересылку данных - +, :=…., числа задают числовые значения - 5,7,…, знаки пунктуации помогают разобраться в структуре программы). Другая разновидность лексем - индентификаторы
, которые не имеют фиксированного смысла - имя программы, слова для именования переменных или констант. Сканер дает каждой лексеме метку и заносит в таблицу.
Синтаксический анализ.
Синтаксический аналзатор получает от сканера лексемы и располагает их в виде структуры, позволяющей компьютеру разобраться в логике программы. Группы лексем объединяются в операторы - основные структурные единицы программы
. Построением структуры управляет набор явно записанных правил; каждой последовательности лексем соответствует один единственный способ разещения их в структуре. Встречая последовательность лексем, которая не укладывается ни в одно правило, синтаксический анализатор выдает предупреждение об ошибке. В интерпретаторе отбирается ровно столько лексем, сколько надо для обработки логически сязанного фрагмента программы.
Контроль типов.
Направлен на выявление ошибок, связанных с несовсестимость типов.
Генерация кода.
Генератор превращает лексемы в последовательность машинных команд.
Убедиться в правильности работы программы можно только во время ее тестирования – пробных запусках программы и при анализе полученных результатов.
В среднем, каждая строка кода в языках программирования систем транслируется примерно в пять машинных команд, сравнительно с одной командой на строку в ассемблерной программе.
4. Алгоритмы и структурное программирование
Алгоритмом
называется четкое описание последовательности действий, которое необходимо выполнить для решения задачи. Так как решение практически любой задачи требует получения результата по заданным исходным данным, то можно сказать, что алгоритм описывает процесс преобразования исходных данных в результат.
Разработать алгоритм – значит разбить
задачу на последовательно выполняемые шаги (этапы), причем результаты выполнения предыдущих этапов могут использоваться при выполнении последующих. Должны быть четко указаны как содержание каждого этапа, так и последовательность выполнения этапов. Отдельный шаг
(этап) представляет собой либо более простую задачу, алгоритм которой также должен быть разработан, либо должен быть настолько простым и понятным, чтобы для него не требовалось дополнительных пояснений.
Если алгоритм разработан, то, в принципе, его можно поручить выполнить человеку, не знакомому с решаемой задачей. Основные свойства алгоритма состоят в следующем.
1. Дискретность. Алгоритм представляет процесс решения задачи как последовательность выполнения простых (или заранее определенных) шагов-этапов. Для выполнения каждого эпата требуется определенное время, т.е. преобразование исходных данных в результат происходит дискретно во времени
.
2. Определенность (детерминированность). Каждое правило алгоритма должно быть четким и однозначным. Отсюда выполнение алгоритма носит механический характер.
3. Результативность (конечность). Алгоритм должен приводить к решению задачи за конечное число шагов.
4. Массовость. Алгоритм решения задачи разрабатывается в общем виде, т.е. он должен быть применим для некоторого класса задач, различающихся лишь исходными данными. При этом исходные данные выбираются из некоторой области, которая называется областью применимости алгоритма
.
Понятия алгоритма и программы разграничены не очень четко. Обычно программой называют окончательный вариант алгоритма, ориентированный на конкретного исполнителя – вычислительную машину.
При разработке алгоритма для представления его промежуточных вариантов часто используют язык схем
(есть 2 распространенных способа: блок-схемы и псевдокод - язык проектирования программ). Схемой
называется наглядное графическое представление алгоритма, когда отдельные этапы изображаются при помощи различных геометрических фигур (блоков), а связи между этапами с помощью линий, соединяющих блоки.
Несмотря на все многообразие решаемых на вычислительных машинах задач, можно выделить несколько типичных действий
(этапов - элементарных конструкций), которые в различной последовательности выполняются при решении задач. Условные обозначения таких этапов на языке схем (ГОСТ предписывает определенные размеры блоков, чему необходимо придерживаться при оформлении окончательной документации) приведены на рис.5. В языках программирования имеются средства, обозначающие и другие, более сложные действия, но они, как правило, могут быть выражены через основные.
Обработка (вычисление)
.
Основная операция, при помощи которой осуществляется обработка данных – это операция присваивания
. При ее выполнении переменной присваиваивается значение. Переменная обозначается с помощью имени. Имя
– обычно некоторая комбинация букв и цифр, выбираемая пользователем с соответствии с установленными правилами. Именами обозначаются также различные функции (sin, sqrt, sqr,…), которые имеют в языках программирования закрепленные за ними имена. Имя обозначает симвоический адрес
той ячейки памяти, в которой записано числовое значение соответствующей переменной или функции после ее вычисления. Числа, используемые, например, в операциях присваивания для задания значения переменных, называются константами. Константы обозначают сами числа, а не ячейки памяти (хотя бывают поименнованные константы
).
Примеры. К = 1 – в ячейку памяти с символическим адресом К помещается 1 (переменной К присваивается значение 1); L = K – в ячейку памяти с символическим адресом L пресылается содержимое ячейки с адресом К, при этом значение К не изменяется; К = К + 1 – к содержимому ячейки К прибавляется 1, результат помещается в туже ячейку К, при этом старое значение К пропадает (стирается); Y = sin(X) – вычисляется sin угла, величина которго находится в ячейке Х, и результат помещается в ячейку Y.
Замечания. 1. Перед выполнением присваивания старое значение стирается. 2. Переменным, которые располагаются в правой части оператора присваивания (в языках программирования оператор – конструкция, предписывающая выполнение какой-либо операции)
, должны быть присвоены определенные значения предшествующими операторами. 3. Если переменной не присвоено значение, то ее значение не определено.
Проверка условия
. Является основой организации разветвлений, т.е. выбора одного из двух (или более) путей вычислительного процесса. Это этап принятия решения о дальнейшем ходе вычислительного процесса в зависимости от полученных промежуточных результатов. Cначала вычисляется логическое выражение (предикат), которое можно привести к условию типа "да-нет", "истина-ложь". Если выражение истинно, то выполняется одно действие, если ложно - другое.
Ввод-вывод данных
.
В операторах ввода-вывода записываются имена тех переменных, значения которых должны вводиться в оперативную память или выводиться из нее. В них могут также указываться внешние устройства, с которыми осуществляется обмен информацией. При вводе (с клавиатуры или носителя информации) данные записываются в те ячейки памяти, символические адреса (имена) которых указаны в операторе. При выводе (на экран монитора или на бумагу в принтере) появляются данные, находящиеся в ячейке памяти, символические адреса которых перечислены в операторе вывода.
Начало и конец вычислительного процесса.
Подпрограмма
.
Группа операторов, которая решает логически самостоятельную часть задачи, может объединяться в подпрограмму.
Соединительные линии и их объединение
.
Все блоки схемы соединяются посредством линий, которые могут снабжаться направляющими стрелками. Основными являются направления сверху вниз и слева направо. Объединение нескольких ветвей в одну обозначается точкой. Если линии пересекаются без точки, то соединение отсутствует.
Точки связи или соединители
.
Ими пользуются в том случае, если соединительная линия не может быть доведена до следующего блока или до точки объединения. Тогда линия оканчивается соединителем, в котором записывается любой символ. Продолжением этой линии считается вторая точка связи, помеченная тем же символом.
Комментарии
можно записывать около любого блока.
При решении сложных задач
обычно составляется несколько схем с различным уровнем детализации. При этом прямоугольник может быть использован для обозначения не только одной операции присваивания, но и более емких этапов преобразования (обработки) данных. Схема не должна быть громоздкой, т.к. это приводит к потере наглядности, что является основным преимуществом схем, и не должна дублировать программу, изображая каждый оператор в виде отдельного блока.
Основные структуры алгоритмов. Понятие о структурном подходе к разработке алгоритмов.
Основные структуры алгоритмов
– это ограниченный набор стандартных способов соединения отдельных блоков или структур блоков для выполнения типичных последовательностей действий. Эти структуры следует использовать, если алгоритмы (и, следовательно, программы) разрабатываются в рамках структурного подхода. Иначе, структурный подход
к программированию предполагает использование только нескольких основных структур, комбинация которых дает все многообразие алгоритмов.
Доказано (Бойм и Якопини (Bohm, Jacopini, 1966), Милс (Mills, 1972)), что программу для любой логической задачи можно составить из структур следование, разветвление и повторение (цикл) (основные структуры). Главная идея этого доказательства сводится к тому, что необходимо преобразовать каждую часть программы в одну из трех основных структур или их комбинацию так, чтобы неструктурированная часть программы уменьшилась. После достаточного числа таких преобразований оставшаяся неструктурированной часть либо исчезнет, либо станет ненужной.
Доказательство относится лишь к простым программам
, которые содержат единственный вход и единственный выход, не содержат бесполезных (недостижимых) фрагментов, не содержат бесконечных циклов.
Рассмотрим основные конструкции, включающие следование, два разновидности цикла и три разновидности разветвления.
Следование
(рис.6а) – это последовательное размещение блоков и групп блоков.
Если некоторая часть программы (т.е. группа блоков на схеме) выполняется многократно и после проверки некоторого условия в какой-то момент времени осуществляется выход из нее, то такую часть программы называют циклом.
Цикл «До»
(рис.6б) применяется при необходимости выполнить какие-либо вычисления несколько раз до выполнения условия (блок 3). Особенность в том, что он выполняется хотя бы один раз, т.к. первая проверка условия выхода из цикла происходит после того, как тело цикла выполнено. Тело цикла
(блок 2) – та последовательность действия, которая выполняется многократно (в цикле). Начальные присвоения (блок 1) – задание начальных значений тем переменным, которые используются в теле цикла. Цикл «Пока»
(рис.6в) отличается от цикла «До» тем, что проверка условия (блок 3) производится до выполнения тела цикла (блок 2). В этом случае, если при первой проверке условия оно выполняется, то тело цикла не выполняется ни разу. Иначе такой цикл называется циклом по условию
.
Возможен случай, когда проверка условия осуществляется внутри тела цикла, т.е. тело цикла разбивается на две последовательности операторов: одна выполняется до проверки условия, вторая – после.
Разветвление
(рис.6г) применяется, когда в зависимости от условия требуется выполнить либо одно действие, либо другое.
Обход (
рис.6д) – частный случай разветвления, когда одна ветвь не содержит ни каких действий.
Множественный выбор
(рис.6е) является обобщением разветвления, когда в зависимости от значения переменной (I, в данном случае) выполняется одно из нескольких действий. Например, при I = 1 действие, обозначенное блоком S1
, и т.д.
Особенностью всех рассмотренных структур
является то, что они имеют один вход и один выход
, и могут соединяться друг с другом в любой последовательности. Каждая структура может содержать в качестве одного из блоков любую другую структуру.
Обычно при составлении схемы блоки размещаются друг под другом в порядке их выполнения. Возврат назад осуществляется только в циклах. Это дает простую и наглядную структуру алгоритма, по которой далее легко составить программу.
Рассмотрим в качестве небольшого примера алгоритм вычисления суммы квадратов первых N (N >= 1) натуральных чисел.
(N должно быть задано перед выполнением алгоритма - программы)
Решение задачи - последовательность простых шагов (рис.7).
Задать количество слагаемых N.
Присвоить S значение 0 и i значение 1.
Далее выполнять операцию S = S + i2
, увеличивая значение i на 1 после очередного выпонения этой операции.
Чтобы обеспечить сложение точно N членов, после каждого изменения i необходимо проверять условие i <= N.
Если условие удовлетворяется, т.е. не все N сложений выполнены, то необходимо повторить действия, начиная с операции (блока), помеченной 4.
Распространен и другой способ описания логики программы до начала ее программирования кроме использования блок-схем. Альтернативный вариант - это применение псевдокода
(или языка проектирования программ PDL – Process Design Language)
. Он занимает промежуточное положение между естественным языком и языком программирования
. Он состоит из внешнего синтаксиса
и внутреннего синтаксиса.
Внешний синтаксис
- заданный набор операторов, построенных по образцу языков программирования и описывающий логику программы (с помощью этих операторов организуются основные структуры, рассмотренные ранее). В этот набор включаются следующие операторы (рис.8).
Следование
. Записываются последовательно операции одна под другой. Для отделения части последовательности операторов используются операторы – do{do-часть}-(end-do) (выполнить);
Индексная последовательность
(цикл по счетчику
). Цикл с заранее определенным числом шагов (среднее между обычными последовательностью и циклом) – for{индексный список}-do{do-часть}-(end-do) (для-выполнить);
Цикл
-
До
– do{do-часть}-until{until-тест}-(end-do). Операции структуры, включая модификацию until-теста, выполняются один или более раз до тех пор, пока until-тест не примет значение истина
;
Цикл-Пока
– while{while-тест}-do{do-часть}-(еnd-do) Do-часть выполняется пока while-тест имеет значение истина
. Естественно do-часть модифицирует условие while-теста для того, чтобы окончить вычисления.
Разветвление
– if{if-тест}-then{then-часть}-else{else-часть}[els]-(end-if) (если-то-иначе);
Множественный выбор
– case{case-тест}-part{список1,case-часть1}-part{список2,case-часть2}-…-part{списокn
,case-частьn
}-else{else-часть}-(end-do) (выбор);
Кроме того, к внешнему синтаксису также относятся процедуры и модули. Процедура
– это хранимые в памяти машины подпрограммы, которые могут вызываться для выполнения из различных мест основной программы, либо из других процедур. Она вызывается и выполняется до завершения без сохранения внутренних данных. Модуль
– это несколько процедур, организованных в систему для удобства работы пользователя. Модуль имеет доступ к общим данным, которые сохраняются между последовательными вызовами модуля.
Внутренний синтаксис
- общий, обычно специально не определяемый синтаксис, пригодный для описания задач в данной области. Почти любое предложение, написанное на естественном языке, либо на специализированном языке (например, математические формулы) может быть использовано. Т.е. единого или формального определения псевдокода не существует.
Рассмотренный ранее пример, представленный на языке PDL, изображен на рис.9.
5. Методы разработки программного обеспечения
Рассматриваются общие методы (не зависят от используемого языка).
При использовании метода пошаговой детализации
(о котором говорилось ранее) первоначально продумывается и фиксируется общая структура алгоритма без детальной проработки отдельных его частей (важно, что при этом также используются основные структуры). Блоки, требующие детализации, иногда обозначают пунктирной линией. Далее прорабатываются-детализируются отдельные блоки. Т.е. на каждом шаге уточняется реализация фрагмента алгоритма (или программы). Полностью закончив детализацию всех блоков, мы получим решение задачи в целом. Описанный метод также называется нисходящим проектированием.
Итак, вначале программист представляет задачу как набор задач (рис.10 – в виде блок-схемы и языка PDL). Затем каждая из задач детализируется, например, в виде нескольких предложений языка PDL. Если она относительно сложная, то можно представить в виде отдельной процедуры.
Менее распространен метод восходящего проектирования. Он используется, например, если требуется обеспечить определенные характеристики системы, которые определяются на самом низком уровне.
На этапах кодирования и тестирования
удобнее оказывается восходящий принцип. Т.е. кодировать и тестировать систему начинают с компонентов, не производящих других компонент.
Введем понятие простой программы
(именно для такой программы может быть доказана теорема о возможности ее преобразования в последовательность основных управляющих структур – следования, повторения и разветвления), для которой
1. Существует единственный вход и единственный выход;
2. Для каждого элемента программы существует путь от входа к выходу через этот элемент (т.е. она не содержит бесконечных циклов и не содержит бесполезных (недостижимых) фрагментов).
Примеры простых программ и непростых приведены на рис.11.
Используя указанное определение, нисходящую разработку можно представить в виде следующего алгоритма:
while
проектирование не окончится
do
заменить некоторый узел простой программой
end-do
Элементарная программа – простая программа, которая не включает простых подпрограмм, состоящих более чем из одного узла (который также считается элементарным). Составная программа – программа, полученная заменой функционального узла элементарной программы элементарной программой.
Одним из способов решения задачи может являться применение рекурсивного решения.
Оно может быть применено в том случае, если возможно сформулировать решение задачи как известное преобразование другого, более простого решения одной и той же задачи, хотя само решение (более простое) может быть неизвестно. Если такой процесс преобразования повторять многократно, пока не получится нужное значение, то подобное решение называется рекурсивным
.
Функция F является рекурсивной,
если
1. F(N) = G(N,F(N-1)), где G – известная функция
2. F(1) = некоторому известному числу.
Типичным примером можем являться вычисление факториала. Например, Factorial(450) можно получить умножением 450 на Factorial(449). В результате задача сводится к вычислению Factorial(449). Если это преобразование повторить 448 раз, то получим Factorial(2), который есть 2*Factorial(1). Поскольку Factorial(1) известен и равен 1, то задача решена.
Часто бывает невозможно получить точное решение задачи по соображениям стоимости, сложности или размера. В этом случае возможно построить представление об определенных свойствах задачи, которое называется моделью
, а остальные свойства не учитываются. Такой подход называется моделированием.
Моделирование широко применяется во многих областях (миниатюрные корабли и самолеты проверяются в иммитированных водоемах и аэротрубах до стадии испытания опытных образцов). Моделирование на вычислительной машине часто применяется для исследования физических процессов, особенно когда известно математическое описание процесса с помощью уравнений. В то же время для разработки программ такой подход используется не так часто. В качестве примера, можно предложить использовать моделирование операционной системы с предполагаемыми затратами 100 человеко-лет перед началом ее разработки.
В течение многих лет программное окружение строилось главным образом на основе процедурных языков
- Фортран, Паскаль, Ада,… (языки 3-го поколения). Программы, написанные на этих языках делятся на две части - данных и процедур. Программист вводит данные в компьютер, а затем описывает компьютеру шаг за шагом последовательность процедур, необходимых для решения задачи.
СТРУКТУРЫ ДАННЫХ
Процедурные языки характеризуются своими типами данных. При этом атрибутом переменной является ее тип, т.е. множество значений, которые может она принимать. При этом существует набор операторов
, которые могут оперировать переменной данного типа.
В каждом языке есть набор элементарных типов (целые, действительные и т.д.). Переменные, объявленные как элементарные типы, называются скалярными
- состоит из одного элемента. Переменные, состоящие из наборов элементов других типов (в т.ч. элементарных) – агрегативными (структурированными).
Простейший агрегативный тип – массивы,
которые есть упорядоченный набор данных одного типа.
Структуры
(в Паскале – записи
) – поименованная совокупность данных различных типов.
Во многих языках есть только эти два типа агрегативных данных.
Полезно рассмотреть и другие возможные агрегативные структуры (могут быть организованы с помощью массивов).
Списки –
упорядоченный набор переменных одного типа (рис.12). Список отличается от массива тем, что его размер является величиной переменной. Операции – добавить, удалить, проверить наличие элемента в списке.
Очереди –
упорядоченный список, в один конец которого элементы добавляются, а из другого изымаются. Иначе – это список FIFO (First In, First Out) – поступивший первым обслуживается первым. Операции – добавить и удалить.
Стеки
– упорядоченный список, в один конец которого элементы добавляются, и из него же изымаются. Список LIFO (Last In, First Out). Операции – поместить, извлечь, функция «пусто» = истина, если стек не заполнен.
Множество –
совокупность переменных одного типа. Аналогичен списку, за исключением того, что порядок расположения переменных не имеет значения. Операции – добавить, удалить, функция «входит в» = истина, если данная переменная находится в множестве.
Граф –
структура, состоящая из узлов и дуг, причем каждая дуга направлена от одного узла к другому.
Дерево –
направленный граф, у которого только корневой узел не имеет дуг, входящих в него, в каждый узел входит не более одной дуги, в каждый узел можно попасть из корневого за несколько шагов.
Важное место при работе с агрегативными переменными занимают такие действия как поиск и сортировка данных. Рассмотрим основные методы таких действий.
ПОИСК
Поиск – это процесс нахождения нужных значений в некотором наборе данных.
Будем считать, что элемент данных
- это запись (структура), состоящая из ключа
(целое положительное число) и тела
, содержащего некоторую информацию. Задача состоит в том, чтобы обнаружить запись с нужным ключом. Тело не влияет на поиск. Для простоты рассмотрим поиск в одномерном массиве.
1. Линейный поиск.
Наиболее простой в реализации, хотя его выполнение занимает наибольшее время. Элементы проверяются последовательно, по одному, до тех пор, пока нужный элемент не будет найден. Если массив содержит N элементов, то каждый раз, в среднем, будет проверяться N/2 элементов. Легко программируется, подходит для коротких массивов.
2. Двоичный поиск.
Для ведения двоичного поиска ключи должны быть упорядочены по величине или алфавиту. Если такое упорядочивание произведено заранее, то среднее максимальное время поиска в массиве с N элементами будет log2
N.
При двоичном поиске ключ (поиска) сравнивается с ключом среднего элемента в массиве. Если значение ключа больше, то та же самая операция повторяется для второй половины массива, если меньше - то для первой. Т.о. за каждый шаг объем поиска сокращается наполовину.
СОРТИРОВКА
Сортировкой
(или упорядочением) называется переразмещение элементов данных в возрастающем или убывающем порядке. Существует большое количество методов сортировки, но ни один из них не является лучшим во всех отношениях.
При выборе метода сортировки необходимо учитывать
- число сортируемых элементов (N);
- до какой степени элементы уже отсортированы.
В качестве основных критериев оценки метода сортировки используют количество необходимых операций сравнения С и объем используемой памяти S. Количество сравнений С(N) для различных методов может быть от N2
до N log2
N. Эффективность использования памяти определяется соотношением
где S(N) - объем памяти, занимаемый элементами данных до сортировки, DS(N) - объем дополнительной памяти, требуемой в процессе сортировки.
Задача сортировки состоит в том, чтобы расположить записи в порядке убывания (возрастания) ключей. Тело не влияет на сортировку. Для простоты рассмотрим сортировку одномерного массива.
1. Сортировка методом выборки
Из массива выбирается наименьший элемент и помещается в первый элемент массива, затем выбирается наименьший элемент из оставшихся и помещается во второй элемент массива.
В качестве метода поиска наименьшего элемента может быть применен следующий ("Турнир с выбыванием"). На первом шаге сравниваем попарно элементы массива в следующем порядке А(1) с А(2), А(3) с А(4) и т.д. При этом наименьшие элементы массива помещаем на первое место, т.е. на А(1), А(3),…, а наибольшие - на второе. На втором шаге сравниваем первые элементы образовавшихся пар: А(1) с А(3), А(5) с А(7), … и наименьшие помещаем на первое место, т.е. в А(1), А(5),… По окончании процесса обнаружим наименьший элемент на месте первого элемента массива А(1). Далее метод поиска применим к части массива, начиная с А(2) и т.д.
Сортировка таким методом требует порядка N(N+1)/2 сравнений и практически не нуждается в дополнительной памяти.
2. Сортировка включением
. Элементы выбираются по очереди и помещаются в нужное место.
Один из возможных способов сортировки данным методом ("сортировка простым включением") таков. Начиная с первого элемента сравниваем последовательно пары соседних элементов и продвигаемся вперед до тех пор, пока выполняется условие А(К) <= А(К+1), К = 1,2,3,… Как только А(К) станет больше А(К+1) меняем их местами и начинаем возврат назад, последовательно проводя сравнение и обмен пар соседних элементов до тех пор, пока не будет нарушено условие А(L) > A(L-1), L = K,K-1,… Как только А(L) станет меньше или равно А(L-1), продолжаем процесс сравнения вперед, начиная с А(К+1) (при этом элементы А(1)…А(К+1) будут
При сортировке простым включением требуется порядка N2
сравнений, при этом практически не используется дополнительная память.
3. Сортировка распределением (метод корзин)
.
Среди методов сортировки распределением широкое распространение получила цифровая сортировка
. Любой элемент массива рассматривается как совокупность цифр, а элементы сначала сортируются по значению старшей цифры, затем полученные подмножества (группы) сортируются по значению следующей цифры и т.д. Пусть каждый элемент массива А размером N представляет собой совокупность цифр С1
С2
С3
…Сm
, где m - количество цифр максимального элемента (если какой-то элемент содержит меньше цифр, то он слева дополняется нулями). Последовательно просматривая элементы массива А, распределим их по группам: в первую группу объединим все элементы, у которых первая цифра - 0, во вторую - если первая цифра 1, в последнюю 10 - первая цифра 9. К каждой группе применим тот же метод, но относительно цифры С2
. Далее до цифры Сm
. Пример.
Алгоритм цифровой сортировки не предполагает сравнение элементов и поэтому нельзя оценить ее эффективность обычным способом. Но если m (число цифр) мало, то ее показатели лучше, чем "N log2
N". Для цифровой сортировки требуется дополнительный массив размером N, и еще массив размером 10, в котором подсчитывается число элементов с выделяемой цифрой 0,1,…9.
4. Сортировка обменами.
Выбираются два элемента, и если друг по отношению к другу они не находятся нужном порядке, то меняются местами. Процесс продолжается пока никакие два элемента не нужно менять местами.
Метод "Быстрой сортировки". Устанавливаем I = 1, J = N. Сравниваем А(I) и А(J). Если А(I) <= А(J), то уменьшаем J на 1 и продолжаем сравнение. Последовательное уменьшение J и сравнение указанных элементов продолжаем пока не окажется, что А(I) > А(J). Тогда меняем местами эти элементы. После чего увеличиваем индекс I на 1 и снова сравниваем. Последовательное увеличение I и сравнение продолжаем до тех пор, пока не окажется, что А(I) > А(J). В этом случае меняем местами элементы и начинаем уменьшение J.
Чередуя увеличение I и уменьшение J приходим к некоторому элементу, который называется пороговым или главным
, и характеризующемуся условием I = J (перед началом сортировки он находился в А(1)). Все элементы массива оказались разделенными главным элементом на две части так, что все элементы слева (сверху) меньше него, а справа (снизу) - больше. К этим двум массивам применяем тот же алгоритм и получаем четыре части и т.д.
Алгоритм "Быстрой сортировки" имеет максимальное число сравнений N2
, а минимальное N log2
N. Все зависит от выбора главного элемента (желательно, чтобы он делил массив на две равные части). Среднее число сравнений 2N log2
N. Дополнительная память используется для хранения стеков, в которые засылаются границы получаемых частей массивов.
5. Сортировка слиянием
.
Два отдельно отсортированных массива затем срединяются в один массив таким образом, чтобы и он стал отсортированным. Пусть имеем два отсортированных массива В и С с числом элементов M и L. Процедура слияния в массив А с числом элементов N = M+L такова. В качестве А(1) выбираем наименьший из В(1) и С(1). Если это В(1), то в качестве А(2) - наименьший из В(2) и С(1) и т.д.
Чтобы применить такой метод к неотсортированному массиву, можно считать каждый его элемент сортированной последовательностью, состоящей из одного элемента. Объединяем каждую пару следующих подряд последовательностей в одну отсортированную последовательность. Затем объединяем по четыре элемента и т.д.
Этот метод требует N log2
N сравнений и один дополнительный массив, содержащий N элементов.
СТРАТЕГИИ РАСПРЕДЕЛЕНИЯ ПАМЯТИ.
Одним из типов задач поиска являются задачи распределения памяти, которые обычно возникают при создании операционных систем и во многих других случаях, связанных с использованием данных.
При распределении памяти (т.е. отведения под выполняемую программу, а затем освобождения) возникают свободные области. Эти области могут оказаться малы для размещения в них данных, и, следовательно, память становится фрагментарной (рис.13). Фрагментация памяти
- это наличие большого числа несмежных участков свободной памяти очень маленького размера (фрагментов). Суммарный объем фрагментов может составить значительную величину, намного превышающую требуемый объем памяти для выбранной программы. Способы размещения вновь поступающих данных:
1. Первое возможное размещение
.
Последовательно просматриваются области памяти, пока не будет найдена область, достаточная для размещения данных. Все свободные области организованы в список (рис.13). Найденная область изымается из списка, на ее место ставится меньшая свободная область - оставшаяся незанятой часть исключенной области. В рамках данного способа легко объединить две соседние области в одну, если они свободны (т.к. список областей упорядочен по адресам). Данный способ довольно прост, однако при его использовании сильно возрастает фрагментарность.
2. Наилучшее размещение.
Последовательно просматриваются все области. Выбирается наименьшая область, размер которой больше или равен требуемому объему для размещения данных. В этом случае стратегия размещения приводит к меньшей фрагментарности. При этом свободные области упорядочены не по адресам, а по размеру. Следовательно, алгоритм объединения двух соседних областей в одну более сложен, чем в предыдущем случае.
Одним из методов борьбы с фрагментацией, который берет на себя ОС, является перемещение всех занятых участков в сторону старших либо в сторону младших адресов, так, чтобы вся свободная память образовывала единую свободную область. Эта процедура называется "сжатием". Сжатие может выполняться либо при каждом завершении какой-то задачи, либо только тогда, когда для вновь поступившей задачи нет свободного раздела достаточного размера. В первом случае требуется меньше вычислительной работы при корректировке таблиц адресов, а во втором - реже выполняется процедура сжатия.
ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ
Объектно-ориентированные языки
создают окружения, радикально отличающиеся от окружения процедурных языков, которое состоит из данных и процедур. ООЯ создает окружение в виде множества независимых объектов
, каждый из которых отличается своими свойствами и способами взаимодействия с другими объектами. Задавая структуру обмена сообщениями
между объектами, программист получает совокупность операций, которые и составляют программу. Объекты можно использовать для решения задач, не понимая механизма их работы (рассматривать как "черный ящик"). ООЯ скрывает многие детали работы внутри объектов (однако при необходимости можно модифицировать детали внутреннего устройства объектов, создавая тем самым другие объекты).
Внутренность объекта.
Каждый объект содержит переменные
, в которых находятся данные, его описывающие (в различных ООЯ названия некоторых характеристик могут отличаться, например в языке Object Pascal (Delphi) переменные называются полями). Пусть объект с именем «Окружность А» (рис.14) – это фигура, которая может быть нарисована на экране компьютера. Переменные этого объекта: позиция
(позиция на экране центра окружности) и размер
(радиус окружности).
Операции над данными объекта запускаются при получении им сообщения, указывающего нужную последовательность действий. Конкретная процедура, которая идентифицируется с сообщением, содержится внутри объекта в виде внутренней программы, которая называется методом
. Метод выполняет манипуляции с данными объекта, а также может посылать сообщения другим объектам с целью получения нужного результата, который затем возвращается назад к первому объекту. Объект «Окружность А» имеет три метода. Метод форма
говорит объекту как описать его форму, обращаясь к своим переменным, а также посылая сообщения другому объекту «Маркер», который выполняет работу по вычерчиванию. На рис. обозначены методы объекта «Маркер» (сообщение, вызывающее метод Маркер сдвигК
посылает координаты центра окружности, т.е. значение переменной Позиция
). В объекте «Окружность А» метод форма
вызывается своими же методами высветить
и стереть.
Методы высветить
и стереть
посылают сообщения Себе
– встроенному имени, позволяющему объекту (в нашем случае «Окружность А») направлять сообщения самому себе. Метод высветить
приказывает Себе
использовать метод форма
для создания окружности, послав предварительно сообщение Маркеру
о том, что он должен рисовать черные линии. Метод стереть
говорит Себе
использовать метод форма
для удаления окружности, что делается с применением Маркера
, рисующего белым цветом.
Каждый объект в объектно-ориентированном окружении имеет собственные характеристики. Однако объекты имеющие одинаковые свойства и ведущие себя сходным образом объединяются в классы
. Класс
задает общие свойства его составляющих (называются экземплярами
), т.е. все они имеют одинаковое число переменных одного и того же типа, реагируют на одни и те же сообщения и реализуют одни и те же методы работы со своими переменными. Экземпляры класса отличаются один от другого конкретными значениями собственных переменных. Рассмотрим пример упрощенного объектно-ориентированного языка, организованного как иерархия суперклассов и подклассов (рис.15). На самом нижнем уровне находится класс «Объект», который задает общие свойства всех объектов, например, их способность отвечать на сообщения. «Дисплейный объект» – это подкласс класса «Объект». Подкласс наследует сообщения и методы суперкласса, кроме того, он может содержать новые сообщения и методы, реализующие его особенности.
«Дисплейный объект» представляет собой как бы эскиз объектов, которые отображаются на экране. Он задает методы построения дуг, прямых, и т.д. Класс «Многоугольник» имеет переменные позиция, стороны, угол,…
и методы форма, высветить, стереть.
Может быть послано сообщение классу «Многоугольник» создать объект этого класса «Многоугольник А» - экземпляр, который будет иметь собственные значения переменных и содержать все методы класса «Многоугольник». Следующий класс «Прямоугольник» имеет две переменные, метод форма
описывается вновь, поэтому перекрывает этот метод от «Многоугольника», новый метод увеличение
, методы высветить и стереть
наследуются.
Программа написанная на ООЯ, не имеет списка переменных, доступных всем объектам; напротив каждый объект контролирует свои собственные атрибуты, никакая процедура не может влиять на данные объекта. Единственный способ получить информация об объекте или изменить его данные – это послать сообщение, вызывающее методы объекта. ООЯ хорошо подходит для написания сложных программ, управляющих компьютерной системой.
Поскольку каждая часть системы может быть представлена объектом, замена одной из них, например, программы управления печатающим устройством или экраном, может быть осуществлена без изменения остальных частей системы.
Показано, что наиболее удобными для реализации программных систем, разработанных в рамках объектно-ориентированного подхода, являются объектно-ориентированные языки программирования, хотя возможна реализация и на обычных (не объектно-ориентированных) языках (например, на языке C и на языке Fortran). Первый объектно-ориентированный язык программирования Simula 67 был разработан в конце 60-х годов в Норвегии. Авторы этого языка очень точно угадали перспективы развития программирования, но их язык намного опередил свое время. Однако современники (программисты 60-х годов) оказались не готовы воспринять ценности языка Simula 67, и он не выдержал конкуренции с другими языками программирования (прежде всего, с языком Fortran). Прохладному отношению к языку Simula 67 способствовало и то обстоятельство, что он был реализован как интерпретируемый (а не компилируемый) язык, что было совершенно неприемлемым в 60-е годы, так как интерпретация связана со снижением эффективности (скорости выполнения) программ. Но достоинства языка Simula 67 были замечены некоторыми программистами, и в 70-е годы было разработано большое число экспериментальных объектно-ориентированных языков программирования: например, языки CLU, Alphard, Concurrent Pascal и др. Эти языки так и остались экспериментальными, но в результате их исследования были разработаны современные объектно-ориентированные языки программирования: C++, Smalltalk, Eiffel и др. Наиболее распространенным объектно-ориентированным языком программирования безусловно является C++. Свободно распространяемые коммерческие системы программирования C++ существуют практически на любой платформе. Широко известна свободно распространяемая система программирования G++, которая дает возможность всем желающим разобрать достаточно хорошо и подробно прокомментированный исходный текст одного из образцовых компиляторов языка C++. Разработка новых объектно-ориентированных языков программирования продолжается.
Дескриптивные языки (языки логического программирования).
6. БАЗОВЫЕ ЭЛЕМЕНТЫ ЯЗЫКА ТURBO РASCAL
Язык программирования Pascal был разработан Никлаусом Виртом. Датой его рождения считается 1970 г. Он обеспечивает возможность создания больших программ, поддерживая их строгую логическую структуру. Считается важнейшим инструментом для обучения методам структурного программирования. В 1984 г. фирмой Borland была представлена первая версия компактного и довольно быстрого компилятора Turbo-Pascal. В состав Turbo-Pascal кроме компилятора входит инструментальная программная оболочка, способствующая повышению эффективности создания программ (среда ТР). Совершенствуя Turbo-Pascal, фирма Borland разрабатывала новые версии пакета. Уже первая версия Turbo-Pascal содержала расширения языка Pascal, хотя и небольшие. В последующих версиях расширений становилось все больше и больше: встроенная графика (версия 3.0), от которой потом отказались, модули (4.0), средства ООП (5.5) и т.д. и т.д. Начиная с версии 7.0, язык стал называться Borland Pascal. Со временем в Turbo Pascal появились дополнительные средства, обеспечивающие поддержку концепции объектно-ориентированного программирования, и язык программирования Turbo Pascal стал именоваться Object Pascal. На базе Object Pascal в фирме Borland был создан принципиально новый программный продукт, который получил название Delphi. Delphi - это среда разработки программ, ориентированных на работу в Windows.
АЛФАВИТ
Алфавит языка ТР включает:
- прописные и строчные латинские буквы от a
до z
и от А
до Z
, а также знак подчеркивания (в ТР нет различия между прописными и строчными буквами алфавита, если только они не входят в строковые и символьные выражения),
- арабские цифры от 0 до 9,
- шестнадцатеричные цифры 0,1,2,...,9,A(a),B(b),...,F(f),
- специальные символы + - * / = , ‘ . : ; < > [ ] ( ) { } ^ @ $ # и пробел,
- составные символы := <> .. <= >= (* *) (вместо {}) (. .) (вместо [])
Неделимые последовательности знаков алфавита образуют слова,
которые несут в программе определенный смысл и отделяются друг от друга разделителями
. Разделителем может служит пробел и другие символы, комментарий. Слова подразделяются на зарезервированные слова
, стандартные идентификаторы
и идентификаторы пользователя.
Зарезервированные слова
являются составной частью языка, имеют фиксированное значение и определенной начертание. Например, begin end array const if then else ...
(всего 69 для ТР 7.0). Зарезервированные слова не могут использоваться в качестве идентификаторов. Исключение составляют заразервированные слова, которые обозначают стандартные директивы
(absolute assembler external far forward interrupt near private virtual
) и связаны со стандартными объявлениями в программе. Можно переопределить стандартную директиву, т.е. объявить одноименный идентификатор. В среде ТР зарезервированные слова выделяются цветом (либо жирным шрифтом).
Стандартные идентификаторы
служат для обозначения заранее определенных разработчиками языка типов данных, констант, процедур и функций.
В модуле Graph
константы цвета Black=0, Blue=1,..., тип ТextSettingsType = records с полями Font:Word, Direction:Word, CharSize:Word, Horis:Word, Vert:Word, процедура MoveTo (X, Y: Integer) - смещает текущий указатель к точке X, Y, функция GetMaxX - возвращает максимальную горизонтальную координату графического экрана.
Например, константа MaxInt = 32767 (максимальное целое в типе integer), встроенные функции Sin, Exp. Любой из стандартных идентификаторов можно переопределить. Однако чаще всего это приводит к ошибкам (использование в стандартных модулях).
Идентификаторы пользователя -
это имена констант, переменных, меток, типов, объектов, процедур, функций, модулей, программ и полей в записях. Есть правила:
- идентификаторы могут иметь произвольную длину, но значащими (уникальными в области определения) будут только первые 63 символа,
- всегда начинается буквой, за которой могут следовать буквы и цифры (символ подчеркивания считается буквой, пробелы и специальные символы недопустимы),
- имена меток могут начинаться как буквой, так и цифрой
Можно использовать как прописные, так и строчные буквы. Компилятор не делает различий между ними. На практике рекомендуется использовать эту особенность для более простого чтения и понимания значений идентификаторов (MyProgram). Для лучшего восприятия и последующего изменения серьезных программ полезно в имена закладывать смысл, которые несут эти переменные, константы и т.д. (например, обозначение дней недели не просто d, а Days или Dni).
КОНСТАНТЫ И ПЕРЕМЕННЫЕ
Как и другие языки программирования, ТР интерпретирует данные как константы и переменные
. Константы и переменные определяются идентификаторами, по которым к ним можно обратиться для получения текущих значений.
Константами
называются элементы данных, значения которых установлены в описательной части программы и в процессе выполнения программы не изменяются. Для определения значения констант служит зарезервированное слово Const
.
Формат:
Const
<идентификатор> = <значение константы>;
Значениями констант в ТР могут быть
- целые числа. Записываются со знаком или без и могут иметь значения от -2 147 483 648 до 2 147 483 647. Если необходимо описать константу выходящую за эти границы, то это нужно делать с использованием десятичной точки и экспоненциальной части, т.е. описывать ее как вещественную.
- вещественные числа. Записываются со знаком или без него, может использоваться десятичная точка и экспоненциальная часть. Экспоненциальная часть начинается символом е(Е)
, за которой может следовать знак + или - и десятичный порядок.
Например, а = 3.14е5; b = -72Е-3;
Если присутствует десятичная точка, за ней и перед ней д.б. хотя бы одна цифра. Если есть символ е - за ним д.б. хотя одна цифра.
- шестнадцатеричные числа. Состоит из шестнадцатиричных цифр, которым предшествует знак $
. Диапазон от $00000000 до $FFFFFFFF.
- логические константы. Это либо слово TRUE
(истина), либо FALSE
(ложь).
- символы. Любой символ из 8-битной таблицы символов компьютера, включающей и русские буквы, заключенный в апострофы
Например, Znak1 = 'd'; Znak2 = 'ф';
Если необходимо записать сам символ апострофа, то он удваивается ' ' ' '.
Допускается использование записи символа путем указания его внутреннего кода, которому предшестует символ #. Например, #90 - символ Z, #161 - символ б.
- строки символов. Любая последовательность символов (кроме символа CR - возврат каретки), заключенная в апострофы.
Если в строке нужно указать сам символ апострофа - он удваивается. Строка м.б. пустой. Можно составить из кодов нужных символов с предшествуюшими каждому коду знаком #. Можно чередовать запись с помощью самих символов в апострофах с записью кодов.
- конструкторы множеств. Список элементов множества, обрамленный квадратными скобками. Например,
[1,2,4..7,12] [red,blue,green] [true]
- и признак неопределенного указателя NIL
.
В отличие от стандартного Паскаля, в ТР разрешается в объявлении констант использовать произвольные выражения, операндами которых могут быть ранее объявленные константы, имена типов и некоторые функции. Например,
Const
Ln10 = 2.3026;
Ln10R = 1 / Ln10;
X = MaxInt div
SizeOf(real); (SizeOf - функция, возвращающая количество байт, отведенных на данный тип, Real - 6).
Переменные
в отличие от констант могут менять свои значения в процессе выполнения программы. Как и константы, любая переменная принадлежит к определенному типу данных. Но если тип констант распознается компилятором автоматически, то тип переменных должен быть в явном виде описан перед тем, как с переменными будут выполняться какие-либо действия.
Формат:
Var
<идентификатор> : <тип>;
Подробнее типы переменных будут рассмотрены позднее.
Существуют типизированные константы
, которые являются к.б. промежуточным звеном между переменными и константами. Они описываются в разделе Const.
Должен быть указан тип, как у переменных. <идентификатор>:<тип>=<значение>. Типизированная константа равнозначна переменной с заранее определенным значением. Действия над ней могут производиться, как над переменной. Смысл введения типизированных констант заключается в упрощении операции присваивания переменной начального значения, поскольку для любой обычной перменной перед ее использованием необходимо задать ее начальное значение в виде отдельной операции присваивания.
ВЫРАЖЕНИЯ И ОПЕРАЦИИ
Переменные и константы всех типов используются в выражениях. Выражение
задает порядок выполнения действий над элементами данных и состоит из операндов (константы, переменные, обращения к функциям), знаков операций и круглых скобок. Операции определяют действия, которые надо выполнить над операндами. В примере (X + Y - 10)*4 X,Y,10,4 – операнды, "+", "-", "*" – знаки операций сложения, вычитания и умножения. Круглые скобки ставятся для управления порядком выполнения операций. Частным случаем выражения может быть одиночный элемент (константа, переменная или обращение к функции).
Операции
в языке ТР подразделяются на арифметические, логические (булевские), отношения, строковые, операции с массивами и др. в зависимости от того какого типа операнды в них используются.
Операции могут быть унарные
и бинарные
. В первом случае операция относится к одному операнду и всегда записывается перед ним, во втором случае операция выражает отношение между двумя операндами и записывается между ними. Унарными операциями являются not
(отрицание), @
(создание указателя на переменную), “+”
(сохранение знака), “-“
(отрицание знака) (например, -Х, not Y). Последние две операции могут быть и бинарными (X + Y).
Рассмотрим три основных типа операций. Особенности остальных будут описаны при рассмотрении соответствующих типов данных.
Арифметические операции
выполняют арифметические действия над значениями операндов целочисленных и вещественных типов.
Операции сложения “+”
, вычитания “-“
, умножения “*”
, деления “/”
выполняются как в обычных арифметических действиях. Для операций “+”, “-“, “*”, если операндами являются целые числа (типы Byte, ShortInt, Word, Integer, LongInt), то результат будет относится к целому типу, наименьшему, включающему типы операндов (общий тип). Т.е. X : Byte, Y : Word, Z := X + Y, Z : Word. Для тех же операций, если операнды – вещественные, то результат – Extended (при установленном сопроцессоре, если его нет – то Real). Аналогично, если один из операндов – целое число, а другой – вещественное. Операция деления “/” дает в результате тип Extended как при целых, так и вещественных операндах.
Целочисленное деление div
отличается от обычного деления тем, что возвращает целую часть частного, а дробная часть отбрасывается. Оба операнда – целые, результат – общий тип. Примеры, 11 div 5 – результат 2, 2 div 3 – 0.
Деление по модулю mod
восстанавливает остаток, полученный при выполнении целочисленного деления. Типы операндов такие же, как у div
. Примеры, 11 mod 5 – 1, 2 mod 3 – 2.
Операции сдвига K shl N
и K shr N
оперируют с двоичным (битовым) представлением целых чисел. Они восстанавливают в качестве результата значение, полученное путем сдвига на N позиций влево или вправо числа К, представленного в двоичном виде. Оперируют с целыми числами, тип результата – общий тип. Например, 2 shl 7. (2)10
= (10)2
. Сдвиг влево на 7 позиций приводит к (100000000)2
(освободившиеся младшие разряда заполняются нулями), что равно (256)10
. Аналогично, 161 shr 2 дает 40. ((161)10 = 10100001, сдвиг на 2 вправо – (101000)2 = 8 + 32 = (40)10. Биты, которые уходят за край разрядности теряются).
Возможность работы с двоичным представлением чисел (так работают кроме рассмотренных операций shl и shr, также и логические операции) часто возникает при работе с видеопамятью в текстовом или графическом режимах. Кроме того, есть ряд число математических задач, которые удобно решать в двоичном представлении.
К логическим операциям
относятся and –
логическое И, or
– логическое ИЛИ, not
– логическое отрицание, xor –
исключительное ИЛИ. Они применяются к операндам как логического типа, так и целого типа. Логические операнды (логическая константа, логическая переменная, элемент массива логического типа,...) могут принимать значения True
или False
. Логические операции над логическими данными дают результат логического типа по следующим правилам (табл.)
Операнд 1
|
Операнд 2
|
and
|
or
|
xor
|
not
|
False |
False |
False |
False |
False |
- |
True |
False |
False |
True |
True |
- |
False |
True |
False |
True |
True |
- |
True |
True |
True |
True |
False |
- |
True |
- |
- |
- |
- |
False |
False |
- |
- |
- |
- |
True |
Если операндами являются целые числа, то оперирование происходит с двоичным представлением чисел. При этом результат – общий тип. При использовании этих операций осуществляется побитное (поразрядное) сравнение операндов. Двоичные цифры результата образуются по следующим правилам (табл.)
Операнд 1
|
Операнд 2
|
and
|
or
|
xor
|
not
|
0 |
0 |
0 |
0 |
0 |
- |
1 |
0 |
0 |
1 |
1 |
- |
0 |
1 |
0 |
1 |
1 |
- |
1 |
1 |
1 |
1 |
0 |
- |
1 |
- |
- |
- |
- |
0 |
0 |
- |
- |
- |
- |
1 |
Например, 12 or 22 соответствует сравнению, соответственно, 01100 и 10110. В соответствии с правилами получаем 11110, что соответствует (30)10
. Т.е. 12 or 22 = 30.
Некоторая особенность появляется при работе с типами целых чисел, которые могут принимать отрицательные значения. К таким типам относятся ShortInt
(-128..127) – 8 бит, Integer
(-32768..32767) – 16 бит, LongInt
(-2 147 483 648..2 147 483 647) – 32 бита. У этих типов старший разряд отводится под знак (0 – при положительном, 1 – при отрицательном). Для остальных разрядов, если число положительное, то кодировка осуществляется обычным образом (если Х = 12 и Х:ShortInt, то (Х)2
= 0 0001100). Если число отрицательное, то в остальных разрядах записывается число, которое в сумме с исходным дает модуль максимально допустимого значения для данного типа. Т.е., если X = -12, Х:ShortInt, модуль максимально допустимого значения для данного типа – 128, 116 + 12 = 128, поэтому (Х)2
= 1 1110100 – последние 7 цифр образуют число 116.
Учитывая это обстоятельство, рассмотрим применение операции not
к переменной, которая имеет определенное значение (Х = 5), но описывается с помощью разных типов.
Х:Byte, (X)2
= 00000101, not
X = (11111010)2
= (250)10
.
X:ShortInt, (X)2
= 0 0000101, not
X = (1 1111010)2
= - (128-122)10
= -(6)10
.
Операции отношения
выполняют сравнение двух операндов и определяют, истинно значение выражения или ложно. Сравниваемые величины должны быть совместимыми (совместимость типов – далее). Результат всегда имеет логический (булевский) тип и принимает одно из двух значений: True или False.
Операция
|
Название
|
Выражение
|
Результата
|
= |
Равно |
А = В |
True, если А равно В |
<> |
Не равно |
A <> B |
True, если А не равно В |
> |
Больше |
А > B |
True, если А больше В |
< |
Меньше |
А < B |
True, если А меньше В |
>= |
Больше или равно |
А >= В |
True, если А больше или равно В |
<= |
Меньше или равно |
А <= B |
True, если А меньше или равно В |
in |
Принадлежность |
А in M |
True, если А находится в списке М |
Операции отношения, а также логические операции интенсивно используются для управления циклами и в условных операторах.
Приоритет операций
.
Первый (высший) приоритет имеют унарные операции @, not.
Следующий (второй) приоритет у операций типа умножения *, /, div, mod, and, shl, shr.
Третий приоритет имеют операции типа сложения +, -, or, xor.
Четвертый (низший) приоритет - у операций отношения =, <>, <, >, <=, >=, in.
Имеются три основных правила для определения старшинства операций:
- операнд, находящийся между двумя операциями с различными приоритетами, связывается с операцией, имеющей более высокий приоритет;
- операнд, находящийся между двумя операциями с равными приоритетами, связывается с операцией, которая находится слева;
- выражение, заключенной в скобки, перед выполнением вычисляется, как отдельный операнд;
Обобщение последних двух правил дает следующее: операции с равным приоритетом производятся слева направо с возможным регулированием порядка выполнения скобками.
СТРУКТУРА ПРОГРАММЫ
Программа на языке ТР состоит из строк. Набор текста программы осуществляется с помощью текстового редактора среды ТР.
Общее число символов в файле не должно быть больше 64535. Компилятор ТР воспринимает строки программы длиной не более 126 символов. Редактор ТР вставляет в конец каждой строки невидимый на экране символ-разделитель (вставляется клавишей Enter).
Программист имеет право произвольно располагать строки на экране. Строка может располагаться с любой колонки, т.е. величина отступа слева устанавливается программистом с целью получить наиболее читабельный, по его мнению, текст программы.
В полном варианте программа на языке ТР имеет следующий вид (по мере объяснения).
Program
<имя программы>;
Uses
<имя1>,<имя2>,…;
Label
1, 234, m1, Stop;
Const
MaxN: word = 100; {Типизированная константа}
Vxod = ‘Блок_1’; {Строковая константа}
Kod = $124; {Константа – шестнадцатеричное число}
Type
Matrix = array[1..10] of real;
LatBukva = (‘a’..’z’);
Dni = 1..31;
Var
A, B, C : integer;
Result, Summa : real;
Vxod : boolean;
Procedure
<имя>;
<тело процедуры>;
…;
Function
<имя>;
<тело функции>;
…;
BEGIN
<операторы>;
END
.
Регистр написания заголовков неважен. Любой раздел, кроме раздела операторов, может отсутствовать (если в нем нет необходимости). Название программы в ТР имеет чисто декоративное назначение, как комментарий. Конструкция Program
<имя> не является необходимой в ТР, по сравнению с другими версиями Паскаля.
Раздел Uses
состоит из этого зарезервированного слова и списка имен подключаемых стандартных из набора ТР и пользовательских (разработанных программистом) модулей. Включение дополнительных модулей расширяет список используемых в программе процедур, функций, переменных, констант. Если директива Uses присутствует, то она должна стоять перед прочими директивами. Она может появиться в программе только один раз.
Раздел описания меток Label
содержит перечисленные через запятую метки переходов, установленные в основном блоке программы. Метка, поставленная перед каким-либо оператором, позволяет выполнить прямой переход к этому оператору из любого места программы с помощью оператора безусловного перехода goto.
Метки могут обозначаться целым числом 0..9999 или символьными конструкциями длиной не более 63 букв (см. пример).
В разделе описания констант Const
производится присваивание идентификаторам констант постоянных значений (см. пример).
Тип данных может быть описан непосредственно в разделе описания переменных, либо определяется идентификатором (именем) типа. Стандартные типы данных не требуют специального описания. Пользовательские типы, которые имеют имена описываются разделе Type
(см. пример) (подробное рассмотрение типов данных ТР – позднее).
Раздел описания глобальных переменных
(переменных, которые могут быть использованы в любом месте, процедуре, функции программы) начинается зарезервированным словом Var
. После чего перечисляются имена переменных с указанием через двоеточие их типа (см. пример).
В отличие от стандартного языка Pascal в ТР разделы Label, Type, Const, Var могут следовать друг за другом в любом порядке и встречаться в разделе описаний сколько угодно раз. Если в них нет необходимости, они могут отсутствовать. Однако поскольку программа компилируется последовательно, начиная с начала, важно соблюдать правило:
все, что в программе используется должно быть перед этим описано. Например, если переменная, описывается, как массив М :array[1..Max] of integer, то величина Мах = 20 должна быть ранее (выше по программе) описана в разделе Сonst.
В разделе описания процедур и функций размещаются тела подпрограмм. Подпрограмма
- это программная единица, имеющая имя, по которому она может быть вызвана из других частей программы. В языке ТР роль подпрограмм выполняют процедуры и функции. В общем случае подпрограмма имеет ту же структуру, что и программа (состоит из раздела описаний и раздела операторов). Для описания подпрограмм используются зарезервированные слова Procedure
и Function
. Заголовки процедур и функций записываются в начале программы до раздела операторов. Обычно за заголовком сразу следует реализация (тело) процедуры и функции, но они могут быть и разнесены внутри программы. Процедуры и функции подразделяются на стандартные и определенные пользователем
. Стандартные - являются частью языка и могут вызываться без предварительного описания. Описание процедур и функций пользователя обязательно. Более подробное рассмотрение будет в следующих разделах.
Часть программы, предшествующая разделу операторов, называется разделом описаний.
Основным в программе является раздел операторов
. Именно в нем с предварительно описанными переменными, константами, значениями функций выполняются действия, позволяющие получить результат, ради которого создавалась программа. Это раздел начинается словом Begin,
далее следуют операторы языка, отделенные друг от друга «;». Завершает раздел слово End
и точка. Операторы выполняются строго последовательно в том порядке, в котором они записаны, а также в соответствии с синтаксисом языка и правилами пунктуации.
Раздел операторов завершает программу.
Кроме конструкций языка программа может содержать комментарии и ключи компиляции. Комментарии
- это произвольный поясняющий текст в любом месте программы, заключенный в фигурные скобки { } или между двойными символами (* *). В ограничителях (* *) пробелы между скобкой и звездочкой запрещены. В тексте не должны находиться ограничители, с которых комментарий начинается (т.е. {пример {1}}). Однако ограничители { } можно вложить в (* *), и наоборот. Ограничений на длину комментрия нет. Комментарий игнорируется компилятором и поэтому никакого влияния на программу не оказывает. Ограничители {} ((* *)) удобно использовать при отладке программы, когда с их помощью временно исключаются части программы.
Текст программы может также содержать директивы компилятора
, которые используются программистом для управления режимами компиляции, т.е. включать или выключать контроль ошибок, использовать или эмулировать математический сопроцессор, изменять распределение памяти и т.д. Эти директивы, как и комментарии, заключаются в фигурные скобки, но они имеют отличительный признак $, после чего указывается буква-ключ с последующим знаком “+” (включить режим) или “-“ (выключить) (не допускаются пробелы между открывающей скобкой, знаком $ и ключом компилятора). Директивы могут быть переключающими, условными и параметрическими. Переключающие директивы воздействуют на те опции, которые включены в диалоговое окно OPTIONS/COMPILER
; условные директивы определяют условия, при которых компилируются те или иные франменты программы; параметрические директивы задают параметры, которые должен учитывать компилятор. Некоторые директивы могут действовать только на часть текста программы (локальные директивы)
. В отличие от этого глобальные
директивы располагаются в начале текста программы (модуля) и действуют сразу на всю программу (модуль) в целом. Например, {$R-} – отключить проверку диапазонов индексов массивов, {$N+} – использовать сопроцессор 80Х87.
7. ТИПЫ ДАННЫХ ЯЗЫКА ТР
Любые данные (константы, переменные, значения функций или выражения) в ТР характеризуются своими типами. Тип
- это множество значений, которые могут принимать объекты программы, а также совокупность операций, допустимых над этими значениями. Кроме того, тип определяет также и формат внутреннего представления данных в памяти компьютера.
Общая структура данных ТР.
ПРОСТЫЕ ТИПЫ
Эти типы определяют тип только одного отдельного значения.
Простые типы можно разделить на два класса. Порядковые типы
отличается тем, что каждый из них имеет конечное число возможных значений. Эти значения можно определенным образом упорядочить, и, поэтому с каждым из них можно сопоставить некоторое целое число - порядковый номер значения. Вещественные типы,
строго говоря, тоже имеют конечное число значений (количество этих значений определяется форматом внутреннего представления вещественного числа). Однако количество возможных значений настолько велико, что сопоставить с каждым из них целое число (его номер) не представляется возможным.
К любому из порядковых типов (целые, логический, символьный, перечисляемый и тип-диапазон) применима функция ORD(X),
которая возвращает порядковый номер значения Х. Применительно к целым эта функция возвращает само значение Х (т.е. ORD(X) = X). Применительно к логическому типу дает в результате 0 или 1, к символьному - целое число от 0 до 255 (коды), к перечисляемому - целое число от 0 до 65535. Тип-диапазон сохраняет все свойства базового порядкового типа, поэтому результат применения к нему функции ORD(X) зависит от свойств этого типа.
К порядковым типам также применимы функции:
- PRED(X)
- возвращает предыдущее значение порядкового типа (значение, которое соответствует порядковому номеру ORD(X)-1). Следовательно, ORD(PRED(X)) = ORD(X) - 1.
- SUCC(X)
- возвращает следующее значение порядкового типа (соответствует номеру ORD(X) +1). Т.е. ORD(SUCC(X)) = ORD(X) + 1;
Запрещены Pred ( первый элемент перечисления ) и Succ (последний элемент перечисления).
Пример,
Var
c,d : char;
begin
c := ‘f’;
d := PRED(c);
end.
Значение переменной d - это символ ‘e’.
Рассмотрим вначале особенности порядковых типов.
ЦЕЛЫЕ ТИПЫ
Диапазон возможных значений целых типов зависит от их внутреннего представления, которое может занимать один, два или четыре байта.
НАЗВАНИЕ |
Длина, байт |
Диапазон значений |
Byte |
1 |
0..255 |
ShortInt |
1 |
-128..+127 |
Word |
2 |
0..65535 |
Integer |
2 |
-32768..+32767 |
LongInt |
4 |
-2 147 483 648..+2 147 483 647 |
Целочисленные значения записываются привычным способом: 123 4 -5 -63333. Число должно быть записано без всяких преобразований. Будет ошибкой задать целое число следующим образом: 1е+2 (в смысле 100), 123.0.
Как говорилось ранее, при действиях с целыми числами тип результата будет соответствовать типу операндов, а если операнды относятся к разным типам, - типу того операнда, который имеет максимальную мощность (максимальный диапазон значений (иначе называется общим типом).
Стандартные функции, которые применяются к целым типам, будут рассмотрены после рассмотрения вещественных типов.
ЛОГИЧЕСКИЙ ТИП
Значениями переменных логического типа может быть либо TRUE
, либо FALSE
(это по сути логические константы).
Требуется 1 байт памяти. С логическими переменными связаны рассмотренные ранее логические операции. Широко применяются в условных операторах языка. Обозначение Var
X:Boolean; (ORD(false) = 0; ORD(true) = 1).
СИМВОЛЬНЫЙ ТИП
Значениями переменных символьного типа является множество всех символов ПК. Каждому символу приписывается целое число (код) в диапазоне 0...255 (оно возвращается функцией ORD) - всего 8 бит (1 байт).
Для кодировки используется код ASCII (American Standard Code of Information Interchange). Это 7-битный код, с его помощью кодируются буквы латинского алфавита, знаки препинания и служебные символы (можно закодировать лишь 128 символов в диапазоне 0...127). Символы с номерами 128...255 отводятся под национальный алфавит и могут меняться для разных компьютеров. Наибольшее распространение в России получила т.н. альтернативная кодировка (альтернативная ГОСТу), в которой символы псевдографики расположены на тех же места как в кодировке IBM (символы 176-223) (в кодировке IBM вместо русских букв расширение латинского алфавита для западноевропейских языков). Формат: Var
X:Char; К типу Char применимы операции отношения, а также встроенные функции:
CHR(X)
(обратная ORD) - преобразует выражение Х:Byte в символ и возвращает этот символ;
UPCASE(X)
- возвращает прописную букву, если Х - строчная латинская буква, в противном случае (например, если Х - русская буква) возвращает сам символ Х.
В программе значения переменных (как и констант) символьного типа должны быть заключены в апострофы (например, Х := ‘ф’). Кроме того, допускается использование записи символа путем указания его кода, которому предшествует символ # (операция присваивания Х := #228 аналогична предыдущей).
ПЕРЕЧИСЛЯЕМЫЙ ТИП
Перечисляемый тип задается перечислением тех значений, которые он может получать. В качестве значения д.б. использован любой идентификатор, он располагается в списке, обрамленном круглыми скобками. Например,
Type
colors = (red,white,blue);
Var
Color1,Color2 : colors;
или иначе
Var
Color3 : (red,white,blue);
Переменные Сolor1,Color2,Color3 могут принимать только одно из трех перечисленных значений. Имена внутри круглых скобок являются константами. Паскаль не поддерживает операции ввода-вывода значений перечисляемого типа. Значения перечисляемого типа должны иметь синтаксис идентификаторов, поэтому не допускаются использование цифр, строк, служебных символов. Соответствие между значениями перечисляемого типа и порядковыми номерами этих значений (которые возвращаются функцией ORD) устанавливается следующим образом: первое значение в списке получает номер 0, второе – 1 и т.д. Максимальная мощность перечисляемого типа составляет 65536 значений.
ТИП–ДИАПАЗОН
Тип-диапазон есть подмножество некоторого базового типа (рассмотренных ранее), в качестве которого может выступать любой порядковый тип, кроме самого типа-диапазона.
Тип-диапазон задается границами своих значений внутри базового типа. Например,
Type
digit1 = ‘1’..’9’; (базовый тип – Сhar)
digit2 = 1..9; (базовый тип – целый)
или
Var
LatChr : ‘A’..’Z’;
Левая граница не должна превышать правую границу. Два символа .. рассматриваются как один, поэтому между ними недопустимы пробелы.
В библиотеке ТР есть две функции, поддерживающие работу с типами-диапазонами:
HIGH(X) –
возвращает максимальное значение типа-диапазона (для digit2 – это 9);
LOW(X) –
возвращает минимальное значение (digit2 – 1).
ВЕЩЕСТВЕННЫЕ ТИПЫ
В отличие от порядковых типов, значения которых всегда сопоставляются с рядом целых чисел и представляются в компьютере абсолютно точно, значения вещественных типов определяют произвольное число лишь с некоторой точностью, зависящей от внутреннего формата вещественного числа. Существуют следующие вещественные типы.
Название |
Длина, байт |
Кол-во значащих цифр |
Диапазон десятичного порядка |
Real |
6 |
11...12 |
-39...+38 |
Single |
4 |
7...8 |
-45…+38 |
Double |
8 |
15...16 |
-324...+308 |
Extended |
10 |
19...20 |
-4932...+4932 |
Comp |
8 |
19...20 |
-9.2*10+18
(диапазон значений) |
Вещественное число в ТР может занимать от 4 до 10 смежных байт и имеет следующую структуру в памяти компьютера:
s |
е |
m |
Здесь s – знаковый разряд числа (один двоичный разряд), е – экспоненциальная часть (содержит порядок в двоичном представлении), m – мантисса числа (в двоичном представлении). Мантисса имеет длину от 23 (для Single
) до 63 (для Extended
) двоичных разрядов, что обеспечивает ту точность в десятичных цифрах, которая обозначена в таблице.
Вещественные значения могут записываться обычным способом с десятичной точкой (Х := -0.009), а также в экспоненциальном формате (Y := 123.4e-23). Если присутствует десятичная точка, за ней и перед ней д.б. хотя бы одна цифра. Если есть символ е - за ним д.б. хотя одна цифра.
Несмотря на большое разнообразие вещественных типов, все они кроме Real
вводятся в расчете на арифметический сопроцессор – устройство, которое подключается непосредственно к центральному процессору и предназначен для выполнения операций над числами в формате с плавающей точкой и длинными целыми числами. Сопроцессор значительно (в десятки раз) ускоряет вычисления, связанные с вещественными числами. В процессорах, начиная с 80486 он встроен. Сопроцессор всегда обрабатывает числа в формате Extended
(наиболее мощном). При этом остальные вещественные типы получаются простым усечением результатов до нужных размеров, что применяется в основном для экономии памяти. Т.е. если
Var
e1,e2 : Extended;
e3 : Double;
result : Single;
. . .
result := e1*e2/e3;
то значение выражения справа будет вычислено как тип Extended
. Но при присваивании его переменной result
меньшей мощности Single
будет произведено усечение и значительно уменьшится число значащих цифр после десятичной точки.
Тип Real
оптимизирован на работу без сопроцессора, поэтому его использование при наличии сопроцессора будет крайне неэффективно (время, «съедаемое» на преобразование его в сопроцессорный тип, перекрывает ускорение, а точности при этом не добавляется). Поэтому, если предполагается использовать разработанную программу на разных компьютерах (с сопроцессором и без него), целесообразно ввести свой вещественный тип (например, Float
), и с помощью него описать все переменные. Конкретный тип, которому соответствует Float
, зависит от наличия сопроцессора.
Type
F
loat = Real; {без сопроцессора}
Float = Double; {с сопроцессором}
Var
X : Float;
Y : array [1..9] of Float;
Чтобы программа могла задействовать возможности сопроцессора она должна иметь в своем начале директиву (ключ компилятора) $N+. Однако даже если в компьютере нет сопроцессора, ТР дает возможность эмулировать его работу программным путем. Т.е. можно создать программу, которая будет работать с вещественными числами большой точности, независимо от наличия сопроцессора. Это достигается существенными потерями в скорости счета. Включением эмуляции управляет ключ компилятора $E.
Особое место занимает тип Comp
. Он трактуется как вещественное число без экспоненциальной и дробной части. Фактически, Comp
– это «большое» целое число со знаком, сохраняющее 19…20 значащих цифр. В то же время тип Comp
полностью совместим с любыми другими вещественными типами, над ним определены все вещественные операции и т.д. Наиболее подходящей областью применения являются бухгалтерские операции, когда денежные суммы выражаются в целых рублях (копейках), а действия над ними могут привести к очень длинным целым числам.
В ТР имеется набор стандартных процедур и функций, которые работают с вещественными и целыми данными.
МАТЕМАТИЧЕСКИЕ ФУНКЦИИ
ABS(X)
– возвращает абсолютное значение аргумента Х (Х – целое/вещественное, результат – как у аргумента);
Pi
– значение числа «Пи» (результат – вещественный); точность зависит от наличия сопроцессора (соответственно, 10 либо 14 знаков после запятой); может использоваться в вычислениях как константа, но не может быть подставлена в вычисляемые константы раздела Const;
SQR(X) –
возвращает квадрата Х (Х – целое/вещественное, результат – как у аргумента);
SIN(X), COS(X), ArcTan(X)
– возвращают значения синуса, косинуса и арктангенса (угол в радианах) (Х – целое/вещественное, результат – вещественное); ArcTan возвращает главное значение в диапазоне от –Pi/2 до Pi/2.
SQRТ(X) –
возвращает квадратный корень из Х (Х > 0 – целое/вещественное, результат – вещественное);
EXP(X), LN(X) –
возвращает экспоненту или натуральный логарифм Х (Х – целое/вещественное, результат – вещественное);
FRAC(X) –
дробная часть числа Х (Х – целое/вещественное, результат – вещественное);
INT(X), TRUNC(X) –
целая часть числа Х (Х – целое/вещественное, результат – у int
– вещественный (т.е. после точки нули), - у trunc
– целый:LongInt) {такая двойственность бывает полезна в операторах присваивания};
ROUND(X) –
округление Х до ближайшего целого (Х – целое/вещественное, результат – целое:LongInt);
RANDOM(X) –
возвращает случайное целое число из диапазона от 0 до Х (X и результат – Word
);
RANDOM –
возвращает случайное число от 0 до 1 (результат – вещественный);
ODD(X) –
возвращает True, если Х – нечетное число, и False, если Х - нечетное (Х – целое, результат – логический)
МАТЕМАТИЧЕСКИЕ ПРОЦЕДУРЫ
RANDOMIZE –
процедура гарантирует несовпадение последовательностей случайных чисел, выдаваемых функцией Random
.
INC(var X : целое), DEC(var X : целое) –
увеличивает или уменьшает значение Х на 1;
INC(var X : целое; N : целое), DEC(var X : целое; N : целое) –
увеличивает или уменьшает значение Х на N;
Пример,
X := 2;
INC(X,4);
дает в результате Х = 6.
CТРУКТУРИРОВАННЫЕ ТИПЫ
Любой из структурированных (агрегативных) типов характеризуется множественностью образующих этот тип элементов, т.е. переменная или константа такого типа всегда имеет несколько компонентов. Каждый компонент может в свою очередь принадлежать структурированному типу, что позволяет говорить о возможной вложенности типов. В ТР допускается произвольная глубина вложенности типов, однако, суммарная глубина любого из них во внутреннем представлении не должна превышать 65520 байт.
МАССИВЫ
Особенность массивов в том, что все их компоненты являются данными одного типа (который тоже может быть структурированным). Компоненты массива легко упорядочиваются, обеспечить доступ к любому компоненту можно простым указанием его порядкового номера
. Описание массива задается следующим образом:
<имя типа> = array
[ <список инд. типов> ] of
<тип>
<список инд. типов> – список из одного или нескольких индексных типов, разделенных запятыми, весь список заключен в квадратные скобки. В качестве индесксных типов можно использовать любые порядковые типы, кроме LongInt
и типов диапазонов с базовым типом LongInt
. Например,
Type
Vector = array [1..3] of Real; {инд. тип – тип-диапазон}
Var
R,V : Vector;
Определить переменную как массив можно и без предварительного описания типа массива, т.е.
Var
R,V : array [1..3] of Real;
В большинстве случаев в качестве индексного типа используется тип-диапазон (как в примере), в котором задаются границы изменения индексов.
Т.к. тип элементов массива (за словом of
) – это любой тип ТР, то он может быть в частности, и другим массивом.
Type
Matrix = array [0..5] of array [-2..2] of array [Сhar] of Real;
Такой 3-х мерный массив может быть записан более компактно
Type
Matrix = array [0..5,-2..2,Сhar] of Real;
Доступ к элементу массива осуществляется через запись индекса в квадратных скобках после записи имени массива. Т.е.
Var
m: Matrix;
Begin
…
m[1,0,’d’] := 5; {обе записи - допустимы}
m[1][0][‘n’] := 6;
End.
В ТР можно одним оператором присваивания передать все элементы одного массива другому массиву того же типа.
Var
a,b : array [1..5] of Real;
Begin
…
a := b;
End.
После этого присваивания все элементы массива а
получат значения соответствующих элементов массива b
.
Операции отношения к массивам не применимы. Т.е. оператор
if a = b then …
неверен. Следовательно, сравнивать массивы можно лишь поэлементно.
ТР имеет специальный режим компиляции, относящийся к массивам и задаваемый ключом {$R}. Если вся программа или ее фрагмент компилировался в режиме {$R+}, то при обращении к элементам массива будет проверяться принадлежность значения индекса объявленному диапазону, и в случае нарушения границ будет выдано сообщение об ошибке (201 – Range check Error). В режиме {$R-} никаких проверок не производится и некорректное значение индекса извлечет из памяти некое значение, не относящееся к данному массиву. Обычно отлаживают программу в режиме $R+, а эксплуатируют – в режиме $R- для уменьшения исполняемого exe-файла и времени его выполнения.
ЗАПИСИ
Запись – это структура данных, состоящая из фиксированного числа компонентов
, которые называются полями записи. В отличие от массива, компонетны (поля) записи могут быть различного типа
. Поля имеют свои имена. Тип «запись» задается следующим образом:
<имя типа> = record
<список полей> end
<список полей> - последовательность разделов записи, между которым ставится точка с запятой. Каждый раздел состоит из одного или нескольких имен полей, отделяемых друг от друга запятыми. Следом на именами ставится двоеточие и описание типа поля (полей). Например,
Type
Data = record
X,Y : integer;
Z : char
end;
Var
D1,D2 : Data;
Begin
…
D1.X := 10;
D2.Z := ‘n’;
…
End.
Обратиться к полю записи можно указав сначала идентификатор переменной-записи, а затем через точку – имя поля (на примере). Независимо от количества объявленных переменных данного типа, поля каждой из них будут называться одинаково (D1.X, D2.X). Кроме того, поскольку имена полей «скрыты» внутри переменной, они могут дублировать обычные перменные (X и D1.X).
Альтернатива введению специального типа состоит в описании записи непосредственно в разделе Var.
Переменная типа запись может участвовать в операциях присваивания, если они относятся к одному и тому же типу: D1 := D2;
Поле записи может иметь практически любой тип, в том числе и другую запись (вложенные структуры).
Var
D : record
X : integer;
R : record
RX : integer;
RZ : char
end
end;
Begin
…
D.R.RX := 2;
…
End.
Чтобы упростить доступ к полям записи, используется оператор присоединения WITH
со следующим форматом:
with <имя переменной-записи> do <оператор>
Внутри оператора (стоит после do, может быть составным) обращение к полям записи уже производится без указания идентификатора самой переменной. Например,
with D do
begin
R.RX := 2; R.RZ := ‘f’;
end;
ЗАПИСИ С ВАРИАНТАМИ
В ТР можно использовать запись с т.н. вариантными полями. Например,
Var
D : record
X : integer;
case V: boolean of
True : (Y_one : integer);
False : (Y_two : array [1..4] of real)
end;
Здесь поле Х – фиксированно. Второе поле V – это поле признака, которое может иметь два значения True и False. Третье поле будет иметь разные варианты в зависимости от значения поля V (либо D.Y_one либо D.Y_two). Вариантное поле м.б. только одно, записывается в конце. В любое время доступны поля только одного из вариантов.
Записи можно считать некоторой ступенью к структурам типа объектов.
МНОЖЕСТВА
Множество – это структурированный тип данных, представляющий собой набор взаимосвязанных по какому-либо признаку, или группе признаков элементов, которые можно рассматривать как единое целое. Количество элементов, входящих в множество, может меняться в пределах от 0 до 256 (множество, не содержащее элементов называется пустым
). Два множества считаются эквивалентными, если все их элементы одинаковы, причем порядок следования элементов в множестве безразличен.
Описание типа множества имеет вид:
<имя типа> = set of
<базовый тип>
<базовый тип> - любой порядковый тип с числом элементов <= 256 (кроме Word, Integer, Longint
). Например,
Type
TypeSet1 = set of Char; {множество из символов}
TypeSet2 = set of 0..9; {множество чисел от 0 до 9, базовый тип – тип-диапазон}
VideoType = (Hercules,CGA,EGA,VGA,SVGA); {заранее описывается перечисляемый тип}
TypeSet3 = set of VideoType; {множество из названий видеоадаптеров}
Если переменная описана как множество, она подчиняется специальному синтаксису. Значение такой переменной задается перечислением элементов базового типа (т.е. того типа, который фигурирует при задании типа переменной) через запятую в квадратных скобках:
Var
Set1 : set of Byte;
Set2 : set of ‘a’..’f’;
Begin
…
Set1 := [1,2,6];
Set1 := [3..10,12];
Set2 := [‘a’,’d’];
…
End.
Пустое множество записывается как []. Порядок следования элементов внутри скобок не имеет значение, также как не имеет значение число повторений (множества [1,2] [2,2,1] эквивалентны). Над множествами определены следующие операции:
* пересечение множеств, результат содержит элементы общие для обоих множеств (Set1 := [0..3,6]; Set2 := [3..9]; Set1*Set2 = [3]);
+ объединение множеств, результат содержит элементы первого множества, дополненные недостающими элементами второго (Set1+Set2 = [0..9]);
- разность множеств, результат содержит элементы первого множества, которые не принадлежат второму (Set1-Set2 = [0,1,2]);
= проверка эквивалентности (логическая операция, как и последующие), возвращает True
, если оба множества эквивалентны;
<> проверка неэквивалентности, возвращает True
, если множества неэквивалентны;
<= проверка вхождения, возвращает True
, если первое множество включено во второе (т.е. все элементы первого множества содержаться во втором);
>= проверка вхождения, возвращает True
, если второе множество включено в первое;
in проверка принадлежности (E in S), возвращает True
, если значение Е
входит в множество S
и принадлежит базовому типу этого множества, Е
может быть выражением (3 in Set1 дает True
, 2*2 in Set1 дает False
).
Естественно, что в рассмотренных операциях могут участвовать только те множества, которые построены на одном базовом типе.
Операции над множествами дополняют две процедуры:
INCLUDE (S,I) -
включает новый элемент I
в множество S
, включаемый элемент должен принадлежать базовому типу множества S
.
EXCLUDE (S,I) -
исключает элемент I
из множества S
.
В отличие от операций + и -, реализующие аналогичные действия над двумя множествами, процедуры INCLUDE
и EXCLUDE
оптимизированы для работы с одиночными элементами множеств и поэтому отличаются большей скоростью выполнения.
Достоинства множеств - гибкость представления наборов значений и удобство анализа. Механизм работы с множествами в ТР соответствует базовым математическим действиям с конечными множествами. Значение типа "множество" очень компактно кодируется (для множества из 256 элементов требуется всего 32 байта). Множества хорошо работают, где нужно проводить анализ однотипных выборок значений или накапливать произвольно поступающие значения. Недостатки - невозможно выводить на экран (отладчик это делает). Внутреннее устройство множества таково, что каждому его элементу ставится в соответствие один двоичный разряд (один бит); если элемент включен в множество соответствующий разряд равен 1, если нет - 0. Однако минимальной единицей памяти является байт (8 бит). Поэтому мощность множества кратна 8 элементам (т.е. описав Set1 : set of 0..4, мы можем добавлять в него числа 5,6,7).
СТРОКИ
Тип String
в ТР широко используются для обработки текстов и трактуется как цепочка символов. Максимально возможная длина строки 255 символов. Тип объявляется как String[N]
, где N -
максимальное число символов в строке. Строки являются динамическими, поскольку могут иметь различные длины в пределах объявленных границ. Например,
Var
S32 : String[32];
S255 : String[255];
Begin
…
S32 := 'Это строка';
…
End.
позволяет хранить в S32 строчные значения, длиной не более 32 символов (в S255 не более 255). Допустимо описание Var S : String;
что равнозначно String[255]
.
К любому символу в строке можно обратиться точно также, как к элементу одномерного массива array [0..N] of Char.
Например, S32[3] := 'а'; Первый значащий символ в строке имеет индекс 1. Индекс 0 имеет символ, код которого равен длине строки (ORD(S32[0]) = 10, пробел является обычным символом, его код = 32).
К строкам можно применить операцию сцепления "+" (S32 := S32 + '!!!').
Существуют стандартные функции и процедуры, реализующие необходимые действия над строками.
LENGTH (S : String) : Byte
– выдает длину строки (функция);
CONCAT (S1, S2,…,Sn) : String
– возвращает конкатенацию (слияние) строк S1…Sn (функция);
COPY (S : String; Start, Len : Integer) : String – возвращает подстроку длиной Len, начинающуюся с позиции Start строки S (функция);
DELETE (Var S : String; Start, Len : Integer)
– удаляет из S подстроку длиной Len, начинающуюся с позиции Start строки S (процедура);
INSERT (Var S, SubS : String; Start : Integer)
– вставляет в S подстроку SubS, начиная с позиции Start (процедура);
POS (SubS, S : String) : Byte
– ищет вхождение подстроки SubS в строке S и возвращает номер первого символа SubS в S или 0, если SubS нет в S (функция);
Процедуры преобразования
STR (X; Var S : String) –
преобразует числовое значение Х в строковое S, возможно задание формата для Х (Str(X:F:n,S) – F – общая ширина поля, n – количество символов в дробной части для вещественных чисел) (процедура);
VAL (S : String; Var X; Var Code : Integer) –
преобразует строковое значение S (строку цифр) в значение числовой переменной (Х – целое или вещественное, параметр Code содержит ноль, если преобразование прошло успешно, в противном случае он содержит номер позиции в строке, где обнаружен ошибочный символ, при этом Х не меняется) (процедура).
Имеется возможность применить операции отношения (= <> > < >= <=) к двум строкам. При этом сравнение строк производится посимвольно, начиная с первого символа в строке. Строки равны, если имеют одинаковую длину и посимвольно эквивалентны. Если при посимвольном сравнении окажется, что символ первой строки больше символа второй (имеется в виду их коды), то первая строка считается большей. Остатки строк и их длины не играют роли. Любой символ больше «пустого». Например, 'aBcd’ < ‘ab’, т.к. ‘B’ (код 66) < ‘b’ (код 98).
Особенности оставшихся типов (файлы, указатели, процедурные и объекты) будут рассмотрены в соответствующих разделах позднее.
СОВМЕСТИМОСТЬ И ПРЕОБРАЗОВАНИЕ ТИПОВ
Если в программе объявлены переменные, то подразумевается, что они будут получать свои значения по ходу ее выполнения. Способ поместить значение в переменную – это использовать операцию присваивания
:
Переменная := Значение;
Оператор присваивания – это составной символ «:=» (становится равным). В операции присваивания слева стоит имя переменной, а справа – то, что представляет собой ее значение (значение как таковое (константа), выражение, вызов функции, другая переменная). После выполнения операции присваивания переменная получает новое значение.
ТР, являясь языком с сильной системой типов, требует соблюдения определенных правил совместимости типов
переменных и значений слева и справа от оператора присваивания.
Очевидно, что не может быть проблем с присваиванием, если типы переменной и значения идентичны (тождественны). Два типа считаются тождественными, если
1. Они описаны вместе, либо одним и тем же идентификатором типа, например
Type
T1 = Boolean;
T2 = Boolean;
T3, T4 = array[1..2] of Real;
Если T1 = array [1..2] of Real и T2 = array [1..2] of Real, то Т1 и Т2 не будут идентичными поскольку конструкции array .. of .. хоть и одинаковые, но не являются идентификаторами, т.е. именами.
2. Типы описаны как эквивалентные. Т.е. при описании
Type
T1 = array [1..2] of Real;
T2 = T1;
T3 = T2;
все типы Т1,Т2,Т3 будут тождественны.
Однако не только тождественные типы являются совместимыми. Прежде, чем рассматривать совместимость по присваиванию, рассмотрим более общее понятие. Совместимость в ТР трактуется несколько шире. Типы считаются совместимыми, если
- оба типа являются тождественны;
- оба типа являются вещественными;
- оба типа являются целыми;
- один тип является поддиапзоном другого;
- оба типа являются поддиапазонами одного и того же базового типа;
- оба типа являются множествами, составленными из одного и того же базового типа;
- один тип является строковым, а другой символьным или строковым;
- один тип является указателем, а другой указателем или ссылкой.
Совместимость гарантирует работу операций отношения
(но не оператора присваивания). Кроме того, что важно, совместимость определяет правила подстановки значений или переменных в вызовы функций и процедур.
Если говорить о операции присваивания, то в ТР существует еще один вид совместимости – совместимость по присваиванию
. Этот вид описывает правила присваивания переменной какого-то значения. Имеется переменная V1 : Type1 и значение V2, которое относится к типу Type2. Переменной может быть присвоено значение (V1 := V2), если выполняется одно из следующих условий:
- Type1 и Type2 тождественные типы, и не один из них не является файловым типом (или структурным типом, содержащим компонент с файловым типом);
- Type1 и Type2 совместимые типы (в смысле, рассмотренном ранее), относящиеся к порядковым, и значения типа Type2 попадают в диапазон возможных значений Type1;
- Type1 и Type2 – вещественные типы и значения типа Type2 попадают в диапазон возможных значений Type1;
- Type1 – вещественный тип, Type2 – целочисленный тип;
- Type1 и Type2 – строковые типы;
- Type1 – строковый тип, Type2 – символьный тип;
Есть еще 4 правила, относящихся к множествам, указателям и объектам.
- Type1 и Type2 совместимые множества и все члены значения множества типа Type2 попадают в диапазон возможных значений Type1;
- Type1 и Type2 совместимые адресные типы;
- Тип объекта Type2 совместим по присваиванию с типом объекта Type1, если Type2 находится в области типа объекта Type1.
- Тип ссылки Ptr2, указывающий на тип объекта Type2, совместим по присваиванию с типом ссылки Ptr2, указывающим на тип объекта Type1, если Type2 находится в области типа объекта Type1.
Нарушение правил совместимости типов переменных и значений обнаруживается, как правило, на этапе компиляции.
В программе данные одного типа могут преобразовываться в данные другого типа. Такое преобразование может быть явным или неявным.
При явном преобразовании
используются вызовы специальных функций, аргументы которых принадлежат одному типу, а значения (результат) другому. Таковыми являются, например, уже рассмотренные функции ORD, TRUNC, CHR…
В ТР, кроме того, имеется специальное мощное средство, позволяющее обойти возможные ограничения на совместимость типов или значений. Механизм заключается в том, что в ту область памяти, которую занимает переменная некоторого типа, можно поместить некоторое значение другого типа, если только длина внутреннего представления вновь размещаемого значения в точности равна длине внутреннего представления переменной. Для этого используется функция преобразования, которая совпадает с именем типа, к которому должна быть приведена переменная. Например,
Type
M2Word = array[1..2] of Word;
M4Byte = array[1..4] of Byte;
Var
V1 : M2Word;
V2 : M4Byte;
V3 : LongInt;
V4 : Word;
Begin
V3 := 100;
V1 := M2Word(V3);
V2 := M4Byte(V3);
V4 := Word(V1[1]);
End.
Переменные V1,V2,V3 в памяти занимают 4 байта, переменная V4 - 2 байта. Сначала задаем значение переменной V3. Две следующие строки помещают значение LongInt
либо в два слова, либо в четыре байта. В данном примере V1[1]=100, V1[2]=0, т.к. длина значащих двоичных разрядов в V3 меньше, чем длина Word. Если будет больше, то такого соответствия не будет, т.к. число будет разбито на два в двоичном представлении. Затем первый элемент массива V1 помещается в переменную V4. Т.о. данный метод позволяет нарушить правила совмещения типов при условии, что соответствующие значения совместимы в машинном представлении. Приведение типа переменной может стоять как слева, так и справа M2Word(V3)[2] := 3. При этом V3 состоит из двух байтов: младший 0..01100100 = (100)10
, старший 0..00000011 = (3)10
, в сумме 0..00000011 0..01100100 = (196708)10
;
Может использоваться и другой механизм, при котором изменяется тип значения как такового. Преобразование достигается применением имени стандартного типа (или пользовательского), как имени функции преобразования к выражению требуемого типа (т.н. автоопределенное преобразование).
Например, Integer('Y') - код символа 'Y' в формате Integer
. Этот символ хранится как код 89 и занимает один байт. Конструкция Integer('Y') представляет собой значение 89 в формате целого числа со знаком (в двух байтах). При преобразовании типа значения соблюдение размеру уже не нужно. Новый тип может быть шире, а может быть короче, чем естественный тип значения. При приведении значения в более широкий тип значения будут целиком записаны в младшие байты, что естественно сохранит само значение. Когда значение приводится к более короткому типу, от него берутся только самые младшие байты, старшие игнорируются. Преобразование типа значения внешне похоже на преобразование типа переменной. Но эффект от него несколько иной (за счет возможности изменения размера), и иные ограничения. В частности тип значения и задаваемый тип должны быть порядковыми. Кроме того, преобразование значения не может появиться в левой части.
Неявное преобразование
возможно в двух случаях:
1. В выражениях, составленных из вещественных и целочисленных переменных, последние автоматически преобразуются к вещественному типу, и все выражение в целом приобретает вещественный тип;
2. Одна и та же область памяти попеременно трактуется как содержащая данные то одного, то другого типа (совмещение в памяти данных разного типа).
Совмещение данных
в памяти, в частности, возможно при явном размещении данных разного типа по одному и тому же абсолютному адресу (а также при использовании записей с вариантными полями, типизированных указателей, содержащих одинаковый адрес). Для размещения переменной по нужному абсолютному адресу она описывается с последующей стандартной директивой ABSOLUTE
, за которой помещается либо абсолютный адрес, либо имя ранее определенной переменной (Адрес указывается парой чисел типа Word
, разделенных двоеточием; первое трактуется как сегмент, второе как смещение (вся память 1 Мбайт для ТР 7.0 разбивает на сегменты длиной 65536 байт (64К), каждому сегменту соответствует непрерывная и отдельно адресуемая область памяти. Сегменты могут следовать один за другим без промежутков или с некоторым интервалом, или перекрывая друг друга. Сегменты адресуют память с точностью до 16 байт, а смещение с точностью до байта): w : LongInt absolute
128:0. При таком описании переменной ее значение помещается в определенную область памяти. Затем эту область памяти уже можно рассматривать как переменную другого типа. Это бывает удобно, когда есть желание использовать некоторую функцию (или процедуру) применительно к переменным различного типа.
8. УПРАВЛЯЮЩИЕ СТРУКТУРЫ ЯЗЫКА TURBO PASCAL
В этом разделе будут рассмотрены управляющие операторы ТР (условный оператор, операторы циклов, выбора и др.), а также вопросы построения процедур и функций, и принципы построения модулей.
ПРОСТОЙ И СОСТАВНОЙ ОПЕРАТОР
Оператор в программе - это единое неделимое предложение, выполняющее какое-либо действие. Простой оператор
не содержит в себе других операторов. Типичный простой оператор - это оператор присваивания. Другим примером может являться вызов процедуры. Важно, что под любым оператором подразумевается какое-либо действие
. В этом смысле составляющие блока описания типов, перменных, констант и т.д. операторами не являются.
Два последовательных оператора должны разделяться точкой с запятой
. Этот символ имеет смысл конца оператора, в частности, он разделяет операторы при записи в строку:
a := 11; b:=a*a; Write(a,b);
Если какое-то действие мыслится как единое (например, присваивание значений элементам массива), но реализуется несколькими различными операторами, то эти операторы могут быть представлены как составной оператор
. Составной оператор - это последовательность операторов, перед которой стоит слово begin
, а после слово - end
. Слова begin
и end
сами не являются операторами
, поэтому после begin
и перед end
точка с запятой не ставится. Чтобы оформить предыдущий пример в один, но составной оператор, необходимо как бы заключить их в операторные скобки begin...end
:
begin
a := 11;
b := a*a;
Write(a,b)
end;
Составной оператор может содержать любое допустимое число простых операторов. Кроме того, он допускает вложенность, т.е. может содержать внутри себя другие составные операторы (в этом случае нужно лишь, чтобы внутренний составной оператор открывался позже, чем внешний, а закрывался - раньше).
Пустой оператор не содержит никаких действий, просто в программу добавляется дополнительная точка с запятой (например, в последнем примере перед end
). В основном пустой оператор используется для передачи управления в конец составного оператора.
УСЛОВНЫЙ ОПЕРАТОР
Условный оператор имеет структуру
if
<условие> then
<оператор1> else
<оператор2>
где if, then, else
- зарезервированные слова (если...то...иначе);
<условие> - логическое значение True
или False
, представленное константой, переменной или логическим выражением.
Если условие представлено значением True
, то выполняется <оператор1>, следующий за словом then
. Но если условие не выполняется, т.е. представлено значением False
, то будет выполняться <оператор2> за словом else
. Например,
if K > 5 then
begin
X := X + 5; Y := 1
end
else
Y := -1;
Здесь после then
следует составной оператор, перед словом else
точки с запятой нет, а затем простой оператор (между ключевыми словами точки с запятой не требуется). Точка с запятой после else
обязательна, т.к. она отделяет весь условный оператор от последующего.
Часть else <оператор 2>
может быть опущена. При этом если условие дает False
оператор пропускается и программа переходит к следующему.
Условные операторы могут быть вложенными друг в друга. При создании такой сложной конструкции самое главное - не запутаться в вариантах сочетаний условий (чему может помочь ступенчатая форма записи). При этом всегда действует правило: альтернатива else
считается принадлежащей ближайшему условному оператору if
, не имеющему своей ветви else
.
Пример:
if Условие1 then
if Условие2 then
ОператорA
else
ОператорБ;
Можно предположить, что ОператорБ
будет выполнен при невыполнении Условия1
. На самом деле, имея в виду введенное правило, ОператорБ
будет выполнен при выполнении Условия1
и невыполнения Условия2
. Точка с запятой после ОператорА
не помогает, т.к. закрывает весь оператор, а else…
становится как бы отдельным оператором. Выход - использование составного оператора:
if Условие1 then
begin
if Условие2 then
ОператорA
end
else
ОператорБ;
В условии оператора if
может стоять достаточно сложное логическое выражение, в котором может потребоваться учитывать приоритет различных операций.
ОПЕРАТОР МНОЖЕСТВЕННОГО ВЫБОРА
Оператор выбора позволяет выбрать одно из нескольких возможных продолжений программы. Если варианта всего два, то можно использовать оператор if
, если больше, то оптимален оператор case
, структура которого имеет вид:
case <
ключ выбора> of
<список выбора> else <
операторы> end
где <ключ выбора> - переменная или выражение любого порядкового типа кроме LongInt
,
<список выбора> - одна или несколько констукций вида:
<константа выбора> : <оператор>; где константа выбора -
константа того же типа, что и ключ выбора.
Если ключ выбора
- выражение, то оно вычисляется при исполнении оператора case.
В зависимсти от значения ключа выбора
выбирается тот оператор, которому предшествует константа выбора,
равная вычисленному значению. Оператор может быть простым или составным. Найденный оператор вычисляется, после чего оператор выбора завершает свою работу. Если в списке выбора не будет найдена константа, соответствующая вычисленному значению ключа выбора
управление передается операторам (их может быть несколько), стоящим за словом else
. Имеется принциальная возможность опускать часть else <операторы>
. В этом случае при отсутствии в списке выбора нужной константы ничего не произойдет, и оператор выбора просто завершит свою работу. По-прежнему, перед end
и else
точка с запятой не обязательна (если поставить - работать будет). Константы выбора могут перечисляться через запятую, а также записываться в виде диапазона. Значения констант должны быть уникальными в каждом наборе, т.е. они могут появиться только в одном варианте. Пересечение набора значений в разных вариантах является ошибкой. Пример:
case I of {I : Word}
1 : X := X +1;
2,3 : X := X +2;
4..9 : begin
Write(X);
X := X + 3 {м.б. ";"}
end {м.б. «;»}
else
X := X*X;
Writeln(X) {м.б. «;»}
end;
Эффективность оператора case
будет максимальным, если размещать наиболее вероятные значений первыми в порядке следования.
ОПЕРАТОР ЦИКЛА "ПОКА" (С ПРЕДУСЛОВИЕМ)
В практике программирования циклы - повторяющиеся выполнения одних и тех же операторов - играют очень важную роль. Один из трех стандартных способов организации циклических вычислений связан с использованием оператора цикла с предусловием. Формат:
while
<условие> do
<оператор>;
Оператор (простой или составной), стоящий после служебного слова do
и называемый телом цикла
будет выполняться циклически до тех пор, пока выполняется логическое условие, т.е. пока значение условия
равно True
. Само условие
цикла может быть логической константой, переменной или выражением с логическим результатом.
Условие проверяется до начала выполнения каждой итерации. Поэтому, если условие не выполняется сразу на первом же шаге, то тело цикла игнорируется и будет выполняться оператор, стоящий сразу за телом цикла.
Следует помнить о двух моментах:
- чтобы цикл имел шанс когда-либо завершиться, содержимое его тела должно влиять на условие цикла;
- условие должно состоять из корректных выражений и значений, определенных
еще до первого выполнения тела цикла.
Пример вычисления 10!
Var
F,N : Integer;
Begin
F := 1; N := 1;
while N <= 10 do
begin
F := F*N;
Inc(N) {N := N + 1}
end;
Writeln(F)
End.
Здесь важно присваивание N := 1 перед началом цикла, без него значение N может быть любым, и условие может быть некорректным.
ОПЕРАТОР ЦИКЛА "ДО" (С ПОСТУСЛОВИЕМ)
Рассмотренный ранее оператор "Пока" решает выполнять свое тело или нет, еще до первой иттерации. Если это не соответствует логике алгоритма, то можно использовать оператор цикла "До" с постусловием, который решает делать или нет следующую иттерацию лишь после завершения предыдущей. В этом операторе тело цикла будет выполнено по крайней мере один раз. Т.о. эти операторы цикла различаются только первым шагом, далее они веду себя идентично
. Формат:
repeat
<операторы> until
<условие>
Операторы между словами repeat
и until
образуют тело цикла. В теле можт быть несколько операторов, при этом слова begin
и end
не требуются, их функции выполнют слова repeat
и until
(перед until
можно не ставить ";"). Cравнение с циклом "Пока", где один оператор в теле.
Условие
- логическая переменная, константа или выражение с логическим результатом. В отличии от цикла "Пока", если условие
имеет значение True
, то цикл завершается. Т.е.
Цикл "Пока" - "пока условие истинно, выполнять операторы тела";
Цикл "До" - "выполнять тело цикла, пока не станет истинным условие, т.е. пока оно ложно";
Известна простая конструкция "вечного цикла", в котором тело - пустой оператор
repeat until False;
ОПЕРАТОР ЦИКЛА С ПАРАМЕТРОМ (ЦИКЛ ПО СЧЕТЧИКУ)
Рассмотренные операторы цикла "Пока" и "До" (они классические) не слишком удобны для организации "строгих" циклов, которые должны быть проделаны данное число раз. Цикл с параметром вводится именно для таких случаев. Синтаксис следующий
for
<параметр цикла> :=
<мл.зн.> to
<ст.зн.> do
<оператор>
Параметр цикла
- переменная порядкового типа, к этому же типу должны относиться младшее
и старшее значении
. На первом шаге параметру цикла
присваивается младшее значение
, после чего идет его наращивание с постоянным шагом, равным интервалу между двумя ближайшими значениями в типе, к которому относится параметр цикла (для целочисленных типов - это 1, для символьного - от одного символа к другому при увеличении кода на 1, и т.д.). Например,
Var
I : Integer; C : Char ; B : Boolean;
E : (E1,E2,E3,E4);
Begin
for I := -10 to 10 do Write(I);
for C := 'a' to 'z' do Write(C);
for B := False to True do Write(B);
for E := E1 to E4 do {здесь оператор - составной}
begin
I := ORD(E);
Write(I)
end
End.
Изменение параметра может быть не только возрастающим, как в рассмотренных примерах, но и убывающим. В этом случае несколько изменяется синтаксис:
for
<параметр цикла> :=
<ст.зн.> downto
<мл.зн.> do
<оператор>
Условие, управляющее работой оператора for
проверяется перед выполнением тела цикла
. Поэтому, если условие не выполняется в самом начале работы, то тело цикла не будет выполнено ни разу. Например, в таком цикле:
for I := 4 downto 5 do …
Запрещается изменять параметр цикла и его младшее и старшее значения (в том случае, если они заданы переменными или выражениями с ними) изнутри тела цикла. Параметр цикла не может участвовать в построении диапазонов этого цикла (for I := I-5 to I+5 do …).
В тех. описании к ТР отмечается, что после выполнения цикла for
значение его параметра не определено. На самом деле скорее всего оно будет иметь конечное значение. Тем не менее корректнее его будет переприсвоить. Исключение выход из цикла с помощью goto
. В этом случае значение параметра будет таким, каким оно было к моменту исполнения goto
.
Циклы с параметром - быстрые и генерируют компактный код. Но они обладают существенным недостатком - параметр должен принадлежать порядковому типу, а шаг не может быть изменен. Для построения циклов с вещественным параметром и меняющимся шагом приходится обращаться к циклам с условием (цикл "До" и цикл "Пока").
Циклы for
допускают вложенность при условии, что никакой из вложенный циклов не использует и не модифицирует переменные - параметры внешних циклов.
ОПЕРАТОР БЕЗУСЛОВНОГО ПЕРЕХОДА
Оператор goto
, который имеется во многих алгоритмических языках, в свое время был подвергнут гонениям, как противоречащий принципам структурного программирования. Ибо было доказано, что можно вообще программировать без него. Однако хотя язык Pascal является классическим языком структурного программирования, в нем тем не менее существует этот оператор. Т.е. выбор между чистотой идеи (структурное программирование) и элементарным удобством (использование goto
в отдельных случаях) был сделан в пользу последнего. Полный синтаксис оператора безусловного перехода:
goto
<метка>;
где метка
- описанный в блоке Label идентификатор (может начинаться с цифры). Метка
может стоять в программе в любом месте между операторами и отделяется от второго оператора двоеточием ":". Каждая метка может появляться только один раз.
Label
L1, L2;
Begin
…
goto L1;
…
L1 : goto L2;
…
L2 :
End.
В примере программа, дойдя до goto L1
, сразу заканчивается. Можно ставить метки перед словом end
, хотя это не оператор (перед begin
нельзя). Следует избегать переходов внутрь составных операторов, вложенных в тот, где выполняется оператор goto
(особенно если это составные операторы цикла). Наиболее распространены случаи использования goto
при выходе из вложенных операторов во внешние, в частности для преждевременного выхода из операторов цикла.
Область действия операторов безусловного перехода строго локализована
. Метки, описанные в основном блоке, действуют только в нем, и не распространяются на процедуры и функции. Запрещены переходы по оператору goto
между процедурами, а также между процедурами и основным блоком.
Есть два оператора, похожих на goto
, предназначенные для выхода из программных блоков (процедур, функций, основного программного блока). Это бывает удобно, т.к. позволяет завершить программу или процедуру без предварительного перехода к меткам.
К таким операторам завершения относятся вызовы системных процедур Exit и Halt
. Вызов Exit
завершает работу своего программного блока. Т.е. если Exit
выполняется в процедуре, то ее выполнение завершится и ход программы вернется к следующему за вызовом этой процедуры оператору. При этом процедура вернет значения, которые успели вычислиться к моменту выполнения Exit
(если она должна их возвратить). Сама программа не прервется. Но если Exit
выполняется в основном блоке, выход из нее будет эквивалентен нормальному ее завершению. В принципе, процедура Exit
избыточна, т.к. эквивалентна оператору goto
на метку, стоящую перед последним словом end
. Но ее использование позволяет избежать лишней возни с метками и улучшает читаемость программы. Процедура Halt
(более полно Halt(n))
действует менее разборчиво. Независимо от того, где она находится, ее выполнение завершает работу программы с кодом завершения n
. Этот код впоследствии может быть проанализирован в среде MS-DOS. Имеет смысл при нескольких вызовах Halt
в тексте программы назначить им разные коды завершения. Тогда можно будет при отладке или работе определить, чем вызвано прерывание программы.
ПРОЦЕДУРЫ И ФУНКЦИИ
В разделе будут рассмотрены вопросы, связанные с написанием и употреблением подпрограмм, представленных в виде процедур и ли функций.
Процедуры и функции представляют собой относительно самостоятельные фрагменты программы
, оформленные особым способом и снабженные именем. Упоминание этого имени в тексте программы называется вызовом процедуры (функции)
.
Подпрограммы представляют собой инструмент, с помощью которого любая программа может быть разбита на ряд в определенной степени независимых друг от друга частей. Такое разбиение имеет смысл по двум причинам
.
Во-первых, это средство экономии памяти и других ресурсов: каждая подпрограмма существует в программе в единственном экземпляре, в то время как обращаться к ней можно многократно из разных точек программы. При вызове подпрограммы активизируется последовательность образующих ее операторов, а с помощью передаваемых подпрограмме параметров нужным образом модифицируется реализуемый в ней алгоритм.
Во-вторых, при таком разбиении максимально реализуется принцип нисходящего проектирования. В этом случае алгоритм представляется в виде последовательности относительно крупных подпрограмм, реализующих более или менее самостоятельные смысловые части алгоритма. Подпрограммы в свою очередь разбиваются на менее крупные подпрограммы нижнего уровня и т.д. Все заканчивается, когда реализуемые подпрограммами алгоритмы не станут настолько простыми, чтобы их можно было запрограммировать.
Структура процедуры во многом повторяет структуру программы в целом:
Procedure <имя> (<список формальных параметров>);
Label <метки внутри тела процедуры>;
Const <локальные константы процедуры>;
Type <локальные типы>;
Var <локальные переменные>;
Procedure <вложенные процедуры и функции>;
…
Function
…
Begin
<операторы – тело процедуры>
End;
Отличие от структуры программы состоит в заголовке и завершающей «;» вместо «.» после оканчивающего End
. Порядок следования разделов описаний подчиняется тем же правилам, по которым строится вся программа.
В отличие от стандартного языка Pascal в ТР разделы Label, Type, Const, Var могут следовать друг за другом в любом порядке и встречаться в разделе описаний сколько угодно раз. Однако поскольку программа компилируется последовательно, начиная с начала, важно соблюдать правило: все, что в программе используется должно быть перед этим описано.
Отличие структуры функции от структуры процедуры состоит в указании типа возвращаемого значения функции:
Function <имя> (<список формальных параметров>) : <тип>;
Label
…
End;
Поскольку процедуры и функции должны обладать определенной независимостью
в смысле использования переменных и т.д., при их введении в программу возникает разделение данных и их типов на глобальные и локальные. Глобальные
константы, типы, переменные – это те, которые объявлены в программе вне процедур или функций. Локальные
– это константы, типы, переменные, существующие только внутри процедур или функций, и объявленные либо в списке формальных параметров (только параметры-значения), либо в соответствующих разделах Const, Type, Var
внутри данной процедуры или функции. Считается, что все имена, описанные внутри подпрограммы (локальные данные
), локализуются в ней, т.е. они как бы "невидимы" снаружи подпрограммы. Подпрограммы, наряду со своими локальными данными, могут использовать и модифицировать и глобальные. Для этого лишь нужно, чтобы описание процедуры (функции) стояло в тексте программы ниже, чем описание соответствующих глобальных типов, констант и переменных.
Program Main;
Var
Xmain, Ymain : Integer; {глобальные переменные}
Procedure P1; {описание процедуры, у которой нет параметров}
Var
Res : Real; {локальные переменные}
begin
Res := 0.5;
Res := Res + Xmain*Ymain;
Xmain := Xmain + 1
end;
…
Var {описание других переменных, недоступных из Р1}
…
Begin
…
P1; {вызов процедуры}
…
End.
При совпадении имен локальной и глобальной переменных (типов, констант) сильнее оказывается локальное имя, и именно оно используется внутри подпрограммы.
Параметры
При описании подпрограммы после ее заголовка в скобках могут быть указаны параметры, которые, по сути, являются ее локальными данными. Параметры могут быть параметрами-значениями, параметрами-переменными или параметрами-константами. При описании необходимо указывать их тип:
Procedure <имя> ( <имя п.-з.> : <тип>;
Var <имя п.-п.> : <тип>;
Const <имя п.-к.> : <тип>);
Общее назначение - параметры обеспечивают обмен значениями между вызывающей частью программы и вызываемой подпрограммой. Описываемые в заголовке объявления подпрограммы параметры называются формальными:
Procedure P1 (A : Integer; Var B : Real)
…
а те, которые подставляются на их место при вызове - фактическими
, ибо они при выполнении как бы замещают все вхождения в подпрограмму своих формальных
"двойников". Вызов
Р1 (Afact,Bfact);
Переменная Аfact (типа Integer) действует в основной программе. Ее значение подставляется в подпрограмму при ее вызове на место формального параметра А.
Количество и тип формальных параметров должно строго соответствовать количеству и типам фактических параметров.
Параметры-значения
- это локальные переменные подпрограммы, стартовые значения
которых задаются при вызове подпрограммы из внешних блоков (в отличие от локальных переменных, описанных в разделе описаний подпрограммы, которые должны получать свои начальные значения в теле подпрограммы). Описанные в заголовке параметры-значения
могут изменять свои значения наряду с прочими переменными, но эти изменения будут строго локальными
и никак не передадутся в вызывающие операторы.
Для того чтобы подпрограмма изменила значение переданной ей переменной, нужно объявлять соответствующие параметры, как параметры-переменные
, вставляя слово Var
перед их описанием в заголовках.
Внутренний механизм передачи параметров подпрограмм следующий. При вызове процедуры или функции каждой локальной переменной, описанной внутри подпрограммы, и каждому параметру-значению
отводится место для хранения данных в специальной области памяти, называемой стеком
. Эти места принадлежат переменным ровно столько времени, сколько выполняется подпрограмма. При этом ячейки, соответствующие параметрам-значениям
, сразу же заполняются конкретным содержимым, заданным при вызове подпрограммы. По-другому организуются параметры-переменные
и параметры-константы
. Вместо копии
их значения (как было у параметра-значения
) подпрограмма получает разрешение работать с тем местом, где постоянно (во время работы самого вызывающего блока) хранится значение переменной или константы, которая указана при вызове на месте параметра-переменной
или параметра-константы
. С рассмотренного следует, что на место параметра-значения
можно подставлять непосредственно значение, а на местах параметра-переменной
и параметра-константы
могут быть только идентификаторы.
Компилятор блокирует любые присваивания параметру-константе
новых значений в теле подпрограммы. Использование параметров-констант
бывает полезным для того, чтобы не дублировать данные большого объема, что получается, если они описаны, как параметры-значения.
Пример удвоения чисел, где одно задается как параметр-переменная, а второй - как параметр-значение.
Const
A : Integer = 5;
B : Integer = 7; {типизированные константы - переменные}
Procedure Plus (Var C : Integer; B : Integer);
begin {в процедуре одна локальная переменная В)
C := C + C;
B := B + B;
Writeln(C,B)
end;
Begin
Writeln(A,B);
Plus(A,B);
Writeln(A,B)
End.
Вначале на экран будут выведены числа "5 7". В вызове процедуры Plus
фактическими параметрами являются параметр-переменная А
и параметр-значение В
. Переменная А
во время действия процедуры меняет свое значение (в течении процедуры она выступает с именем С
), новое удвоенное значение записывается на место старого. Переменная В
копирует свое значение в локальную переменную с тем же именем. После выполнения процедуры на экране появляются "10 14". Последний оператор выводит на экран числа "10 7".
Итак, параметры-переменные
используются как средство передачи результатов своей работы вызывающему блоку. Разумеется, способом передачи может являться использование глобальных переменных
. Однако злоупотребление глобальными связями делает программу, как правило, запутанной, трудной в понимании и сложной в отладке. В соответствии с требованиями хорошего стиля рекомендуется там, где это, возможно, использовать передачу результатов через фактические параметры-переменные. С другой стороны, описание всех формальных параметров, как параметров-переменных нежелательно по двум причинам. Во-первых, это исключает возможность вызова подпрограммы с фактическими параметрами в виде выражений, что делает программу менее компактной. Во-вторых, и главных, в подпрограмме возможно случайное использование формального параметра, что создает опасность непреднамеренно испортить фактическую переменную. Чем меньше параметров объявлено параметрами-переменными и чем меньше в подпрограмме используется глобальных переменных, тем меньше опасность получения побочных эффектов, тем проще подпрограмма в понимании и отладке.
Существенное различие в объявлении переменных в списке формальных параметров от объявления их в разделе описания переменных состоит в том, что типом любого параметра в списке формальных параметров может быть только стандартный или ранее объявленный тип (не годится Procedure P (A : array [1..10] of Real)
). Поэтому для того, чтобы передать массив (или строку) нужно сначала описать его тип. Это накладывает некоторые ограничения в том случае, когда заранее не известна длина массива (строки). Описывать его по максимуму приводит к затратам памяти. Вообще, невозможность использования статических массивов с "плавающими" границами является существенным недостатком ТР, и объектом его критики.
В версии 7.0 язык поддерживает т.н. открытые массивы, с помощью которых легко решается проблема передачи подпрограмме одномерных массивов переменной длины. Открытый массив - формальный параметр описывается как:
Procedure P (OpenArray: array of Real)
. В этом случае можно подставлять в процедуру фактические одномерные массивы любой длины. Верхняя граница возвращается функцией High
.
Особенности функций
Идентификатор функции возвращает после вызова простое (порядковое или вещественное) или строковое значение заданного типа (возвращаемое значение не может быть файлом, массивом, записью, множеством, объектом). Для присвоения функции значения ее имя должно появиться хотя бы однажды в левой части оператора присваивания в теле самой функции.
Функция, как и процедура, может обмениваться значениями с программой и изменять глобальные переменные непосредственно или через параметры-переменные. Обычно, когда функция, кроме выдачи своего значения, меняет какие-либо глобальные значения или производит другие действия, не связанные с вычислениями своего значения, говорят, что она имеет побочный эффект
. Функция вычисления Х в степени Y по формуле (ХY
=exp(Y × ln(X)):
Var
X,Y : Real;
Function Power (Arg, P : Real) : Real;
begin
if Arg <> 0 then {если аргумент ¹ 0}
Power := exp(P * ln(abs(Arg)))
else
if P = 0 then {если аргумент = 0 и степень = 0}
Power := 1
else {если аргумент = 0 и степень ¹ 0}
Power := 0
end;
Begin
Readln(X,Y);
Writeln(Power(X,Y))
End.
Процедуры и функции могут быть вложенными друг в друга (на общем примере в начале параграфа). Число уровней вложенности может быть достаточно большим, но на практике обычно не превышает второго уровня. Вложенная процедура или функция относится к охватывающей ее подпрограмме точно также, как сама подпрограмма относится к основной программе.
Опережающее описание
Как известно, текст программы транслируется в выполняемый машинный код последовательно сверху вниз. При этом все элементы программы (переменные, типы, константы, подпрограммы) должны описываться до того, как начнутся их упоминания в операторах и выражениях. В противном случае компилятор объявит имя неизвестным. В случаях с процедурами и функциями этого можно избежать, используя опережающее описание директивой Forward
:
Procedure <имя> (<параметры>); forward;
...
Procedure <имя> {список параметров уже не нужен}
<тело процедуры>;
Эта директива объявляет заголовок подпрограммы, откладывая описание содержимого «на потом». Местоположение этого описания не играет роли, и в нем можно не указывать параметры, а ограничиться лишь именем. Директива Forward
существует в языке, в основном, для развязки закольцованных вызовов (одна подпрограмма вызывает вторую, которая в свою очередь вызывает первую).
Кроме стандартной директивы Forward
можно использовать и другие стандартные директивы, которые должны указываться за заголовком подпрограммы. Эти директивы уточняют действия компилятора и распространяются на всю подпрограмму и только на нее.
Assembler -
отменяет стандартную последовательность машинных инструкций, вырабатываемых при входе в процедуру и выходе из нее. Тело подпрограммы должно в этом случае реализовываться с помощью команд встроенного ассемблера (ассемблера, встроенного компилятор ТР);
External
- с помощью этой директивы объявляется внешняя подпрограмма. При этом появляется возможность вызова процедур и функций, написанных и откомпилированных с помощью внешнего ассемблера (например, TurboAssembler Borland)
). Возможен импорт объектных кодов, полученных с помощью Turbo C
и других языков, но на практике он труднореализуем;
Far, Near
- компилятор должен создавать код программы, рассчитанный на дальнюю либо ближнюю модель памяти;
Inline -
указывает на то, что тело подпрограммы реализуется с помощью встроенных машинных команд;
Interrupt -
используется при создании процедур обработки прерываний.
Процедурные типы. Параметры-функции и параметры-процедуры
Начиная с версии 5.5 в ТР был введен новый тип - процедурный тип. Этим был сделан важный шаг от языка Turbo Pascal как средства для обучения программированию к языку Turbo Pascal как средству профессионального программирования. Процедурный тип, являясь расширением стандартного Pascal и позволяет интерпретировать процедуры и функции как объекты
, которые можно использовать при передачи параметров (понятие "данные" сюда меньше подходит).
Описание процедурного типа выполняется в соответствии с уже принятым синтаксисом (процедурный тип - общее понятие, объединяющее и функциональный тип):
Type
ProcType = Procedure (S : String; Var B : Byte);
FuncType = Function (X,Y : Integer) : Real;
В описании процедурного типа приводится полное описание параметров, а в случае функции указываются и ее атрибуты (важно, что символические имена параметров роли не играют и в дальнейшем могут не использоваться). Разница с синтаксисом обычного заголовка процедуры (функции) - в отсутствии их идентификатора. Значением переменной процедурного типа является конкретная процедура или функция.
После объявления процедурного типа, его можно использовать в описаниях параметров подпрограмм. При этом естественно нужно написать реальные процедуры и функции, которые будут передаваться как параметры. Основные элементы программы, вычисляющей интеграл некоторой функции:
Type
FuncType = Function (X : Real) : Real; {процедурный тип}
Procedure Integral (LowerLimit, UpperLimit : Real; {вычисл. интеграла функции
Var Result : Real; Funct от точки LowerLimit до UpperLimit с
Funct : FuncType); результатом в параметре-переменной Result}
…
begin
…
end;
Function SinExp (Arg : Real) : Real; {некоторая функция}
begin
SinExp := Sin(Arg) + Exp(Arg)
end
…
Begin
…
Integral (0, 1, Res, SinExp); {процедура вычисления интеграла с
… записью в фактическую переменную Res}
End.
Описав другую функцию, можно определить ее интеграл, подставив на место соответствующего параметра.
Не разрешается объявлять функции, возвращающие значение процедурного типа. Требование к процедурам и функциям, которые передаются как параметры: они должны компилироваться в режиме {$F+}, что обеспечивает использование дальней модели обращения Far
(в примере эта директива должна стоять перед описанием функции SinExp). Не всякую функцию (процедуру) можно подставить в вызов. Нельзя подставлять: процедуры с директивами Inline
и Interrupt
, вложенные процедуры и функции, стандартные процедуры и функции (не годится Integral (0, 1, Res, Sin)).
Применение процедурного типа не ограничивается одним лишь описанием параметров-процедур и параметров-функций. Например, переменная-процедура может являться частью структуры:
Type
RecType = record
X,Y : Integer;
P : ProcType;
end;
Var
Rec1,Rec2 : RecType;
Естественно, в процессе работы программы требуется присвоить некоторое значение полю Rec1.P. Этим значением должна быть специально описанная процедура. Т.е. в такое записи можно хранить не только данные, но и процедуру их обработки. Причем в любой момент можно сменить ту процедуру, которая понимается под именем Rec1.P.
Процедурные переменные по формату совместимы с переменными типа Pointer
и после приведения типов могут обмениваться с ними значениями.
Используя директиву Absolute
можно обмениваться данными между подпрограммами путем совмещения областей памяти их локальных переменных с адресом глобальной переменной.
Обыкновенные локальные переменные в подпрограммах всегда "забывают" свои значения и при повторном вызове стартовые значения локальных переменных совершенно случайны. Для сохранности информации между вызовами можно использовать статические локальные переменные, которые вводятся как локальные типизированные константы. Const A : Word = 240;
Значение типизированной константы может меняться в теле подпрограммы. После чего оно запоминается до повторного вызова (восстановление стартового значения не происходит).
Процедуры и функции могут содержать в своих заголовках параметры-переменные
(т.е. те, которые передаются как адрес, а не как значение) без указания их типа. Procedure P (Var V1,V2);
Через такое объявление можно передавать подпрограммам строки, массивы, записи и т.д. Но при этом процедура (или функция) должна явно задавать тип, к которому внутри нее приравниваются бестиповые переменные.
Традиционное преимущество языка ТР - это использование рекурсии, под которой понимается вызов функции (процедуры) из тела этой же функции (процедуры). Например, вычисление факториала:
Function Fact (n : Word) : LongInt;
begin
if n = 0 then
Fact := 1
else Fact := n * Fact(n-1);
end;
Внесение рекурсивности придает программе изящество, но всегда оно же "заставляет" программы расходовать больше памяти. Дело в том, что каждый "отложенный" вызов подпрограммы это свой набор значений всех локальных переменных этой функции, размещенной в стеке. В ТР размер стека не превышает 64 К.
МОДУЛИ
Стандартный Pascal не предусматривает механизмов раздельной компиляции частей программы с последующей их сборкой перед выполнением. Более того, принцип обязательного описания объекта перед его использованием делает практически невозможным разработку разнообразных библиотек прикладных программ. Точнее, такие библиотеки в рамках стандартного Pascal могут существовать только в виде исходных текстов и программист должен сам включать в программу большие тексты
различных поддерживающих процедур (например, процедуры численного интегрирования, математической статистики и т.д.)
Отсюда понятно стремление разработчиков коммерческих компиляторов включать в Pascal средства, повышающие его модульность. В ТР с этой целью включены компоненты, которые так и называются - модули.
Модуль
- это автономно компилируемая программная единица, включающая в себя различные компоненты раздела описаний (типы, константы, переменные, процедуры и функции) и, возможно, некоторые исполняемые операторы. Важная особенность модулей заключается в том, что компилятор ТР размещает их программный код в отдельном сегменте памяти. Максимальная длина сегмента не может превышать 64 К, однако количество одновременно используемых модулей ограничивается лишь доступной памятью, что дает возможность создавать весьма крупные программы.
Модуль имеет следующую структуру:
Unit <имя>;
Interface
… {интерфейсная часть}
Implementation
… {выполнение - исполняемая часть}
Begin
… {инициирующая часть}
End.
Т.о. модуль состоит из заголовка и трех составных частей, любая из которых, в принципе, может быть пустой.
Для правильной работы среды ТР имя модуля должно совпадать с именем дискового физического файла, в который помещается исходный текст модуля. Например, модуль с именем Unit Unit_1
размещает свой текст в файле unit_1.pas
. Кроме того, имя модуля служит для его связи с другими модулями и основной программой. Эта связь устанавливается специальным предложением:
Uses Unit_1,…;
которое открывает раздел описаний в основной программе, а в других модулях сразу за словом Interface
, либо сразу за словом Implementation
, либо и там и там (т.е. допускаются два предложения Uses
.
В интерфейсной части
(за словом Interface
) содержатся объявления всех глобальных элементов модуля (типов, констант, переменных, функций, процедур), которые должны стать доступными основной программе и другим модулям, к которым подключен данный модуль. При объявлении глобальных подпрограмм указывается только их заголовок. Например:
Unit Unit_1;
Interface
Type
Complex = record
Re, Im : Real
end;
Var
Zmain : Complex;
Procedure P_1 (X,Y : Complex; Var Z : Complex);
Implementation
Procedure P_1; {сложение двух комплексных чисел}
begin
Z.Re := X.Re + Y.Re;
Z.Im := X.Re + Y.Re
end;
Begin
Zmain.Re := 1; Zmain.Im := -1
End.
Теперь, если в основной программе в разделе Uses
есть подключение модуля Unit_1
, то в ней будет доступен тип Complex,
процедура P_1
и переменная Zmain
. Объявление подпрограмм в интерфейсной части автоматически сопровождается их компиляцией с использованием дальней модели памяти. Все константы и переменные, объявленные в интерфейсной части модуля, вместе с глобальными переменными основной программы помещаются компилятором в общий сегмент памяти размером 64 К (65536 байт). В интерфейсной части модуля нельзя использовать опережающее описание. Если при объявлении типов, данных и подпрограмм используются типы и данные, введенные в других модулях, то они должны быть указаны в разделе Uses
сразу после слова Interface
.
Исполняемая часть
(начинается словом Implementation
) обычно содержит описания подпрограмм, объявленных в интерфейсной части. В заголовке, предшествующем описанию подпрограммы, можно опустить список формальных параметров (и тип результата для функции), т.к. он был описан в интерфейсной части (см. пример). Часто этим и ограничиваются. Кроме того, в исполняемой части могут объявляться локальные для модуля
типы, константы, переменные, подпрограммы, а также метки, если они используются в инициирующей части. Эти типы и данные будут являться глобальными по отношению к подпрограммам этого раздела и операторам раздела инициализации, если он имеется. Естественно, что в программе, которая подключает данный модуль, объявленные в разделе Implementation
типы и данные будут недоступны.
Завершает текст модуля раздел инициализации
. Если он отсутствует, то просто ставится слово End
с точкой. В противном случае он открывается словом Begin
, после чего следуют программные действия, которые будут произведены перед выполнением основной программы (работа скомпилированной программы всегда начинается с выполнения блоков инициализации используемых ею модулей, а лишь потом выполняется основной блок самой программы). Обычно в разделе инициализации происходит заполнение стартовыми значениями библиотечных переменных (см. пример) и какие-то одноразовые действия, которые должны выполниться в начале программы (например, открываться нужные файлы и т.д.).
Имеются особенности компиляции модулей
. Определены три возможных режима компиляции:
В режиме COMPILE
все упоминающиеся в разделе Uses
модули должны быть предварительно откомпилированы и результаты компиляции помещены в одноименные файлы с расширением tpu (Turbo Pascal Units)
(для нашего примера при компиляции основной программы должен быть файл unit_1.tpu
. Такой файл появляется при соответствующей компиляции модуля (по аналогии с компиляцией основной программы, когда появляется исполняемый файл с расширением exe
). Используется при отладке основной программы.
В режиме MAKE
компилятор проверяет наличие tpu
-файлов для каждого объявленного модуля. И если он не обнаружен, то пытается найти одноименный файл с расширением pas
, в котором находится исходный текст модуля. Если такой файл найден, то этот модуль компилируется. Кроме того, в этом режиме системы следит за возможным изменением текста модуля. Если он изменялся, то независимо от существования tpu
-файлов модуль компилируется заново. Т.о. режим MAKE
удобен, т.к. избавляет от необходимости следить за соответствием tpu
-файлов исходному тексту. Упрощает процесс обработки многофайловых программ, когда компилируется только необходимый минимум.
В режиме BUILD
существующие tpu
-файлы игнорируются и система пытается откомпилировать соответствующий pas
-файл для каждого модуля. После этого можно быть уверенным, что все изменения внесены.
Первыми подключаются системные модули из библиотеки turbo.tpl
, а затем все остальные, хранящиеся в виде tpu-
файлов и даже pas-
файлов. Если подключаемые модули содержат элементы с одинаковым именем, то обращаться будут к модулю, описанному в разделе Uses
последним. Если нужно использовать другие модули, то это нужно указывать при обращении (например, Unit_1.Zmain
).
В ТР имеется восемь стандартных модулей, в которых содержится большое число разнообразных типов, констант, процедур и функций. Модули GRAPH, TURBO3, GRAPH3
выделены в отдельные tpu
-файлы, а остальные (SYSTEM, DOS, CRT, PRINTER, OVERLAY
) входят в состав библиотечного файла turbo.tpl
. Лишь один модуль SYSTEM
подключается к любой программе автоматически, остальные нужно указывать в разделе Uses
.
SYSTEM
– включает все стандартные математические процедуры и функции, а также обеспечивает работу с командной строкой, ввод/вывод, эмуляцию сопроцессора, работу с динамической памятью, и т.д.;
DOS
– в модуле собраны процедуры и функции, открывающие доступ к средствам ОС МS-DOS, обработка файлов, работа с каталогами;
CRT
– содержит библиотеку процедур, которые работают с клавиатурой и дисплеем, обеспечивая текстовый режим работы экрана (просто ввод/вывод м. и без него);
PRINTER
– делает доступным вывод текстов на матричный принтер;
GRAPH –
содержит обширный набор типов, данных и подпрограмм для управления графическим режимом работы экрана;
OVERLAY –
необходим при разработке громоздких программ, длина которых превышает память, отводимую MS-DOS под исполняемую программу (~ 580 Кбайт без учета резидентных программ и самой системы ТР);
TURBO3, GRAPH3 –
введены для совместимости с ранней версией 3.0 системы ТР.