Языки программирования, 19 лекция (от 14 ноября)
Материал из ESyr's Wiki pages.
ЯП 14.11.06
Обсуждали вопрос опред ТД с помощью классов.
Механизм переопр операторов
В ряде языков сущ возможность для переопр стандартных операций. Это С++ и С#. Дельфи и Джава эту возм отвергают. Для некоторых ТД они фактически допускают возможность переопределения. Прежде всего это относится к операции + у строк. В Джаве мы можем для Джавы пееропределять встроенный метод toString(), и если + требует тип String, то в классе-операнде ищется toString, это делается автоматически, и компилятор знает про этот оператор больше.
Больше всех переопр работает в С++. Нельзя переопр только ., .,*, :: . Всё остальное переопр можно. Более того, можно переопределять все стандартные преобраззования, индексацию. За подобную гибкость надо платить. Платить надо сложностью языка, компилятора и неожиданной для пользователя семантикой. В других ЯП его нет или ограничено (С#). Там можно переопр обычные арифметические операции. Нельзя переопр *, (), []. Это фактически не явл ограничением, так как [] можно переопределять специальным мехпнизмом – индексатом. Если в классе определён метод, который называется T this(T1 e) {}, то считаетс,Я что к этому классу применимы []. И можно писать X[e]. Это делается тогда, когда мы хотим, чтобы класс выглядил как коллекция. И тип операнда, и возвр результата могут быть произв.
В Дельфи есть свойства по умлочанию, и там можно сделать их массивами.
Множество операций, которые можно переопр, сводится к арифметическим. Эти операции и семант, и синтаксич переопр достаточно просто. Как их переопр. В С шарп – единственная возможность – сделать функцией класс. В с++ возможностей больше:
class X {
X operator+();
X operator+(X&);
}
При этом язык требует )любой_, чтобы синтаксис не меняолся, то есть местность не менялась. То есть для плюса либо два арг, либо один (один из них неявный).
Это объявление трактуется как:
a+b – a.operator+(b);
+a – a.operator+();
Вот такое переопр оператора + совершенно нормально. В двуместном операторе привязка не совсем очевидна, поэтому в этом случае есть смысл переопр с помощью внешних функций:
X operator+(X& a, X& b);
Программист должен определять либо в этом синтаксисе, либо в этом, и не смешивать их.
У плюса нет выделенного аргумента.
С другой стороны, какие проблемы у внешнего плюса – проблемы инкапсуляции. Можно объявить + другом класса. Есть более элегантных способ: для каждой лоперации есть такая же с побочным эффектом:
+ - +=, * - *= ... и её мы тоже хотим переопределить, а её уже точно надо переопределять внутри класса, потому что она меняет значение левого аргумента.
X& operator+=(X& a);
И тогда уже + оперделяем через неё:
X operator+(X& a, X& b)
{
tmp = a;
return tmp+=b;
}
Если бы можно было бы так делать всегда, то это было бы сделано техническим ограничением. Но он удобен не всегда. Пример – матрицы. Если передавать по значенеию, то гигантские объёмы в стеке, если же ссылку – то грубая ошибка, так как возвр временный объект.
Переопр операций легко, но это порождает много ошибок и падает эффективность.
Идеология С++ (Страуструп):
Делать язык так, чтобы на нём можно было удобно программировать.
С. даёт практически полную свободу и возлагает на него всю ответственность.
С шарп занимает промежуточное положение – переопределение только части операций. Операторы переопр как статические функции члены какого-то класса.
Главная причина того, что переопр станд операций не является стандартной – чаще всего для + математические ассоциации, в то же время, как только мы уходим от математической семантики, то кроме как для строк, которые являются встроенным классом, что если семантика + может отличаться от общепринятой, то один программист принимает одну семантику, другой – другую.
В С++ можно определить функцию void.
Ограничение программиста не всегда плохо – ограничение количества управляющих стуктур, инкапсуляция.
Ограничивать или нет с переопр – вопрос открытый, с одной стороны программы выразительнее.
В С шарп – компромисс, там можно запрограммировать комплексный класс.
С++ - полная свобода, Джава-Дельфи – ограничение. Там программировать комплексные приложения не так эстетично. Но Джава на это и не претендовала.
Вложенные классы
Класс может содержать в качестве своего подчлена другой класс6
class X {
...
class Y {}
}
Это модульные свойства класса.
С точки зрения видимости внутр класс ведёт себя как член класса: X::Y.
Пример: STL. Если взять любой контейнер, то внутри этого класса определяется класс iterator. И любой контейнер в стиле STL должен содержать итератор.
STL состоит из трёх частей:
Контейнеры
Алгоритмы, которвые их обраб
Итераторы
Если мы пишем в духе STL, то заменить один контейнер на другой ничего не стоит.
Если в С++ есть доступ через имя объекта, то в других только через имя класса, и это правильно.
В языке Java есть понятие статических классов:
class X {
public static class Y {}
}
Тогда он ведёт себя как вложенный класс в других ЯП.
Внутри классов свои пространства имён.
Пример про банк из книжки по Джаве, которую рекомендовал лектор
...
Критическая технологическая потребность, но она может моделироваться ссылкой.
//Полезные вещи впендюривают в язык
Константы
Почти Всё, что можно делать в модульных ЯП, можно делать в классных, и наоборот.
Ада:
X:T constant:=e;
e – статическое выражение.
Если константа внутри блока, то выраж может быть любым, так как выч при входе в блок.
В других ЯП не столь гибко.
package P is
X: T constant := e;
Вопрос, как выразить это в классовых ЯП.
Константы есть во всех ЯП. Весь впорос, как внедут себя константы как члены класса.
Бывают разные константы:
Чисто статические – сущ всё время, пока работает программа
Не меняются, пока существует объект, и могут инициализ нестатич выражениями.
Например (если не внутр классов) – внутри Action ссылка на BankAccount, константное поле, но инициализхироваться лолжна неконст выражением.
С++:
static const int a;
int X::a = e;
или в иниц части конструктора.
Java:
static final int a = e;
Существует неприятная ситуация. В С++ интересная особенность – объявлять в качестве константы объекты любого типа. const X x; Это значит, что члены класса не могут. Как это проконтролировать – в форм параметрах функции перед параметрами можно ставить const, и это значит, что функция не меняет этот параметр.
Константность является частью прототипа, и компиятор умеет их различать. Поэтому через весь язык можно протащить понятие неизменямости объекта. То есть к конст объекту нельзя применять неконст функции.
Пример:
если есть класс string, то у него есть конструктор string(const char *), и по определению объект, порожд от константного, является константным. Все временные объекты также явл константными. С этой тз язык гаранитрует, что константность гарантируется всё время.
Те объекты, коотрые кешируют своё состояние, то они меняют своё состояние, и не могут быть константами. Поэтому появился спецификатор mutable – изменение члена класса не является измменение константного состояния. Некая затычка.
Глава 5. Инкапсуляция и АТД
Очередной бардак в области терминологии.
АТД – термин, который возник в 70е годы. В 80е годы возник термин Абстрактный класс. Абстрактный класс не является АТД.нно поэтому С. вместо абстрактных функций ввёл понятие чисто виртуадльных функций.
АТД
Инкапсуляция
Инкапсуляция – упрятывание
Ада:
package P is
...
type T is ...;
end P;
...
P.T
...
package body P is
type T1 is ...;
end;
incapsulate
Имя Т1 недоступно извне – инкапсулировано внутри пакета.
Данные упрятаны и невидимы для пользователя.
В классах есть модификаторы доступа:
public – доступ разрешён везде
private – разрешён только в функциях-членах этого же класса и друзьях
protected – в пределах класса и в потомках
Во всех языках есть понятие упрятывания.
Для чего важно понятие сокрытия информации.
Один программист кончил давно мехмат. И так как мехматянин, то опыт программирования ограничивался фортраном. И он не понимал, зачем нужны приватные поля. И спрашивал, от кого прячется его хнакомый. Он ответил – от себя.
Цитата Дейкстры про преступление.
При смене интерфейса проконтролировать изменения очень тяжело.
Выход – делать более компактным публичный интерфейс, и прятать все детали реализации.При индустр программировании требования только возрастают.
Вопросы языковой реализации инкапсуляции.
Два подхода: модульные и на классах.
Модуульный подход.
Несколько понятий:
Единица защиты – тип.
Атом защиты – либо тип целиком, либо отдельные члены. В классовом яп – отдельные члены. В ЯП с модульной структурой.
Мы расм 4 языка с мод структ: Ада, Модула-2, Оберон-2, Дельфи.
А, М2 – тип
Д – смешанный подход, есть понятие класса и отдельные члены, но есть интерфейс и реализация.
Модульные языки тяготеют к атому в виде типа.
В Обероне отсут разделение модуля на 2 части, однако это разделение присутствует логически.
TYPE T* = RECORD
Y:T1; - приватно
Z*:T2; - публично
END;
Вирт сказал, что это легко объясняется с помощью понятия проекции:
T1 <= T1*T2
Языки, которые защищают типы, основываются на использовании АТД.
Понятие АТД есть в любом совр ЯП. Просто А и М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 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 |