Лекция 8 (1160841), страница 2
Текст из файла (страница 2)
В зависимости от потоков передачи данных формальные параметры бывают
-
Входные(in-параметры: обязательно определены при входе в процедуру)
-
Выходные(out-параметры – обазательно определены при выходе)
-
Входные/выходные(in/out)
Это была классификация с точки зрения потоков передачи данных(формлаьных параметров).
Приведем классификацию с точки зрения связывания фактических и формальных параметров:
-
по значению – реализуют семантику in
-
По результату – реализуют семантику out
-
По значению/результату – реализуют семантику in/out
-
По адресу/ссылке
-
По имени
4-й и 5-й типы нужны как дополнительные средства для различения параметров.
Вообще на самом деле в Аде версии 1983 года все параметры можно было снабдить спецификацией его вида(in, out, in/out). Например, вот так
Procedure P(in X: T; inout Y: T; out Z: T);
Y –может менять свое значение
Z – обязательно неконстантный объект данных
Компилятор может вставлять квазистатическую проверку и при входе в P
P(a, b, c);
Проверять, стоит ли в а определенное значение.
В Pascal 2 способа передачи параметров – по ссылке и по значению.
Если у параметра прсутствует Var, то объект переддается по адресу. А если не присутствует, то по значению.
Но in и out – это совсем другие вещи.(in-параметр менять запрещено целях повушения надежности языка.). Получается, что в Паскале мы не можем передавать массив по ссылке без риска его испортить!
В С++ такой проблемы нет, так как существуют константные ссылки(аналог открытия файла в режиме только чтения).
Запись активации – место для записи формальных пармеетров. Формальные параметры рассматриваются как разновидность локальных переменных.
При семантике in, out , in/out происходит копирование фактических парметров в запись активации(push в стек). Почему это решение оказалось не самым лучшим?
В записи активации отводится место под указатель на параметры, передающиеся по ссылке. При передаче параметров по ссылке копируется не сам объект, а его адрес, что мощнее. И все три семантики, в принципе, могут быть возможны при передаче параметров по ссылке.
Способ передачи параметров в Фортране основан только на ссылке из соображений эфффективности.
В каких случаях передача параметров по значению эффективнее, чем передача их по адресу?
1) в случае, когда размер данныз меньше размеров адреса
2) в случае, кгда мы передаем объект даных, но при этом постоянно к нему обращаемся(постоянные разыменования – это тоже плохо).
Лучше, если передавать параметр по значению никак нельзя, завести локалную переменную и разыменовать параметр один раз.
Замечание По умолчанию параметр всегда in.
ADA
procedure P(inout X: T; inout Y: T) is
X: =invalue X; //raise error; - тут возбуждается исключительная ситуация
Y: = invalue Y;
end P;
Одна из основных идей стадарта любого языка программирования состоит в том, что подпрограммы на одних ввходныхх данных на раззных машинах дают одинаковый результат. Если передача параметров происходит по значению результата, первое присваивание отрабатывается, а второе – нет. Запись активации исчезает, обратного копиования не происходит. При переаче параметров по значению-результата Y не изменится. При двух способах передачи параметров в одной и той же ситуации выходные данные будут разные – это говорит о том, что компилятор выбирает либо
-
способ передачи параметров по значению
-
способ передачи параметров по ссылке.
Вывод: Ахиллесова пята всех процедурных языков программирования – передача параметров в них слишком низкоуровневая.
Проще всего сделали создатели языка Си: все параметры в Си передаются по значению, даже массивы – ведь ссылка на них не меняется. Чем плох Си? По виду параметра нельзя сказать, что он передает, что препятствует читабельности программы.
В С++ дела с этим обстоят лучше, так как в нем существует возможность константной ссылки. Если ссылка передается со словом const, то она воспринимается как in-параметр, иначе – как out-параметр.
Скрытый параметр this, кстати говоря, передается всем функциям-членам класса как константная ссылка!
В C#, Java, Delphi(TurboPascal+объектно-ориентированная надстройка) – языках с референциальной моделью данных – все параметры автоматически передаются по ссылке.
Типы данных C#:
-
референциальные
-
типы-значения
-
примитивные типы данных(все передаются по значению)
-
типы данных структуры
-
Отдельно рассмотрим структуру. Она в C# является типом-значением. Как она будет передаваться: по ссылке или по значению?
Что делать, если я хочу модифицироать значения объекта примитивного типа данных? В Java для этого есть замечательное решение – класссы-обертки.(Обертка – специальный класс, находящийся в специфицированных модулях или пространствах имен. В C# - System, в Java – пакет Java.lang – семантика априори известна компилятору)
Пример работы с оберткой:
void f(int x) { x=…; }
Проблема передача параметра х, изменяемого внутри f
Решение Java 2005:
Ineteger px=new Integer(i):
Решение C#:
Int32 px=i;
void f(Int32 x)
В C#:
Ключевые слова:
ref – имеет семантику inout
out – имеет – семантику просто out
Пример передачи параметра по ссылке:
void f(ref int x)
{
x=-3;
}
Int a=0;
f(a);//некорректное обращение, надо указать, что а передается по ссылке
f(ref a);;//верно!
void f(out int a)
{
a=-3;
}
f(out a); //компилятор не вставит проверку на то, что а –инициализированная переменная
Проблемы возникают, как мы уже выяснили, при передаче структуры.
struct X{
Int I;
}
void f(X a)
{
a.i=0; //как будет интерпретироваться данная строчка? Как передаются структуры?
}
Для передачи структур надо лишь вспомнить: все, что сущесвует, преобразуется в объект класса Object.
Object o;
int I;
С помощью автоупаквки и автораспаквки преобразуем структуру в объект:
o=i;
//компилятор вставит:
o=Integer(i); //Java
o=new Int32(i); //C#
Этот процесс называется упаковкой.
Распаковка – это получение значения оюъекта из соответствующего ему клласса-обертки.
void f(Object o);//теперь вв функцию f мы можем передавать все типы!!!!!
В 2005 году упаковка и распаковка была «ручной». Теперь она стала «авто».
Вопрос не в тему: разрешать ли в языке процедуру с переменным типом параметров?
Универсального способа для них нет. Чаще всего такие процедуры нужны при вводе/выводе(другие примеры придумать сложно).Страуструп решил проблемуу ввода-вывода при помощи потоков ввода/вывода. Как только в Java появилась автоупаковка и автораспаковка, проблема была решена: промоделировать переменный список параметров можно с помощью массива.
C#
void f(/*перед этим могут быть другие параметры*/param Object[ ] argc/*данный массив может быть только последним параметром*/)
С объектом argc можно работать как в массивом:
f(a, b); //типы а и b не уточняются, так как мы сейчас работаем с объектами класса Object
f();
f(1);//автоупаковка в Int32
System.Console::WriteLine(String fmt, param Object[ ] argc)
В Java появились списки формальных параметров:
void f(Object … argc);//обращаем внимание на многоточие!
В чем существенное отличие от C#?
f(new X[ ] { new X(), new X()});//что может быть передано и будет ли передано?
Это массив из объектов! А надо передавать по одному объекту! Ошибка компиляции!
Верно:
f(new X(), new X());
Таким образом, в C# автоупаковка и автораспаковка – это приведение любого типа в Object и обратно.
Передача параметров по имени
Это самый естественный и универсальный с точки зрения начинающего программиста способ. Похож на макрос.
Передаем объект в том виде, в котором он есть.
Упражнение на Алгол-60. Обосновать невозможность написания процедуры swap(a, b), которая меняет свои параметры
procedure swap(a, b);
В задаче предполагалось, что а и b передаются по имени.
Будем считать, то a и b принадлежат типу Т.
procedure swap(Т a, Т b);
T tmp;
tmp:=a;
a:=b;
b:=tmp;
end
Пример!
Рассмотрим вызовы написанной процедуры:
swap(I, a[i]);
swap(a[i], i);
Как только мы меняем I, то сращу же меняется и a[i], а потому один из вызовов всегда будет работать неверно. Более того, такой способ передачи пааметров по имени работал неэффективно, делая неэффективные соответствующие языки программирования, где они использовались(и Алгол-60 вв том числе!). Тут фактически с любым параметром передавалась процедура thunk. При каждом обращении к формальным параметрам вызывалас процедура thunk, которая пересчитывала все, что нужно.