Лекция 9 (Лекции (2009) (Саша Федорова))
Описание файла
Файл "Лекция 9" внутри архива находится в папке "Лекции (2009) (Саша Федорова)". Документ из архива "Лекции (2009) (Саша Федорова)", который расположен в категории "". Всё это находится в предмете "языки программирования" из 7 семестр, которые можно найти в файловом архиве МГУ им. Ломоносова. Не смотря на прямую связь этого архива с МГУ им. Ломоносова, его также можно найти и в других разделах. .
Онлайн просмотр документа "Лекция 9"
Текст из документа "Лекция 9"
6
Лекция 9.
Пункт 4.1 Подпрограммный тип данных
(зачем он нужен и везде ли есть)
Из рассматриваемых нами языков программирования подпрограммный тип данных есть не везде. То есть почти везде, кроме Java и Ada 1983.
Какие есть аргументы за и против включения функционального типа данных в язык программирования?
Каковы технологические потребности?
-
Передача подпрограмм как параметров.
В Pascal введено 2 вида подпрограмм как формальных параметров: процедуры и функции. Аналогичный подход можно заметить и в других языках.
В Си подпрограммный тип данных усложнен понятием указателя.
Можно рассматривать подпрограммный тип данных как множество констант, тогда единственными операциями для подпрограммного типа данных будут
-
присваивание (передача параметров по значениям)
-
вызов
Типичный пример Integral. У него в качестве параметров существует процедурный тип – f(подынтегральная функция).
Generic в Ада – это параметризованный тип данных. В Аде, помимо обычных типов данных, параметризовываться могут еще и процедуры и функции. Отметим, что параметризация в родовых сегментах происходит во время трансляции.
Итак, как мы выяснили – значения подпрограммного типа данных – это набор констант, образующийся именами соответствующих процедур и функций.
С++, Modula-2, Оберон
void f(int) – это прототип функции, объявление некоторой функциональной константы. Если перед ней поставить typedef, что имя функции станет синонимом нового типа:
typedef void f(); //f стал синонимом нового типа
Можно (и даже часто нужно) делать так:
typedef void (*f)(int)
(А лучше все делать через typedef )
Пример определения функции через typedef:
type Func_Pointer is access function(L, e: Float) return Boolean
function Comparenum(X, Y: Float) return Boolean is
…..
end Comparenum
Теперь если есть
F: FuncPointer
F:=Comparenum;
Указатели служат в Аде-83 только для указателей на обьекты динамической памяти.
А в Аде - 95 введен был специальный указательный тип, который могли
ссылаться на любые обьекты.
Подпрограммные типы данных, реализованные как функции, встречаются во многих языках:
Пример:
Oberon, Modula-2
TYPE FUNC_INT = PROCEDURE (L, R: REAL): BOOLEAN
И пусть есть процедура COMPARE
PROCEDURE COMPARE(X, Y: REAL): BOOLEAN;
VAR F: FUNC_INT;
Тогда можно сказать так:
F:=COMPARE;
F(0, A); //вызов соответсвующей функции
Однако существовала еще и вторая причина, по которой процедурный тип данных был введен в другие языки: потребность в функциях обратного вызова. (Как правило, когда у нас есть некий обьект со сложной внутренней структурой данных и внутри есть функция, связанная с понятием события.
Классический пример – таймер. Есть некий обьект «таймер», в нем помещается указатель на функцию обратного вызова, которая вызывается при наступлении данного события (по истечении времени таймера, например).
В Ада-83 родовые сегменты также связаны с функциями обратного вызова.
Еще один пример данных функций: язык Оберон. Когда в него добавили понятие наследования (Вирт сознательно не хотел называть это наследованием – а лишь «расширением»), «понадобился» еще и динамический полиморфизм, в котором также использовались функции обратного вызова. Таким образом, механизм виртуальных методов в Оберон моделируется при помощи функций обратного вызова.
Пример. X Windows System - система графики в Unix.
Идеи объектно-ориентированных языков все больше проникали в массы, механизм наследования все чаще реализовывался через функции обратного вызова.
Однако реализовывать реакцию на событие с помощью функциональных типов данных, где на любое событие получалась только одна реакция, было не очень хорошо. Реализация на основе процедурных типов данных также была не очень удобной.
Оберон-2 – появилось динамическое связывание методов.
Замечание
В Аде динамическое связывание методов появилось не сразу. Если взять чисто объектно-ориентированный язык, то в нем любой обьект данных принадлежит некоторому классу (из соображений эффективности). В таких объектно-ориентированных языках, как Java, существуют простые типы данных и классы, а также классы-автоупаковки(in-boxing) и автораспаковки, позволяющие любой простой тип данных сопоставить некоторому обьекту. В Java, заметим, так и не появилось процедурного типа данных: все подобные вещи в чисто объектно-ориентированных языках пишутся через интерфейсы. (Такой же подход и в C#: класс, только класс, и нечего кроме класса нам не надо )
Интересно, что в С# существует 2 способа реакции на события (в классических языках программирования существует только один способ реакции на событие). Пример- OnClick()//работает механизм наследования и замещения.
В Java появилось понятие анонимного класса, предназначенного для замещения в нем некоторых функций, что служит эффективным и надежным заменителем подпрограммного типа.
В С# понятие объектно-ориентированного вызова осталось: делегат – интересное расширение понятия подпрограммного типа.
Прежде чем рассматривать подробности Delegat в C#, рассмотрим, какие встречаются подходы к реализации данной особенности языка.
В Turbo Pascal (а точнее, в Delphi) существовал классический подпрограммный тип данных.
type PRC=procedure(var i:integer) of T;
Представим себе прототип процедуры, из которого будут выкинуты все имена – получим определение процедурного типа данных. Теперь объектом типа PRC можно присвоить любой метод класса T.
type T = class
………………………..
procedure FOO(var j:integer);
end
В чем отличие методов класса от глобальных функций? Они не могут существовать без объекта. У метода есть скрытый параметр this(указатель на обьект класса, который вызывает данный метод). Вызов метода может существовать только в контексте объекта:
Обьект t класса Т:
t.f();
this - это некоторый константный адрес, изменяться он никаким образом не может, а следовательно, необходимо хранить не только адрес на саму процедуру, но и то, что ей должно передаваться.
В языке С++ введено понятие указателя на член класса.
Таким образом, для реализации подпрограммного типа мы храним либо и this, и адрес самой процедуры в нем, либо храним только смещение относительно постоянного объекта в памяти.
Пример.
typedef void (*f)(int);
void g(int)
Параллельно с этим Стауструп ввел понятие указателя на член класса:
void f(int)
{
int i;
}
F F_ptr;
F_ptr=g;//может, я не так поняла и здесь стоял &.
Каким будет описание указателя на функцию-член класса? Любой указатель, стало быть, станет ::Х. То есть:
(X:: *)int p;
В C++ указатель на член класса – это смещение его относительно начала объекта (по сути, это не указатель даже). Указатель на член данных – то также смещение относительно начала объекта класса X.
сlass X{
int j;
void f(int);
int i;
}
Согласно подходу Страуструпа, мы «предоставляем» this и информацию, находящуюся в этом указателе (смещения всех обьектов).
Что происходит в случае, если метод класса – статический?
static void p(int);
Тогда можно написать
F ptr = X::p;
Вернемся к понятию делегата в C#. Делегаты в С# используются для функций обратного вызова(Пусть есть некоторый обьект. Иногда в нем происходит что-то интересное. Другие обьекты могут как-то реагировать на это. Как только это «что-то» происходит, вызывается callback. При вызове важно не забыть о том, что функция обратного вызова – это указатель:
if(callback)
callback(0);
)
Идея: если бы мы имели дело с С++ или с Delphi, то надо было бы работать с указателем. Если несколько обьектов «хотят реагировать» на соответствующие вещи, нужно делать контейнеры из соответствующих вызовов.
В С# функция обратного вызова – это не функция, а делегат.(delegate, в System – Multicast + Delegate)
Пример объявления делегата в C#:
public delegate прототип функции
public delegate void f(int);
f в данном случае становится именем делегата. Это не есть прототип функции. Фактически f ведет себя как имя типа, к которому относятся все функции с соответствующей сигнатурой.
class X{
public delegate void delf(int);
delf g; //g – это переменная делегатного типа, так называемая цепочка функций.
}
Операции делегата:
-
присваивание (инициализация)(=)
-
добавление делегата(+=)
-
«убирание» делегата(-=)
-
Вызов (скобочки: ())
В любой из этих операций делегат – это цепочка указателей на функцию.
Тут неважно, статические они или нет. В делегате запоминается и соответствующий указатель this(если функция не статическая), и функция-обертка.
class X{
void fy(int);
}
class Y{
void fy(int)
}
сlass G{
void h(int);
}
void delf(int)
X a=new X();
Y b= new Y();
G c = new G();
a.g=a.f;//не совсем верно, лучше: a.g = new delf(a.f);
a.g+=b.fy;//описана в классе Y
a.g+=G.h
}
Таким образом, операции += и -= позволяет соответственно добавить или убрать делегат.
Таким образом, делегат в С# - заменитель процедурного типа данных.
Механизм подписка-рассылка
Данный механизм представляет собой чистую событийно-ориентированную модель. Это второй способ использования делегатов (первый – для передачи функций как параметров).
Пусть у нас есть объект
Event Producer; //порождает события
И объект
EventConsumer;//«потребитель» события
В общем случае может быть много Producer-ов и Consumer-ов.
Пример.(Почта)
EventProducer делает:
public delegate void GhNewMail(object o);
OnNewMail onNewMail;
Вызов пустого делегата допустим (он ничего не делает).
EventProducer ep = new EventProducer;
EventConsumer ec = new EventConsumer;
EventConsumer ec1 = new EventConsumer;
ec.OnNewMail+=new OnNewMail(ec1); //подписка NewMailMessage. – обьект «подписался» на сообщения от другого объекта
При этом у класса EventConsumer должна быть описана функция void NewMail()
Внутри EventProducer:
public NewMail(Mail Message); //рассылка
Приведенная нами конструкция плоха тем, что подписка произвольна. А рассылка должна быть приватной.
Механизм делегатов инкапсулирует свойства процедурных типов данных, сводя к минимуму недостатки данного типа.
*конец функциональных типов данных*