http://sulfurzona.ru/
News
Service
Magazine
Software (Battle City Game, Wallpaper manager, Superpad, VG-NOW, Puzzle Game, Netler Internet Browser, ..)
Wing-Thunder Game (fly simulator)
Dune Game (Dune III, Dune IV, Cheats, Forum, ..)
Games free
Turbo Pascal (Assembler, Docs, Sources, Debbugers, ..)
Books (Docs for developers)
Guest book
Компьютерная диагностика двигателя автомобиля (адаптер К-линии)Компьютерная диагностика двигателя автомобиля (адаптер К-линии)
 
 
Скачать игру Крыло-Гром (Wing-Thunder) бесплатно
 
 

Паскаль для новичков (часть 15)

 

Механизм параметров подпрограмм

 
Автор: Владислав Демьянишин
 
Паскаль для новичковПродолжая рассмотрение процедур и функций, пришло время коснуться темы передачи параметров подпрограммам.
Как я уже говорил ранее, в заголовке процедуры и функции может быть задан список формальных параметров:
 
 
procedure VectorAdd ( A, B : TVector; var C : TVector );
 
Хочу обратить внимание на то, что следует соблюдать эквивалентность типов (см. главы "Преобразование типов" и "Совместимость типов”), т.е. типы параметров должны в обязательном порядке обозначаться идентификаторами. Например, следующий заголовок является недопустимым:
 
procedure MyProc ( A : array [1..10] of word );
 
так как вызовет ошибку компиляции “Error 12: Type identifier expected.” (не указан идентификатор типа).
В случае необходимости передачи в подпрограмму параметра с типом, определенным программистом, следует указать его идентификатор (имя), например:
 
type
TMyArray = array [1..10] of word;
procedure MyProc ( A : TMyArray );
 
Turbo Pascal предоставляет три способа задания параметров подпрограмм. Например, все параметры подпрограммы могут быть заданы одним из трех способов, или каждый параметр может быть задан любым из трех способов.
 

Параметры-значения

 
Параметр-значение – это самый простой способ передачи параметров, его еще называют передачей параметров по значению. Он осуществляется следующим образом. Перед выполнением подпрограммы в стеке создается локальная переменная, которая инициализируется соответствующим значением фактического параметра, указанного в вызове подпрограммы. Т.е. если в вызове в качестве параметра подпрограммы указана константа или переменная, то ее значение заносится в данную локальную переменную (формальный параметр). Если же в вызове указано выражение, то предварительно производится вычисление результата данного выражения, и значение результата заносится в локальную переменную.
Кто-то, прочитав последний абзац, возможно скажет: “Ну и нагородил ты парень с три короба”.
Тогда поясню для тех, кто возможно не въехал ;O)
 
Формальный параметр – это параметр, описанный в заголовке подпрограммы, т.е. который должна получить подпрограмма при вызове.
 
Фактический параметр – это параметр, который указан непосредственно в вызове подпрограммы, и значение которого передается. Это может быть непосредственное значение, константа, переменная, выражение, имеющее тип, совместимый по присвоению с соответствующим формальным параметром в заголовке подпрограммы.
 
Теперь, после “лирического” отступления вернемся к нашим баранам ;O)
Таким образом, с локальной переменной можно делать все, что угодно. “Она будет сопротивляться, кусаться, кричать “я буду жаловаться в райком!”, но это – красивый кавказский обычай”. Опять меня занесло ;-)
Но главное в этом способе то, что какие бы действия не выполнялись над локальной переменной (формальным параметром), это никак не отразится на значении фактической переменной, указанной в вызове подпрограммы. Рассмотрим пример процедуры, использующей передачу параметров по значению:
 
procedure Add ( A, B : integer );
begin
A := A + B;
writeln( ‘Сумма чисел A и B = ’, A );
end;
 
Если вызвать эту процедуру, например, так:
 
var X, Y : integer;
begin
X := 10;
Y := 15;
Add( X, Y );
 
то значения фактических параметров (переменных) X и Y копируются один раз в соответствующие формальные параметры (переменные) A и B. Внутри процедуры происходит приращение значения переменной A на величину значения переменной B, при этом значение внешней переменной X остается неизменным. Это своего рода изоляция внешних переменных от локальных преобразований.
Еще хочу привести пример с массивом. Пусть
 
type TByteArr = array [0..10000] of byte;
var B : TByteArr;
 
procedure MyProc( A : TByteArr );
var j : word;
Sum : longint;
Begin
Sum := 0;
for j := 0 to 10000 do Sum := Sum + A[ j ];
writeln( ‘Сумма = ’, Sum );
end;
 
begin
MyProc( B );
end.
 
тогда необходимо учитывать тот факт, что передача массива B происходит по значению, т.е. ,как я уже говорил выше, весь массив B загружается в локальную переменную-массив A. К чему я все это? Да к тому, что локальные переменные располагаются в стеке, а стек, как известно, не резиновый и значит, в ходе выполнения программы с многочисленными вложенными вызовами процедуры MyProc очень скоро может наступить момент переполнения стека “Error 202: Stack overflow error.” (стек переполнен). Я уже не говорю о том, что эта ошибка будет возникать всегда при установке, например, директивы {$M 10000,..,..}. Еще один момент, который нельзя сбрасывать со счетов – это быстродействие такого способа передачи параметра применительно к большому массиву. Ведь при загрузке значений массива B в массив A выполняется поэлементное копирование массива B, что при многократном вызове данной процедуры потребует дополнительных затрат времени.
 

Параметры-переменные

 
Параметр-переменная – это способ, передачи фактического параметра с обратной связью, его еще называют передачей параметра по ссылке. Для задания такого способа необходимо указать служебное слово var перед именем (или списком имен через запятую) параметра в списке параметров подпрограммы. Такой способ позволяет манипулировать значениями внешних переменных, указанных в вызове подпрограммы, как с локальными переменными, но все изменения значений этих якобы локальных переменных приведут к изменению значений соответствующих внешних переменных. Таким образом, описывая процедуру, можно сделать так, чтобы она возвращала в точку вызова результат, а то и несколько результатов одновременно.
Поясню это на примере. Допустим, необходимо создать процедуру, которая определяла бы минимальное и максимальное значение элементов массива:
 
var B : TByteArr;
k : word;
 
procedure MinMax ( Buf : TByteArr );
var j : word;
Min, Max : byte;
begin
Min := Buf[0];
Max := Min;
for j := 0 to 10000 do
if Buf[ j ] > Max then Max := Buf[ j ]
else if Buf[ j ] < Min then Min := Buf[ j ];
end;
 
begin
for k := 0 to 10000 do B[ k ] := 10 + random(236);
MinMax(B);
end.
 
Нахождение минимума и максимума обеспечено. Однако результаты заносятся в локальные переменные Min и Max, которые известны только в пределах текущего блока. А ведь нам необходимо передать результаты поиска во внешний блок программы.
Тогда попробуем так:
 
procedure MinMax ( Buf : TByteArr; Min, Max : byte );
var j : word;
begin
 
и исправим основной блок программы
 
var …
MinB, MaxB : byte;
begin
for k := 0 to 10000 do B[ k ] := 10 + random(236);
MinMax( B, MinB, MaxB );
writeln( 'Min= ', MinB, ' Max= ', MaxB );
end.
 
и снова наши изыскания потерпят неудачу, так как хоть переменные Min, Max и являются параметрами, но параметрами-значениями и значит, их значения не будут переданы во внешний блок программы. Т.е. в данном случае необходимо использовать передачу параметров по ссылке:
 
procedure MinMax ( Buf : TByteArr; var Min, Max : byte );
var j : word;
begin
 
В данном случае формальные параметры Min и Max считаются синонимами соответствующих фактических параметров в пределах процедуры. Следует помнить, что фактические параметры должны быть переменными (и ни в коем случае не выражениями и нетипизированными константами) того же типа, что и формальные параметры. Теперь присваивания параметрам Min и Max внутри блока процедуры будут эквивалентны соответствующему присваиванию внешним переменным MinB и MaxB, которые были переданы процедуре как параметры-переменные. По завершении выполнения процедуры эти переменные из внешнего блока будут содержать соответствующие значения.
 
Совершенно очевидно, что в результате выполнения данного примера минимальным найденным значением будет 10 а максимальным 245.
 
В соответствии с тем, что я говорил выше о загрузке массива в стек, гораздо эффективнее и безопаснее будет использовать следующий заголовок процедуры
 
procedure MinMax ( var Buf : TByteArr; var Min, Max : byte );
 
Механизм передачи параметров при данном способе основан на том, что происходит передача не самого значения фактического параметра, а загрузка в стек адреса переменной, указанной в вызове подпрограммы. Поэтому все изменения значений отражаются не в области стека, а в памяти, где расположена переменная, переданная в качестве параметра.
Таким образом, применительно к нашему примеру, при передаче массива как параметра по ссылке происходит загрузка в стек не 10001 байта, а всего лишь четырех байт адреса этого массива, что выполняется гораздо быстрее и не требует большого размера стека.
Вот мы и проследили за эволюцией нашей процедуры MinMax.
Хочу заметить, что переменные файловых типов (см. главу “Файловые типы и ввод-вывод”) могут передаваться в подпрограммы только как параметры-переменные.
Еще раз повторюсь. При данном способе передачи параметров, подпрограмме в качестве фактического параметра-переменной нельзя передавать константы и выражения, но можно типизированные константы и переменные. Иначе произойдет ошибка компиляции “Error 20: Variable identifier expected.” (не указан идентификатор переменной).
 

Бестиповые параметры

 
Данный способ передачи параметров – это способ, при котором тип параметра не указывается, т.е. нет привязки к конкретному типу данных. В данном случае описание формального параметра в заголовке подпрограммы имеет следующий вид:
 
procedure MinMax ( var Buf; var Min, Max : byte );
 
где Buf – имя формального параметра.
 
При вызове подпрограммы фактическим параметром может быть только переменная или типизированная константа любого типа, но ни в коем случае не выражение, так как при данном способе передача параметров происходит по ссылке.
В виду того, что тип формального параметра не указан, то параметр является несовместимым ни с какой другой переменной, т.е. не может применяться ни в каких конструкциях. Чтобы иметь возможность работать с таким параметром, необходимо использовать либо операцию приведения типа (см. главы "Преобразование типов" и "Совместимость типов”), либо описать локальную переменную конкретного типа с совмещением ее в памяти с нетипизированным параметром.
Для примера первого способа использования нетипизированных параметров напишем свой вариант стандартной процедуры Move:
 
procedure MyMove ( var Src, Dest; Count: word );
type TByteArr = array [0..65000] of byte;
var j : word;
begin
for j := 0 to Count-1 do
TByteArr(Dest)[ j ] := TByteArr(Src)[ j ];
end;
 
var s1, s2 : string;
begin
s1 := 'Hello World!';
s2 := 'Good day World!';
MyMove ( s2, s1, length(s2)+1 );
end.
 
Для демонстрации второго способа использования нетипизированных параметров напишем еще один вариант процедуры Move:
 
procedure MyMove ( var Src, Dest; Count: word );
type TByteArr = array [0..65000] of byte;
var j : word;
ASrc : TByteArr absolute Src;
ADest : TByteArr absolute Dest;
begin
for j := 0 to Count-1 do ADest[ j ] := ASrc[ j ];
end;
 
где локальная переменная ASrc размещается по адресу в начале области памяти параметра Src, а переменная ADest размещается соответственно в области памяти параметра Dest.
По коду, полученному в результате компиляции, оба способа использования нетипизированных параметров абсолютно идентичны.
Данный способ передачи параметров дает программисту полную свободу действий над параметрами, но свободой тоже нужно уметь пользоваться правильно. Если, например, изменить описание строк так:
 
var s1 : string[12];
s2 : string;
 
то в результате выполнения последней программы получим строку s1='Good day World!', где окончание строки будет размещаться в области переменной s2, и этим самым строка s2 будет искажена, так как в примере не учитывается размер области назначения пересылки данных, т.е. не учитывается размер памяти, отведенный под переменную-получатель s1. Ведь под переменную s1 выделено 13 байт, а под s2 выделено 256 байт. А так как эти переменные в сегменте данных размещены по соседству, то происходит выше описанная накладка. И дело вовсе не в нашей процедуре MyMove, а в аккуратности ее использования, так как аналогичный вызов стандартной процедуры Move даст тот же ошибочный результат.
Чтобы такой накладки не случалось, следует делать предварительную проверку перед применением процедуры Move или MyMove:
 
var s1: string[12];
s2 : string;
Count : word;
begin
s1 := 'Hello World!';
s2 := 'Good day World!';
if length(s2)+1>SizeOf(s1) then Count:=SizeOf(s1)
else Count := length(s2) + 1;
Move( s2, s1, Count );
s1[0] := Char( Count – 1 );
end.
 
Продолжение следует…
 
© Владислав Демьянишин
 
 
На нашем сайте можно не только бесплатно скачать игры, но и документацию и книги по программированию на MIDLetPascal, Turbo Pascal 6, Turbo Pascal 7, Borland Pascal, по программированию устройств Sound Blaster, Adlib, VESA BIOS, справочник Norton Guide и много другой полезной информации для программистов, включая примеры решения реальных задач по созданию резидентных программ.
 

Журнал > Программирование > Паскаль для новичков (Turbo Pascal, Assembler) > Паскаль для новичков (часть 15): Механизм параметров подпрограмм
 
 
 
 
 
 
На главную страницу На предыдущую страницу На начало страницы