Процедуры и функции
ПРОЦЕДУРЫ И ФУНКЦИИ БЕЗ ПАРАМЕТРОВ
Процедуры и функции
В языке Turbo Pascal, как и во всех других современных языках программирования, имеется возможность разбиения программы на относительно независимые части, называемые подпрограммами. Разбивают программы на подпрограммы для того, чтобы:
1. Облегчить создание и отладку программы (маленькие независимые куски программы легче создавать и отлаживать, чем одну большую программу).
2. Сократить размер исходного текста и кода программы (в программе часто имеются повторяющиеся части, и если их вынести в подпрограммы, то размер исходного текста программы уменьшится).
В разных языках программирования существуют разные правила работы с подпрограммами. Нас с вами интересует только Turbo Pascal версии 7.0. В этом языке существует два вида подпрограмм. Первый - это процедуры, второй - функции. Процедура – это подпрограмма, в которой выполняются определенные действия. Функция – это подпрограмма, в которой также выполняются определенные действия, но, в отличие от процедуры, в функции вычисляется и возвращается значение функции. Определения процедур и функций в Turbo Pascal очень похожи. Определение функций немного сложнее, чем определение процедур, поэтому мы начнем изучать работу с подпрограммами в Turbo Pascal именно с процедур.
Простейшие процедуры
Возьмем такую задачу. Необходимо ввести массив целых чисел, удалить из него минимальный элемент, вывести получившийся массив.
Данная задача имеет следующий общий алгоритм решения:
1. Ввести массив.
2. Найти индекс минимального элемента.
3. Удалить элемент с найденным индексом.
4. Вывести массив.
Таким образом, наша программа должна будет состоять из четырех относительно независимых кусков: 1-й кусок – ввод массива, 2-й кусок – поиск минимального элемента, 3-й кусок – удаление элемента, 4-й кусок – вывод массива. Сначала реализуем эту программу без использования подпрограмм:
{ Задание: ввести массив целых чисел, удалить из него минимальный элемент, вывести получившийся массив.
Реализация программы без подпрограмм.
}
Program WithoutProcedureExample;
Const {Определение констант}
maxN = 20; {Максимально возможное количество элементов
в массиве}
Type {Определение типов}
IndexEl = 1 .. maxN; {Индексы массива лежат в интервале
от 1 до maxN}
arrInt = array[IndexEl] of integer; {Массив целых чисел,
содержащий до maxN элементов}
Var {Объявление переменных}
A : arrInt; {Массив}
N : integer; {Количество элементов в массиве}
I : IndexEl; {Переменная для сканирования массива}
IndMin : IndexEl; {Номер минимального элемента массива}
begin
{1 – ввод массива}
{1.1 - ввод количества элементов}
repeat
write('Введите n:');
readln(n);
until (n >= 1) and (n <= maxN); {Из цикла выйти возможно
только тогда, когда 1<=N<=maxN}
{1.2 - ввод элементов массива поодиночке}
for i := 1 to n do
begin
write('a[', i, ']=');
readln(a[i]);
end;
{2 – поиск индекса минимального элемента}
indMin := 1;
for i := 2 to n do
if A[i] < A[indMin] then IndMin := i;
{3 - удаление элемента массива с индексом indMin}
for i := indMin to n-1 do
A[i] := A[i+1];
dec(n);
{4 – вывод массива}
writeln;
for i := 1 to n do
write(A[i]:3);
writeln;
end. {Конец программы WithoutProcedureExample}
Теперь продемонстрируем на этой же задаче решение с использованием подпрограмм.
Напрашивается мысль, а не разбить ли нашу программу на подпрограммы таким образом: 1-я подпрограмма – ввод массива, 2-я подпрограмма – поиск минимального элемента, 3-я подпрограмма – удаление элемента, 4-я подпрограмма – вывод массива. Сделаем это, причем будем использовать в программе простейшие подпрограммы-процедуры – так называемые процедуры без параметров.
{ Задание: ввести массив целых чисел, удалить из него минимальный элемент, вывести получившийся массив.
Реализация с использованием процедур без параметров.
}
Program SimpleProcedureExample;
Const {Определение констант}
maxN = 20; {Максимально возможное количество элементов
в массиве}
Type {Определение типов}
IndexEl = 1 .. maxN; {Индексы массива лежат в интервале
от 1 до maxN}
arrInt = array[IndexEl] of integer; {Массив целых чисел,
содержащий до maxN элементов}
Var {Объявление переменных}
A : arrInt; {Массив}
N : integer; {Количество элементов в массиве}
I : IndexEl; {Переменная для сканирования массива}
IndMin : IndexEl; {Номер минимального элемента массива}
{Определение процедур}
{==========================================}
{ReadArray - процедура ввода массива с клавиатуры}
procedure ReadArray;
begin
{1 - ввод количества элементов}
repeat
write('Введите n:');
readln(n);
until (n >= 1) and (n <= maxN);
{2 - ввод элементов массива поодиночке}
for i := 1 to n do
begin
write('a[', i, ']=');
readln(a[i]);
end;
end; {Конец процедуры ReadArray}
{==========================================}
{FindIndMin – процедура поиска индекса минимального элемента}
procedure FindIndMin;
begin
{Ищем индекс min элемента}
indMin := 1;
for i := 2 to n do
if A[i] < A[indMin] then IndMin := i;
end; {Конец процедуры FindIndMin}
{==========================================}
{DeleteMin – процедура удаления минимального элемента}
procedure DeleteMin;
begin
{ Удаляем элемент массива с индексом indMin}
for i := indMin to n-1 do
A[i] := A[i+1];
dec(n);
end; {Конец процедуры DeleteMin}
{==========================================}
{PrintArray – процедура вывода массива на экран}
procedure PrintArray;
begin
{ Выводим массив}
writeln;
for i := 1 to n do
write(A[i]:3);
writeln;
end; {Конец процедуры PrintArray}
{Основная часть программы}
begin
{1 – ввод массива}
ReadArray;
{2 – поиск индекса минимального элемента}
FindIndMin;
{3 – удаление элемента}
DeleteMin;
{4 – вывод массива}
PrintArray;
end. {Конец программы SimpleProcedureExample}
Этот пример показывает, как сделать программу с использованием процедур без параметров. Для этого необходимо задачу разбить на относительно независимые куски. Затем каждый из этих кусков оформить в виде отдельной процедуры, а из основной части программы эти процедуры необходимо вызвать. Заметим, что все используемые в процедурах переменные и типы должны быть определены и объявлены выше определения процедур.
Правила определения процедуры без параметров:
1. Из текста программы вырезается нужный кусок и переносится выше основной части программы (выше первого Begin).
2. Этот кусок текста программы заключается в операторные скобки Begin … End;.
3. Перед этим куском вставляется строка:
procedure имя_процедуры; ,
где имя_процедуры – это идентификатор.
Замечания:
1. Правила структурного программирования требуют, чтобы каждая процедура (и функция) была пояснена достаточным (ясным и полным) комментарием, который ставится перед самой процедурой.
2. Имя процедуры (и функции) должно быть смысловым. Заметим, что по отношению к процедурам (и функциям) это требование необходимо выполнять всегда (имеется в виду, что по отношению к именам переменных это требование иногда можно игнорировать, а вот по отношению к именам процедур и функций это требование игнорировать нельзя никогда).
3. Программу разбивают на подпрограммы в первую очередь для того, чтобы облегчить ее разработку и отладку. Облегчение основывается на том, что во время разработки и отладки приходится иметь дело не с “монстром” длиной в сотни и тысячи строк, а с набором независимых подпрограмм небольшого объема. Подпрограмму можно считать небольшой, если ее исходный текст не превышает 40-60 строк. В этом случае подпрограмма целиком помещается на одной странице распечатки. Еще лучше, если подпрограмма целиком помещается на экране компьютера - в этом случае ее текст не должен превышать 20 строк.
4. В тексте программы подпрограммы должны быть отделены друг от друга визуально. Обычно для этой цели между двумя подпрограммами вставляют не меньше двух пустых строк. Иногда к пустым строкам добавляют строки комментариев, подобных таким {=========================================}.
Вызов процедур
Для того чтобы процедура выполнила определенные в ней действия, ее нужно вызвать. Для вызова процедуры используется оператор процедуры. Оператор процедуры представляет собой имя вызываемой процедуры или функции.
Выполнение оператора процедуры приводит к передаче управления на первую строку тела процедуры.
После этого выполняются операторы, составляющие тело процедуры. Когда будет выполнен последний оператор тела подпрограммы, управление возвращается в точку вызова подпрограммы - на оператор, следующий за оператором процедуры.
Пример:
{1 } program CallProcedure;
{2 } procedure HiWorld;
{3 } begin
{4 } Writeln('Привет,мир!');
{5 } Writeln('Hi World!');
{6 } end;
{7 }
{8 } begin
{9 } Writeln('Это начало');
{10 } HiWorld;
{11 } Writeln('Это конец');
{12 } end.
После запуска этой программы управление передается на строку
{8 } begin – начало программы.
Затем выполняется оператор процедуры
{9 } Writeln('Это начало'); - выводится строка ‘Это начало!’
Затем выполняется оператор процедуры
{10 } HiWorld; - управление передается в процедуру HiWorld на строку
{3 } begin – начало процедуры HiWorld.
Затем выполняется оператор процедуры
{4 } Writeln('Привет,мир!'); - выводится строка ‘Привет, мир!’
Затем выполняется оператор процедуры
{5 } Writeln('Hi World!'); - выводится строка ‘Hi, World!’
Следующая строка
{6 } end; - возвращает управление из процедуры HiWorld.
Управление передается в точку вызова процедуры, на следующую за оператором процедуры строку
{11 } Writeln('Это конец'); - выводится строка ‘Это конец!’
Следующая строка
{12 } end. – возвращает управление из программы – программа завершает свою работу.
Глобальные и локальные переменные
Процедуры используются для борьбы со сложностью программ. Разбиение программы на процедуры позволяет разрабатывать (и отлаживать) программу по частям. В единое целое программа связывается двумя механизмами: вызовом процедур и общими переменными. Через вызов процедур производится передача управления в процедуры, а через общие переменные производится передача данных. Общие переменные называют глобальными переменными. Полувековая практика программирования показала, что сложность программы в целом зависит не только от сложности используемых алгоритмов и количества строк в тексте программы, от количества процедур и функций, но и от количества глобальных переменных. Чем больше глобальных переменных, тем более связанными являются процедуры: в одних процедурах значения инициализируются, в других изменяются, в третьих - используются. Причем любая глобальная переменная может быть модифицирована или использована в любой процедуре. Поэтому со временем (в 60-70 гг.) пришли к идее сократить до минимума, в идеале до 0, количество глобальных переменных. Первой альтернативой глобальным переменным являются локальные переменные.
Локальные переменные объявляются, инициализируются и используются только в пределах одной процедуры или функции.
Рассмотрим ранее приведенный пример. В нем используется 4 глобальные переменные:
Var {Объявление глобальных переменных}
A : arrInt; {Массив}
N : integer; {Количество элементов в массиве}
I : IndexEl; {Переменная для сканирования массива}
IndMin : IndexEl; {Номер минимального элемента массива}
В программе определено 4 процедуры:
{1 – ввод массива}
ReadArray;
{2 – поиск индекса минимального элемента}
FindIndMin;
{3 – удаление элемента}
DeleteMin;
{4 – вывод массива}
PrintArray;
Рассмотрим использование всех глобальных переменных.
Переменная A – массив. Элементы массива вводятся в процедуре ReadArray, массив используется в процедурах FindIndMin и PrintArray и изменяется в процедуре DeleteMin. То есть можно сказать, что значение массива A передается из процедуры ReadArray в процедуру FindIndMin, затем в DeleteMin, где он изменяется, и измененное значение передается в PrintArray. Следовательно, переменную A сделать локальной нельзя – она должна быть глобальной.
Переменная N – количество элементов в массиве. Значение переменной вводится в процедуре ReadArray, используется в процедурах FindIndMin и PrintArray и изменяется в процедуре DeleteMin. Следовательно, переменную N, так же, как и массив A, сделать локальной нельзя – она должна быть глобальной.
Переменная I – переменная для сканирования массива. Эта переменная инициализируется, изменяется и используется в каждой из четырех процедур. Между процедурами значение этой переменной не передается. Следовательно, переменную N можно сделать локальной.
Переменная IndMin – индекс минимального элемента массива. Эта переменная получает значение в процедуре FindIndMin и используется в процедуре DeleteMin. Следовательно, ее нельзя сделать локальной – она должна быть глобальной.
Таким образом, из четырех переменных одну можно (и нужно) сделать локальной.
Объявление глобальных и локальных переменных
Глобальные переменные объявляются, вне какой либо процедуры, в разделе VAR. После объявления глобальная переменная доступна (то есть может быть использована) во всех процедурах, описанных ниже.
В нашем примере все четыре переменные объявлены как глобальные, они доступны во всех четырех процедурах.
Локальные переменные объявляются внутри процедуры (или функции), в разделе VAR.
Замечание: если в подпрограмме объявлена локальная переменная, имя которой совпадает с именем глобальной переменной, то внутри этой подпрограммы глобальная переменная будет недоступна. Говорят, что локальная переменная перекрывает одноименную глобальную переменную.
Для примера объявим переменную I как локальную переменную внутри процедуры PrintArray:
{PrintArray – процедура вывода массива на экран}
procedure PrintArray;
Var {Объявление локальных переменных}
I : IndexEl; {Счетчик цикла – локальная переменная}
begin
{ Выводим массив}
writeln;
for i := 1 to n do
write(A[i]:3);
writeln;
end; {Конец процедуры PrintArray}
Функции без параметров
Как говорилось ранее, в Turbo Pascal существует два вида подпрограмм – процедуры и функции. Функции отличаются от процедур тем, что в них вычисляется и возвращается значение. Процедуру можно превратить в функцию, если в ней вычисляется какое-то (желательно одно единственное) значение. При этом превращении сокращается количество глобальных переменных, через которые передаются данные из процедуры (и это очень хорошо).
Проанализируем ранее рассмотренную задачу. В ней имеется 4 процедуры:
{1 – ввод массива}
ReadArray;
{2 – поиск индекса минимального элемента}
FindIndMin;
{3 – удаление элемента}
DeleteMin;
{4 – вывод массива}
PrintArray;
В функцию можно (и нужно) переделать процедуру FindIndMin – единственным действием в этой процедуре является вычисление индекса минимального элемента массива.
После превращения процедуры в функцию эта подпрограмма примет такой вид:
{FindIndMin –функция поиска индекса минимального элемента}
{Возвращаемое значение – индекс минимального элемента}
Function FindIndMin:IndexEl;
Var
Ind : IndexEl; {Локальная переменная, в которой хранится
индекс минимального элемента массива
до возвращения из функции}
I : IndexEl; {Локальная переменная, счетчик цикла}
begin
{Ищем индекс минимального элемента}
Ind := 1;
for i := 2 to n do
if A[i] < A[ind] then Ind := i;
FindIndMin := Ind; {Функция возвращает
найденный индекс}
end; {Конец функции FindIndMin}
Правила определения функций без параметров (из процедуры без параметров):
1. В процедуре определяется локальная переменная, в которой хранится вычисляемый в этой процедуре параметр. Эта переменная в процедуре заменяет соответствующую ей глобальную переменную.
2. Слово Procedure заменяется словом Function.
3. После имени функции вставляется двоеточие и тип возвращаемого значения.
4. В конце тела функции необходимо присвоить имени функции значение локальной переменной, хранящей вычисленный параметр (смотри пункт 1).
Вызов функции
Для того чтобы функция выполнила определенные в ней действия, ее нужно вызвать. Для вызова функции используется оператор процедуры, в тексте программы пишется имя этой функции.
Если мы хотим сохранить значение, возвращаемое функцией, то при ее вызове мы должны присвоить возвращенное из функции значение какой-нибудь переменной.
Пример:
Выведем индекс минимального элемента массива. Для этого воспользуемся определенной выше функцией FindIndMin.
…
Var
IndexMinEl : IndexEl; {Локальная переменная, в которую
занесем возвращенное из функции FindIndMin значение}
…
begin
…
{Ищем индекс минимального элемента}
IndexMinEl := FindIndMin;
{Выводим на экран индекс минимального элемента массива}
Writeln(‘Минимальный элемент массива имеет индекс = ’,
IndexMinEl);
…
Предположим, что значение, возвращенное функцией, нужно использовать только один раз, для вывода его на экран. Тогда можно обойтись и без переменной IndexMinEl. Тогда текст примера сократится до такого куска:
…
begin
…
{Ищем индекс минимального элемента и выводим его на экран}
Writeln(‘Минимальный элемент массива имеет индекс = ’,
FindMinEl);
…
Выполнение функций от выполнения процедур отличается только тем, что после окончания работы функции ее результат заносится в соответствующую переменную.
Пример: Вычислим значение тангенса по введенному значению угла в градусах.
{1 } program CallFunction;
{2 } var
{3 } x : real; {Значение введенного угла в градусах}
{4 }
{5 } function tg:real;
{6 } var
{7 } xRad : Real; {Значение угла в радианах}
{8 } begin
{9 } xRad := x * Pi / 180.0; {Перевод градусов в радианы}
{10 } tg := sin(xRad) / cos(xRad);
{11 } end;
{12 }
{13 } var
{14 } y : real;
{15 } begin
{16 } Write('Введите x = ');
{17 } Read(x);
{18 } y := tg;
{19 } Writeln( 'tg(', x:0:2, ')=', y:0:4);
{20 } end.
Эта программа начинает свою работу со строки
{15 } begin
Затем выполняется
{16 } Write('Введите x = ');
и
{17 } Read(x);
Затем в строке
{18 } y := tg; - вызывается функция tg, в результате чего управление передается на строку
{8 } begin
Затем выполняется
{9 } xRad := x * Pi / 180.0; {Перевод градусов в радианы}
Затем в строке
{10 } tg := sin(xRad) / cos(xRad); - вычисляется значение тангенса, которое в строке
{11 } end; - возвращается в точку вызова функции tg – в строку
{18 } y:=tg; -где переменной Y присваивается возвращаемое из функции значение тангенса.
Следующей строкой выполняется
{19 } Writeln( 'tg(', x:0:2, ')=', y:0:4);
И, наконец, в строке
{20 } end. - управление из программы возвращается операционной системе – программа завершает свою работу.
Пример использования процедур и функций без параметров
Мы рассмотрели правила оформления и использования подпрограмм (т.е. процедур и функций) без параметров. Теперь используем выше полученные знания для решения следующей задачи.
Необходимо посчитать, сколько разных элементов хранится в массиве.
Пример: массив 1 4 3 2 4 3 1 3 5
разные элементы 1 4 3 2 5
всего их 5
Вот текст программы:
{Подсчитать, сколько разных элементов хранится в массиве.
Одномерный массив целых чисел. Количество элементов от 1 до 20.
В программе использовать процедуры и функции без параметров.
До минимума постараться сократить использование глобальных переменных.
}
Program SimpleFunctionExample;
Const
maxNumEl = 20; {Максимально возможное количество
элементов в массиве}
Type
IndexElement = 1 .. maxNumEl; {Индексы массива}
ArrayInteger = array[IndexElement] of integer; {Массив
целых чисел}
Var {*** Объявление глобальных переменных ***}
Arr : ArrayInteger; {Обрабатываемый массив}
NumEl : integer; {Количество элементов в обрабатываемом
массиве}
{*** Определение процедур и функций: ***}
{==========================================}
{ReadArray - процедура ввода массива с клавиатуры}
{Данные вводятся в массив Arr. В переменную NumEl заносится количество элементов в массиве Arr}
procedure ReadArray;
var
i : IndexElement; {*Счетчик цикла*}
begin
writeln;
{*Ввод количества элементов*}
repeat
write('Введите количество элементов в массиве:');
readln(NumEl);
until (NumEl >= 1) and (NumEl <= maxNumEl);
{*Ввод элементов массива*}
write('Введите элементы массива : ');
for i := 1 to NumEl do
read(Arr[i]);
end; {*Конец процедуры ReadArray*}
{==========================================}
{PrintArray - процедура вывода массива на экран}
{Данные берутся из массива Arr. В переменной NumEl хранится количество элементов в массиве Arr}
procedure PrintArray;
var
i : IndexElement; {*Счетчик цикла*}
begin
writeln;
write('Массив : ');
for i := 1 to NumEl do
write(Arr[i]:3);
writeln;
end; {*Конец процедур PrintArray*}
{==========================================}
{NumberOfDifferentElements - функция, вычисляющая количество различных элементов в массиве.}
{Данные берутся из массива Arr. В переменной NumEl хранится количество элементов в массиве Arr}
Function NumberOfDifferentElements:integer;
var
i, j : IndexElement; {*Счетчики циклов*}
num : integer; {Количество разных элементов}
isDifferent : boolean; {Элемент является отличным
от все предыдущих}
begin
{Для подсчета количества разных элементов массива используется
следующий алгоритм:
- количество различных элементов обнуляем;
- начиная с первого элемента, по очереди берем все элементы
и сравниваем их со всеми предшествующими. Если взятый
элемент равен хотя бы одному из предшествующих, то этот
элемент не является новым - мы такой уже учли;
- если же он ни одному из предшествующих не равен, то
увеличиваем количество РАЗЛИЧНЫХ элементов на 1.}
num := 0;
for i := 1 to NumEl do
begin
isDifferent := true; {i-ый элемент является отличным
от предыдущих}
for j := i - 1 downto 1 do
if Arr[i] = Arr[j] then
begin
isDifferent := false; {i-ый элемент совпадает}
break; {с одним из предыдущих}
end;
if isDifferent then num := num + 1;
end;
NumberOfDifferentElements := num; {*Возвращаем
вычисленное количество разных элементов*}
end; {*Конец функции NumberOfDifferentElements*}
{*Тело программы*}
begin
{*Вводим массив Arr*}
ReadArray;
{*Выводим массив Arr*}
PrintArray;
{*Вычисление и вывод количества различных элементов массива*}
Write('В введенном массиве различных элементов : ');
writeln(NumberOfDifferentElements);
end. {*Конец программы *}
В данном примере показан более-менее “правильный” стиль оформления исходного текста программы и его комментирования.
Комментарии, помеченные символами *** ***, в реальной программе не пишутся. Здесь они вставлены по причине того, что этот текст - учебный пример.
Комментарии, помеченные символами * * , в реальной программе могут писаться или не писаться, в зависимости от требований к качеству и количеству комментариев.
Комментарии, не помеченные никакими значками, писать нужно ОБЯЗАТЕЛЬНО, если этот исходный текст программы в дальнейшем (спустя какое-то время) кто-то будет читать.