CFA LogoCFA Logo Computer
Новости Статьи Магазин Драйвера Контакты
Новости
RSS канал новостей
В конце марта компания ASRock анонсировала фирменную линейку графических ускорителей Phantom Gaming. ...
Компания Huawei продолжает заниматься расширением фирменной линейки смартфонов Y Series. Очередное ...
Компания Antec в своем очередном пресс-релизе анонсировала поставки фирменной серии блоков питания ...
Компания Thermalright отчиталась о готовности нового высокопроизводительного процессорного кулера ...
Компания Biostar сообщает в официальном пресс-релизе о готовности флагманской материнской платы ...
Самое интересное
Программаторы 25 SPI FLASH Адаптеры Optibay HDD Caddy Драйвера nVidia GeForce Драйвера AMD Radeon HD Игры на DVD Сравнение видеокарт Сравнение процессоров

АРХИВ СТАТЕЙ ЖУРНАЛА «МОЙ КОМПЬЮТЕР» ЗА 2003 ГОД

Мысли о Паскале

Владислав ДЕМЬЯНИШИН nitromanit@mail.ru

Продолжение, начало см. в МК №46, 51-52, 4, 6-7, 10, 12-13, 16-18, 22, 24, 29, 34, 41, 46, 4, 6, 17 (165, 170-171, 175, 177-178, 181, 183-184, 187-189, 193, 195, 200, 205, 212, 217, 227, 229, 240).

Вычисление значения функции. Завершение подпрограмм

Продолжая тему подпрограмм, рассмотрим функции. Как я уже говорил, функции — помимо того, что могут иметь входные параметры, обязаны обеспечивать возврат выходного параметра (результата) в точку вызова, т.е. предназначены для выполнения некоторой задачи, но при этом по завершении должны возвращать результат в виде значения предопределенного типа. Таким образом, функции могут участвовать в любом выражении, где допустимо применение значения типа результата этой функции. Если говорить более простым языком, функция — это та же процедура (подпрограмма) с теми же входными параметрами или без них. Но функция имеет механизм передачи выходного параметра — результата, в выражение, которое содержит имя этой функции. Описание функции начинается со служебного слова function и может содержать перечень входных параметров, а завершаться должно идентификатором типа возвращаемого результата.

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

демонстрирует описание функции с указанием двух входных параметров A и B типа Real и указанием типа результата, тоже Real, возвращаемого описываемой функцией. В теле функции выполняется некоторое действие, в данном случае сложение, и результат сложения заносится в переменную результата функции командой Add:=A+B. В теле функции Add известна переменная Add с таким же именем, как и у самой функции, при этом допустимо лишь записывать в эту переменную — читать из такой переменной нельзя, так как указание имени функции в правой части оператора присваивания или в условном операторе будет интерпретироваться как попытка вызвать саму себя, что недопустимо (см. ниже о рекурсии). Пример

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

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

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

Процедуру Exit можно использовать не только в теле подпрограммы, но и в глобальном блоке самой программы, и тогда ее выполнение будет немедленно завершено.

Предварительные и внешние описания подпрограмм

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

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

Тогда при первой же попытке трансляции процедуры GoLeft компилятор не может проверить вызов процедуры GoRight на правильность указания входных параметров, так как эта процедура описывается ниже по тексту программы, и информация о ней еще неизвестна. Конечно, можно поменять местами эти подпрограммы, но тогда возникнет аналогичная ситуация с трансляцией вызова GoLeft в процедуре GoRight.

Помочь в сложившейся ситуации может механизм предварительных описаний подпрограмм. Т.е. для первого случая, когда описание процедуры GoLeft предшествует описанию процедуры GoRight, достаточно перед процедурой GoLeft поместить копию заголовка процедуры GoRight и вместо ее тела поставить служебное слово forward, которое указывает, что полное описание процедуры располагается в тексте далее. В данном случае можно разместить предварительные описания для обеих процедур на всякий пожарный случай :-).

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

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

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

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

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

Еще подпрограммы могут быть описаны через внешнее описание, т.е. когда подпрограмма или группа подпрограмм разработана вне системы Turbo Pascal, например, на языке ассемблера. Используя внешнее описание таких подпрограмм, можно подключить их к данной Pascal-программе. Обычно код подключаемых подпрограмм содержится в некотором .obj-файле и для обеспечения корректной работы составлен с соблюдением определенных межъязыковых соглашений о связях, принятых в системе Turbo Pascal.

Для подключения в Pascal-программе необходимо указать заголовок подключаемой подпрограммы, после которого указать служебное слово external вместо тела подпрограммы. Помимо этого в листинге программы необходимо установить директиву компилятора $L, аргументом которой следует указать имя .obj-файла, содержащего код подключаемой подпрограммы. Пример:

Другие специальные случаи

Turbo Pascal имеет еще несколько способов описания подпрограмм. Так, например, тело подпрограммы можно оформлять в виде последовательности машинных команд, используя специальную конструкцию со служебным словом inline, или в виде ассемблерных инструкций, с использованием служебного слова assembler. Подробнее о таких способах описания подпрограмм я расскажу в главе «Программирование на низком уровне».

В заголовке описания процедуры обработки прерывания перед ее телом может быть указано служебное слово interrupt. При описании и применении таких процедур необходимо следовать особым правилам, которые я постараюсь изложить подробнее в главе «Системно-зависимые расширения».

По способу вызова подпрограммы могут разделяться на ближние и дальние. По умолчанию компилятор считает все подпрограммы как ближние, и в соответствии с этим оформляет их вызовы как ближние. Такого же эффекта можно было бы добиться, если в заголовке подпрограммы перед ее блоком указать служебное слово near. Но при использовании процедурных переменных потребуется описать некоторые подпрограммы как дальние, и тогда пригодится служебное слово far. Таким образом, два этих служебных слова позволяют указать «тип вызова» подпрограммы и эквивалентны директивам компилятора {$F+} и {$F–}. Подробнее об этом смотрите далее.

Рекурсия и побочный эффект

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

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

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

Следует помнить, что в языке Pascal нет никаких ограничений на рекурсивные вызовы подпрограмм. При составлении рекурсивных алгоритмов следует учитывать тот факт, что каждый последующий рекурсивный вызов создает очередной блок локальных объектов в стеке. Таким образом, на определенной глубине вложенности может произойти переполнение стека.

Рекурсия подкупает своей наглядностью и простотой решения, но заставляет дорого платить за это.

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

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

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

Хочу заметить, что в выражениях, приведенных в данном примере, вызовы функций производятся справа налево. Это легко заметить, выполнив программу в пошаговом режиме, нажимая клавишу F7 (Trace into). Прежде чем выполнить выражение, необходимо собрать все его составляющие, т.е. компилятор производит поиск таких операндов, представленных в выражении вызовом функции и требующих некоторых предварительных действий для получения их значений. При компиляции поиск организуется справа налево. Затем, когда значения всех операндов получены, действия в выражении выполняются слева направо, с учетом скобок и приоритетных операций (умножение, деление).

К чему я все это? Ага, вспомнил. Я это к тому, что если в некоторой функции имеются операторы, изменяющие значения переменных, описанных в объемлющих блоках, то может возникнуть такая ситуация, при которой значение выражения, содержащего вызов такой функции, полностью зависит от очередности следования операндов. Такая ситуация может оказаться источником трудноуловимых ошибок, и в таком случае будет называться побочным эффектом. Не стоит в своих программах создавать такую зависимость функции от глобальных по отношению к ней переменных.

(Продолжение следует)

Рекомендуем ещё прочитать:






Данную страницу никто не комментировал. Вы можете стать первым.

Ваше имя:
Ваша почта:

RSS
Комментарий:
Введите символы или вычислите пример: *
captcha
Обновить





Хостинг на серверах в Украине, США и Германии. © sector.biz.ua 2006-2015 design by Vadim Popov