Языки программирования, 09 лекция (от 03 октября)
Материал из ESyr's Wiki pages.
Предыдущая лекция | Следующая лекция
Содержание |
Часть 1. Основные понятия традиционных процедурных ЯП
Глава 1. Скалярный базис
Пункт 8. Функциональный (процедурный) тип данных (продолжение)
В C++ указатель на функцию подобен ссылке, можно вызывать любым способом:
(C++) void f() {...} ... void (*p)(); p=f; (*p)(); p();
(Pascal) type PRCI=procedure(integer); var f:PRCI; f(0);
Ada — нет процедурного типа, но есть альтернатива — родовые модули (пакеты, подпрограммы). Обобщённое (родовое) программирование имеется в Ada, С++, С#. Статическими параметры родовых модулей могут быть не просто объекты данных, но и типы и подпрограммы. Динамические параметры — объекты данных.
Пример: вычисление интеграла
(Ada) generic with function f(x:real) return real; function INTEGRAL(A,B,EPS:REAL) return real;
Ещё можно параметризовать тип данных, так как в Аде их много. Передача подпрограмм делается в Ada при помощи родовых модулей.
В Java и C# нет процедурных переменных. А как же ингеграл? В Java нужно унаследовать класс и переопределить функцию.
Callback — аналог динамического связывания. В Обероне использовалось два стиля ООП: на основании RTTI и на основании обработчика (handler) (объекты посылают сообщения друг другу). Обработчик есть процедурная переменная.
Понятие обработчика — понятие адреса, а это уже потенциальная ненадёжность. Как бороться с этой ненадёжностью? Ада предлагает статичное решение, но лучше динамическое связывание. И в современных ЯП эта задача решается достаточно интересно.
В С# ввели аналог — делегат. Можно рассматривать делегат как коллекцию адресов обектов и ссылок на функции. Чем отличается от процедурного типа? В наборе операций. В классических ЯП можно делать только присваивание и вызов. К делегату можно применять операции сравнения на равенство и неравенство, а также += и -=.
В разных ЯП операция неравенства выглядит по-разному (!= (Си) # (Модула) <> (Паскаль) /= (Ада))
(C#) delegate void MyDelegate(); ... MyDelegate e; void g; void g1; void g2; e = new MyDelegate(g); e += new MyDelegate(g1); e += new MyDelegate(g2);
Если вызвать e, то будут вызваны все три функции. Это отвечает событийной модели событий, при которой те, кому интересно событие, подписываются на него при помощи +=.
Для статической функции передаётся указатель/ссылка на объект. В C++ создаётся путаница:
(C++) X x; x.f(); X::f();
В С# и Java можно их вызывать только через имя класса.
Делегаты — статическая или не статическая функция? А это никого не волнует. Может быть как статической так и нет. Делегат представляет коллекцию из пар адрес объекта — адрес функции. Таким образом взломать код на С# невозможно.
Вернёмся к языку Ada. В ней подпрограммного типа нет. В Ada95 — есть. Зачем, хотя без него можно обойтись, более того, там появилось ООП — для совместимости с другими языками.
Мораль Главы 1
Итак, самый богатый типами данных язык — Turbo Pascal. Чуть меньше их в стандартном Pascal и Ada.
Мораль: базисные типы — не главное.
На этом закончим первую главу.
Глава 2. Составные базисные типы
Пункт 1. Классификация
Наиболее полно удовлетворяют классификации Ада и Паскаль.
- Массив
- Запись (объединение)
- Множество
- Файл
- Строка — в С# (System.String) и Джаве (java.lang.String) выглядит как класс
В С/C++ - массивы и записи.
Общая тенденция: массив — базисная вещь, и от него не отказываются. Запись – расширяется классом.
Все остальные вещи уходит в библиотеку. Хеш-таблицы не входят вSTL, так как STL бескомпромиссна в отношении производительности для хэш-таблицы нельзя задать гарантированное время доступа. Это к вопросу, почему в современных стандартных библиотеках очень мало типов данных, потому что их очень трудно реализовать универсально.
Массив — единственный полноправный ТД во всех ЯП.
Пункт 2. Массив
В основном будет рассматриваться вектор. Массивы отличаются в разных ЯП набором атрибутов и временем связывания. Набор атрибутов:
- Базовый тип
- Длина
- L, R — левая и правая границы массива, а также тип индекса
Это только основные атрибуты. Например, в Аде ещё есть универсальный атрибут — адрес начала.
Операции:
- Присваивание
- Копирование
- Индексирование
То есть индексирование — операция, которая принимает качестве параметра тип индекса и возвращает ссылку на объект базового типа.
(C++) x.operator[](i) - возвращает ссылку //альтернативная запись x[i]
Недостаток языка Pascal — статичность связывания всех атрибутов, в том числе длины. А также то, что атрибуты являются частью типа. На эти проблемы обратил внимание Брайан Ричи в тексте "16 причин, по которым Паскаль не является моим любимым языком программирования". В любых расширениях Паскаля были возможность разделять программы на модули и ... .
Почему Вирт пошёл на такой шаг?
- Это позволяет реализовать массивы до предела эффективно
- Pascal планировался как язык для обучения программированию
Пример открытого массива как параметра процедуры:
(Modula-2) procedure SCAL(var X, Y : ARRAY OF REAL):REAL;
Тут отсутствует описание индекса. Соответствующим параметром может быть любой массив с соответствующим базовым типом. Индексным типом может быть только дискретный тип данных. Двумерных открытых массивов нет. Для выяснения длины массива есть High.
В языке Oberon то же самое, только определение массива до предела упростилось.
(Oberon) ARRAY N OF T
N — константа. Таким образом, мы только регулируем только длину, т.е. R+1, а L=0 и IndexT всегда INTEGER.
Также есть открытые массивы, только теперь есть ещё многомерные открытые массивы. Тип индекса и в Modula-2, и в Oberon привязаны к Integer. Для объектов данных является частью типа. Позиция C такая же, только без контроля.
Для открытых массивов возможна только квазистатическая проверка, и компиляторы Модулы и Оберона их вставляют. Понятие с одной стороны упрощается, с другой стороны остаётся под контролем.
Наиболее мощным является понятие массива в Ada.
Массивы в С#, Delphi, Java: Есть простые типы, есть референциальные типы. Ими являются все классы, интерфейсы и массивы. Массив есть ссылка.
(Java) T[] имя; Int[] a={1,2,5,10}; a = new int[n]; a = b;
Cвязываются базовый тип, тип индекса. Длина — динамический параметр. Операция индексирования является контролируемой. Присваивание — присваивание ссылок, а не копирование значений. Операция опасна, если отсутствует сборка мусора (в Дельфи есть сборка мусора для динамических массивов).
А если хочется копировать, существует полная иерархия объектов, которая начинается с корневого объекта (Java, С# — Object, Delphi — TObject) — класс, который прямо или косвенно наследуется другим классом. То же было в Smalltalk. И это решается на уровне Object, у которого есть операция clone. А если мы хотим запретить копирование объектов, то можно перегрузить clone.
В этих языках также есть понятие коллекций, которые могут легко менть свои границы. Массив тоже может расти и сужаться — есть GetLength, SetLength. Но это нужно исключительно для гибкости. В общем случае это просто кусок памяти, который ведёт себя достаточно консервативно.
Языки Программирования
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
Календарь
чт | вт | чт | вт | чт | вт | чт | вт | чт | вт | |
Сентябрь
| 05 | 07 | 12 | 14 | 19 | 21 | 26 | 28 | ||
Октябрь
| 03 | 05 | 10 | 12 | 17 | 19 | 24 | 26 | 31 | |
Ноябрь
| 02 | 14 | 16 | 21 | 23 | 28 | 30 | |||
Декабрь
| 05 | 07 | 12 | 14 | 19 |