И.Г. Головин - Конспект лекций по курсу Языки программирования (1161120), страница 18
Текст из файла (страница 18)
Тоесть ref – inout семантика, а out – только out семантика. Этоиспользуется для большей гибкости языка.Андерс Хейзберг – отец системы TP,Delphi. Почему Хейзберг засунул такие вещи?В TP есть параметры-значения, параметры-переменные. Передаем параметр-значение, то жесамое, что передаем ссылку. А можем ли саму ссылку изменить? В Delphi не можем.То же самое Хейзберг хотел в С#, осуществив ref и out.Пример (Ада)Возьмем невинную процедуру АДАprocedure P(X,Y:inout T) is…beginX:=expr1;//raise errorY:=expr2;End P;В чем состоит беда?105Raise(почти что throw). Почему использовал raise – вопроссовместимости(с С конечно).Появились new,delete и прочее.А что нельзя перекомпилировать? Библиотеки, системные вызовы.Чтобы компилировались все стандартные заголовки Linux.Raise - посылка сигнала самому себе.struct timeTime(…)Имя типа, а не функции.Но была обеспечена совместимость с Unix.Raise error.Является программа стандартной? ДаКак можно реализовать out-семантику? 3.
4.P(a,a) если по ссылке и исключение, то а изменит значение, иначе не изменит. Это нехорошо.Единственный способ обеспечить переносимость – явно специфицировать механизмы.Поэтому большинство языков выбирают самый простой – по значению, и самыйуниверсальный – по ссылке.Образцовый язык – только один (с этой точки зрения): C++const T& x //ссылка на константный объектПривело ли к усложнению языка? В некотором смысле да.Плевать хотел на спецификацию стандартных функций в С++, а это нехорошо.Если указал неявное преобразование для своего класса, то объект получается константный.const T x;Const входит в профиль функцииX* const thisvoid q() const;const X* const this;В STL четко выдерживается in-семантика.Не просто специфицировать константные ссылки, но и говорить о константных методах.106Если поле отмечено как mutable, то даже в константных объектах оно может меняться.mutable X a;const T x;Константный именно объект(а не класс).По умолчанию можно применять только члены с const-ом.Const – спецификация this.X *const this;this – константа (this = нельзя в С++), но она ссылается на неконстантный объект.void g() const;Это значит что const X *const this;А что же сказать про члены – данные – все константы (публичные и нет).X.a = 0 //не статический членmutable – специальный модификатор к членам данным, даже в константных объектах полеможет меняться(например, некоторые объекты допускают кэширование; меморизация)mutable – не значит что плохой стиль программирования (наоборот – думаете оконстантности).Имманентные свойства современных процедурных языков – должны специфицировать нанизком уровне механизм передачи параметров.Несколько слов о 5 способе передачи – самый забитый, однако самый естественный – поимени.Был использован в Алгол 60 (в нем еще было реализовано по значению).value p; //p передается по значениюВ фортране не было рекурсии => можно было реализовывать запись активации встатическом списке.Как только появляется рекурсия, необходима динамическая структура.Если не value p, то передавался по имени.Что записываем в фактический, то и будет формальным.
Например,P(a)Вместо формального параметра подставляется aЕсли P(X[0]) – вместо формального параметра X[0]P(X[i],i) – подставляются X[i],i.107С точки зрения механизма – самый высокоуровневый.Пример (Simula 67)Работал неэффективно, т.к.
был построен на базе Алгола60.Программист говорит «а я не знаю что будет вместо i».Как компилировался код?procedure PP(x,y);integer x,y;Beginx:= …y:=…endvoid PP(xmy) // old-fashion Cint x,y;{…}Алгол60 при компиляции программировал thunk – некая процедура –помощница, кот вызывается, когда нужно что-то делать.Для параметров по значению вычисляют его адрес (не значение), X[i] –вычисляет адрес объекта.И так много разКроме косвенного обращения нужно еще и вычислить адрес – неэффективно!!!!Одна из задач в старых учебниках по программированию: доказать, обосновать, что Алгол60 нельзя написать swap(x,y) – написать то можно, но обращение swap(x[i],i) будетдавать неправильный результат.А в Lisp как передаются параметры – по значению или по ссылке? В функциональныхязыках, let x = … - это не присваивание, это отождествление.В высокоуровневых ЯП нет задачи специфицировать механизм.А в процедурных ЯП нет возможности программировать без спецификации.ПЕРЕГРУЗКА ИМЕНВо многих ЯП существует статический полиморфизм: одной сущности (подпрограмма)может соответствовать несколько форм (несколько тел).
Это перегрузка (overloading).Статический полиморфизм – выбор, какой именно способ статический.Динамический полиморфизм происходит на уровне выполнения.Существует два вида статического и один вид динамического полиморфизма.Динамический полиморфизм – связывание виртуальных методов.108Статический полиморфизм – перегрузка имен и параметрический полиморфизм.Сущность при перегрузке имен – действие. Способ реализации сущности - подпрограмма (вобщем случае).У сущности есть имя, работаем с именованными сущностями, нет возможности именоватьоператор.
Действия в общем случае подпрограмма. Действие ассоциируем с именемСпособов реализации действий может быть много. Сущность ассоциируем с именем.Какая проблемная область чаще всего нуждается в перегрузке? Ввод/вывод. Почему? Дапотому что с точки зрения действия есть вывод, а конкретная суть зависит от реализации.Одна область видимости и несколько определяющих вхожденийВозникает два понятия: определяющее вхождение и использующее вхождение.Подавляющее большинство ЯП для определяющего вхождения используют объявление. Этои называется перегрузкой имен.Какие языки не допускают перегрузки имен? Старые(C, Pascal)+ Modula 2,ОберонВирт не счел понятие перегрузки необходимымсout << “count=”<<cnt<<endl;На Обероне или на МСодуле 2INOUT.writestring(“count=”);INOUT.writeint(cnt);INOUT.writeln;В АДА можно перегрузитьtype color is (red,green,blue);В одной области видимости red может иметь несколько определяющих значений.X:= pi;Как технически реализовать выбор?Procedure p(x,y: T)P(a,b);В АДА в одном из первых появилось перекрытие – в разрешение перекрытия входит еще ивозвращаемое значение.procedure P(X:T)function P(X:T) return boolean;function P(X:T) return integer;По контексту вызова можем распознать все.109ПОДПРОГРАММНЫЙ ТДВ большинстве процедурных ЯП (императивных) процедурный ТД – разновидностьуказательного ТД.
Это еще раз подчеркивает низкоуровневость процедурных ЯП. В языкеАссемблера имя процедуры: ( CALL P ) P – метка. Мы не можем сделать jmp X в языкеАссемблера, а jmp P – можем, P – это метка. И то, и то адреса, но это адрес команды, а неданных.В языке Си честно указано, что когда мы передаем что-то, то мы так и должны написать:void f(void (*arg)());Или с помощью typedef’а:typedef void vf(); void f(vf *arg);Эти определения эквивалентны. В Си нам явно «тыкают пальцами»: это указатель.В Си ++ нет разницы между (*arg)() и arg(), не проводят разницы между указателем нафункцию и именем функции.В языке Модула-2 или Оберон делается просто – опускаем имена.TYPE PIF = PROCEDURE (INTEGER):INTEGER;PRC = PROCEDURE (VAR:INTEGER);Процедурный ТД – это указатели в большинстве языков (большинство – это N-1, где 1 – этоC#).Все объекты первого порядка характеризуются тем, что из одних можно получать другие.Множество значений процедурного типа – это набор имен процедур и функцийсоответствующих прототипов, описанных в Вашей программе.
Каждое имясоответствующей процедуры можно рассматривать как константу. Все, что мы можемделать с процедурным типом: копировать, вызывать, сравнивать (одну с другой). Мы неможем на основе одной процедуры получить другую, поэтому процедуры не являютсяобъектами первого порядка.В первой версии языка Ада процедурный ТД вообще отсутствовал. Вообще, в каких случаяхнам нужен процедурный ТД? 1) передача, как параметров 2) функции обратного вызова(callback’и).
Как же можно в Аде обходится без него? Ответ – обобщения (generic). Чаще всегов литературе generic переводится – родовое. Обобщенный – параметризованный,статический.Возьмем C++, который Вы знаете лучше чем Аду:template<class T, int size> class Stack {…};Stack <int, 128> S;На месте 128 Вы можете поставить константу и только константу.В языке Ада роль функций обратного вызова играли статические переменные.
Но этообходилось довольно дорого – объем кода получался большой. Например, библиотека,занимающая на Аде 90000 строк на С++ занимала 11000 строк.type PRC is access procedure (inout INTEGER);p : PRC;110procedure Sub(X:inout integer);p := Sub access;Есть особый язык C#, он особый, потому что является языком императивногопрограммирования, но в него все чаще «пролезают» вещи из функциональногопрограммирования.C++ - указатель на член. Указатели на член-данное – это не указатели.Всегда, когда хочешь человека завалить, просишь его – напиши объявление указателя начлен класса X.class X {static int s;int i;int j;…};int x::*p;p=&x::i;p=&x::j;int *pp;pp = &x::s;.* - обращение к указателю на член.X a;a.*pУказатели на члены-функции – это не указатели.void (x::*pfx)(int)); Тут конечно без «пол-литра» не разберешься.Тут должен быть более сложный механизм. Нужно учесть виртуальность функций.Собственно говоря, указатели на члены функции (->*) сделали просто «до кучи», я ихникогда не использовал.void (*p)(int);Функции обратного вызова: модель «подписка/уведомление» поддерживается в C#.
Мыговорим, что заинтересованы в этом событии – устанавливаем свой обработчик (подписка).Затем происходит обратный вызов (уведомление). Подписчик должен не зависеть от другихподписчиков. Некая разновидность процедурного типа.В C# часто используются статические функции, потому что нет глобальных. Например,функция main. Мы должны ее вызвать до того как порожден какой-либо объект данных.Также, любая математическая функция – sin, cos, exp,… Изобрели специальный класс mathдля этих функций.import пакет.*;В Java вынуждены писать math.exp. Для того, чтобы этого избежать, необходимо написатьstatic import Math.*;111В языке C# придумали делегатский ТД (делегат).
Синтаксис:class X {delegate void f(int); //аналог объявления функционального типаvoid g(int x);…f a;class Y {void h(int a) {…} static void l(int b) {…} }}Что можно сделать с делегатом? Присвоить. Синтаксис в первом C# он был ужасный:a = new f(this.g);a = new f(Y.l);var x = new Y();…a = new f(x.h);В делегате на самом деле содержится список соответствующих значений. Поэтомувозможны операции: +=, -=. (Это фактически – подписка.) Делегат можно сравнивать.
Такжевызывать a(0); (это уведомление).Прелесть делегатского типа – event. Event – экземпляр делегатского типа.class Server {public event f a {add { value }remove {…}}};Внутри функций членов сервера можно выполнять все операции делегатского типа.class Client{void handler(int a) {…}..a += handler; (подписка)…a -= handler; (отписка)};А внутри клиентского только подписку и отписку.Но не это самое интересное в делегатском типе. Появились анонимные делегаты.a = delegate(int x){return x+1;}Это символ – делегат.112До этого приходилось обязательно придумывать эту функцию, а еще и в какой класс ееразместить.delegate int Func1();private int cnt = 0;private int c;Func1 d;void P(Func1 f){…d=f;…}void X(){…console.Write(d());}P(g1); X(); X(); X(); - будет напечатано: 1,2,3int g1(){return cnt = cnt+1;}P(g1) ~ P(this.g1)void Sample() {int x=0;P(delegate(int){return x=x+1;});}Теперь вызовем вместо P(g1) функцию Sample();sample(); X(); X(); X();Если локальная переменная используется в анонимном делегате, тогда получается, чтообласть жизни становится больше области действия.