лекции (2008) (Фингеров Александр_ Кононов Алексей_ Кузин Сергей) (1160833), страница 6
Текст из файла (страница 6)
П.3 Записи
type R=record
x,y:T;
I,j:integer;
end;
Если массив это некая декартова степень D x .. x D, то запись это произведение D1 x .. x DN
Если есть var rec:R, то доступ осуществляется через rec: rec.i:=0;
C: struct tag{
объявления полей
}
Ада: record
последовательность полей
end record;
В языке Оберон последовательность полей эквивалентна последовательности объявлений переменных.
Запись с вариантами – терминология языков Паскаль и Модула 2.
Объединение – терминология языка C/C++
Параметризованные записи – Ада
Объединение типов
Традиционные ЯП -> каждому объекту данных сопоставлен единственный тип данных (множество значений и операций).
ООЯП – иерархия типов/дерево типов. boxing (- упаковка некоторого значения) и unboxing, существует для каждого из типов класс-оболочка, а всё так или иначе является объектом класс Object.
int i=0;
Object O=i; //Int32(i)
Янус-проблема. Различные объекты могут выступать в разных ролях.
ТД – роль. Ярче всего Янус-проблема проявилась при проектировании пользовательских интерфейсов, основное понятие современного интерфейса – окно, а окна взаимодействуют с помощью понятия события. Возникает проблема как описать ТД окно и ТД событие, когда набор свойств может быть совершенно разный. Общее у всех событий может быть только одно – time, а всё остальное уже различно.
Объединение типов призвано решать эту проблему.
- размеченное объединение (поле, где есть дискриминант)
- неразмеченное объединение
Постоянная часть – последовательность объявлений переменных, вариантная часть – всё остальное.
type Event=record
T:time; - постоянная часть
case EvType:ETEnum of
KBEvent: (scan:integer; up:boolean);
TimerEvent: (ID:integer);
MBEvent(x,y:integer; Button:mousebutton, up:boolean);
end
Недискриминированные записи нужны для легального доступа к каждому биту.
Лекция 13.
Объединение типов позволяет объединить различные данные, мы можем объединить разные типы, даже если они будут иметь разную структура. (осн. операция над такими данными имя.имя_поля) В языках, в которых есть записи, обязательно будут и объединения.
Объединения бывают 2-х типов:
-
Размеченные - имеют поле дискриминанта. Он имеет некоторое количество значений, по которым выбираются варианты распределения.
-
Неразмеченные (в языке С только неразмеченные. Union tag{ T1 v1; T2 v2 … }. )
Паскаль, Модула-2 имеют объединения типов и они сделаны примерно одинаково. В Аде имеется отличие, что все записи обязаны быть различными. В этих языках запись может иметь постоянную и вариантную часть. (вариантная часть по своему виду очень похожа на switch)
Case дискрим-т of
V1: ( об-е полей )
V2: ( об-е полей )
….
В размеченных имя: тип , в неразмеченных просто тип.
В Паскале есть не только команда NEW(P), но и команда NEW(P, t1, t2, t3 … ), где эти «t» и есть переменные дискриминанта. Это значит что нужно выделить память по варианту t (память разделяется по варианту, но дискриминант никуда не присваивается (так как может быть неразмеченная память), поэтому надо писать p1.d:=t для изменения дисриминанта).
Проблемы:
Код который использует объединения провоцирует много ошибок, так как в большом количестве записей можно запутаться и обращаться не к тому объекту. Из-за того что не присваиваются дискриминанты (смотри выше), можно наделать много ошибок. Если же память неразмеченная (т.е нету дискриминанта), то теряется контроль за распределением.
В языке Ада записи с вариантами называются параметризованными, и параметром записи может служить дискриминант.
В языке Ада:
Type VARRec (D : DT) is record
постоянная часть,
case D : DT of
when V1 -> вариант 1;
when V1 | V2 … -> вариант 2
when others ….
end record;
Если дискриминант установлен параметром записи, то мы его не сможем изменить, он выставляется только при определении (очень похоже на зачатки конструктора), то есть ненадежность записей в Аде минимизирована. В ОО языках необходимость записей пропадает. (они могут использоваться только для совместимости при переносе с других языков программирования (например в C++ для совместимостью с С ). Записи, в ОО языках, теперь моделируются наследованием и классами. (постоянная часть -> в базовый класс, вариантная -> в наследуемые классы)
Проблема записей возникает при модификации кода. Если мы что-то добавляем в него, нам придется по всему коду искать «переключатели» и менять их, и если мы что-то пропустим, код корректно работать не будет. Эту проблему решает динамическое изменение, то есть мы можем что-то добавить без всяких изменений последующего кода (проблема типов записей называется проблемой Януса).
В языке C++ структура стала классом. Никакого различия между ними нет, кроме как то, что по умолчанию в структуре права доступа стали public. Проблема перевода структуры в класс состоит в том, что в структуре отдельное «структурное» пространство имен. (некоторые разработчики просто плевали на совместимость и не делали структуры) Компромисс был найден в том, что структуры становились почти как классы. (мы могли бы написать struct C; , а потом C b; ) В Обероне нет записей, но зато есть классы.
В языке Шарп появилась проблема, что классы содержатся в динамической памяти и хранить в них много информации не выгодно, так как приходится потом все освобождать. (переменная класса - это ссылка и потом еще куча ссылок на сами данные, то есть мы хранили не только сами данные, но еще и такое же (если не большее) количество ссылок на них) Структуры же в Шарпе приравнены к обычным типам данных и хранятся они без всяких ссылок ( то есть нам надо хранить только сами данные, одним большим куском, а не куча маленьких указателей и так далее) . Но проблема, что структуры не могут наследовать, и из него нельзя наследоваться, поэтому можно считать что виртуальных функций у них нет. (в Шарпе не существует даже глобального уровня)
Также, в языке Шарп сильно используется процесс запаковки и распаковки. ( фактически можно приводить класс к базовому и наоборот) В С++ нельзя содержать в массиве объекты у которых нет конструктора умолчания, поэтому если очень хочется нужно его описывать. В Шарпе чтобы сохранить семантику не разрешается переписывать конструкторы по умолчанию.
П.4. Другие составные типы данных.
Фактически фундаментальных типов всего два – массивы и классы, а все остальное типы, в современных языках, мигрировало в библиотеки.
Паскаль:
- абстракции ввода/вывода ( файлы и т.д). В базис они не входят, потому что ими фактически никто не пользуется из-за того, что они были сделаны для магнитных лент и сейчас их использовать очень неудобно . Проблема возникла в том, что если писать процедуры ввода/вывода на самом языке, то ничего хорошего не получится, в Паскале это нереально.
На Модуле-2 переменных ввода/вывода не было:
InOut.writestring(“Hello world”); и дальше, если мы хотим перейти на новую строчку, придется написать еще InOut.writeln;
-множества / таблицы. В Паскале и Модуле 2 set of T; В Обероне тоже остался тип данных set, но в остальных языках set не осталось, есть только в некоторых библиотеках, но в базисе их не осталось.
Лекция 14.
П.4. Другие СТД
Файлы и другие абстракции ввода-вывода перешли в стандартную библиотеку, похоже, что вообще критерием мощности и самостоятельности языка стало то, можно ли на нём писать самостоятельную библиотеку ввода-вывода. Такие вещи как множества, таблицы и многие другие вещи ушли из современных ЯП, поскольку отсутствует универсальный способ их реализации. Например есть метод qsort – почти во всех современных ЯП, можно написать свой алгоритм сортировки, но общего алгоритма сортировки нет, и так можно привести море соображений по поводу почти любой структуры данных, за исключением пожалуй последней структуры данных, которую мы обсудим – строки. В стандартном Паскале было понятие строка, но оно сводилось к упакованному массиву символу. В С понятие строки – это массив символов, который заканчивается /0 , этот массив имел дополнительные операции: сравнение на равенство и неравенство и так далее, но фактически строки рассматривались как частный случай массива. По мере развития ЯП, строки не уходили в стандартную библиотеку, а перешли в базис ЯП. В языке Ада83 было понятие неограниченного массива, объявлять его можно было уточняя соответствующий диапазон, тип диапазона фиксировался компилятором, но не фиксировались границы. Там есть модуль STANDARD, внутри которого есть и операции ввода-вывода, и тип данных STRING, который представляет собой неограниченный массив.
Line: STRING (1..80);
В языке Ада легко реализовать операции Left (1,N), Right, Mid с помощью стандартной операции вырезки массива, есть ещё операция которую стандартно обозначают операцией «плюс» - конкатенация. В языке Ада есть возможность переопределять операции.
Есть разница между стандартными операциями и операциями из базиса, вторые из них включены в компилятор, а первые – в стандартную библиотеку. С++ - одно из немногих исключений, где строки не включены в базис, а тип данных String объявлен в stl. Кроме того, там есть ещё структура vector<char>, так зачем понадобился отдельно тип данных String? Можно ли перекрыть операции? Конечно, да. На самом деле, по мере развития ЯП стало ясно, что строки это не просто частный случай, а очень частный случай динамического массива. В строках почти не нужна сортировка, существенно чаще появляется вырезка и копирование. Возникает вопрос – на каких языках всю эту функциональность можно сделать не на уровне компилятора, а с помощью обычного механизма класса – это С++. Компилятор языка Джава сам вызывает метод Object.toString, и если мы пишем writeline(5); то компилятор сам переведет её в строку. Вот такая тенденция современных ЯП – базис в результате упростился, но ТД String, для которого нужна эффективность – единственное расширение.
П.5. О единстве СТД
Основная операция над массивами – это операция индексирования [] A x I->Val. Её можно переопределять.
Основная операция над записями – операция «.» . А вот эту операцию нельзя переопределить.
Почему? Как мы определим аргумент и возвращаемое значение точки? Единственный аргумент, который мы можем определить это ТД записи, а что же второй аргумент? Некоторое смещение от начала этой записи, и казалось бы, что это разные операции. Языки JavaScript, IO, Self говорят, что для объекта есть обе эти операции. Точка и некоторое имя трактуется как obj[“имя”]. Справедливо и обратное. obj[0] – обращение к первому элементу записи. И коль скоро мы можем позволить массивам расширяться неограниченно, то можем и добавлять некоторые свойства записям. Увеличивается гибкость языка за счёт эффективности и надёжности, так как контролировать объект становится сложнее. В реальных крупномасштабных приложениях это вряд ли будет востребовано.
Глава 3. Управление последовательностью действий.
Когда речь идёт о последовательности состояний, мы рассматриваем в первую очередь – Тип Данных, и то, что управляет переходом из состояния в состояние – «поток управления»
П.1 Эволюция понятия «поток управления».
Уровни потоков управления, фактически можно разделить на 3:
-
Внутри выражения
-
Между операторами
-
Между модулями
1. Есть понятие ТД, над каждым ТД – свой набор операций, из языка в язык они практически одни и те же. Поток управления – в каком порядке вычисляются те или иные операции. Приоритеты операций явно заданы в каждом стандарте. Интересный вопрос – когда приоритетов нет, т.е. есть f(a,b), где a и b – выражения, или a and b. В каком порядке они будут вычисляться? Большинство ЯП отвечает на этот вопрос так – если программа зависит от порядка вычисления, то она является нестандартизованной. С логическими выражениями может быть тяжелее.
массив A элемент X