В.Г. Абрамов, Н.П. Трифонов, Г.Н. Трифонова - Введение в язык Паскаль (1107618), страница 43
Текст из файла (страница 43)
Результат выполнения подобного рода действий иназьвается побочным эффектом функции. Итак, в паскале допускаютсяфункции с побочным эффектом.Термин "побочный эффект" взят из фармакологии. Как известно, каждое лекарство направлено на устранение какой-то определенной болезни —в этом состоит его главное назначение.
Однако почти каждое лекарствоимеет и побочный эффект: оно либо попутно способствует лечению и других болезней, либо, напротив, обостряет какие-то другие болезни, еслиони имеются. При этом значимость побочного эффекта может не уступатьглавному эффекту. Как и у лекарств, побочные эффекты функций могутбыть как весьма удобными и полезными, так и вызывать различные неприятности. Поэтому функции с Побочным эффектом надо использоватьочень осторожно, а без достаточного опыта работы следует избегать вводитьв употребление такие функции. Функции с побочным эффектом особеннонежелательны в программах, предназначенных для широкого распространения и требующих последующего их сопровождения.Приведем простейший пример функции с побочным эффектом. Допустим, что в программе дано описание переменныхvar А,В: real;В разделе процедур и функций этой программы дадим следующее описание процедуры-функции, реализующее функциональную зависимость/(*) = 2х2 +0.5:•function F(x: real): real;begin F:=2.0*x*x+0.5;A:=0 endТеперь рассмотрим следующий фрагмент основной программы:А:=3.14; B:=F(3.0); writeln(A,B>Каков будет результат выполнения этого фрагмента? Можно ли утверждать,что по оператору вывода будут отпечатаны числа 3.14 и 18.5? Нет, такоеутверждение было бы ошибочным.
В самом деле, рассмотрим поподробнеепроцесс выполнения выписанной последовательности операторов.По первому оператору присваивания переменной А будет присвоенозначение 3.14. При вычислении арифметического выражения в правой частивторого оператора присваивания производится обращение к процедуре12. В.Г. Абрамов177функции F. При этом вводится в употребление локальная переменная х,соответствующая формальному параметру, которой присваивается значение 3.0.
При выполнении первого оператора тела процедуры определяетсязначение функции, равное 18.5. А поскольку переменная А для процедурыявляется глобальной, то по второму оператору тела процедуры этой переменной (которой до обращения к процедуре было присвоено значение 3.14) присваивается новое значение, равное нулю — это и есть побочныйэффект нашей функции. Таким образом, в результате выполнения оператора присваивания В := F(3.0) переменной В будет присвоено значениефункции, равное 18.5, и кроме того - в результате побочного эффектафункции — переменной А будет присвоено нулевое значение.
Так что насамом деле на печать будут выведены числа 0 и 18.5.Побочный эффект функции может проявляться и в том, что функцияможет изменить значение фактического параметра, вызываемого но имени—за счет того, что в этом случае процедура получает непосредственныйдоступ к этому фактическому параметру и может не только использовать,но и изменять его значение.Приведенный пример показывает, что из анализа текста только основнойпрограммы невозможно установить факт изменения значения переменной Апри выполнении оператора В := F(3.0) - это действие "спрятано" в описании функции F. Ясно, что это обстоятельство существенно затрудняет понимание программы и тем самым снижает ее надежность. В этом главнымобразом и заключается опасность и неприятность использования функцийс побочным эффектом.
В некоторых же случаях побочный эффект можетоказаться весьма полезным для повышения эффективности программы —конечно, при достаточно осторожном его использовании. В качествеиллюстрации использования побочного эффекта функций рассмотрим следующий пример.П р и м е р 9.1. Задаются две последовательности (строки) литер s iи s2 одинаковой длины. Если первые литеры у этих строк одинаковы, топреобразовать строки по-следующим правилам: в строке si оставить только первое вхождение литеры '—', а все последующие вхождения этойлитеры заменить на литеру '+'; в строке s2 оставить только первое вхождение литеры 'х', заменив все последующие ее вхождения литерой 'у' (еслиуказанная литера в какой-либо строке не содержится, то строка изменениюне подлежит).Общую схему программы представим в виде:{ввод и распечатка исходных строк з1 и s2>if s1 С 13=s2C13 thenbegin{преобразование строки si}' {преобразование отроки s2>end{вывод преобразованных строк}Из задачи преобразования строки удобно выделить две подзадачи: анализстроки на предмет вхождения в нее заданной литеры и фактическое преобразование строки.
В этом случае преобразование строки можно делать по178схеме:if{заданная литера А входит в строку}then{последующие вхождения литеры А заменить на литеру В>Поскольку этот алгоритм должен применяться к каждой строке, точастичные алгоритмы решения выделенных подзадач удобно оформить ввиде процедур. Первую из них естественно описать как логическуюфункцию (дадим ей имя ПОИСК). Заметим, что для определения ее значения придется последовательно просматривать элементы строки до первоговхождения литеры А (или до конца строки при отсутствии в ней этой литеры) . Вторую процедуру естественно описать как процедуру-оператор(дадим ей имя ПРЕОБР).
Для преобразования строки надо знать местопервого вхождения в нее литеры А. Но поскольку определение этого местауже делалось в процедуре-функции ПОИСК, то естественно использоватьи этот результат ее выполнения, чтобы не повторять эту работу в процедуре ПРЕОБР. В связи с этим в функции ПОИСК целесообразно предусмотреть побочный эффект: наряду с определением значения функции (trueили false) эта процедура в случае успешного поиска заданной литеры должна глобальной переменной к присвоить индекс соответствующего элемента строки.
В случае безуспешного поиска значение к будем считать неопределенным. Это значение глобальной переменной к и будет использоватьсяв процедуре ПРЕОБР (поскольку эта процедура будет выполняться тольков случае успешного поиска заданной литеры, то при ее выполнении значение к обязательно будет определено).Для ввода и вывода строк введем в употребление соответствующиепроцедуры (которые мы уже описывали в предыдущих примерах).
Паскальпрограмму составим применительно к строкам из 40 литер:{Пример 9.1. Людкевич И.В.ЛьвовГУ23.2.97г.Если первые литеры у задаваемых строк si и з2 одинаковы,то каждую строку преобразовать по правилу: все вхождениялитеры с, кроме первого, заменить на литеру d;для si принять с='-', d='+'; для s2 принять с='х', d='y'}{использование Функций с побочным эффектом}program СТРОКИ<input,output);const N=40;type инд=1..N;стр= array Синд] of char;var k: инд; sl,s2: стр;{}procedure ВВ0ДСТР(var s: стр);var i: инд;begin for i:=l to N do read(sCi]) end;procedure BblBCTP(var s: стр);12*179var l: инд;begin -for i: = l to N do write(sCiЭ); writeln endsf}{Описание логической процедуры-функции, определяющейвхождение заданного символа s строку.Внимание! Эта функция имеет побочный эффект:глобальной переменной к присваивается значение,равное индексу первого вхождениязаданной литеры в строку!•function ПОИСК(уаг s: стр; с: char): boolean;label 5;var i: инд;begin ПОИСК:=false;for i:=1 to N doif sti]=c thenbegin ПОИСК:=true;k:-i;goto 5 end;5:end;{конец ПОИСК}•Сprocedure ПРЕОБР(var s: ctp; c,a: char>;var i: инд;begin for i:=k+lto N doif sCi!l=c then sCi]:=d{end;{конецПРЕОБР}beginВВОДСТР(si);writeln <'СТРОКА si: ');ВЫВСТР(si);ВВ0ДСТР(з2);writeln( 'СТРОКА s2: ' > ;BblBCTP(s2) ;if si С 1]=s2C1] thenbeginif nOHCK(sl,'-') then ПРЕОБР(s1,'-','+');if ПОИСК<s2, x'> then ПРЕОБР(s2,'x','у'>end;wr i tel n ( ' HOB si:'); BblBCTP(sl);wr i tel n ( ' HOB s2: ' ) ; BblBCTP(s2)end.1809.4.
Рекурсивные функцииС понятием рекурсии мы уже встречались при рассмотрении металингвистических формул, используемых для синтаксического определения понятийязыка — это случай, когда какое-либо понятие определяется с использованием этого же самого понятия.Рекурсивным может быть и определение функции. Классическим примером здесь является факториал - ф у н к ц и я / ( л ) = п\ Эту функцию можноопределить различными способами, в том числе и рекурсивно:( 1при и = О,и! = {I п * (п — 1)! при п > 0.Как видно, здесь и! определяется через (и - 1)!, т.е.
через эту же самуюфункцию. Поэтому такое определение и называется рекурсивным.Язык паскаль разрешает такое рекурсивное описание функций. Особенность рекурсивного описания функции состоит в том, что в теле такой процедуры-функции содержится обращение к этой же описываемой функции.Например, на паскале можно дать такое рекурсивное описание функции и!:type натур=0..maxint;•function -fact (п: натур): натур;begini-f n=0 then fact: =1else -fact: =n*-f act (n—1)end;Отметим, что здесь имя fact функции встречается как в левой, так и вправой частях оператора присваивания.
Однако вхождение имени в левуючасть — это еще не рекурсия: по этому имени не производится обращенияк функции, а лишь указывается, что значение выражения в правой частиэтого оператора должно быть принято в качестве значения определяемойфункции. А вот наличие вызова определяемой функции в правой части какого-либо оператора присваивания (именно вызова функции с заданиемнеобходимого количества фактических параметров), в данном случаевызова функции fact (п - 1), свидетельствует об обращении к той же самойфункции в процессе вычисления ее значения, т.е. о рекурсивности определения.В данном случае имеет место явная рекурсия: обращение f a c t ( n — 1) кописываемой функции в явном виде содержится в теле описания этойфункци/ Рекурсия может быть и неявной: вызов описываемой функцииможет содержаться в теле другой процедуры, к которой производится обращение из данной процедуры.Процесс вычисления рекурсивной функции рассмотрим на примере вызова функции fact (3) в основной программе.
При входе в процедуру-функцию по этому вызову, как обычно, вводится в употребление локальнаяпеременная п, соответствующая формальному параметру-значению, и ейприсваивается значение- фактического параметра, после чего выполняетсяраздел операторов процедуры. Поскольку здесь п = 3, то на самом деле181выполняется операторfact:=3*fact(2)В процессе вычисления арифметического выражения в правой части этого оператора опять производится обращение к функции fact для вычисления fact (2). При обращении к процедуре-функции fact опять вводится вупотребление — теперь уже новая — локальная переменная, соответствующая формальному параметру-значению.