Delphi-Help

Главная Азы Урок 21. Подпрограммы (часть 3)

Урок 21. Подпрограммы (часть 3)

Оцените материал
(15 голосов)


Подпрограммы (часть 3)

Введение

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

Способы передачи параметров в подпрограммы

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

Но такой способ не всегда удобен. Более того, в случае, если подпрограмма должна вычислить сразу несколько значений, этот способ не принесёт успеха. Именно поэтому существует другой способ передачи параметров - передача по ссылке (англ. by reference). Смысл этого способа в том, что мы передаём не конкретное значение, а ячейку памяти, т.е. переменную. В чём же отличие? А отличие в том, что подпрограмма уже может работать с этой переменной, т.е. не только получать её значение, но и это значение менять! Получается, что мы берём переменную из основной программы, "отдаём" её подпрограмме, та, в свою очередь, производит какие-то манипуляции с ней, и в результате наша переменная получает новое значение и мы можем далее её использовать. Несложно догадаться, что такой подход позволит подпрограмме отдавать сразу несколько значений (причём не обязательно одного типа).

Для передачи по значению никаких изменений в подпрограмму вносить не нужно - это способ передачи по умолчанию. А вот для передачи по ссылке нужно всего лишь в описании подпрограммы добавить слово var перед нужными параметрами.

procedure Proc(a: Integer); //Передача по значению
...
procedure Proc(var a: Integer); //Передача по ссылке

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

Примерами функций с передачей по ссылке являются широкоизвестные Inc() и Dec(). Вспомните - ведь не нужно писать a:=Inc(a); - достаточно просто Inc(a).

Пример. Любое целое положительное число можно представить в виде n = x2 + y, где x, y - тоже целые положительные числа. Написать подпрограмму, которая для заданного числа находит наибольшее возможное значение x и соответствующее значение y. Например, для 29: x = 5, y = 4.

Определим, что от нас требуется. А требуется от нас подпрограмма, которая принимает на вход число и в результате выдаёт другие два числа. Функцией это сделать не получится, ведь функция может вернуть только одно значение. Выход - процедура с передачей по ссылке. У нас будет 3 параметра: исходное число и две "ячейки" для переменных, куда будут записаны полученные значения. Заголовок процедуры будет таким:

procedure GetXY(n: Word; var x, y: Word);

Тип данных Word выбран специально для того, чтобы отрицательные значения не могли быть переданы процедуре.

Теперь реализация самой функции. Нам нужно найти максимальное значение x. Самый простой способ сделать это - извлечь квадратный корень из числа и округлить его в меньшую сторону. Далее полученный x нужно возвести в квадрат и найти разницу между этим квадратом и исходным числом - вот и число y. Единственное, что нужно учесть - y должен быть положительным числом, а значит не может быть нулём. А нулём он будет в том случае, если квадратный корень из n извлечётся нацело. В этом случае просто уменьшаем x на единицу и пересчитываем y. Окончательный код процедуры:

procedure GetXY(n: Word; var x, y: Word);
begin
  x:=Trunc(Sqrt(n));
  y:=n-Sqr(x);
  if y = 0 then
  begin
    Dec(x);
    y:=n-Sqr(x);
  end;
end;

Проверка работы подпрограммы при нажатии на кнопку:

procedure TForm1.Button1Click(Sender: TObject);
var N,A,B: Word;
begin
  N:=StrToInt(Edit1.Text);
  GetXY(N,A,B);
  Caption:=IntToStr(N)+' = '+IntToStr(a)+'^2 + '+IntToStr(b);
end;

Как видите, передача по ссылке является достаточно удобным механизмом взаимодействия программы и её подпрограмм. Если бы мы захотели решить эту задачу с использованием функций, пришлось бы писать две отдельные функции - одна вычисляла бы x, а другая - y. Что уж говорить о том, что одни и те же вычисления делались бы 2 раза...

На один параметр больше...

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

GetXY(N,A,B,);

Это просто отступление от темы - забавная вещь.

Параметры-константы

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

Например, мы хотим для нашей предыдущей задачи находить x и y не для числа n, а для n+1. Для этого добавим в тело подпрограммы соответствующую строку:

procedure GetXY(n: Word; var x, y: Word);
begin
  Inc(n);
  ...

Но теперь, если мы сделаем параметр n константой, компилятор выдаст ошибку на только что добавленной строке:

procedure GetXY(const n: Word; var x, y: Word);
begin
  Inc(n);
  ...

[DCC Error] E2064 Left side cannot be assigned to

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

Необязательные параметры

Часто подпрограммы имеют большое количество параметров, что подчёркивает их универсальность и обширность выполняемых действий. Однако во многих случаях одни и те же параметры имеют одинаковое значение, т.е. среди вариантов значений одного параметра есть наиболее популярный. В этом случае для упрощения вызова подпрограмм часть параметров делают необязательными. Необязательный параметр может быть передан подпрограмме, а может и быть опущен. Для того случая, когда он опущен, имеется его исходное значение, которое подпрограмма и будет использовать.

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

0021_01

Необязательные параметры указываются в квадратных скобках.

Удобно, согласитесь? Сейчас мы научимся делать необязательные параметры и в своих подпрограммах.

Для начала стоит отметить, что необязательные параметры должны находиться среди всех параметров в конце. Понятно почему - ведь если необязательный параметр будет в середине, откуда компилятор узнает, присутствует здесь этот параметр или нет? Если бы были разные типы данных - понять это было бы возможно, но если одинаковые (например, все параметры - числа), то ошибка неизбежна и поэтому самым разумным выходом является расположение необязательных параметров в конце.
Теперь о том, как сделать параметры необязательными. А здесь всё крайне просто - если указать значение по умолчанию, то параметр и станет необязательным. Указывается значение самым обычным образом - после имени аргумента и типа данных ставится знак равенства "=" и далее значение.

Пример. Создадим функцию, вычисляющую десятичный логарифм заданного числа или логарифм по указанному основанию.

function Logarithm(Num: Real; Base: Real = 10): Real;
begin
  Result:=Ln(Num)/Ln(Base)
end;

В описанной функции параметр, отвечающий за основание логарифма, необязательный. Его исходное значение - 10. Если при вызове функции этот параметр будет опущен, основание будет 10, а если будет задан - будет вычислен логарифм по указанному основанию. Откуда появилась в формула в теле функции не спрашивайте, а лучше загляните в учебник математики :-)

Посмотрите количество параметров функции CreateProcess():

0021_02

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

Привязка подпрограмм к форме

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

Рассмотрим конкретный пример. Пусть у нас на форме есть 3 кнопки - Button1, Button2 и Button3. Нам требуется из разных мест программы то включать эти кнопки, то выключать (через свойство Enabled). Чтобы не копировать один и тот же код, лучше написать подпрограмму, которая будет делать всё необходимое. Итак, вот наша процедура:

procedure EnableButtons(Enabled: Boolean);
begin
  Button1.Enabled:=Enabled;
  Button2.Enabled:=Enabled;
  Button3.Enabled:=Enabled
end;

Всё бы ничего, но при компиляции получаем ошибку: [DCC Error] Unit1.pas(29): E2003 Undeclared identifier: 'Button1'. Процедура "не видит" кнопку Button1. А точнее, не видит она и остальные две - это первое из сообщений об ошибке.

Итак, чтобы "привязать" процедуру к форме, мы должны сделать следующее:

1) В заголовке процедуры перед названием должны добавить имя класса формы. Имя класса формы в Delphi автоматически формируется из буквы "T" и имени самой формы. Т.е. для формы Form1 имя класса TForm1. Имя вы можете увидеть в модуле в разделе var - там описана сама форма:

0021_03

Между именем класса формы и именем подпрограммы должна стоять точка. В результате заголовок процедуры станет таким:

procedure TForm1.EnableButtons(Enabled: Boolean);

2) Теперь нужно добавить заголовок метода в описание самой формы. Для этого нужно скопировать заголовок нашей процедуры в секцию private или public в раздел type, где описана наша форма. Единственное, что ещё нужно сделать - удалить название класса формы в скопированной строке. В результате получим:

0021_04

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

На этом "привязка" процедуры к форме завершена. Теперь программа скомпилируется без ошибок. Осталось только выяснить, как запускать созданную подпрограмму. А здесь никаких сложностей нет - подпрограмма доступна как имя_формы.имя_подпрограммы. В нашем случае это Form1.EnableButtons. Однако, такая запись необходима только если вы вызываете подпрограмму откуда-то извне, например из другой формы. Если вызов происходит откуда-то из самой Form1, можно обращаться к процедуре просто по имени. Например, поместим на форму TCheckBox и в обработчике его события OnClick напишем:

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  EnableButtons(CheckBox1.Checked)
end;

Теперь, если флажок установлен, кнопки активны, а если снят - неактивны.

А если, например, есть Form2 и CheckBox2 на ней, то обработчик будет такой:

procedure TForm2.CheckBox2Click(Sender: TObject);
begin
  Form1.EnableButtons(CheckBox2.Checked)
end;

Заключение

В сегодняшнем уроке мы рассмотрели способы передачи параметров в подпрограммы, необязательные параметры, а также научились осуществлять взаимодействие между подпрограммами и компонентами формы. Теперь вам известно всё, чтобы правильно создавать свои подпрограммы. Домашнего задания в этот раз нет - экспериментируйте самостоятельно. В следующий раз мы перейдём к новой теме. Успехов!

Авторизация



Счетчики