Языки программирования, 11 лекция (от 10 октября)
Материал из eSyr's wiki.
Предыдущая лекция | Следующая лекция
Содержание |
Часть 1. Основные понятия традиционных процедурных ЯП
Глава 2. Составные базисные типы
Пункт 3. Записи и структуры (продолжение)
Промежуточные итоги
(C#) int [][] a; — ступнечатый массив int [,] a; — прямоугольный массив, хранится по строкам (см. рис 1)
Записи:
- Есть во всех ЯП, только в некоторых заменены на классы
- Если нет наследования, то встречаемся с неприятной пробленой: Янус-проблема (Кауфман). Классические ЯП отличаются тем, что там каждому объекту данных однозначно сопоставляется отдельный ТД, причём каждый объект принадлжеит единственному ТД, и всё множество объектов разбивается на непересекающиеся множества
Реально один и тот же объект может выступатьс разных ролях, главное – роли, которые играют объеты.
(Пример) Книга характеризуется шифром, названием, авором. Если речь идёт о переводке библиотеки, то начинают играть роль размеры и масса.
Книга как физический, и как логический объект относятся к одному объекту данных, но имеет разные характеристики.
Быстрее всего на классы обратили внимание в прикладном и системном программировании.
Есть понятие "окно", окна общаются последством событий. Если для окна можно придумать некое обобщение, то для события, у которого одна роль — извещение о том, что в системе что-то произошло, она изменила состояние. Если окно закрывается, то оно посылает сообщение своим детям, закрытие в свою очередь реакция на другое событие. У каждого событие есть свой идентификатор и время. Все, на этом сходство закончилось. У мышиного события координаты и кнопка. У клавиатурного нажатие-отпускание и клавиша. То есть, роль одна, но структура разная.
Пример из системного программирования: Сокеты. Есть понятие адрес сокета и его размер.
Отсюда возникает Янус-проблема — некие типы вынуждены объединятся. Объекты реального мира в разное время должны иметь разный тип. Адрес класса каждый относится к своему классу, но структура их различна. В то же время, с точки зрения роли все они должны объединятся в один ТД.
Решения:
- запись с вариантами — Pascal, Ada, Modula-2. В Oberon нет, ибо там есть наследование. И, извините, Delphi
- объединение — C, C++
Запись с вариантами: эквивалентна по мощности, и более синтаксически наглядна. Есть размеченные и неразмеченные объединения. Есть объект типа Т1, Т2. Union просто позволяет их адресовать с одного места памяти:
(C) union { T1 t1; T2 t2; ... Tn tn; }
При этом программисты на C/C++ постоянно вынуждены постоянно делать однообразные структуры, и потом switch по соответствующему типу, который идентифицируется идентификатором в начале каждого типа.
В Pascal — вариантная часть:
(Pascal) case ... T of v1: часть 1; v2: часть 2; ... vN: часть N; end;
(Pascal) case Boolean of false: (i: integer); true: (b: packed array [1..n] of boolean) end;
Дискриминант отсутствует.
(Pascal) case family : integer of SOCKADDR_UN: (...); SOCKADDR_IN: (...); end;
family — специальное поле. Если дискриминант описан, то он принадлежит постоянной части, но идейно – вариантной.
Выделение памяти, расширенный синтаксис:
(Pascal) new(p,t1,..,tn);
Их много, если дискриминантов несколько. Это позволяет выделять ровно необходимое количесвтво памяти, иначе по максимуму. Идиотизм: память отводится, дискриминант не присваивается. Поэтому, его явно надо присваивать. Тут первый недостаток: если забыть или описаться, то никто это никак не проконтроллирует, и не будет работать.
Запись с вариантами решает очень важную проблему объединения типов. Ни одна графическая система не обходится без записей с вариантами.
Неограниченная запись — запись с дискриминантом, параметризованная запись.
В Ada не бывает неразмеченных объединений вообще.
(Ada) type SOCKADDR(Family : SAType) is record case Family: SAType of when SOCKADDR_IN => ... when SOCKADDR_UN => ... when others => ... end case end record;
В момент размещения в памяти всё должно быть известно (аналогично динамические массивы: P : TArr нельзя, P : Tarr[0..N] можно; Y : SOCKADDR нельзя, Y : SOCKADDR(SOCKADDR_IN) можно).
(Ada) type Paddr is access SOCKADDR; PA : Paddr; PA := new Paddr(SOCKADDR_UN);
Резюме какого-то другого лектора таково: решение проблемы не лучшее.
(Ada) procedure P(X: RecVal) is begin case X.Dic... of when val1 => ... when val2 | val => ... end case end P;
При изменении системы надо расширить тип и все участки, где используется тип данных. Беда. Надо перекомпилировать весь проект.
Страуструп — никаких дискриминантов типа, вы можете употреблять классы, или другие умные слова из пяти или скольки там букв, но это не детерминант.
Страуструп не хотел до последнего включать RTTI, но так как каждые начали включать свои механизмы, пришлось их включить в целях унификации. Но это позволяет программированть свичами — источник ошибок.
Пункт 4. Множества и файлы
Судьба у них одна: все унесены ветром - библиотекой.
Множества
(Pascal) Set of T - множество
При этом:
- T дискретный тип данных
- Диапазон типа не должен быть очень большим
Три волоса – много или мало? Если в супе, то много, если на голове, то мало.
Операции:
- объединение (+)
- пересечение (*)
- несимметричной разности (-)
- принадлежности.
Ограничение на реализацию – Т тип данных небольшой мощности, реализуется битовым массивом. Пример: решето Эратосфена, но обычно хорошо не работает ни на одной реализации Pascal. Самое большое множество, которое видел лектор: на 64 элемента.
В языке Modula 2 введёны специальные псевдофункции (Псевдо - потому что типы не фиксированы)
(Modula 2) INCL(S, X) == S:=S+X; EXCL(S, X) == S:=S-X;
Эффективно и наглядно для управления устройствами ввода-вывода. Нужны обычно для битсетов и только.
В итоге переведено в библиотеку.
Ввод и вывод
То же cамое можно сказать для работы с файлами. Начиная с FORTRAN включали ввод-вывод в язык.
Начиная с C ввод-вывод описывался в терминах языка, и это можно было сделать. В Pascal, например, нельзя. На Oberon тоже честно написан ввод-вывод.
(C++) cout << «Count=» << i << «units» << endl;
(Oberon) InOut.WriteString(«Count=»); InOut.WriteInt(i); InOut.WriteString(«units»); InOut.WriteLn;
Динамическая строка
Пока не было STL, любая фирма делала свою библиотеку С++, и в неё включала строку.
М$ включила Cstring в MFC, и большинство пользовались MFC только из-за неё.
Почему нельзя использовать string как vector<char>? Самая частая операция в строке – поиск подстроки, и прочее, в отличие от вектора. Отличие исключительно в частоте операций. Выгоднее строку реализовывать как специальный динамический вектор. Операция индексирования для строки выдаёт адрес, для строки значение.
С++ мощнее, чем Java & Delphi: В C можно перекрывать операции, в Д-Д нельзя. В Джаве если операция плюс требует текстового контекста, то вызывается метод ToString объекта или обёртки.
Мощности языков Д-Д не хватает, чтобы промоделировать String – компилятор о String знает больше, чем о других - свойство всех современных языков. В Java это сделано логичнее – выделен пакет java.lang, который внешне выглядит как описание классов, но о которых компилятор знает больше. В С# аналогично модуль System. Эти классы более чем полноценные. Тип данных String – единственный ТД, который остался в современных ЯП, остальные – классы ЯП.
Мораль Главы 2
Имеет место тенденция максимального упрощения базиса.
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
Календарь
чт | вт | чт | вт | чт | вт | чт | вт | чт | вт | |
Сентябрь
| 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 |
Материалы к экзамену
Сравнение языков программирования