Файл: Обзор языков программирования высокого уровня(Теоретические основы языков программирования высокого уровня ).pdf

ВУЗ: Не указан

Категория: Курсовая работа

Дисциплина: Не указана

Добавлен: 20.05.2023

Просмотров: 204

Скачиваний: 2

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Итак, чем выше уровень языка, тем меньше трудоемкость программиро­вания на нем. Машинные же языки современных ЭВМ практически не используются для непосредственного программирования.

2 Классификация языков программирования высокого уровня

2.1 Виды языков программирования высокого уровня по способу трансляции

Со времени создания первых программируемых машин человечество придумало около десяти тысяч языков программирования. Некоторыми языками умеет пользоваться только небольшое число их собственных разработчиков, другие известны миллионам людей.

Языки программирования могут классифицироваться по различным основаниям, общепринятой системы в этом вопросе не выработано.

Выше была рассмотрена классификация языков программирования по уровню взаимодействия с аппаратным обеспечением (языки программирования высокого и низкого уровня).

Можно выделить два принципиально разных способа реализации языков программирования: компиляция и интерпретация. Распространено заблуждение, согласно которому способ реализации является присущим конкретному языку свойством. В действительности, это деление до определенной степени условно. В ряде случаев язык имеет формальную семантику, ориентированную на интерпретацию, но все или почти все его действительные реализации являются компиляторами, порой весьма эффективно оптимизирующими (примерами могут служить языки семейства ML, такие как Standard ML, Haskell). Кроме того, большинство современных «чистых» интерпретаторов не исполняют конструкции языка непосредственно, а компилируют их в некоторое высокоуровневое промежуточное представление (например, с разыменованием переменных и раскрытием макросов). Для любого интерпретируемого языка можно создать компилятор - например, язык Лисп, изначально интерпретируемый, может компилироваться без каких бы то ни было ограничений. [1, c. 208].

Рассмотрим различия компиляции и интерпретации как двух разновидностей трансляции. Программу, являющуюся входными данными транслятора, будем называть исходной программой (source program). Компилятор (compiler) переводит исходную программу в эквивалентную программу на языке, понятном компьютеру, то есть на машинном языке (рис. 2).

Рисунок 2 – Процесс компиляции и последующего выполнения программы

Процесс компиляции состоит из двух частей: анализа и синтеза. Анализирующая часть компилятора разбивает исходную программу на составляющие ее элементы (конструкции языка) и создает промежуточное представление исходной программы. Синтезирующая часть из промежуточного представления создает новую, целевую, программу. Отметим, что однажды полученная в результате компиляции целевая программа может в дальнейшем выполняться много раз с различными входными данными. Далеко не всегда исходные программы корректны с точки зрения исходного языка. Более того, некорректные программы подаются на вход компилятору значительно чаще, чем корректные – таков современный процесс разработки программ. Поэтому крайне важной частью процесса компиляции является точная диагностика ошибок, допущенных во входной программе.


В отличие от компилятора интерпретатор не создает никакой новой программы. Входными данными интерпретатора является не только исходная программа, но и входные данные самой исходной программы (рис. 3).

Рисунок 3 – Процесс интерпретации

Интерпретатор, так же, как и компилятор, анализирует программу на входном языке, создает промежуточное представление, а затем выполняет операции, содержащиеся в тексте этой программы. Например, интерпретатор может построить дерево разбора, а затем выполнить операции, которыми помечены узлы этого дерева. В том случае, если исходный язык достаточно прост (например, если это язык ассемблера или Basic), то никакое промежуточное представление не нужно, и тогда интерпретатор – это простой цикл. Он выбирает очередную инструкцию языка из входного потока, анализирует и выполняет ее. Затем выбирается следующая инструкция. Этот процесс продолжается до тех пор, пока не будут выполнены все инструкции, либо пока не встретится инструкция, означающая окончание процесса интерпретации.

Понятно, что при повторном запуске программы она должна интерпретироваться с самого начала. Интерпретация приводит к более гибкой и лучшей диагностике ошибок, чем компиляция. Поскольку исходная программа исполняется непосредственно, интерпретатор может включать хороший отладчик (debugger). Кроме того, интерпретатор может легко справиться с языками, позволяющими создавать программы, некоторые характеристики которых (например, размеры и типы переменных) могут зависеть от входных данных [13, c. 17].

Как правило, скомпилированные программы выполняются быстрее и не требуют для выполнения дополнительных программ, так как уже переведены на машинный язык. Вместе с тем, при каждом изменении текста программы требуется её перекомпиляция, что замедляет процесс разработки. Кроме того, скомпилированная программа может выполняться только на том же типе компьютеров и, как правило, под той же операционной системой, на которую был рассчитан компилятор. Чтобы создать исполняемый файл для машины другого типа, требуется новая компиляция. Интерпретируемые языки позволяют запускать программы сразу же после изменения, причём на разных типах машин и операционных систем без дополнительных усилий, а гомоиконные – и вовсе динамически перемещать программу между разными машинами без прерывания её работы.

Итак, компилятор переводит программу на машинный язык сразу и целиком, создавая при этом отдельную программу, а интерпретатор переводит на машинный язык прямо во время исполнения программы. Примеры компилируемых языков - C, C++, Pascal, примеры интерпретируемых - PHP, JavaScript, Python. Распространен и смешанный подход. Например, Java и С# относятся именно к компилируемо-интерпретируемым языкам программирования. А именно, программа компилируется не в машинный язык, а в машинно-независимый код низкого уровня, байт-код. Далее байт-код выполняется виртуальной машиной. Для выполнения байт-кода обычно используется интерпретация, хотя отдельные его части для ускорения работы программы могут быть транслированы в машинный код непосредственно во время выполнения программы по технологии компиляции «на лету» (Just-in-time compilation, JIT).


2.2 Императивные языки программирования высокого уровня

Под парадигмой программирования понимается набор представлений о некотором классе программных систем, допускающих реализацию с помощью этой парадигмы «способа мыслить о компьютерной системе» [2, с. 176]. Таким образом, парадигма не есть язык или система программирования, а некоторая логика, в рамках которой программы могут писаться на разных языках. Языки программирования ориентированы на те или иные парадигмы, и, соответственно, представляют специальные средства для удобства ее реализации.

Можно выделить две важнейших парадигмы – императивную и декларативную.

Программа, написанная на императивном языке, явно указывает способ получения желаемого результата, не определяя при этом его ожидаемых свойств. Процедура получения желаемого результата имеет вид последовательности операций, поэтому для императивных языков характерно указание порядка выполнения операторов. В основе такого программирования лежат извлечение из памяти значения некоторой переменной, совершение над ним действия и сохранение нового значения с помощью оператора присваивания. Императивные языки тесно связаны с фон-неймановской моделью вычислений [8, с. 82].

Структурное программирование — парадигма программирования (также часто встречающееся определение — методология разработки), которая была первым большим шагом в развитии императивного программирования. Основоположниками структурного программирования были Э. Дейкстра и Н. Вирт. Языками-первопроходцами в этой парадигме были Fortran, Algol и B, позже их приемниками стали Pascal и C. Эта парадигма ввела новые понятия, объединяющие часто используемые шаблоны написания императивного кода. В структурном программировании программист по-прежнему оперирует состоянием и инструкциями, однако вводится понятие составной инструкции (блока), инструкций ветвления и цикла [7].

Следующим шагом стал процедурный подход. При этом подходе какой-то код программы мог объединяться в отдельные блоки (подпрограммы – процедуры и функции). После этого такой блок команд можно вызывать из любой части программы. Любая процедура может быть вызвана из любой точки программы, включая другие процедуры или ее же саму (рекурсивный вызов). Передача данных из главной программы в подпрограмму и возврат результата выполнения функции осуществляются с помощью параметров. Параметром называется переменная, которой присваивается некоторое значение в рамках указанного применения. Различают формальные параметры – параметры, определенные в заголовке подпрограммы, и фактические параметры – выражения, задающие конкретные значения при обращении к подпрограмме. При обращении к подпрограмме ее формальные параметры замещаются фактическими, переданными из главной программы [11, c. 197].


Процедурное программирование широко используется в крупномасштабных проектах, когда на первый план выходят следующие преимущества:

  • легкость повторного использования фрагментов кода, оформленных как процедуры (например, в библиотеках);
  • легкость прослеживания логики программы;
  • возможность сопровождения программного продукта через длительное время после написания кода или кем-то, кроме его автора.

Следует отметить, что принципиально разными типами подпрограмм являются процедуры и функции. Их отличие состоит в том, что функции возвращают некоторое значение, ассоциированное с их именем; этот результат может использоваться в выражениях. В качестве математического примера можно привести выражение y:=sin(x)+cos(x); здесь синус и косинус – это функции. Процедуры такого значение не имеют; все значения возвращаются только через изменение значений параметров. В некоторых языках существуют отдельные инструменты для реализации функций и процедур (например, в Pascal – это procedure и function), в других такого разделения не производится (например, в С++ используются только функции, но можно указать, что она возвращает «пустое» - void – значение).

Развитием процедурного программирования стало модульное программирование, позволяющее реализовать более масштабные проекты. В соответствии с этой парадигмой подпрограммы группируются в отдельные файлы – модули – которые уже образуют проект в целом. Это упрощает отладку программы в целом и более удобно при работе команды разработчиков.

Еще одной парадигмой в рамках императивного программирования, приобретшей популярность в последние годы, стало объектно-ориентированное программирование. Основная идея объектно-ориентированной парадигмы заключается в построении модели на основе выделения понятий предметной области с последующим распределением обязанностей между ними. Программа, решающая пашу задачу, строится так, как задача решалась бы в жизни. По сути, строится модель предметной области, понятия предметной об­ласти представляются в виде классов, а их конкретные проявления в виде объ­ектов. Распределяя обязанности между классами, проектировщик приложения «оживляет» объекты, наделяет их поведением. Оживление (Animation) являет­ся основным принципом, используемым при построении архитектуры объектно-ориентированного приложения [10, c. 181].

Моделирование осуществляется декомпозицией не по действиям (глаголам), как было принято в императивном программировании до появления объектно-ориентированной парадигмы, а по сущностям (суще­ствительным). Сущности представляют собой, как правило, понятия предмет­ной области, но иногда искусственные понятия, которых нет в явном виде в предметной области, но без которых невозможно создать масштабируемый код.


При оживлении пользователю объектов совсем не обязательно знать внут­реннюю структуру объекта, так же как в реальной жизни пользователь обыч­но не знает, как в точности устроена микроволновая печь или телевизор, что не мешает ему разогревать еду и просматривать телепередачи. Скрытие внут­ренней реализации объекта от пользователя — это обычная практика, которая называется инкапсуляцией. Важна только функциональность объекта, его ин­терфейс. Такой подход позволяет ограничить распространение изменений в ко­де класса на все приложение. Это один из важнейших принципов объектно-ориентированного проектирования.

Удобный подход к организации программ «отдельная работа отдельно программируется и отдельно выполняется» успешно показал себя при развитии операционной системы UNIX как работоспособный принцип декомпозиции программ. Но существуют задачи, например, реализация систем программирования, в которых прямое следование такому принципу может противоречить требованиям к производительности. Возможен компромисс «отдельная работа программируется отдельно, а выполняется взаимосвязано с другими работами», что требует совмещения декомпозиции программ с методами сборки – комплексации или интеграции программ из компонентов. Рассматривая комплексацию как еще одну «отдельную» работу, описываемую, например, в терминах управления процессами, можно констатировать, что эта работа больше определяет требования к уровню квалификации программиста, чем объем программирования. При достаточно объективной типизации данных и процессов, возникающих при декомпозиции и сборке программ определенного класса, строят библиотеки типовых компонентов и разрабатывают компонентные технологии разработки программных продуктов – Corba, COM/DCOM, UML и т. п. Одна из проблем применения таких компонентов – их обширность.

Таким образом, ООП может отражать эволюцию подходов к организации структур данных на уровне задач и программ их решения, исходя из парадигмы императивно-процедурного программирования. От попыток реализации математически корректных абстрактных типов данных произошел практичный переход к технически простому статическому контролю типов данных при разработке и применении расширяемых программ. Расширение программы выполняется декларативно, а выбор нужного варианта при исполнении функций, обладающих неединственным определением, – в зависимости от типа данных. Введены дополнительные механизмы: инкапсуляция, уточнение типов данных при компиляции и выбор обработчиков данных, управляемый типами данных.