|
|||
ЯЗЫКИ ПРОГРАММИРОВАНИЯ
Они дают возможность описывать
вычисления многими
способами. Язык
превращает ЭВМ в «виртуальную машину»,
особенности которой определяются
программным обеспечением
Лоуренс Г. Теслер |
|||
|
|||
Язык программирования — это нечто
большее, чем просто система обозначений для команд, которые воспринимают
ЭВМ. Язык в совокупности с программным обеспечением, которое
«понимает» его, может полностью преобразить ЭВМ, превратить ее в машину
совершенно другого типа. Аппаратные компоненты типичной ЭВМ — это регистры
ячейки памяти, сумматоры и т. д. Когда программист пишет на языке машинных
команд, ему приходится работать непосредственно с этими аппаратными
средствами. Новый язык порождает новую модель ЭВМ. У программиста
появляется возможность оперировать переменными вместо ячеек памяти,
файлами данных вместо каналов ввода/вывода и алгебраическими
формулами вместо регистров и сумматоров, хотя аппаратура при этом не
меняется. Некоторые языки даже порождают машины с «расщеплением личности»:
ЭВМ становится как бы коллективом «организацией», каждая из которых выполняет
свои собственные вычисления и обменивается с другими
сообщениями.
Языков программирования и их
диалектов насчитывается по крайней мере несколько сотен, а скорее
всего — несколько тысяч, и хотя естественных языков человеческого общения,
возможно, даже больше, языки программирования в некоторых отношениях более
разнообразны. У каждого языка — свои грамматика и синтаксис, своя
манера выражения понятий. В принципе большинство вычислительных задач
можно решить, пользуясь любым языком, хотя программы при этом выглядели бы
совершенно по-разному; кроме того, для какой-то конкретной задачи написать
программу на одном языке бывает проще, чем на другом. Здесь мы опишем
некоторые широко распространенные языки программирования и попытаемся дать
представление |
как об их общих свойствах, так и
об особенностях, отличающих их друг от друга.
На рисунке на с. 79 показано
несколько этапов составления короткой программы на языке Лого. Этот язык
был предложен в конце 1960-х годов Сеймуром Пейпертом и его коллегами из
Массачу-сетского технологического института (МТИ). Одна из интересных
особенностей Лого состоит в возможности управлять «черепахой» —
небольшим роботом, который может двигаться вперед и назад,
поворачиваться на месте и поднимать и опускать перо, рисующее след
черепахи. Обычно черепаха — это не реальное устройство, а программная
модель, поведение которой имитируется на экране дисплея.
Первоначальная версия программы
состоит только из команд, отдаваемых черепахе. Сначала опускается перо,
затем две команды forward 50 и right 144 повторяются
пять раз, после чего перо поднимается. Исполняя эти команды, черепаха
рисует пятиконечную звезду. Команда forward 50 предписывает
черепахе нарисовать прямую линию длиной в 50 единиц. Команда right
144 задает поворот на 144° по часовой стрелке — изменение направления
движения в каждой вершине пятиконечной звезды.
Если бы запись команд, которые
должны последовательно выполняться, была единственным методом
для общения с ЭВМ, то создание сложных про-грамм едва ли было бы
возможным. В действитель-ности Лого и другие языки программирования имеют
средства для упрощения и обобщения команд. В данном случае сразу
бросается в глаза фрагмент программы, нуждающейся в
усовершенствовании, — это пятикратное повторение инструкций forward
и right. Программист по возможности старается не писать одно и
то же несколько раз, и не просто потому, что набивать такую программу
утомительно. Если программу можно сжать, она будет занимать меньше
машинной памяти. Кроме того, повторение одних и тех же строк повышает
вероятность описки, |
||
© 1984 by Scientific American, Inc.
© перевод на русский язык, «Мир», 1984. |
|||
|
|||
|
|||
Языки программирования 77 |
|||
|
|||
частности, при модификации
программы. В данном случае от повторения можно избавиться, заменив
пять команд движения черепахи инструкцией repeat 5 [forward 50 right
144].
Теперь предположим, что
программист хочет нарисовать девятиконечную звезду со сторонами
длиной в 80 единиц. Это можно сделать, например, посредством
такой инструкции: repeat 9 [forward 80 right 160], но очевидно, что
здесь повторяется та же программная конструкция, что и выше, с
небольшими отличиями в параметрах. Правильнее будет определить
более общую процедуру, в которой число концов и длина сторон задаются как
переменные величины. В Лого определение процедуры вводится словом to.
Так, фраза to звезда указывает, что далее следуют инструкции,
которые надо запомнить как процедуру изображения звезды. После этого
звезда становится новой командой языка, — командой, которую
можно использовать в программе точно так же, как и встроенные команды
forward и right.
Переменные в процедуре звезда
являются параметрами. Они «передаются» в процедуру в момент ее
вызова. В Лого имени параметра предшествует двоеточие. Так, если
определить процедуру при помощи фразы to звезда : размер :
числоконцов, то в результате выполнения строки звезда 80 9
переменной размер будет присвоено значение 80, а
переменной числоконцов 9 и, таким образом, будет построена
девятиконечная звезда со стороной в 80 единиц.
В процедуру звезда можно
внести еще одно усовершенствование. В Лого любая описанная процедура
может вызываться любой другой процедурой. Это ценное качество языка, но
оно повышает вероятность того, что процедуре будут переданы неприемлемые
параметры. В лабиринте программы иногда трудно предвидеть, что черепахе
будет отдана команда нарисовать звезду с одним или двумя концами.
Проблему можно решить, добавив в программе условие if
числоконцов>2. Оно играет роль «часового», который разрешает
черепахе рисовать только в том случае, если заданное ей число концов
больше двух.
Из приведенного выше примера
можно видеть, что Лого обладает по крайней мере поверхностным сходством с
таким естественным языком, как английский. Лого имеет словарь,
содержащий слова, числа и другие символы (все вместе называемые знаками),
которые можно связывать друг с другом с тем, чтобы образовывать более
крупные конструкции, подобные предложениям естественного языка.
Некоторые знаки являются «ключевыми словами» с фиксированным значением,
другие определяются программистом. Одни знаки используются как
глаголы, другие как существительные, определения или знаки
препинания. Правила, определяющие, каким образом можно сочетать различные
знаки, составляют грамматику языка. |
В целом предложения языка
программирования делятся на описания и инструкции. Описания
определяют, что собой представляет тот или иной объект, что он
обозначает или какую он имеет структуру. В программе на Лого строка to
звезда : размер : числоконцов является описанием, которое
определяет звезда как имя процедуры, а размер и
число-концов как переменные, служащие параметрами процедуры
звезда. Инструкция обычно описывает часть алгоритма, она указывает,
какое действие следует выполнить. В большинстве случаев инструкция имеет
форму императива; она начинается с глагола, за которым идут
дополнение или обстоятельство. В инструкции repeat само слово
repeat (повторить) является глаголом, последующее число служит
наречием, а текст в скобках — дополнением к глаголу.
На словаре и синтаксисе процедуры
звезда сказывается специфика Лого, однако подобные
механизмы управления ходом выполнения процедур можно обнаружить
в подавляющем большинстве языков программирования. При отсутствии
каких-либо явных инструкций передачи управления программа выполняется
последовательно. Если вообразить, что ЭВМ читает программу, то это чтение
происходит строка за строкой сверху вниз, и при отсутствии специальных
указаний каждая инструкция выполняется ровно один раз.
Одним из элементов программы,
изменяющих ход выполнения, является инструкция repeat. Она служит
примером циклической или итеративной конструкции. Когда машина встречает
фразу repeat n и следующую за ней группу инструкций в скобках, она
читает и выполняет то, что содержится в скобках п раз. Другим
способом управления ходом выполнения программы является условная
конструкция, которая в Лого (как и во многих других языках) воплощается
в инструкции if. Инструкции,
использование которых определяется действием if, выполняются только
в том случае, если удовлетворено некоторое условие.
У конструкций итеративного и
условного выполнения много вариаций. Так, инструкция repeat
полезна только тогда, когда еще до выполнения цикла известно,
сколько раз его требуется повторить. Другие конструкции позволяют
управлять завершением цикла на основании данных, формируемых в ходе его
выполнения. Для этого в цикл включается некоторое условное выражение.
В ряде языков инструкция, начинающаяся с ключевого слова while,
повторяется до тех пор, пока условие, содержащееся в данной
инструкции, остается истинным. Другим способом изменения хода выполнения
программы служит «безусловный переход» или инструкция goto,
которая попросту передает управление в выполняемой программе в другую
точку. В последние годы среди программистов-теоретиков инструкция goto
пользуется дурной славой. Это объясняется тем, |
||
|
|||
|
|||
78 Современный компьютер |
|||
|
|||
что программы с большим
количеством переходов трудно разобрать (человеку, но не ЭВМ).
Концепция определения процедуры
является жизненно важным элементом программирования. Это главный
механизм абстракции, процесса перехода от частных примеров (пятиконечная
звезда со стороной в 50 единиц) к обобщенным понятиям (звезда с
некоторым числом концов и с некоторой длиной сторон). Процедура
определяется один раз и хранится в памяти только в одном месте, но
вызываться |
|
||
она может из многих мест
программы, тем самым обеспечивается многократное ее использование. Каждый
раз при вызове процедуры точка выполнения перемещается в ту область
памяти, где хранится данная процедура. Когда процедура завершается,
происходит возврат к выполнению инструкции, непосредственно следующей
за инструкцией вызова процедуры. Процедура специального вида,
называемая функцией, возвращает в вызывающую программу некоторое
значение. Например, функция тангенс. |
|||
|
|||
![]() |
|||
|
|||
СНИМОК ПРОГРАММЫ в ходе ее
выполнения демонстрируется системой разработки программ Пекан.
Большая часть исходного текста, или исходной программы, показана в большом окне экрана дисплея вверху
справа. Это программа на языке Паскаль для суммирования нечетных
чисел в массиве целых чисел. Команды, управляющие выполнением
программы, даны в окне вверху слева. Выполнение приостановлено на
инструкции, в которой собственно и происходит суммирование.
|
Эта инструкция заключена в рамку в окне
для исходного текста. Ниже исходного текста показана часть блок-схемы и
слева от нее — бинарное дерево, изображающее логическую
структуру инструкции присваивания. Вложенные рамки внизу слева указывают
на области действия символов (имен переменных, типов и т. п.). Окно
стека дает картину структур данных. Система Пекан разработана С. Рейссом
из университета Брауна, он же является автором этой
иллюстрации. |
||
|
|||
|
||||
Языки программирования 79 |
||||
|
||||
СОСТАВЛЕНИЕ
ПРОГРАММЫ НА ЛОГО прослеживается на ее пяти версиях. Программа дает
команды «черепахе» — механическому рисующему устройству. В
первой версии программы все команды для
изображения
пятиконечной звезды даны в явном виде. Инструкция
repeat во второй
версии сжимает программу и снижает
вероятность ошибки.
Третья версия имеет ту
же структуру, но рисует девятиконечную звезду
больших размеров.
В четвертой версии
определяется процедура, для которой длина сторон и
число концов являются
переменными величинами. В последней версии программы условие
if разрешает выполнение
процедуры лишь
тогда, когда заданное число концов больше двух.
Инструкции repeat и if
служат примерами управляющих структур, играющих важную роль
фактически во всех
языках программирования. |
![]() |
|||
|
||||
При вызове ей в качестве
параметра дается угол, а в качестве значения она возвращает тангенс этого
угла.
Из сотен или тысяч языков
программирования только десяток или около того имеет широкое
распространение. На рисунках на с. 81—85 показана одна и та же
задача, запрограммированная на шести языках: Бейсик, Паскаль, Кобол, Форт,
АПЛ и Лисп. Выбор этих языков отчасти обусловлен их широким
распространением. Программистов, свободно владеющих ими, довольно
много. Пример этих языков хорошо демонстрирует, насколько
разнообразны способы, при помощи которых может быть выражена одна и
та же идея. В каждом примере я пытался писать в стиле, естественном или
удобном для программиста, привыкшего к данному языку.
Задача состоит в том, чтобы
просуммировать нечетные числа из некоторого набора целых чисел. Сама
по себе она не представляет особого интереса. Она выбрана потому, что ее решение легко
запрограммировать на всех этих языках, и потому, что она
демонстрирует важные механизмы определения пе- |
ременных и процедур управления
итеративным и условным выполнением.
Язык программирования Бейсик был
разработан в 1965 г. Дж. Кемени и Т. Курцем из Дартмутского колледжа.
Изначально язык предназначался для вводного курса по информатике. В
дальнейшем в академических кругах этим языком стали пользоваться
меньше, но зато он стал популярным в других сферах, особенно в
программировании для микрокомпьютеров. В Бейсике каждая строка
идентифицируется номером, и управление ходом программы в значительной
степени основано на указании номеров строк. Центральное место в
программе в рассматриваемом примере занимает цикл, в который входят все
инструкции между FOR и NEXT. В цикле их выполнение
неоднократно повторяется. Собственно вычисление происходит в
инструкции присваивания, которая
начинается с ключевого слова LET и дает новое значение
переменной.
Язык
Паскаль был создан примерно в 1970 г. Н. Виртом из швейцарского Федерального
института технологии в Цюрихе.
Это еще один язык, предна- |
|||
|
||||
|
|||
80 Современный компьютер |
|||
|
|||
![]() |
|||
|
|||
В ПРИМЕРЕ ВЫЧИСЛЕНИЙ,
иллюстрирующем свойства нескольких языков программирования, подсчитывается
сумма нечетных элементов в массиве из п целых чисел. Алгоритм, показанный
на блок-схеме слева, непосредственно реализован в программах на
Паскале, Бейсике и Коболе, приведенных на следующей странице.
Центральное место алгоритма — цикл, выполняемый n раз. На каждом
проходе цикла анализируется некоторый элемент
значавшийся для обучения, который
затем был приспособлен для множества других целей. Паскаль в отличие
от Бейсика требует, чтобы программист объявлял каждую переменную и
указывал ее тип. В данном случае переменные представляют целые и массивы
целых чисел. На процедуры и функции здесь ссылаются при помощи имени, а не
номера строки, благодаря чему программу удобнее читать.
Паскаль особенно важен как
прародитель более поздних языков. Так, например, Вирт недавно
предложил язык, названный Модула-2, который основан на многих
концепциях, введенных в Паскале, но, кроме того, особое внимание уделяется
построению программы как набора
независимых модулей. Ада — язык, разработанный по заданию
министерства обороны США, — также в значительной мере базируется на
Паскале, хотя он существенно сложнее.
Язык Кобол был создан в 1960 г.
объединенным комитетом производителей и пользователей ЭВМ. Название языка
— акроним от слов Common Busi- |
массива: если это нечетное
число, оно прибавляется к текущему итогу. Справа можно проследить за
последовательным рядом значений, присваиваемых переменным в ходе
вычислений для массива из четырех чисел. Символ «:=» дает переменной,
стоящей слева, значение, вычисленное справа. Число в скобках, как,
например, в надписи числа [1] — это индекс, указывающий номер элемента
массива числа.
ness-oriented Language (язык,
ориентированный на обработку коммерческой информации). И
действительно, Кобол давно стал основным языком для обработки больших
массивов данных в правительственных учреждениях, банках, страховых
фирмах и т. п. Программа на Коболе состоит из четырех разделов или частей:
раздела идентификации, раздела среды (оборудования), раздела данных и
раздела процедур. На рисунках, приведенных на с. 81—82, показаны только
раздел данных, в котором описываются переменные, и раздел процедур, в
котором формулируются алгоритмы. В противоположность многим языкам
программирования, основанным на системе записи (или нотации), принятой в
математике и формальной логике, в Коболе за образец был взят
синтаксис английского предложения. Поэтому программы на Коболе очень легко
читать, но часто они оказываются слишком многословными.
Язык Форт (Forth) был изобретен
примерно в 1970 г. Ч. Муром, который в то время
работал |
||
|
|||
|
|||
Языки программирования 81 |
|||
|
|||
в Национальной
радиоастрономической обсерватории. Язык был задуман для управления
процессами, в частности для управления телескопами, но и этот язык
распространился на другие области. Его приспособили для многих мини-
и микрокомпьютерных приложений. Отчасти это объясняется тем, что
программы на языке Форт занимают мало места в памяти. В
противоположность Коболу читать программы на Форте трудно, и они
крайне немногословны: несколько ключевых слов являются просто знаками
препинания.
В языке Форт одним из основных
средств, предоставляемых машиной, служит «стек» — область памяти,
организованная наподобие стопки подносов |
в столовой. Таким образом, если
что-то укладывается в такую стопку-стек первым, то забирают его оттуда
последним. В приведенном примере программы на Форте предполагается, что
массив чисел и его размер в момент вызова функции находятся на верху
стека. Все вычисления выполняются на стеке, а значения (результаты)
функций возвращаются на верх стека. Никакие переменные в программе не
описываются. В языке АПЛ синтаксис еще более сжатый, чем у Форта. Название языка — сокращение от слов A Programming Language (APL) (язык программирования). В 1961 г., когда впервые была опубликована книга по АПЛ (К. Айверсон, IBM), язык представлял собой только способ записи для описания |
||
|
|||
![]() |
|||
|
|||
ПРОГРАММА
НА ПАСКАЛЕ суммирует нечетные числа из массива и является функцией SumOdds с
двумя параметрами: целым n
и массивом terms. Собственно функция состоит из инструкций в цветной полосе,
остальная часть программы
объявляет массив для работы SumOdds. |
В Паскале каждая переменная должна
быть объявлена в описании, задающем ее тип. Некоторые типы, как integer
(целый), встроены, другие, как TermIndex, определяются программистом. Цикл задается инструкцией
for...to...do..., а условная
конструкция — if...then... |
||
|
|||
![]() |
|||
|
|||
ПРОГРАММА НА БЕЙСИКЕ суммирует
нечетные числа в массиве. Подпрограмма выделена цветной полосой и не имеет
названия. Обращаться к ней надо по номеру строки, это делает
инструкция GOSUB 1100. Подпрограмма на Бейсике не имеет также и
параметров, значения присваиваются «глобальным» переменным, к которым
имеется |
доступ из подпрограммы. Переменную
Бейсика, если она не имеет индекса, объявлять не надо. В данном примере
DIM (от „dimension" — размерность) определяет, что в массиве Т может быть до 100 элементов.
Инструкция FOR...NEXT... задает цикл, IF...THEN... —
условную конструкцию. |
||
|
|||
|
|||
82 Современный компьютер |
|||
|
|||
![]() |
|||
|
|||
ПРОГРАММА НА КОБОЛЕ суммирует
нечетные числа, используя процедуру СУМ-НЕЧЕТ, которая вызывает
процедуру АНАЛИЗ-ОДНОГО-ЧИСЛА. У процедуры Кобола нет параметров,
поэтому перед вызовом СУМ-НЕЧЕТ инструкцией PERFORM
переменной N и первым N элементам массива ЧИСЛА присваиваются
значения. Ключе-
задач прикладной математики. На
ЭВМ язык был реализован позже. Отличительной чертой АПЛ является то, что в нем одинаково просто
работать как с одиночными величинами, так и с массивами чисел. Так,
команду, которая складывает два числа, без всяких изменений можно
применить для сложения массивов с тысячей элементов. В примере
программы на АПЛ сложение нечетных чисел, хранящихся в массиве,
задается одной инструкцией. Ненулевое значение числа по модулю 2
идентифицирует нечетные числа-элементы массива, которые таким образом
выделяются из массива и суммируются.
Язык Лисп
в некоторых отношениях самый простой из
тех, что здесь рассмотрены. В нем есть только один вид инструкций, а
именно вызов функции.
Главным источником силы языка служит то, что значением функции может быть другая
функция. Язык Лисп был
разработан в конце 50-х годов Дж. Маккарти, в то время работавшим в МТИ.
С тех пор Лисп является самым
популярным всюду, где ведутся
работы по искусственному интеллекту. Название языка происходит от слов «list
processing» — обработка списков — и программы, и данные в нем представлены в виде
списков.
Программу для нашего примера
можно было бы написать на Лиспе как итеративный цикл, однако программист,
привыкший к стилю Лиспа, скорее всего выбрал бы технику рекурсии, когда
процедура вызывает саму себя, затем вызванная процедура вызывает еще раз
саму себя и т. д. Для выхода из такого процесса необходимо иметь
соответствующие средства, иначе рекурсия превратится в бесконечную
цепочку обращений процедуры самой к себе. Обыч- |
вые слова PERFORM...VARYING...
определяют цикл, IF... открывает условное предложение. Числа 01
и 02 обозначают уровни иерархии в структуре данных. PICTURE
описывает, в какой форме должны выдаваться на печать
числа. |
||
ным средством такого рода служат
условные инструкции. Когда условие выполняется, происходит возврат к
ближайшему более высокому уровню.
В программе набор чисел имеет
форму связанного списка. Если список пуст, функция возвращает в качестве
результата нуль. В противном случае, если первый элемент списка нечетный,
он прибавляется к сумме, и функция вызывает саму себя с аргументом
(параметром), состоящим из списка, который остается после того, как первый
элемент удаляется*. В конце концов все элементы будут выбраны из списка,
после чего завершатся все отложенные вычисления.
Вообще ЭВМ не рассчитана на то,
чтобы «понимать» Лого, Бейсик или другие языки программирования,
которые оперируют на подобном уровне абстракции. Аппаратура ЭВМ распознает
только двоичные числа, преобразованные в электрические сигналы. Программа
в той форме, в которой она может быть выполнена (эта форма называется
машинным кодом или машинным языком), представлена
последовательностью таких чисел. Одни из них являются командами для
центрального процессора, другие являются данными, а третьи служат
адресами в памяти ЭВМ.
Написать программу
непосредственно на машинном языке можно, но это весьма утомительное
занятие, и вероятность завершить даже небольшой проект без ошибок
мала. (Для ЭВМ не составляет |
|||
* Суммирование выполнится только
после того, как соответствующая функция-аргумент выдаст результат.
Так возникает цепочка отложенных вычислений. — Прим.
перев. |
|||
|
|||
|
||||
Языки программирования 83 |
||||
|
||||
ПРОГРАММА НА ФОРТЕ для
суммирования нечетных чисел в массиве не описывает ни переменных, ни
каких-либо других структур данных. Она работает только со значениями в
стеке. Когда вызывается СУМ-НЕЧЕТ, предполагается, что элементы массива
находятся в стеке, а число элементов — на его верху. Строка,
следующая за описанием процедуры, приведена для того, чтобы выполнить
программу для массива из четырех элементов. Приведена полная
трассировка выполнения программы, показывающая содержимое стека
после выполнения каждого слова. Числовое «слово» типа 0 или 2 помещает число в
стек; SWAP меняет местами два верхних элемента; DUP помещает в стек копию
верхнего элемента; DROP выбрасывает верхний элемент. Операторы
типа «-)-» и MOD заменяют два
верхних элемента стека на свой результат. Конструкция цикла DO удаляет из стека два элемента (скажем, i и j) и выполняет слова до LOOP i —
j раз. Условная конструкция /F выполняет слова между IF и
ELSE, если на верху стека (TOS) не нуль, в противном случае она
выполняет слова между ELSE и THEN. |
![]() |
|||
|
||||
труда отличить 01101001 от
01011001 и запомнить, что означает каждый из этих кодов, а человеческие
глаз и разум плохо приспособлены для решения таких задач.) В конце 40-х и
в начале 50-х годов для облегчения тяжелого труда по написанию
программ в машинных кодах была изобретена система обозначений,
названная языком ассемблера. Вместо |
выписывания двоичных цифр для
каждой машинной команды программист писал короткое слово или аббревиатуру
типа ADD, SUB или MOVE. Аналогичным образом адреса ячеек памяти, в
которых хранились переменные, заменялись на имена, которые этим
переменным присваивались. Числовые значения выражались в десятеричной
системе. Для |
|||
|
||||
|
|||
84 Современный компьютер |
|||
|
|||
слов, обозначающих машинные
команды, выбирали легко запоминающиеся слова, так называемые
мнемоники.
Первое время перевод с языка
ассемблера на машинный язык делали вручную. Технология здесь проста:
одна таблица фиксирует соответствие между мнемониками и их двоичными
кодами (эта таблица задается раз и навсегда), другую подобную таблицу
можно строить по ходу перевода для имен переменных, появляющихся в
программе. Ясно, что этот процесс легко автоматизировать, и скоро были
написаны программы, названные ассемблерами, которые выполняли
такой перевод.
Языком ассемблера пользуются и
сейчас потому, что он дает возможность непосредственного доступа ко всем
средствам ЭВМ. Программа, тщательно написанная на языке ассемблера,
быстра и эффективна, и если требуется достичь компромисса между
размером программы и скоростью ее выполнения, то, пользуясь языком
ассемблера, программист может сам принимать соответствующие решения.
Современные ассемблеры — это изощренные транслирующие программы.
Однако по-прежнему соблюдается соответствие, грубо говоря, один к
одному между строками языка ассемблера и командами машины, из-за чего
программы получаются, как правило, весьма длинными и возможностей для
ошибок здесь тьма. Структуры управления, доступные в большинстве
языков ассемблера, примитивны. Что особенно важно, язык ассемблера
остается ближе к. машинному языку, чем к языку программиста.
Алгоритмы приходится выражать в терминах того, что должна делать машина, а
не в терминах, естественных для решаемой задачи. (Вариант программы
суммирования нечетных чисел на языке ассемблера показан на верхнем
рисунке на с. 86.)
Работая над решением задачи, вряд
ли кто-нибудь станет мысленно оперировать регистрами и адресами памяти —
задача сама подскажет подходящие обозначения. Если это задача по
физике, программы можно было бы начать строить, отправляясь от
уравнения, например, F = та; в коммерческих задачах можно было бы
выбрать формулу прибыль = доход — вложения.
Действия, описываемые формулой, затем транслируются в точные команды
для машины. Уже давно стало ясно, что такая трансляция также может
быть автоматизирована. На этих соображениях основаны языки типа Фортран, Бейсик
и Паскаль. В течение ряда лет языки такого рода называли языками
высокого уровня. Сейчас кажется более уместным отнести их просто к языкам
программирования, потому что машинные коды и языки ассемблера, по
существу, нельзя считать языками в полном смысле этого слова.
С начала 60-х годов большая часть
программного обеспечения пишется на языках
программирования. |
У них много преимуществ над
средствами представления более низкого уровня. Поскольку одна
инструкция может развернуться в несколько машинных команд, программы
становятся короче, что снижает трудозатраты по их написанию и делает их
яснее. Пользуясь понятиями, присущими решаемой задаче, а не
понятиями, которые определяются реализацией программы на машине, мы
уменьшаем вероятность появления ошибок. Более того, это создает
предпосылки для появления «машинной независимости», когда одна и та
же программа может работать на машинах различных типов.
Важно различать язык
программирования и реализацию языка. Сам язык — это система записи,
набор правил, определяющих синтаксис правильной программы. Реализация
языка — это программа, которая преобразует запись высокого уровня в
последовательность машинных команд.
Имеются два основных вида средств
реализации языка: компиляторы и интерпретаторы. Компиляторы транслируют
весь текст программы, написанной на, языке высокого уровня, в машинный код
в ходе одного непрерывного процесса. При этом создается полная программа в
машинных кодах, которую затем можно выполнять без участия компилятора.
Обычно работа с компилируемым языком состоит из трех этапов: сначала текст
программы создается при помощи редактора текстов или какой-либо другой
программы текстовой обработки, затем текст компилируется, и наконец,
скомпилированная программа выполняется. Термин компилятор был введен
в 1951 г. Грейс М. Хоппер, работавшей тогда в Remington-Rand Univac, и
назвавшей так свою первую транслирующую программу. Одной из операций,
которую выполняла ее программа в ходе трансляции, были выборка стандартных
последовательностей машинных команд из таблиц, хранящихся на
магнитной ленте, и компиляция из них готовой
программы.
Интерпретатор в каждый момент
времени выполняет по одному предложению программы, по ходу дела
превращая каждую конструкцию, записанную на языке высокого уровня, в
машинные команды. Разница между компилятором и интерпретатором подобна
разнице между переводчиком литературы и переводчиком устной речи.
Переводчик литературы берет всю книгу и создает новый текст на другом
языке. Переводчик устной речи переводит каждую фразу, как только она
произнесена. В действительности большинство интерпретирующих программ
проводят некоторую предварительную обработку текста перед тем, как начать
выполнение программы. Так, ключевые слова преобразуются в более короткие
знаки и имена переменных заменяются их адресами. Но все же эти два вида
реализации остаются отличными друг от друга: для выполнения
интерпре- |
||
|
|||
|
|||
Языки программирования 85 |
|||
|
|||
тируемой программы интерпретатор
должен находиться в основной памяти, в то время как для
программы, которая уже скомпилирована, компилятор больше не
нужен.
В принципе любой язык
программирования может быть как интерпретируемым, так и компилируемым, но
в большинстве случаев по традиции у каждого языка есть свой
предпочтительный способ реализации. Фортран, Кобол, Паскаль в
основном компилируют; Лого, Форт и АПЛ почти всегда
интерпретируют; Бейсик и Лисп широко используются в обеих формах.
Основным преимуществом компиляции является скорость выполнения
готовой программы, поскольку интерпретатор должен строить
соответ- |
ствующую последовательность
команд в момент, когда инструкция выполняется. Интерпретируемый язык почти
неизбежно медленнее компилируемого. В то же время интерпретируемый язык
часто более удобен для программиста. Он хорошо подходит для диалогового
стиля разработки программ. Отдельные части программы можно написать,
проверить и выполнить, не выходя из интерпретатора, а когда
найдена ошибка, ее можно исправить немедленно, и при этом нет
необходимости возвращаться к программе редактирования текста и затем
компилировать программу снова.
Внутренние механизмы компилятора
или интерпретатора — тема слишком обширная, чтобы
ее |
||
|
|||
![]() |
|||
|
|||
ПРОГРАММА НА АПЛ вычисляет сумму
нечетных элементов в массиве при помощи функции, действие которой
определяется одной строчкой. Функция имеет один параметр ЧИСЛА
— массив, который «знает», сколько в нем элементов, так что нет
необходимости указывать N в программе. Инструкция в АПЛ выполняется
справа налево, за исключением тех случаев, когда скобки изменяют
порядок вычислений. В этом примере выражение (2 | ЧИСЛА) вычисляется
первым, оно дает остаток от деления каждого элемента ЧИСЛА на 2 и
создает массив такого же |
размера, как ЧИСЛА, для
остатков. Символ «/» может указывать на две различные операции, обе они
встречаются в примере. В выражении (2 | ЧИСЛА)/ЧИСЛА «/» является
оператором «уплотнения», который создает новый массив, где каждый элемент
массива ЧИСЛА появляется только тогда, когда соответствующий
элемент массива остатков ненулевой. В случае «+/» символ «/» служит
оператором «редукции», который приводит массив к одному числу путем
вставления знака «+» в каждую пару элементов. |
||
|
|||
![]() |
|||
|
|||
ПРОГРАММА НА ЛИСПЕ вычисляет сумму
нечетных элементов при помощи функции, которая вызывает себя
рекурсивно. Функция Лиспа является списком, его первый элемент (называемый
CAR) — имя функции, а остаток списка (CDR) задает параметры. DEFUN — это
функция, определяющая функцию, a LAMBDA предшествует именам
параметров. Здесь единственный параметр — список чисел. COND — условная
функция, выполняющая CAR списков, являющихся параметрами COND. Если
результат — Т (т. е. истина), то выполняется CDR от этого
списка, |
в противном
случае COND переходит к следующему списку. В данном случае — три возможности.
Если ЧИСЛА
— пустой список, то NULL истинно и СУМ-НЕЧЕТ возвращает нуль.
Если CAR от ЧИСЛА нечетен, он прибавляется к текущему итогу, а СУМ-НЕЧЕТ вызывается
для обработки CDR от
ЧИСЛА. Если ни одно из условий не истинно, то доходим до предложения Т
(которое всегда истинно). Оно
просто вызывает СУМ-НЕЧЕТ с (CDR ЧИСЛА) в качестве параметра. При каждом
вызове вычисления
откладываются. |
||
|
|||
|
|||
![]() |
|||
|
|||
МАШИННЫЙ КОД И КОМАНДЫ АССЕМБЛЕРА
определяют шаги вычисления нечетных элементов через аппаратные
средства ЭВМ. У каждой машины свой код. Здесь используется код для
микропроцессора Motorola 68000. Алгоритм подобен процедуре Паскаля
SumOdds, хотя программа компактнее, чем машинная программа, которую
сгенерировал бы компилятор Паскаля. Параметры |
передаются процедуре, и результат
возвращается через стек. Адрес, куда нужно вернуться по завершении
процедуры, также находится в стеке. Вариант программы на ассемблере,
где команды представлены в виде мнемонических аббревиатур, можно
перевести прямо в двоичные коды команд, выполняемых
микропроцессором. |
||
|
|||
![]() |
|||
|
|||
РАБОТА КОМПИЛЯТОРА или транслятора
для языка программирования проходит по крайней мере в три этапа. Они
показаны здесь для простого случая арифметического выражения в
Паскале. В процессе лексического анализа знаки, или символы, из
которых состоит программа, идентифицируются и классифицируются.
Синтаксический разбор определяет семантические отношения между знаками. В
арифметическом выражении основная задача синтаксического разбора —
определить, с каким оператором связан каждый операнд. Здесь это
делается путем сравнения приоритетов операторов (в
предположении, |
что умножение предшествует делению,
которое предшествует сложению и т. д.); в скобки заключаются
операции более высокого приоритета. Выражение преобразуется в
«постфиксную» запись сменой мест оператора и второго операнда в каждом
подвыражении. Скобки затем удаляются, в результате выражение можно
вычислять слева направо. Генератор кода превращает разобранное выражение в
машинные команды, применяя простой алгоритм, отводящий под каждую
переменную аппаратный регистр. |
||
|
|||
|
|||
Языки программирования 87 |
|||
|
|||
здесь рассматривать, но структуру
типичного компилятора в общих чертах описать можно. Процесс
компиляции состоит по крайней мере из трех фаз. Первая фаза — лексический
анализ, в процессе которого компилятор идентифицирует различные символы в тексте программы и классифицирует
их на ключевые слова, числовые значения, имена переменных и т. д.
Следующая фаза — синтаксический анализ, в процессе которого
компилятор определяет синтаксические соотношения ключевых слов и строит
каркас структуры программы. Каждое слово if, например, связывается
с последующим then. В третьей фазе генерируется машинный код,
соответствующий структуре разобранной программы. В некоторых компиляторах
имеется еще четвертая фаза оптимизации, в которой сгенерированная
программа еще раз просматривается и корректируется, чтобы повысить ее
эффективность.
За последние 30 лет специалисты
много размышляли о том, как лучше конструировать компиляторы, и
сейчас существует хорошо развитая методология для их создания. Первым
шагом является определение языка как такового в абсолютно точной
форме. Сейчас общепринята практика определения грамматики в терминах
«правил вывода», которые рекурсивно можно применять для создания всех
возможных инструкций языка. После этого создание компилятора является
относительно простой работой для программиста, существуют даже компиляторы
компиляторов, которые могут автоматизировать часть
работы.
Идея языка программирования
появилась так же давно, как большие универсальные цифровые
вычислительные машины. В 1945 г. немецкий математик Конрад Цузе
изобрел систему записи, которую он назвал Планкалкюль. Инструкции в языке
имели двумерный формат: переменные и их индексы располагались вертикально,
а операции над ними |
располагались вдоль
горизонтальной оси. Цузе писал программы на Планкалкюле на бумаге (одна из
них моделировала ходы шахматных
фигур), но язык он не реализовал. Тем не менее многие развитые им
идеи нашли отражение в современных языках.
Несомненно наибольшее влияние на
все последующие языки программирования оказал Фортран, разработанный
Джоном Бэкусом и его коллегами в IBM в 1954—1957 гг. Название языка
происходит от formula translation (трансляция формул), и
предназначался язык для научных и численных расчетов. Для этих целей
он используется до сих пор. В те времена язык был встречен довольно
скептически. Вычислительная техника была тогда редким и дорогим средством,
из-за этого много внимания уделялось эффективности программ.
Предполагалось, что язык высокого уровня неизбежно снизит
эффективность, но Бэкус и его группа совершили поистине чудо.
Они создали компилятор, который генерировал программы, по качеству
эквивалентные закодированным вручную.
Приблизительно в то же время
Хоппер и ее сотрудники в Ramington-Rand Univac разработали язык
программирования, названный Флоу-Матик, для обработки коммерческой
информации. Он был проще Фортрана, но опыт, приобретенный за
несколько лет работы с ним, привел к созданию Кобола. Еще более важным языком, появившимся в
конце 50-х годов, был Алгол (от algorithmic language —
алгоритмический язык). Алгол-58, первая версия языка, был разработан
международным комитетом, который воспользовался идеями, заложенными
как в прагматичном синтаксисе Фортрана, так и в более элегантной системе
обозначений Планкалкюля. Результатом работы комитета явился легко читаемый и практичный язык. Он занял
важное место в генеалогии последующих языков, включая
Паскаль. |
||
|
|||
![]() |
|||
|
|||
НЕПРОЦЕДУРНЫЙ ЯЗЫК Пролог не имеет
инструкций, а состоит полностью из описаний. Другими словами,
Пролог-программа не дает явных указаний для выполнения операции, она
только устанавливает отношения и делает выводы, основанные на них. На
рисунке показана программа на диалекте Микро-Пролог. Первые пять
описаний формулируют определенные отношения
«родитель— |
ребенок». После этого система
может дать ответ на вопросы об установленных фактах, например, указать
родителей Авеля и детей Евы. Затем вводятся два правила логического
вывода для определения отношения «предок» через отношение «родитель».
Система может применять эти правила для отыскания предков или
потомков. |
||
|
|||
|
|||
88 Современный компьютер |
|||
|
|||
Довольно много других языков
уходят своими корнями в ту же эпоху.
Комит (COMIT) был создан для анализа текстов, а АПТ (APT) — для
управления станками. Джовиал (JOVIAL), производная от Алгола, был
первым широко используемым многоцелевым языком. Он был удобен как для
научных, так и для коммерческих расчетов. В начале 60-х годов появился
Лисп и разработана нотация АПЛ (но не реализация).
Стремительное размножение языков
встревожило многих специалистов. Ведь, в конце концов, во всей математике
используется только одна универсальная общепринятая система записи.
Реализация языка — это длительная работа, кроме того, нужно время на
усвоение его программистом. Поэтому многие специалисты срочно занялись
разработкой нового языка, который,
обладая достаточной полнотой и гибкостью, мог бы служить
универсальным средством программирования. Все эти попытки
закончились неудачей. Частичный успех языка ПЛ/1, разработанного
по заказу IBM, показал, что всеохватывающий язык, по всей видимости,
должен быть и трудным в изучении, и громоздким в реализации. Более того,
так как методы вычислений становились все более разнообразными,
специалисты стали приходить к выводу, что необходимость в новых языках для
новых областей будет появляться всегда.
В известном смысле все
исследования в области программирования с 1957 г. были мотивированы
попытками избавиться от недостатков, присущих Фортрану. Вместе с тем и сам
Фортран несколько раз подвергался ревизиям. Первоначальная версия
накладывала определенные ограничения на программиста (например, имя
переменной не могло быть длиннее шести литер) и предоставляла лишь
ограниченные возможности для определения структур данных. Вероятно,
наиболее серьезные недостатки были в управляющих структурах. Все
точки ветвлений приходилось определять номерами строк и внимательнейшим
образом следить, чтобы смысл программы полностью не затерялся в путанице
инструкций GOTO. В более поздних версиях были введены управляющие
структуры, способствующие применению более наглядного стиля
программирования.
Все языки, которые я до сих пор
обсуждал, можно определить как процедурные или предписывающие. Программа,
написанная на таком языке, говорит, как получить результат. Она говорит,
что сначала надо сделать это, а потом делать то и т. д. Существуют
также непроцедурные или описательные языки, и они приобретают все большее
значение. Описательная программа констатирует, какой результат желателен, не указывая, как этого
достичь. В программе скорее формулируются соотношения, а не
последовательность вычислений, и, таким |
образом, программист
освобождается от обязанности разрабатывать шаги алгоритма и определять их
порядок.
Наиболее известными
непроцедурными языками являются автобланковые (spreadsheet) программы,
такие, как Визикальк и Мультиплан, которые стали популярными в связи с
широким распространением персональных ЭВМ. В Мультиплане вычисления задаются написанными формулами, так же как
в Бейсике или Фортране. Однако порядок, в котором должны
выполняться действия формул, определяется не программистом, а реализацией.
Временные соотношения в некоторой степени заменяются пространственными. В
обычном языке выход одной процедуры может служить входом в другую
процедуру, аналогичным образом в автобланковых языках значение одной
ячейки зависит от значения другой.
В языке Пролог — производной от
Лиспа,— которым в последнее время заинтересовались многие
исследователи, работающие в области искусственного интеллекта, определение
процедур играет еще меньшую роль. В Прологе не пишут формул, вместо
этого определяют соотношения между объектами и величинами. Язык состоит
только из описаний и не имеет инструкций. Так, отношение
(произведение высота
ширина площадь) описывает равенство площадь = высота X
ширина, но оно не указывает на то, что высота и ширина являются
заданными величинами или что площадь следует вычислять. То же самое
отношение может служить и для того, чтобы найти высоту, когда известны •
ширина и площадь.
Другая тенденция в эволюции
языков программирования — рост интереса к системам записи,
называемым объектно-ориентированными языками. Как отмечалось выше,
инструкции большинства языков программирования являются императивными:
вычисляющее средство не имеет имени, поскольку оно просто-напросто
представляет собой абстрактный компьютер в целом. В
объектно-ориентированном языке ЭВМ условно делится на объекты, к которым
можно обращаться индивидуально. Более того, объекты могут связываться друг
с другом при помощи посылки сообщений.
Понятие программных объектов было
введено О. Д. Далом и К. Нигардом из Норвежского вычислительного
центра в Осло в языке Симула-67, ведущем свое происхождение от
Алгола-60. Однако идея не привлекла широкого внимания вплоть до разработки
языка Смоллток в 1970 г. А. Кейем и группой его коллег (одним из которых
был автор) в Исследовательском центре в Пало-Альто фирмы Xerox. Смоллток
состоит исключительно из объектно-ориентированных конструкций,
которые делают описание языка компактным и весьма универсальным, но,
с другой стороны, так как все в языке |
||
|
|||
|
|||
Языки программирования 89 |
|||
|
|||
представлено объектами, некоторые
важные механизмы структурирования данных невозможно реализовать
эффективно.
Программный объект состоит из
структур данных и алгоритмов. Каждый объект «знает», как выполнять
операции со своими собственными данными, но для остальной программы объект
может трактоваться как черный ящик, о внутренней работе которого
ничего знать не нужно. Действительно, различные объекты могут пользоваться
совершенно разными алгоритмами при выполнении заданий, определенных одним
и тем же ключевым словом. Очевидно, что так же, как пингвины, лошади и
сороконожки пользуются различными способами для осуществления действия,
которое можно назвать ходьбой, так и объекты, чьи данные состоят из целых
чисел, массивов или комплексных чисел, будут пользоваться различными
методами для выполнения операции сложения.
Я и мои коллеги по фирме Apple
Computer Inc. разработали язык, названный Класкаль, который дополнил
базовые структуры Паскаля концепцией классов объектов. Класкаль, Смоллток,
Симула и несколько других объектно-ориентированных языков позволяют
объектам в классе наследовать свойства от суперклассов, к которым они
принадлежат, так что каждый класс не приходится строить, начиная с нуля.
Нужно лишь описать особенности, отличающие данный класс. Так, к
пингвинам, лошадям и сороконожкам
применимо общее понятие ног. Отличаются они количеством ног и
особенностями способа передвижения. Наследование является еще одним
механизмом абстракции, позволяющим многим подклассам использовать
свойства класса.
Особенно полезным наследование
оказалось в создании графического программного обеспечения — области,
которая сейчас бурно развивается. На графических образах можно
построить целые языки программирования. Действительно, даже некоторые
компьютерные игры, построенные в основном на графике, имеют определенные
черты языка программирования. Замечательным примером является игра
«Робот Одиссей 1», недавно предложенная фирмой Learning Company. В
«роботах», запрограммированных путем соединения электронных
логических вентилей и других компонент на
экране |
дисплея, можно пользоваться
понятиями условного выполнения и определения процедур. Дж. Лейниером и его
коллегами по фирме VPL Research в Пало-Альто сейчас разрабатывается полный
визуальный язык программирования, предварительно названный
Mandate.
Другим направлением, в котором
развиваются языки программирования, является использование параллельных
вычислений в мультипроцессорных системах. Казалось бы, что 100 процессоров
должны решать задачу в 100 раз быстрее, чем один. Но такого ускорения
можно достичь только тогда, когда программное обеспечение способно разбить
задачу на много частей, которые могут решаться
одновременно.
В некоторых языках предусмотрены
явные способы указания заданий, которые могли бы выполняться параллельно. Примером является язык
Оккам, разработанный фирмой British semiconductor manufacturer
Inmos. В других языках предполагается, что компилятор будет
анализировать и выявлять то, что поддается распараллеливанию. Одним из таких языков является Компел (от
compute parallel — вычислять параллельно), над которым мы работали
совместно с X. Енеа в 1969 г. Программа на Компеле полностью состоит из
инструкций присваивания; их необязательно выполнять в том порядке, в
котором они написаны, — компилятор сам должен решить, какие вычисления
должны быть выполнены в первую очередь. Никакого компилятора для языка
Компел написано не было, но языки с подобным механизмом (названные
«языками, управляемыми данными», — "data — flow") сейчас уже
реализованы.
Огромное разнообразие языков
программирования делает невозможным их классификацию по некоторой
единой шкале. Не существует самого лучшего языка программирования, как не
существует и самого лучшего естественного языка. «Я разговариваю
по-испански с богом, по-итальянски с женщинами, по-французски с мужчинами и по-немецки
с моей лошадью», — сказал Карл V (вероятно, по-французски). Выбор
языка программирования, по-видимому, также должен определяться целями его
предполагаемого применения. |
||
|
|||