#14.1 Процедуры - продпрограммы в Паскале. Объявление и использование. Область видимости, глобальные и локальные переменные

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

Пример простой процедуры:

// объявление процедуры
procedure privet(); // имя процедуры
begin // тело процедуры
  writeln('Привет Мир!');
end;

begin  // тело программы
  privet();
end.

Понятие области видимости переменной

Области видимости [переменной или иной сущности в программе] -- это все те фрагменты кода ("места" программы), из которых к данной переменной можно обратиться (напр. получить значение). (взято из Словаря программиста)

Рассмотрим два типа области видимости:

  1. Глобальная -- эти имена [переменных] "видны" везде в программе
  2. Локальная (локальная для указанной процедуры) -- эти имена "видны" только внутри подпрограммы (процедуры)

В глобальной области видимости находятся все переменные, которые мы уже умеем объявлять в секции var.
Эта секция называется "глобальной секцией объявления переменных".

Рассмотрим примеры.

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

Процедура может обладать собственной секцией объявления переменных (локальной), например

var i: integer; // глобальная секция объявления переменных

procedure chitai();
var a: integer; // локальные переменные процедуры
begin
  writeln('Введите число:');
  readln(a);
  writeln('a=', a);
end;

begin  // тело программы
  chitai();
  i:=1;
  writeln(i);
end.  

Глобальных переменных в программе конечно может и не быть:

procedure chitai();
var a: integer; // локальные переменные процедуры
begin
  writeln('Введите число:');
  readln(a);
  writeln('a=', a);
end;

begin  // тело программы
  chitai();
end.  

Пример №2 Обращение к локальной переменной из процедуры

Глобальные переменные "видны" во всех участках кода и мы обращаться с ним из тела процедуры:


var i: integer; // глобальная секция объявления переменных

procedure pishi();
var a: integer; // локальные переменные процедуры
begin
  writeln('Введите число:');
  readln(a);
  writeln('a=', a);
  writeln('i=', i);
end;

begin  // тело программы
  i:=1;
  pishi();
end. 
 

Почему не стоит обращаться к глобальным переменным из подпрограммы

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

var a: integer;  // глобальная секция

procedure pishi();
begin
  writeln('Введите число:');
  readln(a);
  writeln('a=', a);
end;

begin  // тело программы
  pishi();
end. 

И Программа 2:

var b: integer;  // глобальная секция
begin  // тело программы
  writeln('Введите число:');
  readln(b);
  writeln('a=', b);
end. 

-- по сути это программа делает то же самое, что и программа 1, а значит мы могли переиспользовать код процедуры и добиться того же результата, т.е. перенести код процедуры в программу 2 и выполнить ее вызов:

var b: integer;  // глобальная секция

procedure pishi(); // скопировали
begin
  writeln('Введите число:');
  readln(a); // переменной а нет в глобальной секции!!
  writeln('a=', a);
end;

begin  // тело программы
   pishi(); // заменяем код на идентичный вызов процедуры
end. 

-- запустим и получим ошибку о том, что переменная a не определена, что логично - ведь ее нет в глобальной области видимости.

Как решить проблему?
Есть два способа:

  • 1) Объявлять локальные переменные, если они нужны только внутри тела процедуры
  • 2) Передавать на вход (этот вариант рассмотрим ниже в этом уроке)

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

procedure pishi(); 
var a: integer; // "приземляем" имя в локальной секции 
begin
  writeln('Введите число:');
  readln(a); 
  writeln('a=', a);
end;

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

Этот код легко перенести, он игнорирует глобальное пространство т.к. не обращается к нему, Программа 2 теперь будет выглядеть так:

var b: integer;  // глобальная секция

procedure pishi(); 
var a: integer; // "приземляем" имя в локальной секции 
begin
  writeln('Введите число:');
  readln(a); 
  writeln('a=', a);
end;

begin  // тело программы
   pishi(); // заменяем код на идентичный вызов процедуры
end. 

Ну а Программа 1, соответственно, так:

var a: integer;  // глобальная секция

procedure pishi(); 
var a: integer; // "приземляем" имя в локальной секции 
begin
  writeln('Введите число:');
  readln(a); 
  writeln('a=', a);
end;

begin  // тело программы
  pishi();
end. 

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

var a: integer;  // глобальная секция

procedure pishi(); 
var a: integer; // "приземляем" имя в локальной секции 
begin
  a:=3; // обращение к а (на запись)
  writeln('a=', a); // обращение к а (на чтение)
end;

begin  // тело программы
  a:=2; // обращение к а (на запись)
  pishi();
  writeln();
  writeln('a=', a); //  тоже обращение к а на чтение)
end. 

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

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

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

Понятие сигнатуры подпрограммы

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

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

Вызов процедуры и её сигнатура

Сигнатура процедуры определяется:

  1. Именем процедуры
  2. Числом, типом входных переменных и порядком их следования

Например, рассмотрим реализацию процедуры $sum1()$, которая складывает два числа и выводит сумму на экран:

procedure sum1(x, y: integer); // начало объявления процедуры
begin
  write(x + y);
end; // конец тела процедуры

Иллюстрацией к сигнатуре этой процедуры служит её заголовок:

procedure sum1(x, y: integer); 

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

procedure sum1(x, y: integer); // начало объявления процедуры
begin
  write(x + y);
end; // конец тела процедуры

begin // начало тела программы

  sum1(5, 7);  // вызов процедуры

end.

Передача параметров в подпрограмму - процедуру. Получение значений на "вход"

Мы можем передавать параметры "на выход" процедуре и обращаться к ним в ее теле:

procedure pishi(a: integer);
begin
  writeln('a=', a);
end;

begin  // тело программы
  pishi(5);
end. 

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

var b: integer; // "глобальные" переменные

procedure pishi(a: integer);
begin
  writeln('a=', a);
end;

begin  // тело программы
  b := 7;
  pishi(b); // вызываем со значением b
  writeln('Конец!');
end.

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

var b: integer; // "глобальные" переменные
procedure pishiSummu(a: integer; comment: string);
var h: integer;
begin
  h := 5;
  writeln(comment, ':');
  writeln('Summa=', h+a);
end;

begin  // тело программы
  b := 7;
  pishiSummu(b, 'Сумма входящего значения и локального');
  writeln('Конец!');
end. 


ПРИМЕЧАНИЕ:
добавить примеры вызова из цикла.

Передача параметров в подпрограмму по ссылке и по значению

Параметры в подпрограмму (в том числе в процедуру) могут быть переданы:

  • по значению - в этом случае, значение копируется для подпрограммы и не изменяется в области видимости, в которой подпрограмме была вызвана с данным параметром
  • по ссылке - в этом случае значение не копируется и изменяется в области видимости, в которой процедура была вызвана с данным параметром, в случае параметр меняется в подпрограмме

Задачи для самостоятельного решения

  1. Напишите процедуру, которая просит пользователя ввести три числа, и потом выводит их в обратном порядке

    Примечание: если не получается решить или для лучшего понимания см. этот разбор решения.

  2. Напишите процедуру, которая получает на вход два целых числа и выводит на экран то, которое больше.

    Примечание: если не получается решить или для лучшего понимания см. этот разбор решения

  3. У вас есть код:

    var b: integer;
    
    procedure pishi();
    var a: integer;
    begin
      a:=4;
      writeln(a);
      b:=2;
      writeln(b);
    end;
    
    begin  // тело программы
      b:=3;
      pishi();
      writeln(b);
    end. 
    

    -- эта программа в конечном итоге присваивает и выводит на экран 3 значения.

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

    • вывод программы останется тем же
    • процедура, как и раньше работает с двумя переменными a и b, но обе должны быть локальными
    • процедура не должна принимать аргументы, как и раньше
    • а тело программы как и раньше с 1 глобальной переменной
    • Постарайтесь максимально сохранить использующиеся имена переменных.
  4. Напишите процедуру, которая получает на вход три целых числа и выводит на экран их сумму.

    Примечание: если не получается решить или для лучшего понимания см. этот разбор решения

  5. У вас есть код:

    
    var b: integer;
    
    procedure p2();
    var a: integer;
    begin
      a:=4;
      writeln('a=', a);
      writeln('b=', b);
    end;
    
    begin
      writeln('Введите значение b:');
      readln(b);
      p2();
    end. 
    

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

    Примечание: если не получается решить или для лучшего понимания см. этот разбор решения

  6. Пользователь по запросу программы вводит в цикле целые положительные числа, до тех пор пока не введет число большее 30. В ответ на каждое введенное пользователем число выводите все числа от 1 до этого введенного числа $m$.
    При этом:
    • за выводит чисел от 1 до $m должна отвечать процедура
    • получение значения должно проходить в теле основной программы
  7. Составьте программу, которая выводит на экран прямоугольный флаг $N \times M$ вида (используйте циклы repeat):
    - - - - - -
    - - - - - -
    - - - - - -
    - - - - - -
    

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

  8. Напишите процедуру, которая выводит на экран максимальное из трех переданных целых чисел.