Подпрограммы в языке Ада: процедуры и функции. Родовые подпрограммы. Дженерик

Как и в большинстве других языков программирования, в Аде очень важную роль играют подпрограммы: процедуры и функции. Они позволяют значительно упростить и сократить код. Давайте разберёмся, когда нужно использовать подпрограммы и чем процедуры отличаются от функций.

Пусть, например, в программе нам нужно в нескольких местах выводить на экран содержимое массива. Писать каждый раз для этого for i in mas’First..mas’Last… слишком накладно. Хорошо бы написать этот код лишь однажды, а затем вызывать его в нужном месте какой-то командой! Вот именно для этого и нужны подпрограммы. Давайте напишем такую подпрограмму:

--Пусть у нас есть массив mas типа Vector, содержащий переменные типа Integer:
1 type Vector is array(1..10) of Integer;
2 mas : Vector;
…
--тогда можно написать функцию, которая будет выводить содержимое массива на экран:
3 function Show_Mas(vect : Vector) is
4 begin
5 	for i in vect’First..vect’Last loop
6 		Put(Item => vect(i), Width => 3);
7 	end loop;
8 end Show_Mas;
…
--Далее в программе для вывода любого массива типа Vector нам достаточно вызвать
--подпрограмму Show_Mas() и передать ей в качестве параметра (в скобках) массив (типа Vector):
9 Show_Mas(mas);

Рассмотрим этот код подробнее:

  • С объявлением типа и массива мы уже знакомы: строки 1 - 2
  • function – говорит о том, что мы создаём функцию
  • Show_Mas – это название функции;
  • vect: Vector – это параметр функции. В данном случае это массив типа Vector. Обращаю Ваше внимание, что имя параметра в строке 3 и имя массива, передаваемого в функцию (mas) в строке 9 разные. Внутри подпрограммы Вы можете использовать какие угодно имена переменных, даже точно такие же, как используются в основной программе (это будут разные переменные, не смотря на одинаковые имена). Дело в том, что все переменные, созданные внутри функции, - это локальные переменные и область их видимости ограничена этой функцией. При этом функция не видит никакие другие переменные, созданные вне неё. Локальные переменные существуют только во время выполнения функции. Как только функция закончит свою работу, все локальные переменные будут уничтожены.
  • Строки 4 – 8 мы встречали в каждой программе. Между is и begin при необходимости объявляются переменные. Между begin и end записываются необходимые действия.
  • Строка 9 – это вызов функции. При этом то, что стоит в скобках (mas) в данном случае называется аргументом. Т.е. при объявлении подпрограммы то, что указано в скобках – это параметры, а при вызове подпрограммы – аргументы. Зачем так сделали – для меня загадка.

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

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

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

function Num(a, b : Integer) return integer is
	res : Integer;
begin
	res := a * a + b * b;
return res;
end Num;

Тогда вызов функции будет выглядеть так (пусть у нас уже есть объявленная переменная n типа Integer):

n := Num(5, 3);

Пример программы с использованием этой функции:

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
 
procedure main is
	function Num(a, b : Integer) return integer is
		res : Integer;
	begin
		res := a * a + b * b;
		return res;
	end Num;
 
	n: Integer;
begin
	n := Num(5, 6);
	Put(Item => n, Width => 0);
	New_Line;
	--Можно вызвать эту функцию из другой подпрограммы:
	Put(Item => Num(4, 3), Width => 0);
	New_Line;
end main;

Давайте теперь рассмотрим процедуры. При объявлении процедуры вместо слова function нужно использовать слово procedure. Кроме этого для каждого параметра процедуры нужно указать т.н. "вид связи". Видов связи всего три: in, out, in out

  • in означает, что это входящая переменная. Можно считывать значение переменной, но изменять её нельзя.
  • out означает, что можно присвоить переменной какое-то значение, но прочитать её текущее значение не получится. При этом после выхода из процедуры переменная будет содержать новое значение.
  • in out означает, что с переменной можно делать всё, что угодно.

Рассмотрим пример процедуры, например, обмен местами двух переменных:

procedure Change(a, b : in out Integer) is
	tmp : Integer;
begin
	tmp := a;
	a := b;
	b := tmp;
end Change;

После завершения работы процедуры Change переменные a и b поменяются значениями.

Нужно также отметить, что если в процедуре для каких-то параметров используется вид связи out или in out, то аргументы, соответствующие этим параметрам, должны быть переменными. То есть, в случае с процедурой Change() мы можем написать такой код:

x : Integer := 5;
y : Integer := 3;
…
Change(x, y);

но не можем написать

Change(5, 3);

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

Внутри процедур и функций можно также объявлять процедуры и функции. Такая вложенность может быть сколь угодно большой.

Давайте напишем программку с этой процедурой:

with Ada.Text_IO; use Ada.Text_IO;
 
procedure main is
	procedure Change(a, b : in out Integer) is
		tmp : Integer;
	begin
		tmp := a;
		a := b;
		b := tmp;
	end Change;
 
	x, y : Integer;
begin
	x := 7;
	y := 10;
	Put("Переменные x и y до обмена: x :=" & Integer'Image(x) & " y :=" & Integer'Image(y));
	Change(x, y);
	Put("Переменные x и y после обмена: x :=" & Integer'Image(x) & " y :=" & Integer'Image(y));
end main;

Процедура Change() может обменивать местами только переменные целого типа, но что делать, если нам нужно обменять местами переменные вещественного типа? Писать аналогичную процедуру для переменных вещественного типа? Не обязательно. Оказывается в Аде можно использовать так называемые "родовые подпрограммы" (или, как их принято называть в других языках, обобщённые подпрограммы). Их параметры представляют собой переменные любого типа.

Перепишем объявление процедуры Change() и её реализацию следующим образом:

generic
	type T is private;
procedure Change(a, b : in out T) ;
 
procedure Change(a, b : in out T) is
	tmp : T;
begin
	tmp := a;
	b := a;
	a := tmp;
end Change;

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

generic
	type T is private;

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

Для вызова такой процедуры её предварительно нужно конкретизировать:

procedure Change_Integer is new Change(Integer);
procedure Change_Float is new Change(Float);

Именно для этого и нужен дженерик. В скобках передаётся тот тип, для которого будет создаваться процедура.

В процессе компиляции произойдёт создание обычных процедур/функций.

После такой конкретизации процедура Change_Integer будет обменивать переменные типа Integer, а процедура Change_Float – переменные типа Float.

Также при конкретизации можно для всех процедур задать одно и то же имя. Единственное условие – это имя не должно совпадать с именем родовой (обобщённой) подпрограммы:

procedure New_Change is new Change(Integer);
procedure New_Change is new Change(Float);

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

Напишем эту программу полностью:

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
 
procedure main is
	a, b : Integer;
	x, y : Float;
 
	generic
		type T is private;
	procedure Change(a, b : in out T);
 
	procedure Change(a, b : in out T) is
		tmp : T;
	begin
		tmp := a;
		a := b;
		b := tmp;
	end Change;
 
	procedure New_Change is new Change(Integer);
	procedure New_Change is new Change(Float);
 
begin
	a := 5;
	b := 3;
	x := 0.1;
	y := 5.5;
 
	Put("Переменные a и b до обмена: ");
	Put(Item => a, Width => 0);
	Put(" ");
	Put(Item => b, Width => 0);
	New_Line;
	New_Change(a, b); --Обмен
	Put("Переменные a и b после обмена: ");
	Put(Item => a, Width => 0);
	Put(" ");
	Put(Item => b, Width => 0);
	New_Line(2);
 
	Put("Переменные x и y до обмена: ");
	Put(Item => x, Fore => 1, Aft => 1, Exp => 0);
	Put(" ");
	Put(Item => y, Fore => 1, Aft => 1, Exp => 0);
	New_Line;
	New_Change(x, y); --Обмен
	Put("Переменные x и y после обмена: ");
	Put(Item => x, Fore => 1, Aft => 1, Exp => 0);
	Put(" ");
	Put(Item => y, Fore => 1, Aft => 1, Exp => 0);
	New_Line;
end main;

Перейти к решению тематических задач.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *