Компонентный Паскаль/Введение в процедуры

Процедуры править

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

Оформление процедуры править

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

PROCEDURE Start*;
VAR

BEGIN
	Log.String('Привет, мир!');
	Log.Ln
END Start;

Это та самая процедура, которая уже встречалась в примере "Hello, World!".

Процедура объявляется ключевым словом PROCEDURE, после которого следует само имя процедуры, заканчивается объявление процедуры -- точкой с запятой. Как было описано раньше, по соглашению принятому в КП -- имена процедур начинаются с большой буквы.

Начинается любая процедура с ключевого слова BEGIN, а заканчивается ключевым словом END. Причём после END обязательно указывается имя процедуры. Так компилятор может контролировать содержимое процедуры. После имени процедуры следует точка с запятой, которая говорит компилятору о том, что это ещё не конец модуля (только END с точкой может быть указателем того, что модуль закончился).

Весь полезный код начинается после ключевого слова BEGIN. Ключевое слово VAR также может быть использовано за объявлением процедуры, в этой секции описываются переменные, которые могут быть использованы только в текущей процедуре, но не могут быть использованы во всём модуле. В отличии от модуля использовать ключевое слово IMPORT в процедурах запрещается. Ключевое слово CONST вполне употребимо, и компилятор не будет возражать.

Как было описано в одном из предыдущих разделов, после последней инструкции в теле процедуры -- перед END -- точку с запятой ставить не нужно.

Процедура без параметров править

Параметром называется какая-либо переменная, которая передаётся в процедуру. Процедура, которая определена без параметров (например, Start) называется "процедурой без параметров".

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

Процедура с параметрами править

Это основной вид процедур в КП. Для того, чтобы процедура приняла какие либо параметры, нужно их описать в объявлении процедуры:

PROCEDURE LogReal (r: REAL);
	VAR
BEGIN
	Log.Real(r);
	Log.Ln
END LogReal;

В приведённой процедуре "LogReal" используется формальный параметр "r". Тип этого параметра -- REAL. Далее, те параметры, которые процедура требует на входе, вместо "формальные параметры" (как официально принято) будем их называть "входные параметры", или совсем коротко "параметры"[2]. В объявлениях процедур можно описывать несколько параметров, все они записываются в скобках подряд и разделяются точкой с запятой.

Вызов процедуры с параметрами править

Вызов процедуры с параметрами можно выполнить передав процедуре фактические параметры (официальное название). Далее такие параметры будем называть "передаваемые аргументы" или совсем коротко "аргументы"[3] Полный код программы с вызовом процедуры представлен ниже:

Hello03.odc

MODULE TestHello03;
	(* это третья программа на языке
	Компонентный Паскаль. Она выполняет
	кое-какие математические операции и
	показывает как использовать процедуры *)

	IMPORT Kernel, Log, Math;
	CONST
		c = 2;

	VAR
		i: INTEGER;
		i1: REAL;

	PROCEDURE LogReal (r: REAL);
		VAR
	BEGIN
		Log.Real(r);
		Log.Ln
	END LogReal;


	PROCEDURE Start*;
		VAR
	BEGIN
		i := 3;
		i1 := c / i;
		Log.String('Привет, мир!'); Log.Ln;
		LogReal(i1)
	END Start;

BEGIN
END TestHello03.

Этот вариант программы делает всё тоже самое, что и предыдущая, но с использованием дополнительной процедуры "LogReal" описанной выше. Передаваемый аргумент "i1" должен иметь тот же тип, что и требуемый параметр в объявлении процедуры. В противном случае, КП посчитает это ошибкой. Теперь вместо двух инструкций Log.Real(i1); Log.Ln можно писать только одну, что приводит к сокращению набираемого кода. В этом одна из ключевых идей процедурного стиля. Но обратите внимание, что для вызова процедуры компьютеру требуется выполнить ряд дополнительный служебных действий, и при возврате из процедуры также выполняются некоторые действия, что приводит к дополнительным небольшим затратам времени. Поэтому, вызов процедуры должен быть оправдан. Само тело процедуры не должно состоять из одной инструкции, так как в этом нет никакого смысла.

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

Возврат значения из процедуры править

Процедура может возвращать какое-либо значение в случае необходимости. Для этого существует специальное ключевое слово RETURN. После него размещается переменная, значение которой должно быть возвращено. Вместо переменной можно написать какое-либо выражение (например, вызов другой процедуры)[4]. Давайте модифицируем процедуру "LogReal", чтобы она это могла делать:

PROCEDURE MathLogReal (p1, p2: INTEGER): REAL;
VAR
	r: REAL;
BEGIN
	r := p1 / p2;
	Log.Real(r);
	Log.Ln;
	RETURN r
END MathLogReal ;

Теперь процедура "LogReal" не только выводит значение переменной типа REAL, но и может рассчитать его. Поэтому, было изменено название процедуры на "MathLogReal". Обратите внимание, как определены два входных параметра типа INTEGER -- подряд через запятую. За списком параметров указан тип возвращаемого значения. Также впервые была использована секция процедуры VAR, в которой определена переменная "r" типа REAL, значение которой, рассчитывается внутри процедуры, и в конечном итоге возвращается. Если ключевое слово RETURN разместить перед вычислениями, то сами вычисления не будут выполнены, а в процедуру "Start" будет возвращено непонятно что ("мусор").

Полный код модифицированной программы приведён ниже:

Hello04.odc

MODULE TestHello04;
	(* это четвёртая программа на языке
	Компонентный Паскаль. Она выполняет
	кое-какие математические операции и
	показывает как использовать процедуры,
	которые возвращают значение *)

	IMPORT Log, Math;
	CONST
		c = 2;

	VAR
		i: INTEGER;
		i1: REAL;

	PROCEDURE MathLogReal (p1, p2: INTEGER): REAL;
		VAR
			r: REAL;
	BEGIN
		r := p1 / p2;
		Log.Real(r);
		Log.Ln;
		RETURN r
	END MathLogReal;


	PROCEDURE Start*;
		VAR
	BEGIN
		Log.String('Привет, мир!'); Log.Ln;
		i := 3;
		i1 := MathLogReal(c, i)
	END Start;

BEGIN
END TestHello04.

Как видно из текста модуля, процедура "Start" стала существенно короче. Кроме того, константа "с" успешно передаётся в процедуру. Это говорит о том, что компилятор КП автоматически присвоил ей тип INTEGER (мы тип этой константы не описали). Кроме того, ни модуль, ни процедура "Start" не подозревают о наличии переменной "r" внутри процедуры "MathLogReal". А значит ни одна процедура модуля (и сам модуль) не могут испортить её значение. Так достигается сокрытие информации.

Экспорт процедуры править

Самые внимательные читатели уже обратили внимание на то, что за именем процедуры "Start" стоит символ (*) ("Start*"). Этот символ как раз и указывает компилятору сделать экспорт процедуры. Попробуйте уберите звёздочку и скомпилируйте -- вы не сможете вызвать процедуру "Start". После экспорта, указанная процедура может быть вызвана извне модуля. Именно поэтому её можно запустить на выполнение через КОММАНДЕР. Тема экспорта очень важна, и в этом разделе получила лишь минимальное освещение, о других особенностях экспорта речь ещё пойдёт дальше.

Заключение править

В этой части учебника были рассмотрены в первом приближении очень важные положения КП процедурного стиля:

  • Как объявить процедуру
  • Как объявить входные параметры процедуры
  • Как вызвать процедуру
  • Как заставить процедуру возвращать результат
  • Как сделать процедуру доступной для исполнения извне модуля

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

Примечания править

  1. Основная программа на современных компьютерах не ждёт, пока подпрограмма выполняется (если это требует много времени). Если такая подпрограмма выполняется в потоке главной программы она называется сопрограмма или легковесный поток. Если в состав компьютера входит множество вычислительных узлов, то такая подпрограмма называется процесс
  2. Отказ от официального названия "формальные параметры в пользу "входные параметры, или просто "параметры" продиктован схожесть понятий "формальные параметры" и "фактические параметры", что не очень приятно на слух. Подробнее можно ознакомиться здесь
  3. Неофициальные названия "формальных параметров" и "фактических параметров" вводятся целенаправленно. Различать "параметры" и "аргументы" на слух значительно легче.
  4. Вообще, в других языках программирования (например, Visual Basic) процедуры возвращающие результат называются функции. И для определения функций предусмотрено отдельное ключевое слово. Но с точки зрения современного подхода это является неоправданным излишеством (функции из текста программы вызываются также, как и процедуры, отличить можно только по факту возврата результата).