лекции (2011) (1160854), страница 4
Текст из файла (страница 4)
В Фортране обычно параметры передаются по адресу, но когда передаётся простой объект данных, чтобы не происходило лишних операций разыменования, можно передавать «по значению и результату»( /<параметр>/)
• В Ада-95 – по значению, по ссылке
• В Си – не определяется семантика использования. Способ передачи только по значению
• В Си++ – по значению, по адресу (ссылке). Контроль семантики: in – ссылка константная, out, inout – не константна
void f( T &); //Компилятор считает, что f будет менять значение => константный объект
//передавать нельзя
Это указание для компилятора, чтобы он следил за соблюдением семантики.
В C#, Delphi(TurboPascal+объектно-ориентированная надстройка) – языках с референциальной моделью данных – все параметры автоматически передаются по ссылке.
Типы данных C#:
-
референциальные
-
типы-значения
-
примитивные типы данных (все передаются по значению)
-
типы данных структуры
• С#
void f( T x) {….;}
……
T a; // a – ссылка, если Т – объект
f(a); // передаётся ссылка
Оба языка поддерживают идею примитивных типов (которые в C# являются подмножеством типов-значений — value types), и оба для трансляции примитивных типов в объектные обеспечивают их автоматическое «заворачивание» в объекты (boxing) и «разворачивание» (unboxing) (в Java — начиная с версии 1.5).
Тип-значение(value type) – тип, объекты которого передаются по значению. Если где-то нужен объект такого типа, то отводится место под сам объект. типами-значениями являются простые (примитивные) типы данных и структуры
Референциальный тип – тип, объекты которого передаются по ссылке. Если где-то нужен объект такого типа, то отводится место под адрес. Референциальными типами являются классы (любой массив – наследник класса Array, строка это объект класса String, и т.д.)
В принципе, в C# можно передавать объекты простых типов в функции с помощью классов-обрёток, но C# также поддерживает явное описание передачи параметров по ссылке – ключевые слова ref и out. «ref» реализует семантику inout, а «out» реализует семантику out. При использовании out компилятор вставляет квазистатический контроль на наличие в методе присваивания значения, зато не требует инициализированность фактического параметра перед вызовом метода.
void f(ref int x){x = -3;}
….
int a = 0;
f( ref a); // а будет передано по ссылке, если бы объект «а» был структурой, то он так же // передался бы по ссылке
Java:
Все передается по значению, но для классов в созданный объект копируется адрес (ну то есть реально это все равно передача по ссылке)
Структур в Java нет.
Передача объектов примитивных типов в методы «как по ссылке» выполняется через классы-обёртки:
void f(Integer X){…X = ….; }
…..
int i = 1;
Integer px = new Integer(i);
f(px);
i = px;
Integer – класс-обёртка для примитивного типа «int». Суть способа – преобразовать объект примитивного типа в объект класса и работать внутри функции с объектом класса.
Алгол-60
Передаём сам объект «как он есть» (по имени). Фактически передаётся его идентификатор как строчка. Далее в теле процедуры идентификатор формального параметра заменяется на идентификатор фактического.
Обоснование невозможности написание процедруры swap на Algol-60:
procedure Swap(a,b); //a, b передаются по имени
Begin
T tmp;
tmp := a; a:= b; b := tmp;
End;
….
swap(i, A[i]);
T tmp;
tmp := i;
i := A[i];
A[i] := tmp;// A [ A[i] ] := i; неправильно!!!
swap(A[i], i);
T tmp;
tmp := A[i];
A[i] := i;
i:= tmp;// i := A[i] – всё правильно
Решение проблемы: С каждым параметром передавалась процедура thunk, которая выполнялась при каждом обращении к фактическому параметру.
В С++ можно задавать параметры по умолчанию: void f(int a = 4){ …;}
В C# эту возможность убрали. Вместо этого предлагается использовать перегрузку:
f(){ f(1, 2); }
f(int i ){ f(i , 2) ;}
f(int i, int j ) { … ; }
Статический полиморфизм и перегрузка имен подпрограмм.
Подпрограммные типы данных. Проблемы, связанные с
подпрограммными типами данных и способы их решения в современных ЯП.
Ада 83, Java – нет.
Передача подпрограмм, как параметров.
Присваивание [:=]
Вычисление [()]
В Pascal введено 2 вида подпрограмм как формальных параметров: процедуры и функции. Аналогичный подход можно заметить и в других языках.
Значения подпрограммного типа данных – это набор констант, образующийся именами соответствующих процедур и функций.
Ада 83: generic не только для ТД, но и для процедур, функций.
//generic – параметризованный тип данных. Отметим, что параметризация в родовых сегментах происходит во время трансляции
с/с++: typedef void (*f)(int);
Отсюда, процедурный тип – указатель.
Проблема в том, что в Ада 83 и Java отказались от указателей, т.е. и от П/П ТД.
//В Java целиком, в Ада частично.
Ада 95:
type Func_Pointer is access function (L,R: float) return Boolean;
function Compare (X,Y: float) return Boolean … end Compare;
F: Func_Pointer
F:=Compare’access
С++, Modula-2, Оберон
void f(int) – это прототип функции, объявление некоторой функциональной константы. Если перед ней поставить typedef, что имя функции станет синонимом нового типа:
Модула-2, Оберон:
TYPE FUNC_INT = PROCEDURE (L,R: REAL): BOOLEAN;
PROCEDURE COMPARE (X,Y: REAL): BOOLEAN;
VAR F: FUNC_INT;
F:=COMPARE;
C#: Делегаты(расширение П/П ТД)
Операции делегата:
-
присваивание (инициализация)(=)
-
добавление делегата(+=)
-
«убирание» делегата(-=)
Вызов (скобочки: ())
this хранится вместе с указателем на функцию. Делегатом может быть и статический и нестатический член класса.
1.Параметр функции – делегат.
delegate int Processor(int i);
Prcessor p = new Processor(func);
Толькo += или -=.
Механизм делегатов инкапсулирует свойства процедурных типов данных, сводя к минимуму недостатки данного типа
Таким образом, делегат в С# - заменитель процедурного типа данных.
5. Определение новых типов данных. Логические модули. Классы.
Концепция уникальности типа в традиционных языках
программирования и понятие строгой типизации.
Чем по большому счёту отличаются традиционные ЯП от ОО. ОО опираются на традиционную, но немного отличаются. ОО отличаются понятием типа.
Понятие типа опирается на 4 аксиомы уникальности типа:
-
Каждый объект данных принадлежит единственному типу данных. - самое сильное ограничение традиционных ЯП
-
У каждого типа есть имя, для анонимных – неявное. Например, когда в Паскале описываем var A : array [1..N] of integer; 1..N – анаонимный тип. В Аде это два типа. Типы считаются различными, если их имена различны. Эта аксиома носит название именная эквивалентность типов
-
Каждая операция над типом данных имеет фиксированный тип и порядок операндов. Понятие типа характеризуется набором операций.
-
Разные типы данных несовместимы по операциям.
Все 4 аксиомы уточняют понятие строгой типизации. Чем строгая отличается от сильной, нигде не сказано. Аналогично со слабой типизацией. В любых языках со строгой или сильной типизацией выполняется 1 аксиома.
2 аксиома. Рад языков от неё отходит. Например: Type T = T1. То есть, часто в ЯП вводится синонимия типов. В Си фактически typedef и есть определение синонима. Но если про неё забыть, то если типы не имеют синонимов, то они различны. В Аде пошли дальше: type T1 is new T . В некоторых ЯП существует структурная эквивалентность типов – типы эквивалентны, если их структура совпадает. Как только возникают ссылочные типы, то задача может стать алгоритмически неразрешимой.
Любой современный ЯП удовлетворяет двум аксиомам.
А вот от четвёртой аксиомы многие отступают. Например, операция присваивания. Слева и справа могут стоять разные типы. В Си правила преобразования сложны, в Си++ ещё сложнее. 4 аксиома ослабляется тем, что в ЯП есть неявные преобразования. 4 аксиома нарушается чаще всего за счёт неявных преобразований.
Язык Ада обладает наиболее строгой типизацией, так как удовлетворяет всем 4 аксиомам.
Какие преимущества несёт концепция уникальности ТД:
Каждая операция зафиксирована, смысл их известен, операции несовместимы, поэтому поведение любого объекта данных легко прогнозируемы. Программы получаются как надёжные, так и эффективные. То же относительно контроля.
Проблемы:
1. Полиморфизм операции. 3 аксиома говорит, что каждый ТД характеризуется своим набором операций. Любой ЯП является полиморфным. Это справедливо в первую очередь для встроенных операций. Тип операции выводится из типов операндов. То есть все стандартные операции полиморфны. Современные ЯП решают проблему полиморфизма с помощью перекрытия операций. Там допускается делать пользовательские полиморфные операции (в J и D – нельзя перекрывать стандартные операции, в C# и C++ - можно).
Ада: есть overloading – перекрытие операции – статический полиморфизм, то есть выбор производится статически, во время компиляции. Это недостаток. Современные ЯП допускают динамический полиморфизм. Интересно, что в О-2 отсутствует статический, и присутствует динамический полиморфизм.
-
Янус проблема – объекты реального мира играют разные роли. Как решается проблема в традиционных ЯП – объединение типов.
И решение 1 проблемы перекрытием, и янус проблемы объединением, их проблема в том, что они статические. При этом отсутствует какой-либо динамический контроль.
Конструкции объявления новых типов данных. Тип данных как
множество значений и множество операций. Походы к определению новых
типов данных: модули и классы.
Контейнер - средство создания новых типов данных. В некоторых языках контейнером выступают модули (Модула – 2, Ада, С#(namespace), Java(package)), в других - классы, структуры(С++, С). Delphi – гибрид, cодержащий как модули, так и классы
Понятие логического модуля. Использование модулей для определения
новых типов данных. Особенности понятия модуля в современных ЯП.
Модуль - независимая единица трансляции. Подключить модуль чаще всего означает заимствовать написанный кем – то код, сохраненный в модуль. Множество языков имеют кучу встроенных библиотечных модулей, реализация которых опирается на РОРИ. Структура модулей как правило древовидная.
Один из важнейших ресурсов Ада – определение новых типов данных.
DEFINITION MODULE STACKS;
TYPE stack=RECORD
B: Array[0..N] of T;
TOP: [0..N];
END; //конец записи
PROCEDURE PUSH(VAR S: STACK; X: T);
PROCEDURE POP(VAR S:STACK; VAR X: T);
Пусть внутри модуля находятся процедуры
IsEmpty
IsFull
PROCEDURE Init(VAR S: stack);//(инициализация стека)
и переменная
VAR done: Boolean;//сообщающая последний результат операции над стеком
END STACKS;
В модуле определений (модуле реализации) соответствующие процедуры должны быть полностью описаны.
Переменная Done говорит нам о том, выполнилась ли какая-то операция над стеком или нет. В чем недостаток? Дело в том, что мы вынуждены экспортировать переменную на полный доступ – а это не есть хорошо. Выход из данной ситуации:
PROCEDURE Init*(VAR S:stack);
VAR DONE * BOOLEAN
PROCEDURE GetError(): Boolean;
Но тогда переменная DONE может быть испорчена. Функция Get Error, очевидно, должна возвращать значение переменной DONE. Но вызов функции – это нелокальный переход, и с точки зрения современной архитектуры он плох.
Импорт и экспорт имен. Видимость имен: непосредственная и
потенциальная. Управление видимостью. Области видимости и пространства
имен.
Модуль – набор взаимосвязанных ресурсов, которые служат для использования других модулях.
Видимость подразумевает возможность обращения с объектом, то есть, к примеру, знаем его имя и тип. непосредственно видимы в глобальном пространстве имен только модули. Имена полей из модуля видимы только потенциально.