Оценивая возможности поддержки ОО-концепций языками программирования, можно разделить их на три широкие категории (игнорируя самый низкий уровень, в основном, ассемблерных языков, не поддерживающих даже понятия подпрограммы):
Для категории инкапсулирующих языков, поддерживающих механизм абстракции данных, но не поддерживающих классы, наследование, полиморфизм и динамическое связывание, в литературе используется термин основанный на объекте, введенный в статье Питера Вегнера. Поскольку слова основанный и ориентированный близки и не отражают концептуальной разницы между языками, довольно трудно объяснить, особенно новичкам, суть термина "основанный на объекте". Поэтому я решил придерживаться выражений "инкапсулирующие языки" и "объектно-ориентированные языки". |
Еще немного о терминологии. Термин "функциональные языки" двусмысленен, поскольку в литературе он применяется к классу языков, основанных на математических принципах и часто прямо или косвенно происходящих от
Общее замечание по ОО-эмуляции. В своей основе объектная технология - это "программирование с абстрактными типами данных". Даже на функциональном уровне можно применять рудиментарную форму этой идеи, определив набор строгих методологических правил, требующих вызова подпрограмм для доступа к данным. Предполагается, что начинать надо с ОО-построения, определяющего АТД и его компоненты. Затем пишется набор подпрограмм, представляющих эти компоненты - put, remove, item, empty, как в нашем стандартном примере стека. Далее требуется, чтобы все клиентские модули использовали только эти подпрограммы. При отсутствии языковой поддержки, но при условии, что все в команде подчиняются навязанным правилам, это можно рассматривать как начало объектного подхода. Назовем эту технику дисциплинарным подходом.
Язык Pascal, введенный в 1970г. Никласом Виртом, много лет являлся доминирующим языком начального обучения программированию на факультетах информатики и оказал большое влияние на построение последующих языков. Pascal - это функциональный язык в только что определенном смысле.
Многое ли из ОО-подхода можно реализовать в Pascal?
К сожалению, немногое. Структура программы в Pascal основана на совершенно другой парадигме. Программа на языке Pascal состоит из последовательности описательных разделов, следующих в неизменном порядке: метки, константы, типы, переменные, подпрограммы (процедуры и функции) и раздела выполняемых инструкции. Сами подпрограммы рекурсивно имеют ту же структуру.
Это простое правило облегчает однопроходную компиляцию. Но любая попытка использования ОО-техники обречена. Рассмотрим, что нужно для реализации
Результирующая программная структура противоположна ОО-проектированию. Использование Pascal противоречит принципу Лингвистических Модульных Единиц: любая политика модульности должна поддерживаться доступными конструкциями языка.
Итак, если рассматривать официальный стандарт Pascal, то сделать можно немногое, не считая использования дисциплинарного подхода.
За пределами стандарта Pascal многие коммерчески доступные версии снимают ограничения на порядок объявлений и включают поддержку модульности, включая независимую компиляцию. Такие модули могут содержать константы, типы и подпрограммы. Эти языки более гибкие и сильные, чем стандартный Pascal, сохраняют имя Pascal. Они не стандартизированы, и в действительности больше напоминают инкапсулирующие языки, такие как
Некоторые компании предложили ОО-расширения языка Pascal, широко известные как "
Clascal и используемого для компьютера Macintosh и его предшественника, - Lisa;Наше обсуждение не затрагивает эти языки, так как реально их связь с Pascal проявляется только в имени, стиле синтаксиса и статически типизированном подходе. В частности, Borland Pascal - это ОО-язык с обработкой исключений. Он не поддерживает механизмов универсальности, утверждений,
FORTRAN должен фактически устранить кодирование и отладку.
Самый старый уцелевший язык программирования
Язык
Для большинства специалистов в компьютерных науках
Постороннему наблюдателю иногда кажется, что программирование в физике и других научных дисциплинах, где позиции
Возможная техника состоит в том, чтобы представить данные в так называемом общем блоке COMMON, а экспортируемые компоненты (например, put и т. д. для стеков) реализовать в виде независимых подпрограмм. Блок COMMON - это механизм put для стека действительных чисел:
SUBROUTINE RPUT (X)
REAL X
C
C ВТАЛКИВАНИЕ X НА ВЕРШИНУ СТЕКА
C
COMMON /STREP/ TOP, STACK (2000)
INTEGER TOP
REAL STACK
C
TOP = TOP + 1
STACK (TOP) = X
RETURN
END
Эта версия не управляет переполнением (будет исправлено в следующей версии). Функция, возвращающая элемент вершины:
INTEGER FUNCTION RITEM
C
C ВОЗВРАЩЕНИЕ ВЕРШИНЫ СТЕКА
C
COMMON /STREP/ TOP, STACK (2000)
INTEGER TOP
REAL STACK
RITEM = STACK (TOP)
RETURN
END
Здесь также необходимо было бы проверять стек на пустоту. Подпрограммы REMOVE и другие строятся по тому же образцу. Имя общего блока - STREP - объединяет различные подпрограммы, дающие доступ к одним и тем же данным.
Ограничения очевидны: данная реализация описывает один абстрактный объект (один отдельный стек), а не абстрактный тип данных, из которого во время выполнения можно создать множество экземпляров. Мир RPUT и RITEM, где R означает Real. Можно справиться с этими проблемами, но не без значительных усилий.
Техника, основанная на блоке COMMON, как это видно, нарушает Принцип Лингвистических Модульных Единиц. В модульной
Эту ситуацию можно улучшить (не убирая другие перечисленные ограничения) посредством особенности языка, легализованной в
Это расширение, введенное, возможно, для других целей, можно использовать во благо ОО-подхода. Клиентские подпрограммы могут вызывать точки входа, как если бы они были автономными подпрограммами, и разные входы могут иметь разные аргументы. Вызов входа начинает выполнение подпрограммы с этой точки. Все входы разделяют хранимые данные подпрограммы, появляющиеся с директивой SAVE и сохраняемые от одной активизации подпрограммы к другой. Понятно, куда мы клоним: эту технику можно использовать для определения модуля, инкапсулирующего абстрактный объект, почти как в инкапсулирующем языке. Здесь модуль моделируется подпрограммой, структура данных - набором объявлений с директивой SAVE, и каждый компонент соответствующего класса в ОО-языке - входом, заканчивающимся инструкцией RETURN:
ENTRY (arguments) ... Инструкции ... RETURN
В отличие от предыдущего решения, основанного на COMMON, теперь все необходимое сосредоточено в единой синтаксической единице. В таблице 34.1 дан пример реализации стека действительных величин. Вызовы клиента выглядят следующим образом:
LOGICAL OK
REAL X
C
OK = MAKE ()
OK = PUT (4.5)
OK = PUT (-7.88)
X = ITEM ()
OK = REMOVE ()
IF (EMPTY ()) A = B
Взглянув на этот текст, можно почти поверить, что это использование класса, или, по крайней мере, объекта, через его абстрактный, официально определенный интерфейс!
Подпрограмма в |
C --РЕАЛИЗАЦИЯ
C --АБСТРАКТНЫЙ СТЕК ЧИСЕЛ
C
INTEGER FUNCTION RSTACK ()
PARAMETER (SIZE=1000)
C
C --ПРЕДСТАВЛЕНИЕ
C
REAL IMPL (SIZE)
INTEGER LAST
SAVE IMPL, LAST
C
C --ВХОД С ОБЪЯВЛЕНИЯМИ
C
LOGICAL MAKE
LOGICAL PUT
LOGICAL REMOVE
REAL ITEM
LOGICAL EMPTY
C
REAL X
C
C -- СОЗДАНИЕ СТЕКА
C
ENTRY MAKE ()
MAKE = .TRUE.
LAST = 0
RETURN
C
C -- ДОБАВЛЕНИЕ ЭЛЕМЕНТА
C
ENTRY PUT (X)
IF (LAST .LT. SIZE) THEN
PUT = .TRUE.
LAST = LAST + 1
IMPL (LAST) = X
ELSE
PUT = .FALSE.
END IF
RETURN
|
C --УДАЛЕНИЕ ВЕРШИНЫ
C
ENTRY REMOVE (X)
IF (LAST .NE. 0) THEN
REMOVE = .TRUE.
LAST = LAST - 1
ELSE
REMOVE = .FALSE.
END IF
RETURN
C
C --ЭЛЕМЕНТ ВЕРШИНЫ
C
ENTRY ITEM ()
IF (LAST .NE. 0) THEN
ITEM = IMPL (LAST)
ELSE
CALL ERROR
* ('ITEM: EMPTY STACK')
END IF
RETURN
C
C -- ПУСТ ЛИ СТЕК?
C
ENTRY EMPTY ()
EMPTY = (LAST .EQ. 0)
RETURN
C
END
|
Этот стиль программирования может успешно применяться для эмуляции
Созданный в тиши кабинета язык C быстро стал известным. Большинство людей, интересующиеся и С, и объектной технологией, перешли к ОО-расширениям С, обсуждаемым в следующей лекции (C++, Objective-C, Java). Но по-прежнему интересно, как можно заставить сам С эмулировать ОО-концепции.
Язык C создавался в ATT's
Все, кто интересуется развитием языков программирования, и даже те, кто не очень обращает внимание на сам язык, должны быть признательны языку С по крайней мере по двум причинам.
Стандарт ANSI для С впервые был опубликован в 1990 году. Более ранняя версия языка известна как KR (по инициалам авторов первой книги по С - Кернигана и Ритчи). Со временем противоречие между двумя взглядами на С - как на язык программирования высокого уровня и переносимый язык ассемблера - стало более явным. Эволюция стандарта сделала язык более типизированным и, следовательно, менее удобным для использования в качестве целевого
Хотя, возможно, стоило бы иметь более простое, чем C++ и Java, ОО-расширение С, но не ясно, является ли эта разработка правильной для С. ОО-язык, основанный на С, всегда будет казаться странным изобретением. А идея простого, переносимого, универсального, эффективно компилируемого, машинно-ориентированного языка остается по-прежнему полезной. Он может служить и в качестве целевого языка для компиляторов высокого уровня, и инструмента низкого уровня для написания коротких подпрограмм для доступа к операционной системе (то есть, для того же, что язык ассемблера обычно делал для С, только на следующем уровне).
Дисциплинарный подход применим к языку С, как и к любому другому языку. За его пределами для реализации модульности можно использовать понятие файла. Файл - это понятие языка С, балансирующее на границе между языком и операционной системой. Файл - единица компиляции, он может содержать функции и данные. Некоторые функции могут быть скрытыми от других файлов, другие - общедоступны. Это прямой путь к
Обычная техника программирования на С не расположена к ОО-принципам. Большинство программ С используют "файлы заголовков", описывающих include " (управляемую встроенным препроцессором С):
#include <header.h>
где header.h - это имя h - обычный суффикс для таких имен файлов). Это эквивалентно копированию
Помимо
С внешней точки зрения "каждый объект имеет доступ к операциям, применимым к нему". Возможно, это немного наивно, но не является концептуально неверным. Язык С буквально поддерживает это понятие! Экземпляр "структуры" языка С (эквивалент записи в Pascal) может содержать среди своих полей указатели на функции.
(рис 16.1) Объект С со ссылками на функциюНапример, структурный тип REAL_STACK можно объявить так:
typedef struct
{
/* Экспортируемые компоненты */
void (*remove) ();
void (*put) ();
float (*item) ();
BOOL (*empty) ();
/* Закрытые компоненты (реализация) */
int count;
float representation [MAXSIZE];
}
REAL_STACK;
Фигурные скобки |
В REAL_STACK два последних компонента - переменная и массив, остальные - ссылки на функции. В данном тексте комментарии предупреждают об экспортируемых и закрытых компонентах эмулируемого класса, но на уровне языка клиентам доступно все.
Каждый экземпляр типа должен инициализироваться так, чтобы поля ссылок указывали на соответствующие функции. Например, если my_stack является переменной этого типа, а C_remove - функция, реализующая выталкивание из стека, то можно присвоить полю remove объекта my_stack ссылку на эту функцию таким образом:
my_stack.remove = C_remove
В эмулируемом классе remove не имеет необходимого для нее аргумента. Для доступа к соответствующему стеку следует объявить функцию C_remove так:
C_remove (s)
REAL_STACK s;
{
... Реализация операции remove ...
}
Тогда клиент сможет применить remove к стеку my_stack:
my_stack.remove (my_stack)
В общем случае, подпрограмма rout, имеющая n аргументов в эмулируемом классе, порождает функцию C_rout с n+1 аргументами. Вызов ОО-подпрограммы:
x.rout (arg1, arg2, ..., argn)
эмулируется как:
x.C_rout (x, arg1, arg2, ..., argn)
Описанная техника будет работать в определенных пределах. Ее даже можно расширить для эмуляции наследования.
Но она неприменима к серьезным разработкам, что иллюстрируется на рис. 16.1. Каждый экземпляр любого класса должен физически содержать ссылки на все применимые к нему подпрограммы. Это приведет к существенным
Для снижения потерь заметим, что подпрограммы одинаковы для всех экземпляров класса. Поэтому для каждого класса можно ввести структуру данных периода выполнения, дескриптор класса, содержащий ссылки на подпрограммы. Его можно реализовать как связный список или массив. Требования к пространству значительно уменьшаются: вместо n*m указателей можно иметь их n+m, где n - число подпрограмм, а m - число объектов, как показано на рис. 16.2.
(рис 16.2) Объекты C, разделяющие дескриптор классаЭто приводит к незначительным временным потерям, но экономия пространства и простота стоят этого.
В этой технике нет секрета. Именно она сделала С полезным в качестве средства реализации для компиляторов ОО-языков, начиная с Objective-C и C++ в начале 80-х. Способность использовать указатели функций, в сочетании с идеей группирования этих указателей в дескриптор класса, разделяемый произвольным числом экземпляров, служит первым шагом к реализации ОО-техники.
Конечно, это только первый шаг, и нужно еще найти способы реализации наследования (особенно непросто множественное наследование), универсальности, исключений, утверждений и f в динамически связываемом вызове x.f (...) (написанном здесь в ОО-нотации). Другими словами: в дополнение к официальным полям объекту необходимо дополнительное внутреннее поле, порождаемое компилятором и указывающее на тип объекта. Описанный подход показывает возможную реализацию такого поля - как указателя на дескриптор класса. По этой причине на рис. 16.2 для такого поля используется ярлык type.
Обсуждение показало, что в С есть технические способы введения ОО-идей. Но это еще не значит, что программисты должны их использовать. Как и в случае с языком
Опасность попыток навязывания языку С ОО-идей может привести к несостоятельной конструкции, ухудшающей процесс разработки ПО и качество получаемых продуктов. Лучше использовать С для того, что он может делать хорошо: создания интерфейсов для оборудования и операционных системы, и как машинно-генерируемый целевой код. Для применения объектной технологии лучше использовать инструмент, созданный для этой цели.
Способы написания пакетов в
Я благодарен Полю Дюбуа за замечание, что техника
[Cox 1990] содержит обсуждение технических приемов C для реализации ОО-концепций.
Ссылки на историю классических языков программирования даны в трудах конференции [Wexelblat 1981], см. также [Knuth 1980].
Напишите на
Как бы Вы преобразовали на С эмуляцию класса "real stack" в эмуляцию с родовыми параметрами, адаптируемую к стекам любого типа G, а не просто float?
Постройте и реализуйте простое ОО-расширение С, используя идеи этой лекции. Вы можете написать либо препроцессор, переводя расширенную версию языка на С, либо функциональный пакет, не изменяющий самого языка.
Подойдите к задаче через три последовательные уточнения:
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.