5.6. ПРОЦЕДУРЫ И ФУНКЦИИ
Процедуры и функции (я часто буду использовать их общее название - подпрограммы} представляют собой важный инструмент Object Pascal, позволяющий писать хорошо структурированные программы. В структурированных программах обычно легко прослеживается основной алгоритм, их проще понять любому читате лю, они удобнее в отладке и менее чувствительны к ошибкам программирования. Все эти свойства являются следствием важной особенности подпрограмм, каждая из которых представляет собой во многом самостоятельный фрагмент программы, связанный с основной программой лишь с помощью нескольких параметров. Самостоятельность подпрограмм позволяет локализовать в них все детали программной реализации того или иного алгоритмического действия, и поэтому изменение этих деталей, например, в процессе отладки обычно не приводит к изменениям основной программы.
Многие примеры в этой книге невелики по размерам (не более 30-40 строк), поэтому в таких программах можно обойтись и без подпрограмм. Иное дело - создание крупных программ в сотни, тысячи и десятки тысяч строк. Писать такие программы как нечто единое целое, без расчленения на относительно самостоятельные фрагменты, т. е. без структурирования, просто невозможно. Практически во всех языках программирования имеются средства структурирования. Языки, в которых предусмотрены такие механизмы, называются процедурно-ориентированными. К их числу принадлежит и Object Pascal.
Процедурой в Object Pascal называется особым образом оформленный фрагмент программы, имеющий собственное имя. Упоминание этого имени в тексте программы приводит к активизации процедуры и называется ее вызовом. Сразу после активизации процедуры начинают выполняться входящие в нее операторы, после выполнения последнего из них управление возвращается обратно в . основную программу и выполняются операторы, стоящие непосредственно за оператором вызова процедуры (рис. 5.8).
Для обмена информацией между основной программой и процедурой используется один или несколько параметров вызова. Как мы увидим дальше (см. гл.11), процедуры могут иметь и другой механизм обмена данными с вызывающей программой, так что параметры вызова могут и не использоваться. Если они есть, то они перечисляются в круглых скобках за именем процедуры и вместе с ним образуют оператор вызова процедуры.
Функция отличается от процедуры тем, что результат ее работы возвращается в виде значения этой функции, и, следовательно, вызов функции может использоваться наряду с другими операндами в выражениях.
Рис. 5.8. Взаимодействие вызывающей программы и процедуры
С примерами процедур и функций мы уже сталкивались - это
Стандартные процедуры Exit, ShowMessage, функции StrToInt, FioatToStr, Random, математические функции и др. Стандартными они называются потому, что созданы одновременно с системой Delphi и являются ее неотъемлемой частью. В Delphi имеется много стандартных процедур и функций. Наличие богатой библиотеки таких программных заготовок существенно облегчает разработку прикладных программ. Однако в большинстве случаев некоторые специфичные для данной прикладной программы действия не находят прямых аналогов в библиотеках Delphi, и тогда программисту приходится разрабатывать свои, нестандартные процедуры и функции.
Нестандартную подпрограмму необходимо описать, чтобы компилятор смог установить связь между оператором вызова и теми действиями, которые предусмотрены в подпрограмме. Описание подпрограммы помещается в разделе описаний (до начала исполняемых операторов).
Учебная программа UPSTRING
Не вдаваясь в дальнейшие подробности, попробуем составить собственную процедуру, чтобы пояснить сказанное. Пусть в этой процедуре преобразуется некоторая символьная строка таким образом, чтобы все строчные буквы заменялись соответствующими прописными. В Object Pascal имеется стандартная функция upcase (см. гл. 6), которая выполняет аналогичные действия над одиночным символом. Наша процедура (назовем ее upString) будет преобразовывать сразу все символы строки, причем сделаем ее пригодной не только для латинских букв, но и для букв русского алфавита[ В Delphi имеется стандартная функция AnsiUpperCase, реализующая те же действия. ].
Разработку программы проведем в два этапа. Сначала сконструируем основную (вызывающую) часть программы. Ее действия очень просты: она должна получить входную строку из компонента edinput, преобразовать ее с помощью процедуры upString в выходную строку и поместить результат в iboutput.Text. Эти действия нетрудно запрограммировать в обработчике bbRunciick:
procedure TfmExample.bbRunClick(Sender: TObject);
procedure UpString(stinp: String; var stOut: String);
{ Эта процедура преобразует все буквы строки stinp в прописные и помещает результат в выходную строку stOut }
begin
stOut := stinp;
end; // UpString var
SI, S2: String;
begin
SI := edinput.Text; // Получаем исходную строку
UpString(SI,S2); // Преобразуем ее
IbOutput.Caption := S2; // Выводим результат
edinput.Text := '' ;
edinput.SetFocus ;
end ;
В этой программе используется замещение процедуры UpString так называемой “заглушкой”, т. е. процедурой, в которой на самом деле не осуществляется нужных нам действий, а выходная строка просто копирует входную. (Однако эта программа синтаксически абсолютно правильна, и при желании ее можно запустить на счет.) Заглушка понадобилась нам по двум причинам. Во-первых, приведенная программа очень проста, в ней отсутствует детальная реализация процедуры, и это позволяет наглядно проиллюстрировать механизм ее описания. Во-вторых, на ее примере мы знакомимся с универсальным методом конструирования сложных программ, получившим название нисходящее программирование. В соответствии с этим методом создание программы начинается “сверху”, т. е. с разработки самого главного, генерального алгоритма. На верхнем уровне обычно еще неясны детали реализации той или иной части программы, поэтому эти части следует заменить временными заглушками. Желательно, чтобы временный вариант программы был синтаксически правильным, тогда можно его откомпилировать и убедиться в отсутствии в нем синтаксических ошибок. Такой прогон даст определенную уверенность перед разработкой и реализацией алгоритмов нижнего уровня, т. е. перед заменой заглушек реально работающими процедурами. Если реализуемый в заглушке алгоритм достаточно сложен, его вновь структурируют, выделяя главный алгоритм и применяя новые заглушки, и т. д. Процесс продолжается “вниз” до тех пор, пока не будет создан полностью работоспособный вариант программы.
Как видим, описание процедуры начинается зарезервированным словом procedure, за которым следуют имя процедуры и список формальных параметров. Список параметров заключается в круглые скобки и содержит перечень параметров с указанием их типа. Заметим, что перед параметром stout, с помощью которого в вызывающую программу возвращается результат преобразования, стоит зарезервированное слово var. Именно таким способом компилятору указываются те параметры, в которых процедура возвращает вызвавшей ее программе результат своей работы (подробнее см. гл. 10). Зарезервированное слово procedure, имя процедуры и список ее параметров образуют заголовок процедуры. За заголовком следует тело процедуры, содержащее новый раздел описаний (этот раздел пока еще пуст) и раздел исполняемых операторов (оператор stout:= stInp).
Приступим к разработке алгоритма процедуры. Для этого учтем, что в соответствии с принятой в Windows кодировкой символов (так называемой ANSI-кодировкой) символы кириллицы (русского алфавита) располагаются сплошным массивом от “А” до “я”, за исключением “ё” и “Ё”, которые предшествуют “А”. Кроме того, символ “я” имеет внутренний код 255, т. е. максимально возможное значение одного байта (для каждого символа строки выделяется по одному байту).
Вот возможный вариант процедуры:
Procedure UpString(stinp: String; var stOut: String);
{ Эта процедура преобразует все буквы строки stinp в прописные и помещает результат в выходную строку stOut } var
k: Integer;// Параметр цикла
begin
stOut := stinp;
// Цикл побуквенного преобразования
for k := 1 to Length(stOut) {Length - длина строки} do
begin
stOut[k] := UpCase(stOut[k]); // Преобразуем латиницу
if stOut[k] >= 'a' then // Строчная буква кириллицы?
st0ut[k] := // Да: преобразуем ее
Chr(ord('A') + ord(st0ut[k]) - ord('a'));
if stOut[k]='e' then
stOut[k] := 'Ё'; // Преобразуем ё в Ё
end;
end; // UpString
Комментарий к программе
В процедуре вначале с помощью оператора
stOut := stinp;
исходная строка копируется в выходную строку. Затем реализуется циклический перебор всех символов выходной строки для преобразования букв. Для доступа к отдельным символам строки используется замечательное свойство типа данных string, позволяющее рассматривать строку как набор (массив) символов. Первый символ этого набора имеет индекс 1, второй - 2 и т. д. Индекс указывается сразу за именем строки в квадратных скобках. Таким образом, stOut [i] - это г-ный символ строки stOut. Перебор символов реализован в виде счетного цикла от 1 до длины входной строки stOut (эта длина получается с помощью стандартной функции Length), в ходе которого каждый символ сначала преобразуется функцией UpCase (эта функция возвращает прописную латинскую букву для строчной буквы, передаваемой ей в параметре вызова; функция не работает с кириллицей). Затем осуществляется проверка принадлежности буквы к русской строчной (оператор if st0ut[k] >= 'a' then; символ “а” - русская буква) и осуществляется необходимая коррекция ее внутреннего кода. При коррекции используются отмеченные выше свойства непрерывности и монотонности массивов кодов, т. е. за “А” идет “Б”, за “Я” - “а”, за “а” - “б” и т. д. Стандартная функция ord возвращает внутренний код символа, который передается ей в качестве параметра обращения, а функция chr преобразует код в символ. Поскольку буква “ё” стоит особняком, ее преобразование осуществляется отдельным оператором.
Рассмотрим иной способ реализации той же программы - оформим алгоритм преобразования в виде функции:
procedure TfmExample.bbRunClick(Sender: TObject) ;
Function UpString(stinp: String): String;
{ Эта функция преобразует все буквы строки stinp в прописные и помещает результат в выходную строку Result }
var
k: Integer; // Параметр цикла begin
Result := stinp;
// Цикл побуквенного преобразования
for k := 1 to Length(Result) do
begin
Result[k] := UpCase(Result[k]);// Преобразуем латиницу
if Result[k] >= 'a' then // Строчная буква кириллицы?
Result[k] := // Да: преобразуем ее
Chr(ord('A') + ord(Result[k]) - ord('a'));
if Result[k]='e' then
Result[k] := 'Ё'; // Преобразуем ё в Ё
end;
end; // UpString
begin
{ В следующем операторе исходная строка edinput.Text преобразуется и помещается в выходную строку IbOutput.Caption: }
IbOutput.Caption := UpString(edinput.Text);
edinput.Text := '';
edinput.SetFocus ;
end;
Комментарий к программе
При описании функции необходимо за списком используемых для ее вызова параметров указать тип возвращаемого ею результата. Именно поэтому за закрывающей круглой скобкой в заголовке функции стоит двоеточие и тип string. В теле любой функции определена стандартная переменная Result, которая трактуется как результат, возвращаемый функцией.
С помощью оператора
Result := stInp;
мы сначала присвоили результату входную строку, а затем в цикле осуществили перебор символов и их коррекцию. Единственный оператор
IbOutput.Caption := UpString(edInput.Text);
заменил сразу три первых оператора в предыдущей реализации программы.