Рейтинг:  3 / 5

Звезда активнаЗвезда активнаЗвезда активнаЗвезда не активнаЗвезда не активна
 

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

Иногда необходимо выполнять один и тот же код программы несколько раз. Копировать этот код в необходимые места - неочень удобно (увеличивает код программы, уменьшает понимание нашего кода), а делать запуск данного кода через оператор goto невсегда помогает (если в коде нашей программы нужно будет большое кол-во раз вызвать данный код, то нужно будет очень сильно постораться, чтобы обойтись только оператором goto. Поэтому придумали разделять весь код программы на подпрограммы, каждая из которых выполняет строго поставленную задачу. такое разбиение улучшает восприятие вашей программы, уменьшает ее код, увеличивает шансы найти ошибку в алгоритме, если она есть. Вобщем, только одни преимеущества. Только у этого разделения есть один небольшой минус - увеличивает время работы программы. Чем больше вызовов подпрограмм, тем больше будет время работы вашей программы. Но по сравнению с полученными плусами, этот минус не такой уж значительный.

Подпрограммы - это программа, которая имеет, почти, полную независимость от основной программы. Зависимость заключается только в одном случае, но о нем чуть дальше расскажу.

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

код основной программы

коды подпрограмм

свободная память

запись активации подпрограммы 2

запись активации подпрограммы 1

помять под переменные

При запуске программы в память компьютера (оперативную память) записывается весь код основной нашей программы, за ней следует коды каждой из попрограмм по отдельности, после этотих всех кодов подпрограмм идет память под переменные и память под подпрограммы (так называемая стеком). Стек заполняется снизу - вверх. Для нас это кажется немного неудобным, но а какая для нас разница, ведь не мы работаем с этой памятью, а компьютер. А ему удобно именно так. Размер стека не может быть безграничным, поэтому при запуске он получает строгий размер, который компилятор сам высчитывает, исходя из кода программы. Максимальный размер стека в PascalABC.NET составляет 3 Мб. Кажется, что это немного, но этого вполне хватает на большинство программ.

Как я сказал, стек заполняется снизу вверх. В саиый низ компьютер кладет все переменные, которые нам потребуются. После того, как компьютер загрузит Вашу программу в память и выделит под переменные память, компьютер начинает выполнять код основной программы. Как только в коде основной программы он встречает вызов подпрограммы, он в стеке выделяет под нее память. Память выделяется под все переменные, необходимые подпрограмме, под временные переменные и память для математических вычислений, места, куда нужно вернуться после выполнения подпрограммы, кол-во памяти, требуемое подпрограмме. У Вас, наверняка, возник вопрос: а последнее зачем нужно? Вы об этом узнаете чуть позже в этой статье.

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

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

Это все называется накладными расходами на вызов подпрограммы. Из-за этого ход выполнения программы будет замедлен. Но это замедление будет не существенным, если кол-во вызовов подпрограмм будет разумным.

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

Кроме того код программы, в которых используются подпрограммы, очень понятен. За таким алгоритмом нетрудно проследить ход его действий и понять, что он делает. (по крайне мере это бывает очень часто нужно, особенно, если Вы какой-либо проект забросили, а затем через какое-то время снова к нему возвращаетесь).

Давайте же начнем изучать подпрограммы в данном языке программирования.

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

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

Оформление подпрограмм очень похоже на оформление обычной программы. У них также есть имя (только в подпрограммах оно пишется обязательно), раздел описания переменных (здесь также можно описать свои подпрограммы), раздел операторов.

Описание процедуры начинается с ключевого слова «procedure», после которого идет имя процедуры, которое будет использоваться в дальнейшем для вызова данной подпрограммы. После этого в скобках указываются параметры (формальные), которые нужны будут подпрограмме для работы. Для каждой из переменных указывается ее тип данных, способ передачи данных (чуть дальше поговорим об этом), параметры по-умолчанию (если программист ничего не укажет при вызове, то в подпрограмме будут использоваться параметры по-умолчанию). После описания загаловка подпрограммы идет секция описания локальных переменных. В этой секции, как и в секции описания переменных в обычной программе, можно описывать те переменные, которые нам понадобятся для выполнения поставленной задачи. Эти переменные будут доступны только внутри данной подпрограммы и нигде больше. (почему я так напечатал, Вы поймете чуть позже). После раздела описания следует раздел операторов, который начинается с ключевого слова «begin» и заканчивается словом «end;». (!)(обратите внимание, что после слова «end» пишется точка с запятой. Это слово с точкой употребляется только в конце программы, а не подпрограмм). Так мы получили следующую конструкцию:

procedure Procedure_Name(Parametr1:Parametr1_type; Parametr2,Parametr3:Perametr23_type; ... ParametrN:Parametr_type);

var my_perem1:type1;

var my_perem2:type2;

...

var my_peremN:typeN;

begin

  Action1;

  Action2;

  ....

  ActionN;

end;

Обратите внимание: при описании формальных параметров процедуры/функции, каждая переменная описывается через точку с запятой (но, если несколько переменных должны быть одного типа, то эти переменные можно писать через запятую как в этой конструкции «Parametr2,Parametr3»). После имени переменной при описании должно стоять двоеточие, после которого должен стоять тип данных данного параметра «Parametr1:Parametr1_type;», после его должен стоять символ точки с запятой. Если этот параметр идет самым последним, то точка с запятой после него не ставится «ParametrN:Parametr_type)». Все, что идет в скобках - это все параметры, которые принимает данная подпрограмма.

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

procedure print_sum(a:integer; b:integer);

var s:integer;

begin

  s:=a+b;

  write(s);

end;

var a,b:integer;

begin

  a:=readinteger();

  b:=readinteger();

  print_sum(a,b);

end.

Вообще я не советую писать такие «мелкие» подпрограммы, поскольку при большом кол-ве вызовов «мелкие» подпрограмма затормаживают выполнение программы. Вот мое исследовние: при 10000000000 вызовов потеря составила почти 500 милисекунд. Естественно, при более сложных подпрограммах и большем кол-ве вызовов потери увеличатся.

var d1:=system.DateTime.Now;

var d2:=system.DateTime.Now;

begin

  for var i:integer:=1 to 10000000000 do

  begin

  var s:=i+5;

  end;

  d2:=system.DateTime.Now;

  writeln(d2-d1);

  //00:00:04.9688333

end.

procedure SS(a,b:integer);

begin

  var s:=a+b;

end;

var d1:=system.DateTime.Now;

var d2:=system.DateTime.Now;

begin

  for var i:integer:=1 to 10000000000 do

  ss(i,5);

  d2:=system.DateTime.Now;

  writeln(d2-d1);

  //00:00:05.4688436

end.

Теперь разберем тот пример с суммой.

При запуске программы, компилятор увидит ключевое слово «procedure» в секции инициализации и он отделит эту подпрограмму от основной программы. Для нее отдельно выделит память в оперативной памяти и запомнит ее имя (в моем случае это «print_sum»). После этого компьютер пропустит код этой подпрограммы (но не компилятор. Компилятор будет все проверять на ошибки) и будет дальше выполнять код секции инициализации. А в этой секции программа выделяет память под две переменные «a» и «b». (да, конструкция вида «var a,b:integer;» в Паскале допустима), которые в коде программы считываются с клавиатуры с помощью комманды «readinteger();» (даже не комманды, а функции). Далее идет вызов нашей процедуры «print_sum». Компьютер вначале ищет код этой подпрограммы, и, если он нашел этот код (а не найти он в большинстве случаев не может (поскольку все программы собирают компиляторы, то они заранее проверяют код на наличие ошибок и поэтому в таких программах такой ошибки быть не может. Ошибка может быть только тогда, когда в машинный код (в уже собранную программу) залезет пользователь и удалит часть кода программы)), то он прерывает ход выполнения нашей программы, кладет запись активации на стек, в общем (чтобы не забивать Вам голову этим сложным процессом, я обьясню более простым языком) выполнение кода основной программы прекращается, запоминает значение переданных подпрограмме параметров (фактические параметры), выделяется под формальные параметры (те параметры, которые стоят при описании подпрограммы) память и в них записывается те значения, которые им были переданы и после этого компьютер начинает выполнять код подпрограммы. Если же и в ней есть вызов другой подпрограммы, то происходит аналогичный процесс.

Так при вызове нашей подпрограммы, программа приостанавливает выполнение своей главной программы. Выделяет память под переменные «а» и «b» для подпрограммы «print_sum». В значение переменной «а» подпрограммы копируется значение переменной «а» из главной программы, в значение переменной «b» из подпрограммы копируется значение переменной «b» из главной программы. (!)(Внимание! Такой способ передачи параметров, при котором в подпрограмму передаются значения, называется передачей параметров по значению. При такой передаче действует следующее правило: при вызове процедуры/функции фактические параметры (которые стоят при вызове) подставляются на место формальных (которые стоят при описании подпрограммы), после чего выполняется тело подпрограммы). После этого выполняется секция инициализации, а именно выделяется под переменную «s» память и в ходе выполнения кода подпрограммы этой переменной присваивается сумма параметров «a» и «b». После этого на экран выводится значение переменной «s» и завершается выполнение данной подпрограммы. А именно программа возвращается к выполнению кода основной программы. А после вызова происходит конец программы.

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

procedure print_sum(a:integer; b:integer);

begin

  write(a+b);

end;

begin

  print_sum(readinteger(),readinteger());

end.

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

Теперь перейдем к функциям. Почти все, что мы изучили для процедур действует и для функций, только есть небольшие изменения.

Так функция задается аналагичным способом, только вместо ключевого слова «procedure» пишется ключевое слово «function», а после всех параметров пишется еще и тип возвращаемого значения через двоеточие, кроме этого внутри функции будет переменная «result», которая хранит возвращаемое значение.

function function_Name(Parametr1:Parametr1_type; Parametr2,Parametr3:Perametr23_type; ... ParametrN:Parametr_type):function_type;

var my_perem1:type1;

var my_perem2:type2;

...

var my_peremN:typeN;

begin

  Action1;

  Action2;

  ....

  ActionN;

  result:=function_result;

end;

Вообще нутри функии можно сколь угодно раз менять значение переменной «result». Функция вернет только самое последнее значение, которое поличится перед завершением выполнения функции. (!)(Внимание! То, что можно использовать внутри функции переменную «result» сколь угодно раз, есть не во всех языках программирования. В некоторых языках программирования типа C# это запрещено, поэтому в таких языках все, что находится после присваивание возвращаемого значения, не выполняется и часто компиляторы таких языков на это ругаются. Учтите это в дальнейшем). Вообще, возвращаемое значение функции должно быть ровно того типа, который указан при описании.

Рассмотрим же пример с функцией нахождения суммы двух переменных

function Sum(a,b:integer):integer;

begin

  result:=a+b;

end;

begin

  writeln(sum(readinteger(),readinteger()));

end.

Во время запуска программы, компьютер сразу увидит ключевое слово «function» и запомнит имя этой функции для дальнейшего использования. Далее компьютер перейдет к выполнению кода основной программы. В основной программе идет вывод на экран возвращаемого значения вызова функции «sum», которая была описана в секции инициализации. Поскольку значение этой функции не определено, компьютер переходит к параметрам этой функции. В параметрах этой функции опять видим функции. Поскольку их значения не определены, то компьютер начинает выполнять их тело. Считав оба значения с клавиатуры, компьютер передаст их функции «sum», которая под них выделит память и туда запишет переданные ей значения. После этого программа перейдет к выполнению тела подпрограммы. В секции инициализации этой подпрограммы ничего нет, поэтому компьютер перейдет к выполнению самого кода. В коде подпрограммы переменной «result» (возвращаемому значению) присваивается сумма двух переменных «a» и «b». После этого компьютер возвращается к основной программе. После этих действий значение функии «sum» будет известно и, наконец, будет выполнена процедура вывода на экран.

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

function Sum(a,b:integer):integer;

begin

  result:=a+b;

end;

begin

  var Name1:integer:=readinteger();

  var Name2:integer:=readinteger();

  var Name3:integer:=Sum(Name1,Name2);

  writeln(Name3);

end.

Где «Name1», «Name2», «Name3» - это имена временных переменных. Приблизительно в такой код превратит компилятор предыдущую программу.

А вообще есть еще один способ передачи возвращаемого значения. Я писал обе программы с возвращаемым значением, передаваемым переменной «result». Эта переменная была введена недавно и поэтому написание подпрограмм с такой переменной называется написанием новым стилем. Но раньше в качестве возвращаемого значения функции использовалась переменная, имеющая имя самой функции.

function Sum(a,b:integer):integer;

begin

  sum:=a+b;

end;

Такое написание было какое-то время ранее в Паскале. На данный момент в PascalABC.NET допускается оба стиля. Поэтому выбирайте сами: каким стилем Вам удобнее писать программы.

Иногда нужно пояснять нашу подпрограмму. Ну по крайне мере нужно пояснить, что она делает, что должны содержать параметры (какие значения). Поэтому в Паскале есть пояснительные комментарии к подпрограмме. Для их написания нужно перед описанием подпрограммы поставить «///», а после этих символов писать свой пояснительный комментарий к программе. Этой комментарий будет отображаться при вызове подпрограммы.

///Функция принимает два значения и возвращает их сумму.

function Sum(a,b:integer):integer;

begin

  sum:=a+b;

end;

begin

  writeln(sum(readinteger(),readinteger()));

end.

Этот комментарий называется сокращенным, потому что он не дает понятий о параметрах. В Паскале есть и полный комментарий, который поясняет еще отдельно каждую переменную при вызове подпрограммы. Такой комментарий очень похож на написание страниц html. Теперь о том, как он пишется. Он также пишется перед подпрограммой. Каждая его строка пишется с тремя символами «/». На первой строке пишется тег (набор символов) «< summary >», после этого можно писать многострочное описание вашей подпрограммы. После того, как Вы описали вашу подпрограмму должен идти тег «< /summary >», говорящий о конце многострочного комментария. После этого идет описание каждого из принимаемых параметров. Для этого пишется следующая конструкция:

< param name="< Parametr_Name >" >< Parametr_Text >< /param >

где - это имя параметра подпрограммы, к которму мы пишем комментарий

- это комментарий, который будет отображаться при написании вызова подпрограммы программистом, при вводе параметра .

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

/// < returns > Text < /returns >

где Text - это комментарий о возвращаемом значении

/// < summary >

/// Функция принимает два значения и возвращает их сумму.

/// < /summary >

/// < param name="a" >первая переменная для суммирования< /param >

/// < param name="b" >Вторая переменная суммирования< /param >

/// < returns >Сумма двух чисел< / returns >

function Sum(a,b:integer):integer;

begin

  sum:=a+b;

end;

begin

  writeln(sum(readreal(),readreal()));

end.

Учтите, что эти комментарии только делают Вам или другим программистам, которым Вы отправите Ваш код, более понятным. Пользователям Вашей программы эти комментарии ничего не дают. И, если Вы будете копировать этот код, то уберите пробелы в коде.

И еще одно замечание: при вызове подпрограммы кол-во и типы фактических параметров должно соответствовать кол-ву и типам формальных параметров. Небольшим исключением являются параме