Pascal. Подпрограммы: процедуры и функции - 1


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

Использование подпрограмм позволяет избежать дублирования кода, в случае если необхоидмо один и тот же код писать в разных местах программы. 
Библиотеки, которые импортируются в программу (например, математическая библиотека math) состоят из подпрограмм, которые уже кем-то составлены. Программистам не нужно думать о том, какие алгоритмы в них реализованы, а просто применяют их, задумываясь только о том, что именно они делают. Это большая экономия времени. Нет нужды писать алгоритм, который уже был кем то написан.

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

Подпрограммы бывают двух типов - процедуры и функции

Подпрограммы-процедуры выполняют некоторые действия, например, выводят результат на экран в определенном виде (простой пример, оператор writeln()- это стандратная подпрограмма-процедура, которая выводит на экран информацию)

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

Давайте попробуем написать простую процедуру:
Предположим, что нам нужно выводить на экран строку "Error" каждый раз, когда в коде может возникнуть ошибка по вине пользователя (например, когда он вводит неверные данные)
Это можно сделать, написав оператор
writeln('Error');
А теперь представим, что такую строчку нужно вставить во многих местах программы. Конечно, можно везде ее просто написать. Но это решение имеет два недостатка.
1) данная строка будет храниться в памяти много раз
2) если мы захотим изменить вывод при ошибке, то придется менять эту строку по всей программе, что достаточно неудобно

Для таких случаев процедуры и нужны.
Программа с процедурой может выглядеть следующим образом:
...
using namespace std;

procedure printError();    // описание процедуры
begin  
    writeln('Error');   // тело процедуры - команды, которые будет выполнять процедура
end;

// основная программа
begin;
...
  printError();      // запуск процедуры на выполнение. Просто указываем имя процедуры, которую хотим выполнить
...
  printError();
...
end.

Процедура начинается со слова procedure. После имени процедуры записаны скобки, в которых указываются переменные и их типы, от которых зависит выполнение процедуры. Например:

var a, b, answer: integer;
procedure Sum(a, b: integer);
begin
    answer := a + b;
end;

Все операторы, которые выполняются в процедуре, записываются с отступом. 

Процедуры записываются до главной программы

Чтобы выполнить процедуру, в основной программе необходимо вызвать ее по имени и не забыть написать скобки!
Вызывать процедуру в программе можно сколько угодно раз.

А теперь представим, что нам необходимо в ответ на ошибку пользователя вывести разные сообщения, в зависимости от того, какую именно ошибку он сделал.
В этом случае можно для каждой ошибки написать свою процедуру: 
procedure printErrorZero();
begin
    writeln('Error. Division by zero!');
end;

procedure printErrorInput();
begin
    writeln('Error in input!');
end;

А если возможных ошибок будет намного больше? Такое решение нам не подойдет!
Надо научиться управлять процедурой, указывая ей, какое сообщение на ошибку нужно вывести.
Для этого нам понадобятся параметры, которые мы будем записывать в круглых скобках, после имени процедуры
procedure printError(s: string);
begin
    writeln(s);
end;
В данной процедуре s - это параметр - специальная переменная, которая позволяет управлять процедурой.
Параметр - это переменная, от значения которой зависит работа подпрограммы. Имена параметров перечисляются через точку с запятой в заголовке подпрограммы. После параметра через двоеточие записывается его тип.

Теперь при вызове процедуры нужно в скобках указывать фактическое значение, которое будет присвоено параметру (переменной s) внутри нашей процедуры
printError('Error! Division by zero!');
Такое значение называется аргументом
Аргумент - это значение параметра, которое передается подпрограмме при ее вызове.
Аргументом может быть не только постоянное значение, но и переменная или арифметическое выражение.

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

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

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

Проанализируйте три программы:
1) В этой программе переменная i локальная. Локальная переменная описывается внутри подпрограммы 2) Здесь, даже если есть переменная i в основной программе (со значением 7), будет создана новая локальная переменная i со значением 5. 
При выполнении этой программы на экране появится значение 75
3) В этой программе существует глобальная переменная i. Ее значение можно изменить внутри подпрограммы, и внутри основной программы
Процедура будет работать с глобальной переменной i и ей будет присвоено новое значение, равное 2. На экран выводится значение 2
procedure test();
var i: integer;
begin
    i := 5;
    writeln(i);
end;
var i: integer;

procedure test();
var i: integer;
begin
    i := 5;
    writeln(i);
end;

begin
    i := 7;
    write(i);
    test();
end.
var i: integer;

procedure test();
begin
    i := 2;
end;

begin
    test();
    writeln(i);
end.

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

Попробуем написать процедуру таким образом:

var x, y: integer;
procedure Swap (a, b: integer);   // при таком описании параметров процедуры,
var c: integer; 
begin                            // будет происходить копирование значений аргументов (x и y)
                      // переменные a и b - самостоятельные переменные, не связанные с x и y  
c := a;
a := b;
b := c;
end;

begin
  x := 1;
  y := 2;
  Swap (x, y);         //значения переменных x и y (аргументы) копируются в параметры a и b
  writeln('x = ', x, ', y = ', y);       // x = 1, y = 2   
end.                                       
Если запустить данную программу, то можно увидеть, что значения переменных x и y не изменилось. Для того чтобы параметры изменяли значения аргументов необходимо использовать передачу данных по ссылке. Для этого после названия типа данных в заголовке подпрограммы необходимо поставить слово var 
procedure Swap (var a, b: integer);   // теперь переменные a и b получают адреса переменных x и y в памяти
var c: integer; 
begin
c := a;
a := b;
b := c;
end;
Применение: Если вы передаете аргумент по ссылке, то при вызове процедуры на этом месте может стоять только имя переменной (НЕ число и НЕ арифметическое выражение)!

НЕЛЬЗЯ вызвать процедуру таким образом:
Swap (x, 4);
Swap(5+x, y);