Глава 5. ЯЗЫКИ ПРОГРАМММИРОВАНИЯ
Эволюция языков программирования
Электронно-вычислительные машины первого поколения находили свое применение (как правило) в сфере научных исследований при проведении сложных расчетов. Программы для них писались непосредственно в машинных кодах, а основными носителям информации были перфокарты и перфоленты. Писать такие программы (состоящих из тысяч команд) - занятие утомительное. Программист должен был помнить комбинацию нулей и единиц двоичного кода каждой программы, а также двоичные коды адресов данных, используемых при её выполнении. Со временем стало понятно, что гораздо проще написать программу на каком-нибудь языке, более близком к естественному, а работу по переводу этой программы в машинные коды поручить компьютеру. Так возникли языки, предназначенные специально для написания программ, - языки программирования (формальная знаковая система, предназначенная для записи программ).
Первым полноценным языком программирования стал язык Assembler - язык программирования низкого уровня, представляющий собой формат записи машинных команд, удобный для восприятия человеком (благодаря ему заметно уменьшилось время разработки и возросла надежность работы программ). Команды этого языка один в один соответствуют командам процессора и, фактически, представляют собой удобную символьную форму записи (мнемокод) команд и их аргументов. Кроме того, язык обеспечивает базовые программные абстракции - связывание частей программы и данных через метки с символьными именами и директивы, которые позволяют включать в программу блоки данных (описанные явно или считанные из файла), повторить определённый фрагмент указанное число раз, компилировать фрагмент по условию, задавать адрес исполнения фрагмента, менять значения меток в процессе компиляции, использовать макроопределения с параметрами и др. Язык ассемблера – машинозависимый. Это обусловлено тем, что каждая команда языка ассемблера обычно транслируется точно в одну команду машинного языка. Так как машинные языки у различных ЭВМ были разные, то и «ассемблеры» тоже различаются.
Следующий шаг - 1954 год, когда был разработан первый язык высокого уровня – Fortran (FOR mula TRAN slator – переводчик формул), широко используемый при написании программ для сложных научных и инженерных вычислений. Языки высокого уровня имитируют естественные языки, используя некоторые слова разговорного языка и общепринятые математические символы. Они более удобны для человека (с помощью них, можно писать программы до нескольких тысяч строк длиной). Однако легко понимаемый в коротких программах, этот язык становился трудно читаемым и управляемым, когда дело касалось больших программ. Эта задача была решена посредством создания языков структурного программирования, таких как Algol (ALGO rithmic L anguage - алгоритмический язык) – 1958 г., Pascal (высокоуровневый язык программирования общего назначения) – 1970 г., C (стандартизированный процедурный язык программирования) – 1972 г.
Структурное программирование предполагает точно обозначенные управляющие структуры, программные блоки, отсутствие инструкций безусловного перехода (GoTo), автономные подпрограммы, поддержка рекурсии (вычисление функций по определенному алгоритму) и локальных переменных. Суть такого подхода заключается в возможности разбиения программы на составляющие элементы.
В это же время создавались функциональные (аппликативные) языки, например, Lisp (LIS t P rocessing – «обработка списков») – 1958 г. и логические языки, например, Prolog (PRO gramming in LOG ic – программирование в терминах логики) - 1972 г. Хотя структурное программирование, при его использовании, дало выдающиеся результаты, но даже оно оказывалось трудным для исполнения тогда, когда программа достигала определенной длинны. Для того чтобы написать более сложную (и длинную) программу, нужен был новый подход к программированию.
В итоге в конце 70-х – начале 80-х гг. прошлого столетия были разработаны принципы объектно-ориентированного программирования, сочетающие лучшие принципы структурного программирования с новыми мощными концепциями, базовые из которых называются инкапсуляцией, полиморфизмом и наследованием. Примером объектно-ориентированных языков являются: Object Pascal (объектно-ориентированный диалект языка Pascal), C++ (компилируемый строго типизированный язык программирования общего назначения, основанный на синтаксисе C), Java (в достаточной степени схож с С++) и др. Объектно-ориентированные языки позволяют оптимально организовать программы, разбивая проблему на составные части и работая с каждой из них по отдельности. Программа на объектно-ориентированном языке, решая некоторую задачу, по сути, описывает часть мира, относящуюся к этой задаче.
Большинство компьютерных архитектур и языков программирования ориентированы на последовательное выполнение операторов программы, но в настоящее время существуют программно-аппаратные комплексы, позволяющие организовать параллельное выполнение различных частей одного и того же вычислительного процесса. Для программирования таких систем необходима специальная поддержка со стороны средств программирования, в частности, языков программирования. Некоторые языки общего назначения содержат в себе элементы поддержки параллелизма, однако программирование истинно параллельных систем требует специальных приемов. Для этой цели были созданы язык Occam (процедурный язык параллельного программирования высокого уровня) – 1982 г., Linda (язык многопроцессорных систем распределенной обработки данных) – 1985 г. и др.
Появление и активное развитие компьютерных сетей, широкое распространение высокопроизводительных компьютеров и ряд других факторов стали причиной создания многочисленных версий так называемых скриптовых языков. Эта языки первоначально ориентировались на использование в качестве внутренних управляющих языков во всякого рода сложных системах. Однако многие из них вышли за пределы сферы своего изначального применения и используются ныне во многих областях. Характерными особенностями данных языков являются, во-первых, их интерпретируемость (компиляция либо невозможна, либо нежелательна), во-вторых, простота синтаксиса и, в-третьих, легкая расширяемость. Они идеально подходят для использования в часто изменяемых небольших программах. К ним относятся – многоцелевой язык для решения системных задач Perl (1987), язык для описания сложного поведения веб-страниц JavaScript (1995) и др.
Со времени создания первых программируемых машин придумано тысячи языков программирования и каждый год их число пополняется новыми. Некоторыми языками умеют пользоваться лишь небольшое число их создателей, другие становятся известными миллионам людей. В развитии языков программирования специалисты отмечают пять поколений, которые постепенно улучшая свои характеристики, становятся всё более доступными в освоении пользователем. К языкам первого поколения относятся машинно-зависимые языки. Второе поколение характеризуется созданием языков программирования ассемблерного типа. Третье поколение начинается с появления первого языка высокого уровня для решения инженерно-технических и научных задач. Языками четвертого поколения являются языки параллельного программирования, которые в отличие от всех ранее созданных средств ориентированы, прежде всего, на создание системного и прикладного программного обеспечения для вычислительных сред нетрадиционной параллельной архитектуры. Естественные языки программирования, разрабатываемые в настоящее время, составят пятое поколение и позволят непрофессиональному пользователю определять необходимые процедуры обработки информации, используя предложения языка, весьма близкого к естественному и не требующего соблюдения особого синтаксиса.
5.2. Понятие «язык программирования»
Электронно-вычислительная машина непосредственно выполняет программы на машинном языке программирования данной ЭВМ. При этом программа представляет собой набор отдельных команд для компьютера. Эти команды являются достаточно простыми (например, сложение, умножение, сравнение или пересылка отдельных данных и др.). Каждая команда содержит в себе сведения о том, какая операция должна быть выполнена (код операции), с какими операндами (адреса данных или непосредственно сами данные) выполняются вычисления и куда (адрес) должен быть помещен результат.
Машинные языки были первыми языками программирования. Программирование на них затруднительно ввиду того, что, во-первых, эти языки различны для каждого типа ЭВМ, во-вторых, являются трудоемкими для большинства пользователей по причине необходимости знания особенностей конкретной ЭВМ и большого количества реализуемых ею операций (команд). Человеку свойственно формулировать и решать задачи в выражениях более общего характера, чем команды ЭВМ. Поэтому с развитием программирования появились языки, ориентированные на более высокий уровень абстракции при описании решаемой на ЭВМ задачи.
Язык программирования - это формальная знаковая система, предназначенная для записи программ (строится в соответствии с той или иной базовой моделью вычислений, т.е. совокупностью стратегий, методов, подходов и понятий, определяющих стиль программирования). Программа обычно представляет собой некоторый алгоритм записанный в форме, понятной для исполнителя (компьютера). Язык программирования определяет набор лексических, синтаксических и семантических правил, используемых при составлении компьютерной программы. Он позволяет программисту точно определить на какие события будет реагировать компьютер, как будут храниться и передаваться данные, а также какие именно действия следует выполнять над этими данными при различных обстоятельствах (условиях). Лексика (словарный запас) является центральной частью языка, именующей, формирующей и передающей знания об объектах реальной действительности. Синтаксис – сторона языка программирования, которая описывает структуру программ как наборов символов. Семантика в программировании – это система правил, определяющих поведение отдельных языковых конструкций (определяет смысловое значение предложений алгоритмического языка).
Создатели языков по-разному толкуют понятие «язык программирования». К наиболее распространенным утверждениям, признаваемым большинством разработчиков, относятся следующие. Язык программирования предназначен для написания компьютерных программ, применяемых для передачи компьютеру инструкций по выполнению того или иного вычислительного процесса и организации управления отдельными устройствам. Язык программирования – это способ передачи команд, приказов, четкого руководства к действию. Язык программирования использует специальные конструкции для определения и манипулирования структурами данных и управления процессом вычислений. В отличие от естественных языков, язык программирования имеет ограниченный запас слов (операторов) и строгие правила их написания, а правила синтаксиса и семантики, как и для любого формального языка, однозначно и четко сформулированы.
Языки программирования, ориентированные на команды процессора и учитывающие его особенности, называют языками низкого уровня. Терминология «низкий уровень» означает близость операторов языка к машинному коду. Наиболее используемый и популярный язык низкого уровня - ассемблер. Каждый оператор этого языка представляет в виде мнемокодов команду микропроцессора (что позволяет запоминать команды не в виде последовательности двоичных нулей и единиц, а в виде осмысленных сокращений слов человеческого языка – обычно английских). Для каждого типа микропроцессора создаётся свой ассемблер, поддерживающий все его команды. При помощи языков низкого уровня создаются компактные и быстродействующие программы, так как программист получает доступ ко всем возможностям процессора (однако, при этом необходимо хорошо понимать устройство компьютера, а написанная программа не может быть использована на компьютере с процессором другого типа). Такие языки программирования применяются для написания небольших системных приложений, драйверов устройств, модулей стыков с нестандартным оборудованием, когда важны компактность, быстродействие, прямой доступ к аппаратным ресурсам.
Языки программирования, которые имитируют естественные и обладают укрупненными командами, удобными для использования программистом, называют языками высокого уровня. Основная черта таких языков - это абстракция (введение смысловых конструкций, кратко описывающих такие структуры данных и операции над ними, которые воспроизвести на низкоуровневом языке программирования весьма затруднительно). Особенности конкретных компьютерных архитектур в них не учитываются, поэтому исходные тексты программ легко переносимы на другие платформы, имеющие трансляторы этого языка. Короче говоря, высокоуровневые языки стремятся не только облегчить решение сложных задач программирования (программы на языках высокого уровня с помощью понятных и мощных команд писать значительно проще, а число ошибок, допускаемых в процессе программирования, намного меньше), но и упростить портирование (переносимость) программного обеспечения. Использование разнообразных трансляторов и интерпретаторов обеспечивает связь программ, написанных при помощи языков высокого уровня, с различными операционными системами и оборудованием. Однако, «оторванность» высокоуровневых языков от аппаратной реализации компьютера помимо плюсов имеет и минусы. В частности, она не позволяет создавать простые и точные инструкции к использованию оборудования. Следствием этого становится нормой добавление поддержки современных профессиональных высокоуровневых языков программирования языком низкого уровня (языком ассемблера).
Современной тенденцией является появление языков программирования ультра высокого уровня. Такого рода языки характеризуются наличием дополнительных структур и объектов, ориентированных на прикладное использование. Прикладные объекты, в свою очередь, требуют минимальной настройки в виде параметров и моментально готовы к использованию. Использование ультра-высокоуровневых языков программирования снижает временные затраты на разработку программного обеспечения и повышает качество конечного продукта за счет, опять таки, уменьшения объема исходных кодов.
Кроме того, языки программирования могут быть разделены на компилируемые и интерпретируемые. Программа, написанная на компилируемом языке, при помощи компилятора (переводящего текст программы с высокоуровневого языка программирования в эквивалентную программу на машинном языке) преобразуется в набор инструкций для данного типа процессора и далее записывается в исполняемый модуль, который может быть запущен на выполнение как отдельная программа (при этом осуществляется поиск синтаксических ошибок, выполняется семантический анализ, нередко производится оптимизация с помощью набора методов, позволяющих повысить быстродействие программы и др.). Если программа написана на интерпретируемом языке, то интерпретатор (осуществляющий пооператорную обработку исходной программы) непосредственно выполняет ее (по тексту) без предварительного перевода. При этом программа сохраняет исходный текст (язык) и не может быть запущена без интерпретатора. Короче говоря, компилятор переводит программу, написанную на высокоуровневом языке, на машинный язык сразу и целиком, создавая при этом отдельную программу, а интерпретатор переводит на машинный язык прямо во время исполнения программы.
Как правило, скомпилированные программы выполняются быстрее и не требуют для этого (своего выполнения) дополнительных программ, так как уже переведены на машинный язык. Вместе с тем при каждом изменении текста программы требуется ее перекомпиляция, что создает определенные трудности. Кроме того, скомпилированная программа может выполняться только на том же типе компьютеров и, как правило, под той же операционной системой, на которую был рассчитан компилятор. Чтобы создать исполняемый файл для машины другого типа, требуется новая компиляция. Интерпретируемые языки обладают некоторыми специфическими возможностями (непосредственно исполняют текст без предварительного перевода), поэтому программы на них можно запускать сразу же после изменения. Программа на интерпретируемом языке может быть зачастую запущена на разных типах машин и операционных систем без дополнительных условий. Однако интерпретируемые программы выполняются заметно медленнее, чем компилируемые и, кроме того, они не могут выполняться без дополнительной программы-интерпретатора.
Разделение на компилируемые и интерпретируемые языки является несколько условным. Так, для любого традиционно компилируемого языка (например, Pascal) можно написать интерпретатор, а для любого интерпретируемого языка можно создать компилятор (например, Lisp – изначально интерпретируемый, может компилироваться без каких бы то ни было ограничений). В реальных системах программирования (например, Java) смешаны технологии компиляции и интерпретации. В процессе отладки такие программы можно выполнять по шагам (трассировать), а результирующий код не обязательно будет машинным. Он может быть, например, аппаратно-независимым промежуточным кодом абстрактного процессора, который в дальнейшем будет транслироваться в различных компьютерных архитектурах с помощью интерпретатора или компилятора в соответствующий машинный код. Этот подход позволяет использовать плюсы как интерпретаторов, так и компиляторов.
Итак, процесс создания программы в общем виде включает:
· составление исходного кода программы на языке программирования;
· этап трансляции, необходимый для создания объектного кода программы;
· построение загрузочного модуля, готового к исполнению.
Все перечисленные действия требуют наличия специальных программных средств, совокупность которых образует систему программирования. К ним относятся:
· текстовый редактор;
· компилятор;
· редактор связей;
· отладчик;
· библиотеки функций;
· справочная система.