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
Компьютерная диагностика двигателя автомобиля (адаптер К-линии)Компьютерная диагностика двигателя автомобиля (адаптер К-линии)
 
 
 

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

 

Вычисление значения функции.

Рекурсия

 
Паскаль для новичковПродолжая тему подпрограмм, рассмотрим функции. Как я уже говорил, функции – помимо того, что могут иметь входные параметры, обязаны обеспечивать возврат выходного параметра (результата) в точку вызова, т.е. предназначены для выполнения некоторой задачи, но при этом по завершении должны возвращать результат в виде значения предопределённого типа. Таким образом, функции могут участвовать в любом выражении, где допустимо применение значения типа результата этой функции.
 
Если говорить более простым языком, функция – это та же процедура (подпрограмма) с теми же входными параметрами или без них. Но функция имеет механизм передачи выходного параметра – результата, в выражение, которое содержит имя этой функции. Описание функции начинается со служебного слова function и может содержать перечень входных параметров, а завершаться должно идентификатором типа возвращаемого результата.
 
Для организации возврата вычисленного значения в теле функции должен присутствовать оператор присваивания, в левой части которого должно быть указано имя данной функции, а в правой части – либо выражение, вычисляющее результат, либо переменная, которая уже должна содержать результат. В теле функции может находиться сколько угодно таких операторов присваивания.
 
Главное, чтобы хотя бы один из них срабатывал до завершения выполнения функции, иначе результат функции считается неопределённым, так как его значение будет случайным. Тип результата, вычисляемого в правой части оператора присваивания должен быть совместим с типом функции, указанным в её заголовке после списка входных параметров. При этом типом результата функции может быть простой, строковый или ссылочный тип. Пример
 
function Add( A, B : Real ) : Real;
begin
Add := A + B;
end;
 
демонстрирует описание функции с указанием двух входных параметров A и B типа Real и указанием типа результата, тоже Real, возвращаемого описываемой функцией. В теле функции выполняется некоторое действие, а в данном случае сложение, и результат сложения заносится в переменную результата функции оператором Add:=A+B. В теле функции Add известна переменная Add с таким же именем как и у самой функции, при этом допустимо лишь записывать в эту переменную. Читать из такой переменной нельзя, так как указание имени функции в правой части оператора присваивания или в условном операторе будет интерпретироваться как попытка вызвать саму себя, что допустимо (см. ниже о рекурсии). Пример
 
var C, D : Real;
begin
C := Add( 5, 10 );
D := ( 100 – Add( 4, 16 ) )*3;
if Add( C, D ) > 30 then begin
   …
   end;
end.
 
показывает применение функции Add. Функция может применяться в операторе присваивания значения некоторой переменной, либо в выражении, либо в условном операторе в качестве элемента логического выражения, главное, чтобы тип результата функции был совместим с типом переменной или выражения. Все подобные указания функции приводят к вызову оной, выполнению операторов, содержащихся в её теле и возвращению её результата, которое как бы подставляется вместо имени этой функции и далее фигурирует как непосредственное значение. Такое вхождение функции называется точкой вызова функции.
 
По определению, работа подпрограммы завершается после выполнения последнего оператора её тела. Однако, могут возникать моменты, когда необходимо прервать выполнение подпрограммы в некоторой точке её тела. Для этого Turbo Pascal предоставляет стандартную процедуру Exit, которая не требует параметров. При её вызове подпрограмма немедленно завершает своё выполнение и возвращает управление в точку вызова.
 
Программируя функции, важно помнить, что прежде, чем завершить её выполнение командой Exit, необходимо выполнить оператор присваивания результата с именем данной функции в левой части. Вот пример:
 
function FindChar( C : char; S : string ) : boolean;
var j : byte;
begin
FindChar := false;
if length(S) = 0 then exit;
j := 1;
while ( j < length(S) ) and ( S[ j ] <> C ) do inc( j );
FindChar := ( S[ j ] = C );
end;
 
begin
writeln( FindChar( 'm', 'program') );
writeln( FindChar( 'y', 'program') );
end.
 
Процедуру Exit можно использовать не только в теле подпрограммы, но и в глобальном блоке самой программы и тогда её выполнение будет немедленно завершено.
 

Предварительные и внешние описания подпрограмм

 
Как я уже говорил, сразу после заголовка подпрограммы следует её блок, однако возможны исключения из этого правила.
 
Не редко могут возникать случаи, когда подпрограмма, описанная выше по тексту программы (листингу), вызывает другую подпрограмму, описанную ниже её по тексту, или когда две подпрограммы содержат взаимные вызовы друг друга. Пример:
 
procedure GoLeft ( X, Y : integer );
begin
GoRight ( X+2 , Y );
end;
 
procedure GoRight ( X, Y : integer );
begin
GoLeft ( X-2, Y );
end;
 
Тогда при первой же попытке трансляции процедуры GoLeft компилятор не может проверить вызов процедуры GoRight на правильность указания входных параметров, так как эта процедура описывается ниже по тексту программы и информация о ней ещё неизвестна. Конечно можно поменять местами эти подпрограммы, но тогда возникнет аналогичная ситуация с трансляцией вызова GoLeft в процедуре GoRight.
 
Помочь в сложившейся ситуации может механизм предварительных описаний подпрограмм. Т.е. для первого случая, когда описание процедуры GoLeft предшествует описанию процедуры GoRight, достаточно перед процедурой GoLeft поместить копию заголовка процедуры GoRight и вместо её тела указать служебное слово forward, которое указывает, что полное описание процедуры располагается в тексте далее. В данном случае можно разместить предварительные описания для обеих процедур на всякий пожарный случай ;o)
 
procedure GoLeft ( X, Y : integer ); forward;
procedure GoRight ( X, Y : integer ); forward;
procedure GoLeft ( X, Y : integer );
begin
 
procedure GoRight ( X, Y : integer );
begin
end;
 
Вот теперь при обработке вызова GoRight в процедуре GoLeft компилятор сможет использовать информацию о процедуре GoRight из заголовка её предварительного описания, так как этот заголовок содержит исчерпывающую информацию, необходимую для проверки корректности вызова подпрограммы GoRight.
 
Хочу добавить, что если для некоторой подпрограммы указано предварительное описание, то в её основном заголовке все параметры можно опустить, и указать лишь служебное слово и имя:
 
 
 
 
 
procedure GoRight;
begin
GoLeft ( X-2, Y );
end;
 
и это вполне логично, ведь интерфейс входных параметров указан в предварительном описании. Что касается функций, то помимо описания входных параметров можно опустить и указание типа функции. Лично мне больше нравится подробное описание основного заголовка подпрограммы, но как я уже говорил – это дело вкуса.
 
Должен отметить, что если в программе указано предварительное описание некоторой подпрограммы, то листинг программы обязательно должен содержать её основное описание, даже если нигде в программе не встречается вызов оной.
 
На мой взгляд, лучше размещать предварительные описания не где попало, а группировать их в список и размещать его, например, сразу за описанием глобальных переменных, т.е. перед основным описанием подпрограммы, идущей самой первой по листингу.
 
Ещё подпрограммы могут быть описаны через внешнее описание, т.е. когда подпрограмма или группа подпрограмм разработана вне системы Turbo Pascal, например, на языке ассемблера. Используя внешнее описание таких подпрограмм можно подключить их к данной Pascal-программе. Обычно, код подключаемых подпрограмм содержится в некотором OBJ-файле и для обеспечения корректной работы составлен с соблюдением определённых межъязыковых соглашений о связях принятых в системе Turbo Pascal.
 
Для подключения в Pascal-программе необходимо указать заголовок подключаемой подпрограммы, после которого, указать служебное слово external вместо тела подпрограммы. Помимо этого в листинге программы необходимо установить директиву компилятора $L, аргументом которой следует указать имя *.OBJ-файла, содержащего код подключаемой подпрограммы. Пример:
 
function Tan ( Angle : real ) : real; external;
{$L MATH.OBJ}
 

Другие специальные случаи

 
Turbo Pascal имеет ещё несколько способов описания подпрограмм. Так, например, тело подпрограммы можно оформлять в виде последовательности машинных команд, используя специальную конструкцию со служебным словом inline, или в виде ассемблерных инструкций, с использованием служебного слова assembler. Подробнее о таких способах описания подпрограмм я расскажу в главе “Программирование на низком уровне”.
 
В заголовке описания процедуры обработки прерывания перед её телом может быть указано служебное слово interrupt. При описании и применении таких процедур необходимо следовать особым правилам, которые я постараюсь изложить подробнее в главе “Системно-зависимые расширения”.
 
По способу вызова подпрограммы могут разделяться на ближние и дальние. По умолчанию, компилятор считает все подпрограммы как ближние, и в соответствии с этим оформляет их вызовы как ближние. Такого же эффекта можно было бы добиться, если в заголовке подпрограммы перед её блоком указать служебное слово near. Но при использовании процедурных переменных потребуется описать некоторые подпрограммы как дальние, и тогда пригодится служебное слово far. Таким образом, два этих служебных слова позволяют указать “тип вызова” подпрограммы и эквивалентны директивам компилятора {$F+} и {$F–}. Подробнее об этом смотрите далее.
 

Рекурсия и побочный эффект

 
Как я уже говорил ранее, в пределах блока подпрограммы известны все объекты, описанные в объемлющем блоке. Вдобавок ко всему этому в подпрограмме известно и доступно её собственное имя. Исходя из этого, внутри её тела возможен вызов её самой, т.е. этой подпрограммы.
 
Подпрограммы, содержащие вызовы “самих себя”, называются рекурсивными. Помимо этого, есть возможность осуществлять косвенную рекурсию, например таким способом: одна подпрограмма вызывает другую подпрограмму, которая в свою очередь, вызывает первую.
 
Исходя из рекурсивной природы многих математических алгоритмов, рекурсивное построение алгоритма может пригодиться при решении математических задач, таких как вычисление факториала и нахождение определителя многомерной матрицы. Применение рекурсии может упростить такие алгоритмы, как: сортировка данных; заливка сложных графических фигур некоторым цветом; сканирование дисковых подкаталогов с глубокой вложенностью; поиск пути по карте для прокладывания маршрута и другие задачи.
 
Следует помнить, что в языке Pascal нет никаких ограничений на рекурсивные вызовы подпрограмм. При составлении рекурсивных алгоритмов следует учитывать тот факт, что каждый последующий рекурсивный вызов создаёт очередной блок локальных объектов в стеке. Таким образом, на определённой глубине вложенности может произойти переполнение стека.
 
Рекурсия подкупает своей наглядностью и простотой решения, но заставляет дорого платить за это.
 
Лично я сторонник решения задач, требующих рекурсивных алгоритмов, методом эмуляции (имитации) рекурсии. При этом программист не связан ограниченным объёмом стека, и может располагать большим объёмом динамически выделяемой памяти, вплоть до виртуальной. Но об этом пока ещё рано говорить.
 
На мой взгляд, профессия программиста заключается в том, чтобы достичь цели наиболее приемлемыми средствами, при этом, не снижая надёжности работы программы.
 
В дополнение к рассмотрению вопросов, связанных с подпрограммами приведу пример, который демонстрирует очерёдность выполнения выражений, составленных из вызовов функций.
 
function One : integer;
begin
One := 10;
end;
 
function Two : integer;
begin
Two := 20;
end;
 
function Three : integer;
begin
Three := 30;
end;
 
var X : integer;
 
begin
X := 15 + One*Two*Three;
X := 10 + Three + Two + One;
X := 10*(Three + Two) + One;
end.
 
Хочу заметить, что в выражениях, приведённых в данном примере,
вызовы функций производятся справа налево. Это легко заметить, выполнив программу в пошаговом режиме, нажимая клавишу F7 (Trace into). Прежде, чем выполнить выражение, необходимо собрать все его составляющие, т.е. компилятор производит поиск таких операндов, представленных в выражении вызовом функции и требующих некоторых предварительных действий для получения их значений. При компиляции поиск организуется справа налево. Затем, когда значения всех операндов получены, действия в выражении выполняются слева направо, с учётом скобок и приоритетных операций (умножение, деление).
 
К чему я всё это? Ага, вспомнил. Я это к тому, что если в некоторой функции имеются операторы, изменяющие значения переменных, описанных в объемлющих блоках, то может возникнуть такая ситуация, при которой значение выражения, содержащего вызов такой функции, полностью зависит от очерёдности следования операндов. Такая ситуация может оказаться источником трудноуловимых ошибок и в таком случае будет называться побочным эффектом.
 
Не стоит в своих программах создавать такую зависимость функции от глобальных по отношению к ней переменных.
 
Продолжение следует…
 
© Владислав Демьянишин
 
 
Вы находитесь на официальном сайте Владислава Демьянишина - разработчика игры Dune IV (Dune 4). На нашем сайте Вы можете бесплатно скачать игры Dune IV (Dune 4), Battle City (Танчики с Dendy/Nintendo), читы к играм и многое другое. Также Вы можете скачать бесплатно программы и полезные утилиты. Все программы чистые, т.е. не содержат вирусов и иного вредоносного ПО.
 
Среди доступных программ есть мобильная читалка книг, менеджер переноса файлов с фото- и видеокамер на компьютер, текстовый редактор, WYSIWYG редактор, 3D аниматор, GIF аниматор, AVI аниматор, пакетный конвертор изображений, редактор электрических схем, программа для скриншотов, диспетчер тем рабочего стола и другие.
 
На нашем сайте можно не только бесплатно скачать игры, но и документацию и книги по программированию на MIDLetPascal, Turbo Pascal 6, Turbo Pascal 7, Borland Pascal, по программированию устройств Sound Blaster, Adlib, VESA BIOS, справочник Norton Guide и много другой полезной информации для программистов, включая примеры решения реальных задач по созданию резидентных программ. Предлагаю также посетить Марья искусница - сайт о рукоделии (http://mariya-iskusnica.ru).
 
 

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