Глава 23 Функции



Процедуры и функции – сестры-близнецы, потому и носят общее имя – подпрограммы. Все, что сказано о передаче параметров, относится и к тем, и к другим. И все же функции чем-то отличаются от процедур, иначе, зачем их придумали? А затем, чтобы упростить возвращение результата.

Нередко таким результатом бывает число, строка, символ или булево значение. Конечно, вернуть результат можно и через ссылку на переменную, но функция сделает это удобней – через своё имя. Результат, возвращаемый функцией, можно вставлять внутрь выражений наряду с переменными и константами. Взять хотя бы знакомые нам функции Random и Length, вызовы которых можно применить, например, так:


    x:= 1+ Random(10);     { арифметическое выражение }

    Writeln(Length(S)); { печатается длина строки S };


Функции избавляют программиста от объявления лишних переменных, упрощая программы и повышая их надежность. Сейчас мы научимся создавать собственные функции.

Объявление функции

Подобно объявлению процедуры, объявление функции состоит из заголовка и тела. Тело строят по тем же правилам, что и для процедур, а вот заголовок выглядит немного иначе.


function Имя_Функции : Тип;     { функция без параметров }

function Имя_Функции (Параметры) : Тип;     { функция с параметрами }


Отличий от процедуры всего два. Во-первых, вместо ключевого слова PROCEDURE указано слово FUNCTION. А во-вторых, завершает заголовок тип функции (тип возвращаемого ею результата), – его указывают после двоеточия.

Пример функции

Разберем все это на примере. Создадим функцию, выбирающую большее из двух чисел. Разумеется, что функция будет принимать два параметра – сравниваемые числа, и возвращать будет тоже число. Стало быть, её заголовок может быть таким:


function Max(arg1, arg2 : integer) : integer;


Имя функции выбираем на свой вкус, здесь имя Max вполне подходит, оно означает MAXIMUM (наибольший). К этому заголовку прилепим тело функции, состоящее из одного условного оператора.


function Max(arg1, arg2 : integer) : integer;

begin

    if arg1 > arg2

    then Max:= arg1

    else Max:= arg2

end;


Но откуда взялась переменная Max, которой присваиваем значение? Ведь мы её не объявляли! А её и не надо объявлять, – это имя нашей функции, оно и принимает в себя результат. Мало того, если результату не присвоить значение, он останется неопределенным, и это будет ошибкой!

Созданная нами функция может вызываться так:


    A:= Max( 20, 10 );     { A = 20 }

    Writeln( Max( A, B ) );     { печатается большее из A и B }


Вызов функции можно использовать даже как фактический параметр в её собственном вызове, то есть организовать вложенные вызовы, например:


    A:= Max ( Max( 20, 10 ), 40 );     { A = 40 }

    A:= Max ( Max( 20, 10 ), Max( 200, 100 ) ); { A = 200 }


В первом случае сначала вызывается функция Max(20,10), вставленная как первый фактический параметр, а затем Max(20,40), – то есть результат первого вызова подставляется параметром во второй. Похоже работает и другой пример, только функция вызывается трижды. Полезно понаблюдать за такими вызовами через отладчик. Напишите главную программу для исследования функции Max и прогоните её в отладчике.

Подсчет символов в строке

В прошлой главе я предложил вам написать процедуру для подсчета заданного символа в строке. Если вы справились с той задачей, то для возврата результата наверняка воспользовались ссылкой на переменную. Теперь рассмотрим решение с применением функции.

Начнем, разумеется, с заголовка функции, дадим ей имя Count (подсчет).


function Count(const Str : string; Ch : char): integer;


Функция принимает два параметра: ссылку на строку и символ, который надо подсчитать. Напомню, что ключевое слово CONST в объявлении параметра позволяет ссылаться и на константу, и на переменную. Тело функции строим на базе цикла со счетчиком.


function Count(const str : string; ch: char): integer;

var N, i: integer;

begin

N:=0; { обнуляем счетчик }

for i:=1 to Length(str) do

    if str[i]=ch then N:= N+1;

Count:= N; { определяем результат функции }

end;


Подсчет символов в массиве ведется в локальной переменной N, и лишь по завершении цикла результат копируется в имя функции. Грубой ошибкой было бы накапливать счетчик прямо в имени функции:


    if str[i]=ch then Count:= Count+1; { – это ошибка! }


Запомните: в теле функции её имя применяется только слева от оператора присваивания! Есть исключения из этого правила, но мы пока не будем их касаться.

И, наконец, напишем программу «P_22_1» для проверки функции Count. В главной программе функция вызывается сначала для переменной S, а затем для константы «BANAN». Причем во втором случае она вызывается дважды, а результат суммируется. Испытайте эту программу.


{ P_23_1 – подсчет заданных символов в строке }


function Count(const str : string; ch: char): integer;

var N, i: integer;

begin

N:=0; { обнуляем счетчик }

for i:=1 to Length(str) do

    if str[i]=ch then N:= N+1;

Count:= N; { передаем результат через имя функции }

end;

var S: string;

begin {--- главная программа ---}

S:='PASCAL';

Writeln( Count(S, 'A'));

Writeln( Count('BANAN', 'N') + Count('BANAN', 'B'));

Readln;

end.


Возврат строк

Вернемся к программе «P_20_1», заменяющей символы «A» на символы «B». Помните сколько крови она попортила прежде чем заработать? Заменив процедуру Scan на функцию с тем же именем, мы решим проблему возврата результата. Результат, разумеется, должен иметь строковый тип. Обратите внимание на то, что ключевые слова VAR или CONST в заголовке не указаны, а потому параметр arg можно употребить в теле функции в качестве локальной переменной.


{ P_23_2 – замена символов в строке с применением функции }


function Scan(arg : string): string;

var k: integer;

begin

    for k:=1 to Length(arg) do

    if arg[k]=’A’ then arg[k]:=’B’; { замена в параметре arg }

    Scan:= arg;

end;

var S: string; k: integer;

begin {--- главная программа –--}

    for k:=1 to 3 do begin

    Write(’Введите строку: ’); Readln(S);

    Writeln(Scan(S));

    end;

    Readln;

end.


Когда результат не важен

Хорошая функция возвращает правильный результат, а отличная делает ещё что-нибудь полезное. Программисты нередко поручают одной функции несколько дел, вот пример: напишем функцию Swap (обмен) булевого типа, принимающую ссылки на две переменные. Функция должна сравнить эти переменные и вернуть TRUE, если первая из них окажется больше второй. Мало того, в этом случае она должна обменять значения этих переменных (как в процедуре Swap, рассмотренной ранее). Короче, функция будет такой.


function Swap( var a1, a2 : integer) : Boolean;

var t: integer;

begin

    if a1 > a2

    then begin

    { обмен значений переменных }

    t:=a1; a1:=a2; a2:=t;

    Swap:= true

    end

    else Swap:= false

end;


Где применить такую функцию? Пусть переменные N1, N2, N3 содержат три разных числа. Переложим эти числа так, чтобы в N1 оказалось наименьшее, а в N3 – наибольшее число, то есть, чтобы соблюдалось условие: N1 < N2 < N3. Такая сортировка выполняется тремя вызовами функции Swap (в комментариях показаны результаты обмена).


    Swap(N1, N2);     { N1 < N2 }

    if Swap(N2, N3)     { N2 < N3 }

    then Swap(N1, N2);     { N1 < N2 < N3 }


Здесь в первой и третьей строках функция вызывается как процедура, поскольку возвращаемый ею булев результат не используется. Во второй строке она вызывается как функция, поскольку результат использован оператором IF.

Возможность вызывать функцию как процедуру называют расширенным синтаксисом (Extended syntax), – он должен быть разрешен в настройках компилятора, иначе вызов функции как процедуры компилятор сочтет ошибкой.

Неявная переменная Result

Современные версии компиляторов дают новую возможность в части построения функций. Так, компилятор Delphi позволяет, наряду с именем функции, для возврата результата использовать автоматически объявляемую переменную Result. Тип переменной Result совпадает с типом функции. Тогда функцию подсчета символов можно упростить так:


function Count(const str : string; ch: char): integer;

var i: integer;

begin

Result:=0; { обнуляем счетчик }

for i:=1 to Length(str) do

    if str[i]=ch then Result:= Result + 1;

end;


Как видите, переменную Result можно использовать как в левой, так и в правой части оператора присваивания. Последнее значение переменной станет результатом функции.

Итак, потратив три главы на изучение процедур и функций, мы готовы, наконец, к настоящему делу. Сколько можно в цацки играть? В следующей главе приступим к шифрованию файлов!

Итоги

• Функции – это подпрограммы, возвращающие результат через свое имя. Тип возвращаемого результата указывают в заголовке функции.

• В теле функции обязательно присваивают значение функции (через её имя), иначе результат останется неопределенным, случайным.

• Вызовы функций можно использовать в выражениях наряду с константами и переменными.

• Когда результат функции не используется, её вызывают как процедуру. При этом через настройки компилятора должен быть позволен расширенный синтаксис – «Extended syntax».

А слабо?

А) Напишите функцию для поиска буквы в заданной строке. Она должна возвращать TRUE, если в строке есть хоть одна эта буква, и FALSE в противном случае. Напишите программу для проверки функции. Или слабо?

Б) Напишите функцию для определения позиции буквы в заданной строке. Функция должна вернуть позицию первой такой буквы или ноль, если буквы в строке нет. Напишите программу для проверки функции.

В) Напишите функцию и программу для её проверки, принимающую число и возвращающую строку: слово «четное» или «нечетное» в зависимости от четности или нечетности параметра. Подсказка: для проверки четности числа N надо проверить остаток от его деления на два: if (N mod 2) = 0 then …

Загрузка...