СИСТЕМЫ ПРОГРАММИРОВАНИЯ
Лекция
СИСТЕМЫ ПРОГРАММИРОВАНИЯ
Под системой программирования понимают совокупность языка программирования и виртуальной машины, обеспечивающей выполнение на реальной машине программ, составленных на этом языке.
Языком программирования называют систему обозначений, служащих в целях точного описания алгоритмов для ЭВМ.
Виртуальная машина - это программный комплекс, эмулирующий работу реальной машины с определенным входным языком на ЭВМ с другим машинным языком, а иными словами реализующий входной язык программирования.
Главным классифицирующим признаком языков и, следовательно, систем программирования, является принадлежность к одному из оформившихся к настоящему времени стилей программирования, основные среди которых - процедурное, функциональное, логическое и объектно-ориентированное.
Кратко охарактеризуем основные стили программирования и рассмотрим относящиеся к ним системы программирования.
- Процедурное программирование
Процедурное (императивное) программирование является отражением архитектуры традиционных ЭВМ, которая была предложена фон Нейманом в 40-х годах.
Программа на процедурном языке программирования состоит из последовательности операций (инструкций), задающих те или иные действия. Основным является оператор присваивания, служащий для изменения содержимого областей памяти. Вообще концепция памяти, как хранилища значений, содержимое которого может обновляться операторами программы, является фундаментальной в императивном программировании.
Выполнение программы сводится к последовательному выполнению операторов с целью преобразования исходного состояния памяти (т.е. значений переменных) в заключительное. Таким образом, с точки зрения программиста имеется программа и память, причем первая последовательно обновляет содержимое памяти.
Процедурные языки характеризуются:
значительной сложностью;
отсутствием строгой математической основы;
необходимостью явного управления памятью, в частности необходимостью описания переменных;
малой пригодностью для символьных вычислений;
высокой эффективностью реализации на традиционных ЭВМ.
Из-за наличия побочных эффектов (т.е. взаимного влияния различных программных модулей через общую память) программы на таких языках трудно читаемы, плохо модифицируемы и трудно проверяемы, следовательно, ненадежны. По этой же причине они предполагают лишь последовательное выполнение.
Одним из важнейших классификационных признаков процедурных языков является их уровень.
Уровень языка программирования определяется семантической (смысловой) емкостью его конструкций и его ориентацией на программиста-человека. Язык программирования (частично) ликвидирует семантический разрыв между методами решения задач человеком и машиной. Чем более язык ориентирован на программиста, тем выше его уровень.
Распределение основных реализованных на ПЭВМ императивных языков по уровням приведено на рис. 1.
Языки:
АРL
Выше Modula - 2 Языки
Pascal высокого
уровня
Развитие версии Basica
Уровень Fortran
Простой Basic
C
Язык Макроассемблера
Язык детализированных
схем программ
Ниже Простой язык Ассемблера
Шестнадцатеричный язык
Двоичный язык
Рис. 1. Уровни процедурных языков
Двоичный язык является не чем иным, как непосредственно машинным языком, в настоящее время такие языки программистами не применяются.
Шестнадцатеричный язык обеспечивает некоторое упрощение записи программы на машинном языке путем представления четырех цифр одной шестнадцатеричной. Этот язык используется в качестве дополнения к языкам высокого уровня, таким как Pascal, для программирования критичных к времени выполнения фрагментов алгоритмов.
Язык Ассемблера - это язык, предназначенный для представления в удобночитаемой символической форме программ, записанных на машинном языке. Он позволяет программисту пользоваться мнемоническими кодами операций, по своему усмотрению присваивать символические имена регистрам ЭВМ и ячейкам памяти, а также задавать наиболее удобные в том или ином контексте схемы адресации.
Язык детализированных схем программ - это не язык программирования, а язык представления алгоритмов при разработке программ, некогда широко используемый. В связи с достаточно низким уровнем этот язык в настоящее время на практике почти не применяется.
Язык Макроассемблера является расширением языка Ассемблера за счет включения макросредств. С их помощью предоставляется возможность описывать в программе последовательности инструкций с параметрами (макроопределения) и использовать снабженные аргументами макрокоманды, которые автоматически замещаются в процесс ассемблирования макрорасширениями, представляющими собой макроопределения с подставленными вместо параметров аргументами.
Языки высокого уровня будут рассмотрены в Лекции 13.
- Функциональное программирование
Кратко и точно сущность функционального (аппликативного) программирования определена А.П.Ершовым, как «...способ составления программ в которых единственным действием является вызов функций, единственным способом расчленения программы на части является введение имени функций, а единственным правилом композиции - оператор суперпозиции функции. Никаких ячеек памяти, ни операторов присваивания, ни циклов, ни, тем более, блок схем, ни передачи управления».
Роль основной конструкции в функциональных языках играет выражение. К выражениям относятся скалярные константы, сконструированные объекты, функции.
Любой аппликативный язык программирования включает следующие элементы:
1.Классы констант, которыми могут манипулировать функции;
2.Набор базовых функций, которые программист может использовать без предварительного определения;
3.Правила построения новых функций из базовых;
4.Правила формирования выражений на основе вызовов функций.
Программа представляет собой совокупность описаний функций (возможно вложенных) и выражения, которое необходимо вычислить. Оно вычисляется посредством редукции (т.е. серии упрощений) до тех пор, пока это возможно.
Функциональное программирование не использует концепцию памяти как хранилища значений переменных. Операторы присваивания отсутствуют, вследствие чего переменные обозначают не области памяти, а объекты программы, что полностью соответствуют понятию переменных в математике. Кроме того, нет существенных различий между функциями и константами, т.е. между программами и данными. В результате этого функция может быть значением вызова другой функции (функции порядка выше первого), и может быть элементом структуированного объекта. Число аргументов в вызове функции не обязательно должно совпадать с числом параметров при ее описании. При недостатке аргументов значением вызова будет функция, а при избытке, либо функция, либо константа, если такой вызов имеется.
Аппликативное программирование можно считать дальнейшим развитием идей структурного программирования за счет структуризации не только управляющих средств связей и данных, но и информационных связей. Действительно, процедурным языкам неотъемлемо присуща неупорядоченная связь по данным между различными участками программы. В аппликативной же программе любой программной функции аргументы могут быть переданы только явно, посредством вызова последней, а вычисление значения функций не способно привести к изменению значений каких-либо переменных. Это обеспечивает ясную иерархическую структуру программ (хорошую читабельность, проверяемость и совместимость), а следовательно, более высокую надежность. Функциональные языки отличаются своей простотой, легкостью реализации, компактностью представления алгоритмов, полностью автоматическим распределением памяти и пригодностью для символьных вычислений. Последнее объясняется тем, что по соображениям общности и эффективности реализации основными структуированными объектами в аппликативных языках являются списки (упорядоченные последовательности объектов, в том числе и списков), удобные для символьной обработки, кроме того, в аппликативном программировании легко организуется рекурсивная обработка структуированных объектов. Числовые же вычисления - не та область, где ярко проявляются достоинства функционального программирования.
Кроме преимуществ аппликативных языков как языков программирования, они также привлекательны в качестве средств описания семантики других языков, а также в роли средств проектирования программных комплексов.
Самым первым функциональным языком явился LISP (List Processing - обработка списков), разработанный и реализованный группой авторов под руководством Дж. Маккартни из Массачусстского технологического института в 1959 г. Цель его создания в удобстве обработки символьной информации.
LISP непрерывно совершенствовался и сейчас существует множество развитых его версий с превосходной средой программирования, среди которых особо следует отметить INTERLISP и Common LISP.
LISP послужил стартовой площадкой для разработки языков PLANNER и CONNIVER, которые реализуются для реализации процедурных моделей знаний.
Сейчас известно множество функциональных языков, значительно более мощных, чем LISP. Наиболее интересны из них ML (Milner Language) Милнера и Miranda Д Тернера.
С аппликативными тесно связаны языки машин потока данных. Среди последних известность приобрели VALID, VAL, ID и LUCID.
Разработка аппаратных и программных средств функционального программирования вошла составной частью в проекты ЭВМ пятого поколения.
Таким образом, функциональное программирование по сравнению с императивным, по крайней мере, в области символьных вычислений, имеет преимущество как с точки зрения пользователя, так и с точки зрения реализации (благодаря параллелизму).
На ПЭВМ в настоящее время из функциональных языков широко используются только LISP.
Для эффективной работы с системой функционального программирования необходимо мощная ПЭВМ, так как ресурсоемкость таких систем значительна.
- Логическое программирование
Французский ученый А.Кольмероэ создал язык PROLOG (PROgramming in LOGic - программирование в терминах логики), первоначально предназначенный для работы с естественными языками, и опубликовал его писание 1973 г.
Появление PROLOGa открыло новую область исследований - логическое или реляционное программирование, где практические результаты зачастую предшествуют их теоретическому осмыслению и обоснованию
Центральным понятием в логическом программировании является отношение. Программа представляет собой совокупность определенных отношений между объектами (в терминах, условий или ограничений) и цели (запроса). Процесс выполнения программы трактуется как процесс установления общезначимости логической формулы, построенной из программы по правилам, установленным семантикой того или иного языка.
Результат вычисления является побочным продуктом этого процесса. В реляционном программировании нужно только специфицировать факты, на которых алгоритм основывается, а не определять последовательность шагов, которые требуется выполнить. Это свидетельствует о декларативности языков логического программирования. Она метко выражена в формуле Р.Ковальского: «алгоритм = логика + управление».
Языки логического программирования характеризуются:
сверхвысоким уровнем;
жесткой ориентацией на символьные вычисления (числовая обработка затруднена);
зачастую логической неполнотой в двух аспектах: невозможность выразить в программе определенные логические конструкции, а также невозможностью получить из программы все правильные выводы.
Логические программы отличаются принципиально низким быстродействием, т.к. вычисления осуществляются методом проб и ошибок (посредством поиска с возвратами), а также высокой степенью параллелизма. Однако организация параллельного исполнения затруднительна; сам же параллелизм требует чрезвычайно много ресурсов ЭВМ.
Таким образом, языки логического программирования являются достаточно мощными, но неэффективными, с точки зрения реализации, языками.
Тем не менее языки логического программирования играют центральную роль в проектах ЭВМ пятого поколения.
В настоящее время, для ПЭВМ существует более15 реализаций PROLOGa. Наиболее удачными системами считаются Arity/Prolog 5.0 и Turbo Prolog 2.0, оформленные в виде интегрированных сред.
- Объектно-ориентированное программирование
Имя использования программных «объектов» развивалось в течение многих лет разными исследователями.
В самом общем виде парадигма объектно-ориентированного программирования может рассматриваться как способ управления сложностью: это взаимосвязанная совокупность ряда важных идей, работающих на нескольких уровнях.
На самом верхнем уровне находится понятие объекта. В физическом мире объектом может быть: автомобиль, человек, интегральная схема. Объекты обладают свойствами, такими как, например, цвет или размер. Они обнаруживают поведение, скажем начинают функционировать или менять состояние в ответ на определенный набор внешних воздействий.
Объекты реального мира можно использовать многократно, их не нужно каждый раз создавать вновь. Так, значительную долю схемных компонентов на печатных платах составляют стандартные, серийно изготовляе мые элементы. Существование таких элементов позволяет разработчику сосредоточиться на решении стоящей перед ним задачи вместо того, чтобы заново изобретать средства для ее решения.
Хотелось бы, чтобы также были устроены и программы. Ведь сколько сил при программировании уходит при реализации часто повторяющихся типовых задач - поиска, сортировки, чтения записи и т.п. Теоретически, при проектировании «сверху вниз» должна обеспечиваться модульность, благодаря которой отдельные программные компоненты будут хорошо стыковаться друг с другом. На практике же эта стыковка редко получается идеальной, и программные модули, полученные обычным способом, для своего повторного использования почти всегда требуют какой-либо модификации. Объектно-ориентированное программирование в корне меняет положение, снабжая, программные объекты встроенными характеристиками, которые помогают справиться со все возрастающей сложностью разработки программного обеспечения.
Три важнейших характеристики объектной парадигмы - это инкапсуляция, наследование и полиформизм.
- Инкапсуляция
Понятие инкапсуляции означает, что в качестве единого целого, называемого объектом, рассматривается некоторая структура данных, определяющая его свойства или атрибуты и некоторая группа функций. Например, в Borlaud C ++ свойства объектов хранятся в структурах данных, а поведение объектов реализуется в виде функций, называемых «функции-члены». Это представляет программисту широкие возможности в области управления доступом к атрибутам объектов и их функциям-членам. Например, если какие-либо атрибуты и функции объекта объявлены приватными, то к ним нет доступа извне, за исключением функций, объявленных дружественными данному объекту. Атрибуты и функции, объявленные общими, доступны любому внешнему объекту, а к тем которые объявлены защищенными, доступ имеют лишь некоторые из остальных объектов. Это особенность ООП называется ограничением доступа; она позволяет делать данные «невидимыми», и за счет этого добиваться, чтобы все манипуляции с данными выполнялись только через общие функции-члены.
Ограничение доступа повышает надежность и модифицируемость программ, ослабляя взаимозависимость между объектами. Фактически если общие функции - члены описаны корректно, то приватные структуры данных и функции-члены объекта можно изменять, не затрачивая программную реализацию других объектов. Этот ограниченный доступ к информации аналогичен тому, что мы наблюдаем с объектами реального мира где часто нет способа (и обычно нет необходимости) узнать во всех подробностях внутреннее устройство какого-либо объекта, например, телефонного аппарата.
- Наследование
Наследование позволяет одним объектам приобретать атрибуты и поведение других. Наследование помогает сделать разработку более экономной и обозримой, так как объекты пользуются одними и теми же атрибутами и формами поведения без дублирования реализующих их программных кодов.
Аналогией может служить схема, которой пользуются зоологи и ботаники для классификации живых организмов (Рис. 2).
Типы
... ... Классы
... ... Отряд
...
Семейство
Рис. 2. Соотношение основных понятий
По этой схеме растительное и животное царство делятся на группы, так называемые типы. Каждый тип, в свою очередь делится на классы, отряды, семейства и т.д. Группы более низкого уровня наследуют (разделяют) характеристики групп более высокого уровня.
Так из утверждения о том, что волк относится к семейству псовых, вытекает сразу несколько положений. Из чего следует, что у волков хорошо развиты слух и обоняние, поскольку таковы характеристики псовых. Так как псовые входят в отряд хищных, это утверждение говорит о том, что волки питаются мясом. Поскольку хищные относятся к млекопитающим, это говорит о том, что волки имеют волосяной покров и регулируемую температуру. Наконец, так как млекопитающие являются позвоночными, мы узнаем и то, что у волка есть позвоночник.
Программные объекты выстраиваются в иерархию примерно таким же образом. Например, объект высокого уровня - Окно. Любое окно на экране компьютера имеет определенное положение по координатам Х и У, а также высоту, ширину, стиль рамки и цвет фона. Если рассматривать меню, то можно отметить, что объект Меню обладает всеми свойствами объекта окна, но вдобавок имеет и ряд собственных - например, строки позиций меню и, возможно линейку прокрутки. Ниже Окна можно было бы поместить и Редактор, обладающий всеми характеристиками окна и, кроме того, способностью принимать символы с клавиатуры и манипулировать ими. Заметим, что как Меню так и Редактор можно с полным правом назвать Окнами, т.к. оба они имеют ширину, высоту и т.д., но при этом они различаются между собой по виду и способу функционирования.
Такая иерархия объектов может иметь много уровней. К примеру, при дальнейшей конкретизации объекта Редактор мы могли бы ввести нечто под названием Поле приглашений, которое наследует все черты Редактора, но ограничено одной строкой текста, перед которой стоит цепочка символов, образующих приглашение.
Важно отметить, что сами по себе классы это не объекты, а шаблоны для создания объектов. Когда нужно создается экземпляр класса (его обычно называют просто объектом), который и используется. Отношение между классом и экземпляром класса, такое же, как между типом данных и переменной.
В нашем примере мы рассмотрели атрибуты Окна и его производных классов, но было сказано, что объекты наследуют и поведение.
Создавая класс Окно, мы , конечно, предусмотрим функцию - член, позволяющую перемещать окно по экрану. Меню унаследует эту функцию от Окна, а это значит, что любое меню можно тоже перемещать по экрану, не программируя эту функцию. Объекты - меню используют для этого тот же программный код, что и объекты - окна. Это свойство, называемое многократностью использования кода, не только позволяет избежать ненужного дублирования программных кодов, но и гарантирует, что коль скоро функция запрограммирована корректно, она будет правильно работать со всеми объектами, входящими в иерархию. Иначе говоря, как только вы напишите функцию - член, которая правильно перемещает по экрану Окна, объекты всех классов; производных от Окна, тоже станут перемещаться правильно.
Кроме того, все такие объекты будут двигаться по экрану одинаково, а это большое преимущество для пользователя: весь создаваемый пакет в целом станет более согласованным и лучше увязанным.
- Полиформизм
Полиформизм - способность объекта реагировать на запрос (вызов метода) сообразно своему типу, при этом одно и то же имя метода может использоваться для различных классов объектов.
Полиформизм в сочетании с поздним связыванием весьма продуктивная идея.
Термины «раннее связывание» и «позднее связывание» относятся к этапу, на котором обращение к процедуре связывается с ее адресом. В случае раннего связывания адреса всех функций и процедур известны в тот момент, когда происходит компиляция и компановка программы. Это позволяет приписать каждому обращению к процедуре соответствующий адрес. В противоположность этому, в случае позднего связывания адрес процедуры не связывается с обращением к ней до того момента, пока обращение не произойдет фактически, т.е. во время выполнения программы.
Вернемся к нашему примеру с окнами. Нельзя заранее предсказать сколько окон будет на экране, каких они будут типов (Меню, Редактор и т.д.) и в какой последовательности пользователь будет с ними работать. В программе, где используется только ранее связывание, вся информация о количестве, координатах и типах окон хранится в основной программе.
Все возможные действия пользователя под окнами тоже должны быть предусмотрены в этой программе. Каждый раз, когда пользователь производит какие то действия с окном (будь то Редактор, Поле приглашения или что другое), программа должна разобраться, что именно и с каким именно окном произошло, и вызвать соответствующие процедуры для выполнения подлежащих действий с этим окном. Таким образом, программе приходится отслеживать очень многое, она усложняется и теряет гибкость. Стоит добавить один тип окна или изменить поведения одного из окон, и придется скорректировать программу во всех тех местах, где определяется, какие подпрограммы подлежат вызову.
Рассмотрим каким образом можно улучшить положение с помощью позднего связывания. Пусть одно окно частично перекрывает другое. Если «верхнее» окно передвинуто или закрыто, то нижнее следует перерисовывать для восстановления ранее перекрытой части.
Так как меню перерисовывается иначе, чем поле или редактор, то каждый объект в оконной иерархии должен знать, как перерисовать себя. Таким образом, в каждом классе будет своя функция - член, которая перерисовывает данный объект на экране. Следовательно, если требуется перерисовать объект, то программе не нужно анализировать к какому типу окна он относится (как это требовалось бы при раннем связывании). Она просто вызывает функцию - член данного объекта «Перерисовка» Объект исполняет эту свою функцию и корректно перерисовывает себя на экране.
Если пакет содержит семь типов окон, то в нем будет семь различных правил перерисовки, но все они будут называться «Перерисовка», но для каждого объекта это делается по своему, так как это нужно именно для него. Эта множественность форм, которые может принимать правило с одним и тем же именем, называется полиформизмом (греч. Многообразие). Программисту надо заботиться только о том, чтобы указать требуемое действие, а не о том, как это действие выполнить.
СИСТЕМЫ ПРОГРАММИРОВАНИЯ