Процедуры и функции

Структурное программирование

Историческое развитие языков программирования к какой-то момент привело к формированию так называемой нисходящей технологии конструирования программ. Для этого были причины. Аппаратное обеспечение развивалось, программы для него становились все больше и сложнее. Интуитивное программирование уже не могло быть эффективным. Уже требовалась какая-нибудь технология программирования.

Технология нисходящего программирования заключается в разбиении одной большой задачи на более мелкие подзадачи, каждая из которых решается отдельно. В результате программа становится похожа на иерархическую структуру. Поэтому в данном случае чаще используется понятие структурного программирования.

При таком подходе важна грамотная декомпозиция задачи, что достигается путем использования ограниченного числа управляющих конструкций (следование, ветвление, цикл).

Обязательным элементом структурного программирования является наличие подпрограмм. Чаще всего во многих языках программирования роль подпрограмм выполняют только функции. В языке Pascal есть деление на функции и процедуры.

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

Структурное программирование делает программу более понятной. Ее легче отлаживать и сопровождать. Разные модули (в которых содержатся функции и процедуры) могут разрабатывать разные люди, в результате чего проще организовать коллективное решение одной большой задачи, т.к. каждый будет решать свою подзадачу.
Структурное программирование было особенно популярным в 70-х годах. На сегодняшний день программирование зачастую начинают изучать именно с него.

Стандартные функции языка Pascal

В программировании, как и в любой науке (хотя это и искусство также), с течением исторического времени накапливается опыт, методы решения различных задач. Решение многих задач является достаточно универсальным. Незачем каждый раз писать алгоритм для ее решения, если он уже был написан много лет назад и одобрен сообществом программистов. Такие алгоритмы оформляются в виде функций и модулей, а затем используются в программах, которые пишутся здесь и сейчас.

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

Ниже описаны стандартные (включенные в язык) функции языка программирования Паскаль.

Арифметические функции

Арифметические функции можно использовать только с величинами целого и вещественного типа.

Функция Назначение Тип результата
abs (x) абсолютное значение аргумента совпадает с типом аргумента
sqr (x) квадрат аргумента совпадает с типом аргумента
sqrt (x) квадратный корень аргумента вещественный
cos (x) косинус аргумента вещественный
sin (x) синус аргумента вещественный
arctan (x) арктангенс аргумента вещественный
exp (x) ex вещественный
ln (x) натуральный логарифм вещественный
int (x) целая часть числа вещественный
frac (x) дробная часть числа вещественный

Функции преобразования типов

Эти функции предназначены для преобразования типов величин, например, символа в целое число, вещественного числа в целое и т.д.

ord (x) - возвращает порядковый номер аргумента и, таким образом, преобразует величину порядкового типа в величину целого типа.
round (x) - округляет вещественное число до ближайшего целого.
trunc (x) - выдает целую часть вещественного числа, отбрасывая дробную.

Функции для величин порядкового типа

odd (x) - проверяет аргумент на нечетность. Аргумент функции величина типа longint, результат true, если аргумент нечетный, false – если четный.
pred (x) - определяет предыдущее значение величины x.
succ (x) - определяет последующее значение величины x.
ord (x) - возвращает порядковый номер величины x.
Задачи к данной теме

Арифметические функции

Следующие две функции можно применять к целым параметрам, и в этом случае они возвращают целый результат. Этим функциям можно также передавать вещественный параметр, получая вещественный результат.

abs(выражение) – абсолютное (т.е. положительное) значение параметра.
abs(-2), abs(0), abs(2). Результат: 2, 0, 2
abs(-2.0), abs(0.0), abs(2.0). Результат: 2.0, 0.0, 2.0

sqr(выражение) – квадрат параметра.
sqr(-2), sqr(0), sqr(2). Результат: 4, 0, 4
sqr(-2.0), sqr(0.0), sqr(2.0). Результат: 4.0, 0.0, 4.0

Остальные арифметические функции принимают целый или вещественный параметр; результат в любом случае будет вещественным:

sqrt(выражение) – квадратный корень.
sqrt(16), sqrt(0.64), sqrt(0). Результат: 4.0, 0.8, 0.0
sqrt(-16). Результат: ошибка

ln(выражение) – натуральный логарифм.
ln(1), ln(2.7182):4:1, ln(7.5):4:1. Результат: 0.0, 1.0, 2.0
ln(0), ln(-1). Результат: ошибка

exp(выражение) – экспонента.
exp(0):4:1, exp(1):8:5, exp(2.014903):4:1. Результат: 1.0, 2.71828, 7.5
exp(-1):7:4. Результат: 0.3579
Примечание: e-1 = 1 / e

Задачи к данной теме

Тригонометрические функции

Ниже приведены тригонометрические функции, используемые в языке программирования Pascal. Их аргумент может быть целым или вещественным; результат в любом случае – вещественный.

1 радиан = 180 / пи

sin(выражение) – синус угла, измеренного в радианах
sin(-pi / 6):4:1. Результат: -0.5
sin(0):4:1. Результат: 0.0
sin(pi / 2):4:1. Результат: 1.0

cos(выражение) – косинус угла, измеренного в радианах
cos(-pi / 6):4:1. Результат: 0.8
cos(0):4:1. Результат: 1.0
cos(pi):4:1. Результат: -1.0

arctan(выражение) – арктангенс
arctan(1e35):8:5. Результат: 1.57080
arctan(0):4:1. Результат: 0.0
arctan(-1):8:5. Результат: 0.78540

Функции преобразования из вещественного в целый тип

Кода целое значение присваивается вещественной переменной, оно автоматически преобразуется в вещественный тип и никакие функции для этого не требуются. Такое преобразование типов называется неявным. Так, если переменную объявить как real, а затем присвоить ей целое число 5, то последнее автоматически преобразуется в 5.0.

Обратного неявного преобразования нет: будет ошибкой пытаться присваивать переменной целого типа вещественный результат.

Перед присваиванием целой переменной вещественного значения это значение следует преобразовать к целому типу отбрасыванием дробной части или округлением. Для этих целей служат функции trunc и round соответственно.

trunc(вещественное_выражение) – преобразует вещественное в целый тип, отбрасывая дробную часть.
trunc(3.1), trunc(3.8). Результат: 3, 3
trunc(-3.1), trunc(-3.8). Результат: -3, -3

round(вещественное_выражение) – преобразует вещественное в целый тип, округляя до ближайшего целого.
round(3.1), round(3.8). Результат: 3, 4
round(-3.1), round(-3.8). Результат: -3, -4

Здесь возможны недоразумения. Пусть вещественная переменная x имеет значение 3.499999. Если это значение напечатать с использованием оператора write(x:8:5), то получится 3.50000, в то время как write(round(x)) даст 3, а не 4. Это затруднение можно обойти при помощи небольшой поправки, например write(round(x + 0.000001)) (в предположении, что значение переменной x заведомо положительное).

Применять функции trunc и round к параметрам целого типа нельзя. Например, будут ошибкой такие выражения, как trunc(3) или round(3).

Задачи к данной теме

Логические функции

Функция odd используется для проверки четности или нечетности целого выражения.
odd(целое_выражение) – возвращает true, если параметр – нечетный, в противном случае возвращает false.
odd(3), odd(2), odd(0). Результат: true, false, false
odd(-3), odd(-2). Результат: true, false
odd(3.0). Результат: ошибка

Следующие функции служат для определения конца строки или конца файла соответственно. Функция eoln используется только с текстовыми файлами, которые организованы как строки символов. Функцию eof не следует использовать при вводе данных с клавиатуры.
eoln(имя_файла) – возвращает true, если была прочитана последняя литера текущей строки.

while not eoln do begin
    read(i); {целый тип, пробелы пропускаются}
    writeln(i:3)
end;
while not eoln do begin
    read(a); {вещественный тип, пробелы пропускаются}
    write(a:5:1)
end;

eof(имя_файла) – возвращает true, если была прочитана последняя литера файла (попытка дальнейшего чтения ведет к ошибке).

while not eof(f) do begin
    while not eoln(f) do begin
        read(ch); {тип char, пробелы учитываются}
        write(ch)
    end;
    writeln
end;
while not eof(g) do begin
    read(ch); {признак конца строки читается как пробел}
    write(ch)
end;

Функции над дискретными типами

Функция ord

Буквы от ‘A’ до ‘Z’ следуют в возрастающем порядке, иными словами, каждая буква имеет порядковое значение, соответствующее ее месту в алфавите. Это порядковое значение может быть получено посредством функции ord.
ord(выражение) – возвращает порядковый номер литеры или значения другого дискретного типа.
ord(‘I’), ord(‘J’). Результат: 73, 74 (код ASCII)

Порядковый номер литеры зависит от используемого кода. Но, независимо от используемого кода, порядковые значения букв следуют по возрастанию:
ord(‘A’) < ord(‘B’) < ord(‘C’) … < ord(‘Z’)
хотя ord(‘Z’) – ord(‘A’) и не обязательно равно 25. То же самое и со строчными буквами:
ord(‘a’) < ord(‘b’) < ord(‘c’) … < ord(‘z’)

Определенной связи между прописными и соответствующими строчными буквами нет, но можно без опасений полагаться на то, что ord(‘a’) – ord(‘A’) имеет то же значение, что и ord(‘z’) – ord(‘Z’).

Независимо от используемого кода, порядковые значения цифр также расположены по возрастанию:
ord(‘0’) < ord(‘1’) < ord(‘2’) … < ord(‘9’)
и, более того, порядковые значения соседних цифр отличаются на 1; так, ord(‘9’) – ord(‘0’) = 9. Отсюда следует, что численное значение цифры d (типа char) может быть получено так
value := ord(d) – ord(‘0’).

Паскаль поддерживает типы char, integer и т.д. В дополнение к ним программист вправе определить и другие типы путем перечисления последовательности констант. Например, тип, заданный перечислением:
type days = (mon, tue, wed, thu, fri, sat, sun);

Константы типа, заданного перечислением, имеют порядковые значения, отсчитываемые от нуля. Например, ord(mon) возвращает 0, ord(sun) возвращает 6; mon < sun.

Тип boolean – перечисляемый тип, который автоматически задается как
type boolean = (false, true);
следовательно, ord(false) дает 0, ord(true) дает 1; false < true.

Функция chr

Обратной для ord является функция chr.
chr(выражение) – возвращает литеру, порядковое значение которой задается параметром; неправильное значение влечет ошибку.
chr(73), chr(74). Результат: I, J (кодировка ASCII)
chr(1000). Результат: ошибка

Функции succ и pred

Порядковые значения редко бывают нужны сами по себе. Часто достаточно знать следующий или предыдущий элемент в установленном порядке. Для этой цели служат функции succ и pred.
succ(выражение) – возвращает элемент, следующий за тем, который указан в качестве параметра.
succ(‘A’), succ(‘0’), succ(0). Результат: B, 1 (символ), 1 (число)
succ(false). Результат: true
pred(выражение) – возвращает элемент, предшествующий тому, который указан в качестве параметра.
pred(‘Z’), pred(‘9’), pred(9). Результат: Y, 8 (символ), 8 (число)
succ(true). Результат: false

Эти две функции можно использовать для определения следующих и предшествующих элементов для типа, заданного перечислением. Возьмем тип days, определенный ранее: pred(sun) возвращает sat, succ(mon) возвращает tue.

Однако было бы неверно писать writeln(pred(sun)), поскольку элементы перечисляемого типа нельзя читать или печатать, что, конечно, снижает выгоду от использования таких типов. Наилучшее приближение к writeln(pred(sun) – это оператор writeln(ord(pred(sun))), печатающий число 5 (порядковое значение элемента sat).

Функцию succ удобно использовать для управления циклом:

i := 0;
repeat
    i := succ(i);
    ……
until i = 10;

Процедуры и функции

Использование подпрограмм является главной особенностью структурного программирования. По сути, подпрограмма представляет собой составной оператор, объединяющий ряд простых операторов. При этом этот «составной оператор» оснащается именем и внешне оформляется как программа. В подпрограмме бывают свои константы, переменные и другие атрибуты полноценной программы. Локальные переменные никак не связаны с одноименными глобальными переменными (относящимися к основной программе).

Зачем нужны подпрограммы? Их использование удобно, когда в программе несколько раз решается одна и та же подзадача, но для разных наборов данных. Кроме того, использование подпрограмм естественно для человека, которому проще воспринимать логически связанные объекты, чем множество разнородных данных.

Программный код подпрограммы описывается единожды перед телом основной программы, затем из основной программы можно им пользоваться многократно. Обращение к этому программному коду из тела основной программы осуществляется по его имени (имени подпрограммы).

В большинстве языков структурного программирования подпрограммы существуют только в виде функций. В Паскале же их два типа: процедуры и функции. Их заголовки выглядят соответственно так:

procedure имя (параметры);

function имя (параметры): тип результата;

Между функциями и процедурами есть существенное отличие. Значение, полученное в результате выполнения кода функции, жестко соотносится с ее именем путем присвоения этому имени конкретного значения. Тип, который может принять вычисляемое значение, указывается в заголовке функции (тип результата). И в теле основной программы функция вызывается только в том случае, если ее имя фигурирует в каком-либо выражении. В то время как процедура вызывается отдельно.

Параметры не являются обязательным компонентом, однако их использование повышает гибкость процедуры или функции, т.к. они перестают быть привязанными к определенным данным.

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

Тело подпрограммы, как и любого составного оператора на языке Паскаль, ограничивается скобками begin и end.

Примеры использования процедуры и функции.

procedure box (s: char; w,h: integer);
    var i,j:integer;
    begin
        for i := 1 to h do begin
            for j := 1 to w do
                write (s);
            writeln
        end;
        writeln
    end;
 
begin
    box ('+', 10, 5);
    box ('r', 20, 3);
    box ('|', 50, 10);
    box ('$', 12, 4);
 
readln
end.

var num: integer;
 
function digits (n:integer): integer;
    var i: integer;
    begin
        i := 0;
        while n > 0 do begin
            n := n div 10;
            i := i + 1
        end;
 
        digits := i
    end;
 
begin
    write ('Введите положительное число: ');
    readln (num);
 
    num := digits (num);
    writeln ('Количество разрядов = ', num);
 
readln
end.

Презентация с пояснениями во вложении

Задачи к данной теме

Формальные параметры

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

Пусть, например, процедура sq осуществляет решение квадратного уравнения ax2 + bx + c = 0. Тогда она должна иметь пять формальных параметров: для значений коэффициентов a, b, c и для результатов: x1 и x2.

Для того, чтобы запустить процедуру в работу, необходимо к ней обратиться (ее вызвать). Вызов процедуры N производится оператором вида

N (p1, p2, p3, …);

здесь N – имя процедуры, p1, p2, p3 – фактические параметры.

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

Соответствие между фактическими и формальными параметрами должно быть следующим:
а) число фактических параметров должно быть равно числу формальных параметров;
б) соответствующие фактические и формальные параметры должны совпадать по порядку следования и по типу.

Соответствующие параметры не обязательно должны быть одинаково обозначены.

Пример. Вызвать процедуру sq можно так:

sq(p, q, r, y, z);

здесь p, q, r – коэффициенты квадратного уравнения, а y и z – корни этого уравнения. Если вызвать sq оператором sq(x1, x2, a, b, c); то машина воспримет x1, x2, a как коэффициенты уравнения, а корни зашлет в переменные b и c.

Пример. Составим процедуру sq решения квадратного уравнения ax2 + bx + c = 0 в предположении, что дискриминант не отрицателен. С помощью этой процедуры решим квадратное уравнение 5.7y2 – 1.2y – 8.3 = 0.

var
    y1, y2: real;
 
procedure sq(a, b, c: real; var x1, x2: real);
    var
        d: real;
    begin
        d := b * b - 4 * a * c;
        x1 := (-b + sqrt(d)) / (2 * a);
        x2 := (-b - sqrt(d)) / (2 * a);
    end;
 
begin
    sq(5.7, -1.2, -8.3, y1, y2);
    writeln('y1 = ', y1, '; y2 = ', y2);
 
readln
end.

Как видно из примера, процедура помещается после декларативных операторов программы. Первым выполняется оператор обращения к процедуре:

sq(5.7, -1.2, -8.3, y1, y2);

здесь первые три фактические параметра соответствуют формальным a, b, c, а последние два фактических параметра y1 и y2 соответствуют формальным x1 и x2. После того как процедура «запустится», в ячейки a, b, c попадут числа 5.7, -1.2, -8.3 и начнут выполняться операторы процедуры.

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

Задачи к данной теме

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

Если в качестве формального параметра указана переменная, то такой параметр и есть параметр-значение. Примерами таких параметров служат параметры a, b и с в процедуре sq:

procedure sq(a, b, c: real; var x1, x2: real);

В этом случае фактическим параметром, соответствующим a либо b либо c, может быть любое выражение соответствующего типа, в частности, константа.

Например, обратиться к sq можно так:

sq((25./3 + 2) * 2, -1.5, (8.2 – 3.1) / 3, x1, x2);

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

Если фактический параметр есть имя переменной, например, r, то значение этой переменной пересылается в соответствующий формальный параметр, например, a. На этом всякая связь между a и r обрывается.

Если даже фактический и формальный параметры одинаково обозначены, в памяти ЭВМ эти параметры занимают разные ячейки. Это полезно знать, чтобы не допустить распространенной среди начинающих программистов ошибки – пытаться передать информацию из процедуры в вызывающую программу через параметр-значение.

Пример.

var
    i: integer;
 
procedure p(i: integer);
    begin
        i := i * 2
    end;
 
begin
    i := 2;
    p(i);
    writeln(' i = ', i);
 
readln
end.

В программе происходит засылка числа 2 в ячейку, отведенную для переменной i, затем идет обращение к процедуре p с фактическим параметром i = 2. При этом значение 2 пересылается в другую ячейку, отведенную для формального параметра i. В этой ячейке после выполнения оператора i := i * 2 появляется число 4. Но после возврата из процедуры на оператор writeln программа "знает" только одну переменную i, которая по-прежнему содержит число 2. Поэтому программа выведет i = 2.

Если формальный параметр есть параметр-значение, то соответствующим фактическим параметром должно быть выражение того же типа, что и формальный параметр.

Задачи к данной теме

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

Если перед именем формального параметра стоит ключевое слово var, то такой параметр есть параметр-переменная. Примерами таких параметров служат x1 и x2 в заголовке

procedure sq(a, b, c: real; var x1, x2: real);

Фактический параметр, соответствующий параметру-переменной, может быть только переменной (не константой и не выражением).

При вызове процедур (функций) параметры-переменные обрабатываются так: для формального параметра используется именно та ячейка, которая содержит соответствующий фактический параметр.

Пример. При вызове процедуры sq оператором sq(p, q, r, y, z) для переменных x1 и x2 используются непосредственно те ячейки, которые отведены для y и z. Поэтому оператор присваивания x1 := (-b + sqrt(d)) / (2 * a) засылает полученное значение в y.

Под формальные и фактические параметры-значения транслятор отводит разные области памяти. Поэтому результат выполнения процедуры может быть передан только через параметр-переменную.

var
    a, b: integer;
 
procedure h(x: integer; var y: integer);
    begin
        x := x + 1;
        y := y + 1;
        writeln(x, y)
    end;
 
begin
    a := 0;
    b := 0;
    h(a, b);
    writeln(a, b);
 
readln
end.

Результаты, выдаваемые процедурой h – 11; основная ветка программы выводит 01.

Разберем этот пример: фактический параметр a соответствует формальному параметру-значению x, а фактический параметр b – формальному параметру-переменной y. Под параметры a и x отведены две разные ячейки памяти, а под b и y – одна и та же ячейка.

При обращении к h(a, b) из ячейки a пересылается значение 0 в ячейку x, а в ячейку y засылается адрес ячейки b, содержащей 0, т.к. в процедуре h параметр x – это параметр-значение, а y – параметр-переменная.

При выполнении оператора x := x + 1 в ячейку x прибавляется 1 и в ячейке x окажется 1, а в ячейке a по-прежнему 0.

Выполнение оператора y := y + 1 имеет следующий смысл: «взять число из ячейки, адрес которой находится в y (т.е. из ячейки b), прибавить 1 и заслать в ту же ячейку (т.е. в b)».

Поэтому в результате выполнения оператора y := y + 1 значение ячейки b станет 1. Оператор печати из процедуры выдаст содержимое ячейки x и ячейки y, т.е. 1 и 1. Оператор печати в программе напечатает содержимое a, которое осталось равным 0, и содержимое ячейки b, которое теперь равно 1.

Процедуры в Паскале допускают рекурсию, т.е. процедура может вызвать сама себя.

Если в процедуре p есть обращение к процедуре q, описанной ниже, то перед описанием p процедура q декларируется как forward: после заголовка процедуры q ставится двоеточие, а затем ключевое слово forward. В этом случае параметры процедуры описываются только в операторе с forward. В заголовке самой процедуры параметры опускаются.

Пример.

procedure q(x: t1): forward;
 
procedure p(x: y);
    begin
        q(a)
    end;
 
procedure q;
    begin
        p(b)
    end;

Задачи к данной теме

Побочные эффекты

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

Пример. Пусть функция f(x) имеет такой вид:

function f(x: real): real;
    begin
        v := v * x;
        f := sqrt(v) + x
    end;

т.е. в процессе работы функция f изменяет некоторую нелокальную величину v. Рассмотрим теперь два выражения, которые вычисляются в программе: f(x) + v и v + f(x).

Эти выражения дадут разные результаты, т.к. в первом случае к f(x) прибавляется уже измененное значение v (в процессе работы f), а во втором случае к первоначальному значению v добавляется f(x).

Вторая опасность заключается в неправильном использовании параметров-переменных в качестве формальных параметров.

Пример. Найти 5-й член последовательности

an + 1 = 3an – 2,
a1 = 1.

Опасно оформлять функцию в виде

function f(var a, n: integer): integer;
    var i: integer;
    begin
        for i := 1 to n do
            a := 3 * a – 2;
        f := a
    end;

Так, если обратиться к этой функции оператором b := f(1, 5), будет «испорчена» константа 1, т.к. в ячейку памяти (первый фактический параметр), содержавшую ранее единицу, функция f поместит текущий член последовательности, и при дальнейшей работе программы вместо 1 будет использоваться значение a5. Такие ошибки бывает трудно найти, поэтому полезно придерживаться следующего правила: в функциях не использовать параметры переменные.

Процедуры и функции в качестве параметров

Такие параметры в списке формальных параметров представляются ключевыми словами procedure и function.

Примеры.

procedure p(procedure a); 

Здесь процедура p имеет один параметр-процедуру a.

procedure q(function s: real; b: real);

Процедура q имеет два параметра: параметр-функцию s и параметр-значение b.

procedure q(function f(i: integer): real);

Здесь формальный параметр f – функция от одного целого аргумента, результат f – вещественный.

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

Обратиться к процедуре q(function f(i: integer): real) можно так: q(sinus(k)); где sinus(k) есть sin(k). Если k имеет тип integer, тогда sinus(k) – типа real. Это совпадает с типами i и f в заголовке q. Нельзя, однако, обратиться к q с функцией abs(k), а именно q(abs(k)); в этом случае тип формального параметра freal, а тип фактического abs(k) – integer, т.е. формальный и фактический параметры не совпадают по типу.

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

procedure tab(function f: real; low, up, step: real);
    var 
        x: real;
        j: integer;
    begin
        x := low;
        for j := 0 to trunc((up – low) / step) do begin
            writeln(x:10, f(x):10);
            x := x + step
        end
    end;

Выражение trunc((up – low) / step) дает число точек, в которых выполняется функция f (при счете от 0).

Если к функции tab обратиться оператором

tab(sin, 0.0, 6.4, 0.33);

то будет напечатана таблица функции sin x для x от 0 до 6.4 с шагом 0.33. Алгоритмы, употребляемые наиболее часто различными пользователями, оформляются в виде процедур и функций, и составляют библиотеку стандартных программ (модулей).

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

  1. Ошибки, допускаемые программистом в процедурах, имеющих параметры-процедуры и параметры-функции, иногда бывает трудно найти, что ведет к длительной отладке таких процедур.
  2. Если число и тип параметров формального параметра-функции не совпадает с числом либо типом параметров соответствующего фактического параметра-функции, то такая программа не может быть правильно выполнена, а многие версии трансляторов с Паскаля не выдают в этом случае никакой диагностики.
  3. Правила языка Pascal требуют, чтобы фактические параметры-функции содержали только параметры-значения. Это накладывает серьезные ограничения на использование параметров-процедур и параметров-функций.

Локальные и глобальные переменные

Напомним, что каждый модуль (процедура, функция, программа) состоит из заголовка (procedure…, function…, program…) и блока.

Если блок какой-либо процедуры p1 содержит внутри процедуру p2, то говорят, что p2 вложена в p1.

Пример.

procedure p1(x: real; var y: real);
    var c: integer;
    procedure p2(var z: real);
        …………………….
    end;
    begin
        …………………….
    end;

Любые идентификаторы, введенные внутри какого-либо блока (процедуры, функции) для описания переменных, констант, типов, процедур, называются локальными для данного блока. Такой блок вместе с вложенными в него модулями называют областью действия этих локальных переменных, констант, типов и процедур.

Пример.

procedure t1;
    var y1, y2: real;
    procedure sq1;
        var a, b, c, d: real;
        begin
            { Переменные a, b, c, d являются локальными для sq1,
               область их действия – процедура sq1 }
            ……………………………………
        end;
    begin
        { Переменные y1, y2 - нелокальные для sq1,
           область их действия – t1 и sq1 }
    end;

Константы, переменные, типы, описанные в блоке program, называются глобальными. Казалось бы, проще иметь дело вообще только с глобальными переменными, описав их все в program. Но использование локальных переменных позволяет системе лучше оптимизировать программы, делать их более наглядными и уменьшает вероятность появления ошибок.

При написании программ, имеющих вложенные модули, необходимо придерживаться следующих правил:

  1. Описывать идентификаторы в том блоке, где они используются, если это возможно.
  2. Если один и тот же объект (переменная, тип, константа) используются в двух и более блоках, то описать этот объект надо в самом внешнем из них, содержащем все остальные блоки, использующие данный объект.
  3. Если переменная, используемая в процедуре, должна сохранить свое значение до следующего вызова этой процедуры, то такую переменную надо описать во внешнем блоке, содержащем данную процедуру.

Локализация переменных дает программисту большую свободу в выборе идентификаторов. Так, если две процедуры a и b полностью отделены друг от друга (т.е. не вложены одна в другую), то идентификаторы в них могут быть выбраны совершенно произвольно, в частности, могут повторяться. В этом случае совпадающим идентификаторам соответствуют разные области памяти, совершенно друг с другом не связанные.

Пример.

var k: integer;
procedure a;
    var x, z: real;
    begin
        { через x, z обозначены две величины –
        локальные переменные для a;
        k – глобальная переменная для a }
        …………………………………
    end;
procedure b;
    var x, y: integer;
    begin
        { через x, y обозначены две другие величины –
        локальные переменные для b;
        k – глобальная переменная для b }
        …………………………………
    end;
begin
{ k – единственная переменная, которую 
можно использовать в основной ветке программы }
…………………………………
end.

Если один и тот же идентификатор описан в блоке b и второй раз описан во вложенном в b блоке c, то надо помнить, что эти два одинаковых идентификатора соответствуют разным ячейкам памяти.

var
    i: integer;
    a: real;
 
procedure p(var d: real);
    var i: integer;
    begin
        i := 3;
        d := i + 10 * d;
    end;
 
begin
    a := 2.0;
    i := 15;
    p(a);
    writeln(' i = ', i, ' a = ', a);
 
readln
end.

Глобальным переменным i и a отводятся две ячейки памяти. Первыми выполняются операторы a := 2.0 и i := 15. Затем вызывается процедура p(a). В процессе работы p отводится ячейка для локальной переменной i и туда засылается число 3. После окончания работы процедуры p эта ячейка i программой «забывается». После возврата на оператор writeln программа знает только одну ячейку i – глобальную, т.е. ту, которая содержит число 15. Поэтому программа выдаст на печать i = 15, a = 23.0, т.к. a = 3 + 10 * 2.

Если локальная и глобальная переменная принадлежат к одному и тому же сложному типу, то этот тип надо описать в разделе type, а сами переменные описывать через этот общий тип.

Пример.

type ab = array[1..3] of real;
var a: ab;
procedure q;
    var b: ab;
    …………………………..
end;

В этом примере переменные a и b описаны через общий тип ab. Если же локальная и глобальная переменные описаны одинаково, но не через общий тип, то программа может «не понять», что эти переменные принадлежат одному типу.

Пример.

var a: array[1..3] of real;
procedure q;
    var b: array[1..3] of real;
    ……………………….
end;

В этом примере переменные a и b – одинаковые массивы, т.е. типы этих переменных одинаковы, но программа, тем не менее, «не считает», что a и b принадлежат одному типу. Это происходит из-за того, что описание массивов дано в разных блоках.

Задачи к данной теме

Рекурсивные функции

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

procedure rever (n: integer);
    begin
        write (n mod 10);
        if (n div 10) <> 0 then
            rever (n div 10)
    end;
 
begin
    rever (3096);
 
readln
end.

В приведенном примере процедура rever выводит цифры, переданного ей в качестве фактического параметра числа, в обратном порядке. Т.е. если мы передаем число 35, то процедура выведет на экран число 53. Рассмотрим, как она это делает:

  1. Мы передаем число 3096.
  2. Процедура rever выводит на экран остаток от деления на 10. Это число 6.
  3. Переход на новую строку не происходит, т.к. используется write.
  4. Проверяется условие того, что 3096 при деление нацело на 10 больше нуля.
  5. Вызывается rever с фактическим параметром, равным 309.
  6. Вторая запущенная процедура выводит на экран цифру 9 и запускает третью процедуру с параметром 30.
  7. Третья процедура выводит 0 и вызывает четвертый rever с 3 в качестве параметра.
  8. Четвертая процедура выводит 3 на экран и ничего больше не вызывает, т.к. условие (3 div 10) <> 0 ложно.
  9. Четвертая процедура завершается и передает управление третьей.
  10. Третья процедура завершается и передает управление второй.
  11. Вторая процедура завершается и передает управление первой.
  12. Первая процедура завершается и передает управление в основную ветку программы.

В итоге, процедура rever была вызвана четыре раза, хотя из основной программы к ней было единственное обращение.

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

Задачи к данной теме