#0C Процедуры

Вступление

Добрый день!
Сразу хочу извиниться за опоздание выпуска - очень много работы. Так же дошли печальные вести, что невозможно открыть архив с сайта. Теперь я выложу его в формате html. Ссылка по которой он будет обновляться - http://www.ibp7.narod.ru/arxiv.rar - запакован WinRar 3

Теория

Сегодня перед вами откроется страшная тайна.... всё это время мы вас беспощадно обманывали !!!!!!!!!

:)
На самом деле всё не так плохо, просто решили, что не стоит сначала загружать народ не понятными терминами. Сегодня всё встанет на свои законные места.

Помните функцию ClrScr - так вот тайна её рождения состоит в том, что это ПРОЦЕДУРА.

Процедуры и функции очень похожи друг на друга. Основное отличие состоит в том, что функция может нам что-то возвратить, а процедура нет. ClrScr - ничего нам не возвращает => процедура, Round - возвращает => функция.

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

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

procedure ИМЯ ( СПИСОК_ПАРАМЕТРОВ );
Список параметров может отсутствовать (например всё та же ClrScr - нет параметров). В остальном процедура очень похожа на программу. Кстате процедуры и функции вместе называют - под-программы!

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

procedure DisplayString;
var
   i : integer;
begin
     ClrScr;
     for i := 0 to 10 do
       writeLn ('Write something ....' )
end;
Как видите у процедуры есть своя секция переменных, секция меток, констант и секция выполняемых инструкций (секция кода). Код процедуры начинается соответственно со слова begin и кончается словом end. Обратите внимание, что после end мы поставили точку с запятой! Как вы помните точка - это признак конца программы, а программа не заканчивается процедурой!

Можно сказать, что до этого наша программа и была одной процедурой. Только без имени. Например в том же С - у этой фунции есть имя main.

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

ПРОГРАММА
Program ...;

begin
........
end.
Если мы добавим две под программы, то она изменится в такую:

ПРОГРАММА
Под-программа А

Под-программа B

Program ...;

procedure A;
begin
.......
end;

procedure B;
begin
........
end;

begin
........
end.

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

ПРОГРАММА
Под-программа А
под-программа А1

под-программа А2

Под-программа B

Program ...;

procedure A;

	procedure A1;
	begin
	........
	end;

	procedure A2;
	begin
	..........
	end;

begin
.......
end;

procedure B;
begin
........
end;

begin
........
end.

А если мы теперь к под-программе А2 добавим ещё пару под-программ, то структура превратится в следующее:

ПРОГРАММА
Под-программа А
под-программа А1

под-программа А2

под-программа А21

под-программа А22

Под-программа B

Program ...;

procedure A;

	procedure A1;
	begin
	........
	end;

	procedure A2;

		procedure A21;
		begin
		.......
		end;

		procedure A22;
		begin
		...........
		end;

	begin
	..........
	end;

begin
.......
end;

procedure B;
begin
........
end;

begin
........
end.

А если мы ещё к под-программе А21 добавим три под-программы, то ....... впрочем хватит. Нужно знать меру :)

Существует так называемая видимость под-программ. Мы назваем под-программу видимой если можем её вызвать в противном случае она для нас невидима.

Например для основной программы видимы только 2 под-программы А и В. То что находится внутри их является "чёрным ящиком". Мы не знаем что там внутри А. Однако для под-программ А1 и А2 под-программа А является как бы родительской поэтому мы можем вызывать А1 и А2. Точно так же мы можем вызывать А21 и А22 только из А2.

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

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

procecure PrintString (x, y : integer; st : String);
begin
    gotoxy (x, y);
    write (st)
end;
...........
XS := 10;
YS := 10;
PrintfString (XS, YS, 'Hello world!');
Здесь в процедуру передаётся 3 параметра - X, Y, ST. Первые 2 типа integer и 3-ий типа string.

Однако нам может понадобится изменять параметры. При способе описанном выше переменные XS и YS не изменятся при выходе из процедуры, даже если добавим внутрь её что-то типа этого XS := 3; Этот метод передачи парамеров называется передача значения. В процедуру передаётся только значение XS и YS.

Существует и другой способ передачи параметров - адрессный. Тогда передаётся адресс переменной! Адресс - это точное указание места переменной в памяти (как например ваш почтовый адресс точно указывает где вы живёте). Так вот мы передаём этот самый адресс. И процедура может менять значение по этому адрессу. Т.е. мы поменяем значение переменой! Для этой цели нужно указать перед переменной слово var. Например:

procedure SQUARE (var x : integer);
begin
     x := x * x
end;

var
   i : integer;
begin
     i := 2;
     SQUARE (i);
     writeLn (i)
end.
Выведет на экран 4. Т.к. мы меняем значение i (а вернее значение параметра) внутри процедуры. Если бы мы объявили процедуру так:
procedure SQUARE ( x : integer);
то тогда на экране появилась бы 2, т.к. мы не меняем значение параметра. Давайте мы рассмотрим ещё один пример: в позапрошлом выпуске мы говорили о том как поменять значения двух переменных (помните A = 5, B = 6 а надо A = 6, B = 5). Напишем такую проостую процедуру:
procedure swap (var a, b : integer);
var
   c : integer;
begin
     c := a;
     a := b;
     b := c
end;
Теперь её можно вызывать например так:
X := 3;
Y := 5;
swap (X, Y);
после этого X=5, а Y=3.

Вернёмся к области видимости. Теперь поговорим об области видимости переменных. Например что выведет такая программа:

var
   i : integer;

procedure P;
var
 i : integer;
begin
     writeLn (i)
end;

begin
     i := 2;
     P
end.
Дествительно затруднительная ситуация. У нас есть 2 переменные с одним и тем же именем. Давайте условно назовём первую i (первую сверху) - i1, а вторую i2. Так вот i1 - является глобальной переменной по отношению к под-программе Р. Поэтому если представить, что мы не объявили i2, то на экран выведется 2. Переменная i2 называется локальной в под-программе Р. Считается, что локальные переменные "забивают" глобальные. Поэтому на экран выведется значение i2, а так как она не инициализированна, то это может быть что угодно (у меня 3631).

Поэтому нужно быть аккуратнее с именами. Иначе может случится казус :) при этом вы не всегда сможете быстро сообразить почему программа работает не правильно. Предпочтительнее давать глобальным и локальным переменным разные имена. Кстати всё выше сказанное относится и к константам и к меткам.

В Турбо Паскале допускается произвольная последовательность описания переменных, констант, меток, под-программ. Например мы можем несколько раз объявитеть секцию var (const, label) внутри под-программы. Например:

var
   V1 : integer;

procedure S;
var
   V2 : integer;
begin
 .......
end;

var
   V3 : integer;
 ..............
Здесь из под-программы S доступны переменные V1 и V2, а V3 нет (т.к. описание V3 следует после процедуры S).

Программа

-- Ну,-- сказала Сова,-- обычная процедура в таких случаях нижеследующая...
-- Что значит Бычья Цедура?-- сказал Пух, который никогда не видел PASCAL и вообще предпочитал HTML и поэтому не понимал зарезервированного слова PROCEDURE.-- Ты не забывай, что у меня в голове опилки и длинные слова меня только огорчают.
[c] 2001 " Винни Пух и *.* "

Вы никогда не задумывались над следующими фактами:

  1. Почему что бы выключить Windows нужно сначала нажать кнопку Start (в произвольном русском переводе Пуск) ?
  2. Почему праздник ужасов называется Hello Win?
  3. Почему слово "аббревиатура" такое длинное ?
  4. Как работает "вечный" календарь ?
На перые три вопроса я не в состоянии ответить, но вот на последний.... Сегодня мы напишем нечто подобное.

Итак задача. Дано: дата (день, месяц, год). Найти: день недели. Решение:

Вряд ли вас заинтересует откуда взялась формула. Я по крайней мере за время отведённое на подготовку этого выпуска отыскать её не смог (хотя не очень то и старался :). Так вот она эта чудестная формула:

день недели = остаток от деления Х на 7
где:
X = abs (trunc (2.6 * m - 0.2) + d + y / 4 + y + с / 4 - 2 * c )
m - номер месяца
d - число
с - номер столетия
y - номер года в столетии
В записи формулы я использовал 2 функии: abs (Х) (absolute - абсолютный) возвращает модуль числа Х, trunc (X) (truncate - усекать) "усекает" значение Х до целой части. Например abs (-3) = 3, abs (3) = 3. trunc(1.4) = 1, trunc (1.7) = 1

Однако не зря я выделил слово вечный в кавычки: эта формула действительна только для григорианского календаря нового стиля (от 1582 до 4903).

Так же следует учесть несколько другой порядок следования месяцов - март имеет номер 1, апрель номер 2 ... декабрь - 10, январь - 11, февраль - 12. При этом февраль и январь следует отнести к предыдущему году. Т.е. для n-ого февраля 2002 месяц должен быть равен 12, а год 2001. Результат вычисления получается от 0 до 6, при этом 0 - соответствует воскресенью, 1 - понедельнику .... 6 - субботе.

Как читать программы содержащие функции и процедуры ?
Всегда начинайте с самого верхнего уровня. Т.е. с начала программы (кода, со слова begin), опустив при этом описания процедур и функций. Когда создастся общее впечатление о работе программы можете заглянуть внутрь функций или процедур. Почему "можете" ? Просто не всегда это необходимо - назначение некоторых функциии и процедур понятно по названию или описанию (комментариям). Так что и ниже написанную программу нужно читать не с процедур, а чуть ниже :)
Program Day_of_week;

uses CRT;

procedure GetDay (var d, m, y : integer);
var
   correct : byte;
begin
     correct := 1;
     repeat
          writeLn ('Введите дату.');
          write ('День - ');
          readLn  (d);
          write ('Месяц - ');
          readLn (m);
          write ('Год - ');
          readLn (y);

         { Проверим правильность года.  Если год не "правильный", то попросим ввести дату ещё раз }
	  if y <= 4903 then
            if y >= 1582 then
               correct := 0;
     until correct = 0
end;

procedure CountDay (d, m, y : integer);
var
   week : array [0 .. 6] of string[11];
   c, w : integer;
begin
     week [0] := 'воскресенье';
     week [1] := 'понедельник';
     week [2] := 'вторник';
     week [3] := 'среда';
     week [4] := 'четверг';
     week [5] := 'пятница';
     week [6] := 'суббота';

     {  Месяц январь или февраль = > нужно преобразовать }
     if m < 3 then
     begin
          m := m + 10;
          y := y - 1
     end
     else
          m := m - 2;

     c := y div 100;
     y := y mod 100;
     w := abs (trunc (2.6 * m -0.2) + d + y div 4 + y + c div 4 - 2 * c) mod 7;
     writeLn ('соответствует день недели -  ', week [w])
end;

var
   d, y, m : integer;

begin
     ClrScr;
     GetDay (d, m, y);
     CountDay (d, m, y)
end.
Программа работает по принципу "спросил-ответил". Пользователь вводит дату, программа выдаёт день недели. Думаю вам понятно как построить на основе этих данных "вечный" календарь.

Комментировать особенно нечего, кроме того, что я пренебрёг собственными рекомендациями и назвал параметры в процедурах и глобальные переменные одинаково :)

Обратите внимание на следующее - в процедуре получения даты (GetDay) мы передаём адресса переменных => при выходе из процедуры их значения изменятся. А в процедуре вычисления дня недели (CountDay) мы передаём просто значения переменных => после выхода из процедуры они не изменятся.

Неплохо бы исселдовать программу в отладчике. До этого мы для пошагового исполнения использовали клавишу F8. Однако что бы заходить в функции и процедуры нам подребуется нажимать клавишу F7. Используя F8 (пошагово без захода в функции и процедуры) мы увидим исполнение 3-х строчек. А что б увидеть исполнение всех процедур и функций программы нам нужно использовать пошаговый режим с заходом в функции F7.

Ещё можно обратить внимание на объявление массива строк. String это такой же тип как и integer поэтому мы вполне можем объявить массив типа String. Так же можно добавить проверку на корректность введённого дня и месяца.

Голосование

Опрос по поводу объяснения материала в рассылке всё ещё доступен - http://narod.yandex.ru/survey/?id=79839.

Послесловие

Вот и всё. Тема довольно сложная, так что если появились вопросы - пишите.


[Назад] [Содержание] [Дальше]
Hosted by uCoz