Языки программирования, 17 лекция (от 31 ноября)

Материал из ESyr's Wiki pages.

Перейти к: навигация, поиск

ЯП


Лектор сказал: Лекций 7, 9 ноября не будет


п3. Определение типов классами.


Тип данных = Множество операций + множество значений.


До этого определяли структуры, определяли операции, и связывали это модулем. Но при этом ощущается некоторая избыточность. Например, описываем пакет Стеки, определяем там тип Стек. Уже дублирование имён. Ображение: Стеки.Стек. Класс – дуален – и структура, и обёртка.

Класс по определению предназначен исключительно для ТД.

Каким обращом множество операций связывается со множеством значений.


Основные языки: Си++, Си шарп, Джава, Дельфи.


Дельфи – экле..тичный язык, там можно работать и в классич парадигме, и в ООП.


По синтаксису Дельфи далеко отстоит от святой троицы.


Члены класса:

  1. Члены-данные определяют структуру, множество значени

  2. Функции (процедуры) – оперделяются множество операций

Синтаксис:

Сишный

class Name {

описание членов

};


Синтаксически похоже на описание структуры, класс без процедур – та же структура.


Описание членов:

ТД – описание переменных

ЧФ – прототип


Мы рассм класс как ТД.


Примеров несть тому числа.


Класс является и модулем. Внутри класса может описано статические члены.


Внутри класса могут быть влооженнвые опеределния типов. - по аналогии с модулем.


Отличия других ЯП от С++ - в Си шарп то же самое, только кроме классов могут быть перечислимые типы. То же самое в Джаве – опеределение членов-функций, вложенных классов.


В чём отличия Си шарп Джавы, от С++:

Кроме прототипов функцйий могут быть опеределния классов.

Class X {

vois f()int i;

void g() {}

}


Это некий синтаксический сахар. Здесь идёт речь об inline – функциях

Inline – функции – функции, тело которых может вставлять в месте вызова.

Похожи на макросы. Отличие от макросов -

Типичный пример inline-функции – максимум:

inline T Max(T x, T y) {return x<y ? y : x; }

и вместо max(a,b) будет подставлен a<b ? a : b;

Очень похоже на соотв define. В чём основная разница -

Если поытаться описать такой макрос:

#define max(x,y) ((x)<(y)) ? (y) : (x);

Скобки нужны, чтобы вместо x и y

Внешне похоже, Но инлайн предпочтительнее. В inline типы должны совпадать. При вызове инлайн будет сделано преобразование типов, если нужно, или выдана ошибка в терминах вызова. Дефайн при ошибке будет ругаться в терминах макроподжстановки.

Инлайн функции хороши для современных суперскалярны процессоров. Срывает конвейер операция перехода, а любой вызов функции – нелокальный переход, и это может серьёзно уронить производительность. Inline – программист рекоммендует компилятору провести подстановку тела функции. В случае виртуальных функции инлайнятся функции очень редко, ибо во время компиляции можно не знать, что делать. Ещё не инлайнятся циклы, ибо там есть переходы. И сущ быстродействия это не даст. Зачем это нужно было Страуструпу? Это было и в Си, но в каждом отдельном компиляторе для этого были свои прагмы, и Страуструпу не нравилось, что это не стандартизовано.

А зачем Страуструпу понадобилось повышать быстродействие подобного рода функции? Это связано с ООП, в частности, с появлением geyters-setters. Например, если мы хотим сделать переменную read-only, то делаем только getter: T getA() {return a; } И его хорошо бы инлайнить.

Впервые понятие класса появилось в Симуле, и С. Обратил внимание на её неэффективность, так как там подобные функции не инлайнились. И вместо того, чтобы решать проблему в частности, он решил её глобально.

И был введен некоторый синтаксический сахар. Чтобы вместо того, чтобы писать

class X {

T GetA();

};

innline T X::GetA() { return a; }

молжно просто определить функцию прямо внутри класса, и тогда она будет считаться inline. Это было сделано для того... Есть физическое разделение интерфейса и реализации (хедеры и цппшники). И опеределние класса помещается в хедеры, а определение фукнции в реализацию. В С++ есть принцип РОРИ.

Но для inline это не катит, ибо компилятор должен знать оперелеление inline-функции, поэтому их надо помещать в хедер. Поэтому для инлайн-функции С. Разрешил такую возможность.

В С++ реализовано РОРИ, а в Си шарп и Джаве – нет. Там если описали функцию-член, то обязаны указать её реализацию, только если это не чисто виртуальная функция. То, что это неудобно с точки зрения чтения, это да. Во время отладки это удобно. А во время чтения средства систем разработки позволяют вырезать интерфейс и смотреть на него. Сам язык не нагружается соотв понятиями. Это характерно для совр ЯП, которые проектируются с учётом того, что будут погружены в соотв среду.


Дельфи (идейно ближе всего к С++):

type X=class

объявление членов;

i:integer;

procedure P;

function f(k:integer):integer;

end;


Тут чётко РОРИ.

В объявлении (спецификации) класса только переменные и прототипы. Это в интерфейсной части. Правда, может быть и в реализации, но это уже внутренние классы, только для реализации.


Implementation

procedure X.P; begin ... end;

function X.F ... begin ... end;


Небольшие синтаксические отличия:

Мы пока не говорили, каким образом скрывается реализация. Во всех этих языках есть возможность возможность экспорта членов, для этого используется модификатор publiс, действие которого распротраняется на вс объявления после него. В Джаве-Си шарп спецификатор перед каждым объявлением. Различия в умолчаниях.

Си++: для классов по умолч прайвейт, в структурах – private

C#: для классов и структур – private

Java, Delphi: в Джаве есть пакетный, в Дельфи – для юнита – там доступ в перделах пакета-юнита по умолчанию. Сами классы могут объединяться в модули – и это модульный доступ.


Про доступ мы пока больше гововрить не буду.


Множество операций+множество значений

Множество операций – public функции-члены

Множество знач – Члены-данны, хорошим тоном счиатеется их скрытие.


Сосредосточимся неа деталях:

Синтаксис обращения.

Члены локализоканы внутри класса. Есть Класс Х, есть член х. Синтаксис доступа – Х.х. Этот синтаксис взят из синтаксиса записи. Этот синтаксис просто обобщён на класс. ФЧ отличаются тем, что им неявно передаётся ссылка/указатель на объект класса.

Claas X {

void f() {...}

};

E а будет неявный параметр – ссылка на объект. При вызове x.f(); юудет передана ссылка на х. И в теле функции будет определёна переменная:

C++ - указатель this

С# - ссылка this

Java, Delphi – сслка Self

почему Страуструп не использовал self, хотя оно использовалось в Smalltalk, который явл оперделяющим с тз терминологии: Вилдимо, у него что-то там конфликтовало. В Си шарп было взято по принципу чтобы не было похоже на Джаву.


К члену класса обращаемся снаружи только через имя объекта класса. Изнутри – можно через this/self, а можно просто через имя члена. Класс образует свою зону видимости – внутри членов класса можем обращаться к другим просто по имени. Класс образует свою область видимости. И если есть глобальное описание одноимённых переменных, то он и перекрываются. В Джаве и Си# глобальные переменны еотстусттвуют, но там есть вложенные пространства имён и та же проблема. Но this/self нужен, когда, например, класс передаёт ссылку на себя. В Си шарп this испоьлзуется в документации, когда

есть i, j как ячлен класса

void (int i) {int i; - нельзя, ибо имена формальных параметры как бы локализованы внутри тела функции

}

тело функции – вложенная по отношению класса область видимости, а что делать с объявлением в классе? Везде закрывается, кроме Delphi, в Delphi просто нельзя так делать, и это правильно. В остальных языках к члену может обратиться через this/


Попробуе мпровести параллель между определением в омдульных языках и в ОО.

Ада (Модула-2)

package M is

type T is ///;

procedure P(X:T);

procedure P;

X: Y;

end M;

package body M is

procedure G(x:T) is ... end G;

Z : Y;


ОО:

class T {

public:

void P();

а что делать с P без параметров? Он может обращаться к X, Z. Аналог – статические члены, им не передаётся никакой неявный параметр.

static void PP();

};

Как обращаться к PP? А как к P без параметров в Аду? Через имя модуля – T.P, в ОО – через имя класса – T.PP(). Но в С++, и за это язык ругают, можно ещё через объект класса – t.PP(), а через имя класса – T::PP().

Точка и два двоеточия не перегружаются.

Public:

Y X;

private

void G();

static Y Z;

};


Если брать модульный язык, где мы описываем отдельно стоящие параметры и переменные, то в ОО мы и описываем статически. Статические члены являются глобальными, но они локализованы в имени типа, и до них мы добираемся через имя класса. Класс – полный аналог модуля.


В Си шарп и Джаве употр статич членов очень часто, ибо это аналог глобальных членов и функции. У программиста на С++ есть возможность вынести подобные функции на глобаьлный уровень, а в Си шарп и Джаве эти понятия отсутствуют.


Зачем нужны статические данные:

Корнстанта.

Class Stack {

Int top;

T body[N];

const int N; - глупость, потому что будет для каждого своя, надо делать статик.

}

Статические функции нужны для того, чтобы работать со статическими данными.


Статические члены существуют вне зависимости от объектов класса.


Пример: пусть мы хотим располагать объекты класса только в динамической памяти (актуально только для С++). Можно ли это обеспечить? Легко. Оставить публичной одну функцию – static T * MakeT() {return new T(); } Если сделать все конструкторы приватными, то единственый споособ создать объект класса – вызвать эту функцию.


Статические члены есть во всех ОО-языках, так как некоторые ыещи можно проможедировать только ими.


Константы – часть объекта или класса?

В Смаллтоке есть пноятие класс, и есть понятие экземпляр. У класса есть методы, есть переменные. Переменные в См. Делились на переменные экземпляра, которые были для каждого экземпляра свои и переменнаяе класса, коотрые размещались в единственном числе. Осталось обобщить на функции – к нестатическим передаётся ссылка на объект.


Любая ли константа должна быть переменной класса? Не все. Одни принадлежат экземпляру, другие всему классу. В Джаве для констант экз есть final. Где они инициализируются? Есть кноструктор, который вызывается при создании класса - и там инициализируются константы, только один раз. В Си шарп – специали=ьный синтаксис конструктора – инициализирующая часть.

Где инициализируются статические данные – единственная возможность инициализировать статические данные – в cpp.

Class X {

static const int i; - обязаны инициализировать. В теле класса инициализацию сделать нельзя.

};

Вопрос размещения – из-за раздельной трансляции компилятор не знает, где размещается статические данные. Ибо он не знает, откуда взялся класс – из инклюда или из текста. Поэтому, если описана статическая переменная, программист должен указать её размещение.

Поэтому в цппшнике надо написать int X::i = 0; таким образом явно указали размещение. А если в хедере – то досявые-виндовые редакторы связей будут ругаться? Ld – выдадет ворнинг.

Аналогом размещения функции члена для фугкции – оперделение.

Пока мы не вызываем функции-не обращаемся к переменным, ошибки нет, как только обратимся – ошибку найдёт редактор связей.

Где будут инициализироваться статические члены –


в Си шарп можно гибко управлять порядком загрузкой модулей, поэтому можно предсказать порядок, в котором будут выполняться конструкторы, посему в инициализации можно писать что-то содержательное.

В Джаве есть статический блок инициализации:

class X {

static int i = exp; - немедленная инициализация справедлива и для Си шарп и дляДжавы

static { ... }


Одна из самых интересных созможностец – наличие понятие конструктора. Есть понятие специальная функция – про них есть дополнительная семантика, и компилятор знает, когда их вызывать. Наиболее популярная – конструктор, в Си-подобных языках синтаксис – имя функции совпадает с именем класса X().

Статические конструкторы – видно, что это специальная функцияю Когда модуль загружается в память, происходит вызов статических конструкторов (Си шарп-Джава), при этом сверху вниз выполняется инициализация, потом статик-конструкторы. Статический конструктор генерируется. И он обладает неукоторой семантикой.

Стандартная семантика – вызыыаются конеструкторы базовых классов, инициализация (конструкторы) подобъектов, после этого выаолняется тело конструктора. У статических конструторов то же самое – вызовет конструкторы базового класса, выполнены статические инициализаторы членов, и потом тело.

Личные инструменты