М. Бен-Ари Языки программирования. Практический сравнительный анализ. Предисловие
Вид материала | Документы |
- Рабочей программы учебной дисциплины языки программирования Уровень основной образовательной, 47.91kb.
- Существуют различные классификации языков программирования, 174.02kb.
- Лекция 3 Инструментальное по. Классификация языков программирования, 90.16kb.
- Аннотация рабочей программы учебной дисциплины языки программирования Направление подготовки, 135.09kb.
- Лекция Языки и системы программирования. Структура данных, 436.98kb.
- Государственное Образовательное Учреждение высшего профессионального образования Московский, 1556.11kb.
- Программа дисциплины Языки и технологии программирования Семестры, 20.19kb.
- Календарный план учебных занятий по дисциплине «Языки и технология программирования», 43.35kb.
- Пояснительная записка Ккурсовой работе по дисциплине "Алгоритмические языки и программирование", 121.92kb.
- Утверждены Методическим Советом иэупс, протокол №8 от 24. 04. 2008г. Языки программирования, 320.93kb.
18.4. Полиморфные структуры данных
В языках Ada и C++ есть два пути построения полиморфных структур данных: generics в Ada и templates в C++ для полиморфизма на этапе компиляции, и типы в Ada и указатели/ссылки на классы для полиморфизма на CW-этапе выполнения. Преимущество generies/templates состоит в том, что структура данных фиксируется при создании экземпляра во время компиляции; это позволяет как генерировать более эффективный код, так и более экономно распределять память для структур данных.
В языке Java решено реализовать полиморфизм только на этапе выполнения. Как и в языках Smalltalk и Eiffel, считается, что каждый класс в Java порождается из корневого класса, названного Object. Это означает, что значение любого непримитивного типа8 может быть присвоено объекту типа Object. (Конечно, это работает благодаря семантике ссылки.)
Чтобы создать связанный список, класс Node должен быть сначала определен как содержащий (указатель на) Object. Класс списка тогда должен содержать методы вставки и поиска значения типа Object:
Java |
Object data;
Node next;
}
class List {
Java |
void Put(Object data) {...};
Object Get() {...};
}
Если L является объектом типа List (Список), и а является объектом типа Airplane_Data, то допустимо L.Put(a), потому что Airplane_Data порождено из Object. Когда значение выбирается из списка, оно должно быть приведено к соответствующему потомку Object:
Java |
Конечно, если возвращенное значение не имеет тип Airplane_Data (или не порождено из этого типа), возникнет исключение.
Преимущество этой парадигмы состоит в том, что в Java очень просто писать обобщенные структуры данных, но по сравнению с generics/tem-plates имеются два недостатка: 1) дополнительные издержки семантики ссылки (даже для списка целых чисел!), и 2) опасность, что объект, размещенный не в той очереди, приведет при поиске к ошибке на этапе выполнения программы.
18.5. Инкапсуляция
В разделе 13.1 мы обсуждали тот факт, что в языке С нет специальной конструкции инкапсуляции, а в разделе 13.5 отметили, что операция разрешения области действия и конструкция namespace (пространство имен) в C++ уточняет грубое приближение языка С к проблеме видимости глобальных имен. Для совместимости в язык C++ также не была включена конструкция инкапсуляции; вместо этого сохраняется зависимость от «h»-файлов. В Ada есть конструкция пакета, которая поддерживает инкапсуляцию конструкций в модули (см. раздел 13.3), причем спецификации пакетов и их реализации (тела) могут компилироваться отдельно. Конструкции with позволяют разработчику программного обеспечения точно определить зависимости между модулями и использовать порожденные пакеты (кратко упомянутые в разделе 15.2) для разработки модульных структур с иерархической достижимостью.
Java содержит конструкцию инкапсуляции, названную пакетом (package), но, к сожалению, конструкция эта по духу ближе к пространству имен (namespace) в языке C++, чем к пакету Ada! Пакет является совокупностью классов:
package Airplane_Package;
public class Airplane_Data
Java |
int speed; // Доступно в пакете
private int mach_speed; // Доступно в классе
public void set_speed(int s) {...}; // Глобально доступно
public int get_speed() {...};
}
public class Airplane_Database
{
public void new_airplane(Airplane_Data a, int i)
{
if (a.speed > 1000) // Верно !
a.speed = a.mach_speed; // Ошибка !
}
private Airplane_Data[ ] database = new Airplane_Data[1000];
}
Пакет может быть разделен на несколько файлов, но файл может содержать классы только из одного пакета.
Спецификаторы public и private аналогичны принятым в языке C++: public (общий) означает, что элемент доступен за пределами класса, в то время как private (приватный) ограничивает достижимость для других членов класса. Если никакой спецификатор не задан, то элемент видим внутри пакета. В примере мы видим, что элемент int speed (скорость) класса Airplane_Data не имеет никакого спецификатора, поэтому к нему может обратиться оператор внутри класса Airplane_Database, так как два класса находятся в одном и том же пакете. Элемент mach_speed объявлен как private, поэтому он доступен только внутри класса Airplane_Data, в котором он объявлен.
Точно так же классы имеют спецификаторы достижимости. В примере оба класса объявлены как public, что означает, что другие пакеты могут обращаться к любому (public) элементу этих классов. Если класс объявлен как private, он доступен только внутри пакета. Например, мы могли бы объявить private класс Airplane_File, который использовался бы внутри пакета для записи в базу данных.
Пакеты играют важную роль в развитии программного обеспечения Java, потому что они позволяют группировать вместе связанные классы при сохранении явного контроля над внешним интерфейсом. Иерархическая библиотечная структура упрощает построение программных инструментальных средств.
Сравнение с другими языками
Пакеты Java служат для управления глобальными именами и достижимостью аналогично конструкции namespace в языке C++. При заданных в нашем примере объявлениях любая Java-программа может содержать:
Java |
a.set_speed(100);
потому что имена класса и метода объявлены как public. He изучив полный исходный текст пакета, нельзя узнать, какие именно классы импортированы. Есть оператор import, который открывает пространство имен пакета, разрешая прямую видимость. Эта конструкция аналогична конструкциям using в C++ и uses Ada.
Основное различие между Java и Ada состоит в том, что в Ada спецификация пакета и тело пакета разделены. Это не только удобно для сокращения размера компиляций, но и является существенным фактором в разработке и поддержке больших программных систем. Спецификация пакета может быть заморожена, позволяя параллельно разрабатывать тело пакета и вести разработку других частей. В Java «интерфейс» пакета является просто совокупностью всех public-объявлений. Разработка больших систем на Java требует, чтобы программные инструментальные средства извлекали спецификации пакета и гарантировали совместимость спецификации и реализации.
Конструкция пакета дает Java одно главное преимущество перед C++. Пакеты сами используют соглашение об иерархических именах, которое позволяет компилятору и интерпретатору автоматически размещать классы. Например, стандартная библиотека содержит функцию, названную Java.lang.String.toUpperCase. Это интерпретируется точно так же, как операционная система интерпретирует расширенное имя файла: toUpperCase является функцией в пакете Java.lang.String. Библиотеки Java могут (но не обязаны) быть реализованы как иерархические каталоги, где каждая функция является файлом в каталоге для своего класса. Отметим, что иерархичность имен как бы вне языка; подпакет не имеет никаких особых привилегий при доступе к своему родителю. Здесь мы видим четкое отличие от пакетов-детей в Ada, которые имеют доступ к private-декларациям своих родителей при соблюдении правил, которые не позволяют экспортировать эти декларации.
18.6. Параллелизм
Ada — один из немногих языков, в которых поддержка параллелизма включена в сам язык, в отличие от делегирования этих функций операционной системе. Язык Java продолжает идеологию языка Ada в отношении переносимости параллельных программ вне зависимости от операционных систем. Важное применение параллелизма в Java — программирование серверов: каждый запрос клиента заставляет сервер порождать (spawn) новый процесс для выполнения этого запроса.
Параллельно исполняемые конструкции Java называются нитями (thread). Собственно в параллелизме нет никаких существенных различий между нитью и тем, что называют стандартным термином процесс; отличие состоит в реализации, ориентированной на выполнение многих нитей внутри одного и того же адресного пространства. Для разработки и анализа параллельных алгоритмов используется та же самая модель, что и в гл. 12 ,— чередующееся выполнение атомарных инструкций процессов.
Класс Java, который наследуется из класса Thread, объявляет только новый тип нить. Чтобы реально создать нить, нужно запросить память, а затем вызвать функцию start. В результате будет запущен метод run внутри нити:
class My_Thread extends Thread
{
public void run(){...}; // Вызывается функцией start
Java |
My_Thread t = new My_Thread(); // Создать нить
t.start(); //Активизировать нить
Одна нить может явно создавать, уничтожать, блокировать и разблокировать другую нить.
Эти конструкции аналогичны конструкциям в языке Ada, которые позволяют определить тип task (задача) и затем динамически создавать задачи.
Синхронизация
Java поддерживает форму синхронизации аналогично мониторам (см. раздел 12.4). Класс может содержать методы, специфицированные как synchronized (синхронный). С каждым таким объектом в классе связывается блокировка-пропускник (lock), которая гарантирует, что только одна нить в данный момент может выполнять синхронный метод в объекте. Следующий пример показывает, как объявить монитор для защиты разделяемого ресурса от одновременного использования несколькими нитями:
class Monitor
{
synchronized public void seize() throws InterruptedException
{
while (busy) wait();
Java |
}
synchronized public void release()
{
busy = false;
notify();
}
private boolean busy - false
}
Монитор использует булеву переменную, которая указывает состояние ресурса. Если две нити пытаются выполнить метод seize в мониторе, то только одна из них пройдет пропускник и выполнится. Эта нить установит переменную busy (занято) в состояние true (истина) и перейдет к использованию ресурса. По завершении метода пропускник откроется, и другая нить сможет выполнить метод seize. Теперь, однако, переменная busy будет иметь значение false (ложь). Вместо ненужных затрат времени ЦП на непрерывную проверку переменной нить предпочитает освободить ЦП с помощью запроса wait (ждать). Когда первая нить заканчивает использование разделяемого ресурса, она вызывает notify (уведомление), которое позволит ожидающей нити снова возобновить выполнение синхронного метода.
Конструкции Java для параллелизма достаточно просты. Нет ничего похожего на сложные рандеву Ada для прямой связи процесс-процесс. Даже по сравнению с защищенными объектами конструкции Java относительно слабы:
• Барьер защищенного объекта автоматически перевычисляется всякий раз, когда его значение может измениться; в Java нужно явно программировать циклы.
• Java предоставляет простую блокировку-пропускник для каждого объекта; в Ada заводится очередь для каждого входа. Таким образом, если несколько нитей Java ожидают входа в синхронный объект, вы не можете знать, какой из них будет запущен по notity, поэтому трудно программировать алгоритмы с гарантированно ограниченным временем ожидания.
18.7. Библиотеки Java
В языках программирования очевидна тенденция сокращения «размеров» языка за счет расширения функциональности библиотек. Например, write — это оператор в языке Pascal со специальным синтаксисом, тогда как в Ada нет никаких операторов ввода/вывода; вместо этого ввод/вывод поддерживается пакетами стандартной библиотеки.
Стандартные библиотеки Ada предоставляют средства для ввода/вывода, обработки символов и строк, для вычисления математических функций и для системных интерфейсов. Язык C++ также поддерживает контейнерные классы, такие как стеки и очереди. Точно так же Java содержит базисные библиотеки, названные java.lang, java.util и java.io, которые являются частью спецификации языка.
В дополнение к спецификации языка имеется спецификация для интерфейса прикладного программирования (Application Programming Interface — API), который поддерживают все реализации Java. API состоит из трех библиотек: Java.applet, Java.awt и java.net.
Java.applet поддерживает создание и выполнение аплетов и создание прикладных программ мультимедиа.
Абстрактный комплект инструментальных оконных средств (Abstract Window Toolkit — AWT) — это библиотека для создания графических интерфейсов пользователя (GUI): окна, диалоговые окна и растровая графика.
Библиотека для сетевой связи (java.net) обеспечивает необходимый интерфейс для размещения и пересылки данных по сети.
Подведем итог:
• Java — переносимый объектно-ориентированный язык с семантикой ссылки.
• Интерфейс прикладного программирования API представляет переносимые библиотеки для поддержки развития программного обеспечения в сетях.
• Защита данных и безопасность встроены в язык и модель.
• Многие важные концепции Java заложены в языково-независимую машину JVM.
8.8. Упражнения
1. Задано арифметическое выражение:
(а + b) * (с + d)
Java определяет, что оно должно вычисляться слева направо, в то время как C++ и Ada позволяют компилятору вычислять подвыражения в любом порядке. Почему в Java более строгая спецификация?
2. Сравните конструкцию final в Java с константами в Ada.
3. Каково соотношение между спецификатором friend в C++ и конструкцией пакета в Java.
4. C++ использует спецификатор protected (защищенный), чтобы разрешить видимость членов в порожденных классах. Как именно конструкция пакета влияет на понятие защищенности в Java?
5. Сравните интерфейс в Java с многократным наследованием в C++.
6. Проанализируйте различия между пространством имен (namespace) в C++ и пакетом в Java, особенно относительно правил, касающихся файлов и вложенности.
7. Конструкция исключения в Java совершенно аналогична конструкции исключения в C++. Одно важное различие состоит в том, что метод Java должен объявить все исключения, которые он может породить. Обоснуйте это проектное решение и обсудите его последствия.
- Сравните мониторы Java с классической конструкцией монитора.
9. Сравните возможности обработки строк в Ada95, C++ и Java.
10. Сравните операции clone и eguals в Java с этими операциями в языке Eiffel.
Ссылки
Официальное описание языка дается в:
James Gosling, Bill Joy and Guy Steele. The Java Language Specification. Addison-Wesley, 1997.
Sun Microsystems, Inc., где разработан язык Java, имеет Web-сайт, содержащий документацию и программное обеспечение: ссылка скрыта.
Приложение А
Где получить компиляторы
В течение многих лет студентам было сложно экспериментировать с языками программирования: компиляторы могут дорого стоить, и, возможно, не так просто убедить компьютерный центр установить и поддерживать программное обеспечение. Сегодня ситуация изменилась, и можно получить свободно распространяемые компиляторы для большинства, если не для всех, языков, которые мы обсудили.
Эти компиляторы предназначены для рабочих станций и даже персональных компьютеров, поэтому вы можете установить их дома или в своей лаборатории. Кроме того, они легко доступны через Internet.
Чтобы получать информацию относительно компиляторов для какого-либо языка, просмотрите файлы, называющиеся FAQ (Freguently Asked Questions — часто задаваемые вопросы). Их можно загрузить через анонимный ftp по адресу rtfm.mit.edu. В директории /pub/usenet находится (очень длинный) список поддиректорий; просмотрите comp.lang.x, где х — одно из ada, apl, с, с++, eiffel, icon, lisp, ml, prolog, Smalltalk и т.д. Переходите в одну и; этих поддиректорий и загружайте файлы, в которых есть символы FAQ. Эта файлы будут содержать списки доступных компиляторов языка, в частности те, которые можно загрузить, используя ftp. Хотя эти программы не представляют интереса как коммерческие пакеты, они вполне подходят для изучение и экспериментирования с языком.
В FAQ вы также найдете названия и адреса профессиональных ассоциаций, которые издают бюллетени и журналы, дающие современную информацию о языках.
C++
Свободно распространяемый компилятор для языка C++, называющийся дсс, был разработан в рамках проекта GNU компании Free Software Foundation. Более подробно см. FAQ в поддиректории gnu.g++.help на узле rtfm.mit.edu. Компилятор дсс был перенесен на большинство компьютеров включая персональные. Так как язык C++ все еще стандартизуется, дсс может отличаться от других компиляторов C++.
Ada 95
Нью-Йоркский университет разработал свободно распространяемый компилятор для языка Ada 95, названный gnat (GNU Ada Translator), gnat использует выходную часть дсс и перенесен почти на все компьютеры, которые поддерживают дсс. Современную информацию относительно ftp-сайтов для gnat см. в Ada FAQ; главный сайт находится в директории /pub/gnat в cs.nyu.edu. Там вы также найдете управляемую с помощью меню среду программирования для gnat, которая была разработана Университетом Джорджа Вашингтона.
AdaS
Pascal- S — это компилятор для подмножества языка Pascal, который вырабатывает Р-код, являющийся машинным кодом для искусственной стековой машины. Включен также интерпретатор для Р-кода. Его автор разработал версию Pascal-S, названную AdaS, которая компилирует небольшое подмножество языка Ada. Исходный код AdaS можно найти в файле adasnn.zip (где пп — номер версии) в каталоге /languages/ada/crsware/pcdp в общей библиотеке языка Ada (PAL) на хосте wuarchive.wustl.edu.
AdaS не годится для серьезного программирования, но это превосходный инструмент для изучения методов реализации конструкций языка программирования, в частности управления стеком при вызовах подпрограмм и возвратах из них.
Приложение Б
Библиографический список
Обзоры по языкам программирования можно найти в:
Ellis Horowitz (ed.). Programming Languages: 4 Grand Tour. Springer Verlag, 1983.
Jean E. Sammet. Programming Languages: History and Fundamentals. Prentice Hall, 1969.
Richard L. Wexelblat. History of Programming Languages. Academic Press, 1981.
Особенно интересна книга Векселблата (Wexelblat); это запись конференции, где разработчики первых языков программирования описывают истоки и цели своей работы.
Превосходное введение в теорию вычисления (логику, машины Тьюринга, формальные языки и верификацию программ) можно найти в:
Zohar Manna. Mathematical Theory of Computation. McGraw-Hill, 1974.
В учебниках, рассчитанных на подготовленных учащихся обсуждается формальная семантика языков программирования:
Michael Marcotty and Henry Legrand. Programming Language Landscape: Syntax, Semantics and Implementation. SRA, Chicago, 1986.
Bertrand Meyer. Introduction to the Theory of Programming Languages. Prentice Hall International, 1991.
По компиляции смотрите следующие работы:
Alfred Aho, Ravi Sethi and Jeffrey D. Ullman. Compilers: Principles, Techniques and Tools. Addison-Wesley, 1986.
Charles N. Fisher and Richard J. LeBlanc. Grafting a Compiler. Benjamin Cummings, 1988.
Хорошим введением в объектно-ориентированное проектирование и программирование является:
Bertrand Meyer. Object-oriented Software Construction. Prentice Hall International, 1988.
Обратите внимание, что описанная там версия языка Eiffel устарела; если вы хотите изучить язык, смотрите современное описание:
Bertrand Meyer. Eiffel: the Language. Prentice Hall, 1992.
Конкретные языки программирования
Мы даже не будем пытаться перечислить множество учебников по языкам С, Ada и C++! Формальное описание языка Ada можно найти в справочном руководстве:
Ada 95Reference Manual. ANSI/ISO/IEC-8652:1995.
Справочное руководство очень формальное и требует тщательного изучения. Существует сопутствующий документ, называемый Объяснением (Rationale), в котором описана мотивация языковых конструкций и даны обширные примеры. Файлы, содержащие текст этих документов, можно бесплатно загрузить, как описано в Ada FAQ.
Стандарт языка С — ANS ХЗ.159-1989; международный стандарт — ISO/IEC 9899:1990. В настоящее время (конец 1995 г.), язык C++ еще не стандартизирован; информацию о том, как получить последний предлагаемый вариант стандарта языка C++, см. в FAQ. Более доступно справочное руководство:
Margaret A. Ellis and Bjarne Stroustrup. The Annotated C++ Reference Manual. Addison-Wesley, 1990 (reprinted 1994).
Следующая книга является «обоснованием» языка C++ и должна быть прочитана всеми серьезными студентами, изучающими этот язык:
Bjarne Stroustrup. The Design and Evolution of C++. Addison-Wesley, 1994.
Другие широко используемые объектно-ориентированные языки, которые стоит изучить, — Smalltalk и CLOS:
Adele Goldberg and David Robson. Smalltalk-80, the Language and its Implementation. Addison-Wesley, 1983.
Sonya E. Keene. Object-Oriented Programming in Common Lisp: a Programmer's Guide. Addison-Wesley, 1989.
В разделе 1.3 мы рекомендовали вам изучить один или несколько языков, основанных на конкретной структуре данных. Следующий список позволит вам начать это изучение:
Leonard Oilman and Alien J. Rose. APL: An Interactive Approach. John Wiley, 1984*.
Ralph E. Griswold and Madge T. Griswold. The Icon Programming Language (2nd Ed.). Prentice Hall, 1990.
J.T. Schwartz, R.B.K. Dewar, E. Dubinsky, and E. Schonberg. Programming with Sets: An Introduction to SETL Springer Verlag, 1986.
Patrick H.Winston and Berthold K.P.Horn. LISP (3rd Ed.). Addjson-Wesley, 1989.
Наконец, введение в языки и языковые понятия, которые были только кратце рассмотрены в этой книге, можно найти в:
М. Ben-Ari. Principles of Concurrent and Distributed Programming. Prentice Hall International, 1990.
Ivan Bratko. Prolog Programming for Artificial Intelligence (2nd Ed.). Addison-Wesley, 1990.
Chris Reade. Elements of Functional Programming. Addison-Wesley, 1989. Leon SterUng and Ehud Shapiro. The Art of Prolog. MIT Press, 1986. Jeffrey D. Ullman. Elements of ML Programming. Prentice Hall, 1994.