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

Паскаль для новичков (часть 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 и много другой полезной информации для программистов, включая примеры решения реальных задач по созданию резидентных программ. Предлагаю также посетить Марья искусница - сайт о рукоделии (http://mariya-iskusnica.ru).
 

Журнал > Программирование > Паскаль для новичков (Turbo Pascal, Assembler) > Паскаль для новичков (часть 15): Механизм параметров подпрограмм
 
 
 
932
 
ВКонтакте
Facebook
 
 
 
На главную страницу На предыдущую страницу На начало страницы
 
 
Украинский портАл Украина онлайн Рейтинг@Mail.ru Рейтинг Сайтов YandeG Rambler's Top100