Сторінка
1
1. Область дії означень та побічний ефект Програма може містити кілька підпрограм, і в них самих можуть бути підпрограми тощо. Виникає питання, чи можна в одній підпрограмі використовувати імена, означені в іншій? І зокрема, чи можна в підпрограмах користуватися іменами з програми, і навпаки? У цьому параграфі ми дамо відповіді на ці та деякі інші питання. Означення імені задає позначення деякого об'єкта, наприклад, змінної або підпрограми. Так, після означення var aaa : integer; ім'я aaa позначає змінну цілого типу, а не сталу або щось інше. Таке завдання позначення об'єкта називається його іменуванням. Виникає питання, чи можна різні об'єкти іменувати однаково, тобто давати їм те саме ім'я? Виявляється, можна, але не завжди. Уточнимо це, увівши нове поняття. Область дії означення імені, або область дії іменування – це сукупність місць у програмі, де ім'я позначає об'єкт, указаний саме в цьому означенні. Область дії іменування часто називають "область дії імені", що не зовсім точно. За правилами мови Паскаль, означення імені діє від того місця програми або підпрограми, де воно записано, до кінця її блоку. Якщо в цій області є підпрограми, то означення діє й у них. Але якщо вони містять своє власне означення цього імені, то за тими самими правилами до кінця їх блоків діють їх власні означення. Можна сказати, що власне означення в підпрограмі "перекриває" означення, записане вище. Приклад 1. У програмі program twovarst(input, output); var t:integer; {1} procedure plus; {1} procedure minus; {1} begin t:=t-1 end; {1} var t:integer; {2} begin t:=1; t:=t+1; minus end; {2} begin t:=1; plus; writeln(t) end. {1} коментарі {1} і {2} позначають перше й друге означення імені t й відповідні рядки програми, у яких вони діють. При виконанні виклику процедури minus зменшується значення змінної, означеної в програмі, а при виконанні операторів процедури plus збільшується значення зовсім іншої змінної – іменованої в означенні {2}. Означення {1} перекрите в процедурі plus її власним означенням {2}. Тому при виконанні програми друкується 0. Таким чином, різні об'єкти можна іменувати однаково, але в різних підпрограмах (однією з них вважається програма). В означеннях тієї самої підпрограми усі імена повинні бути різними. Тепер підійдемо з протилежного боку. Нехай у деякому місці програми використовується ім'я. Як дізнатися, якому з означень, записаних вище, воно відповідає? Будемо говорити, що програма або підпрограма охоплює підпрограми, записані в ній, і що вони вкладені в неї. Наприклад, програма twovars охоплює процедури plus і minus (і вони обидві вкладені в неї), а процедура plus охоплює minus. З правила, яким задається область дії означення, випливає, що ім'я в підпрограмі відповідає найближчому з його означень, записаних вище в підпрограмі або підпрограмах, що її охоплюють, зокрема, в програмі. Звідси одразу маємо відповіді на питання, поставлені спочатку: у підпрограмі можна використовувати ім'я, означене в підпрограмі, що її охоплює, або програмі, але не можна використовувати ім'я, означене не вище в блоці або не в підпрограмі, що її охоплює. Основний практичний висновок з цих правил – намагатися уникати вкладених підпрограм і прагнути до того, щоб усі підпрограми були вкладені тільки в програму, тобто щоб у них не було своїх підпрограм. У цьому розумінні програма twovarst явно невдала. Означення підпрограми починаються з її параметрів, а якщо їх немає, то з початку блоку. Тому підпрограма іменується не в ній самій, а в програмі, або, в гіршому випадку, в підпрограмі, що її охоплює. Звідси випливає, що означення імені підпрограми діє до кінця блоку, в якому її записано. Тому її можна викликати в інших підпрограмах, записаних нижче в цьому ж блоці. Приклад 7.2. Раціональне число подається нескоротним дробом A/B, де B>0. Сумою двох дробів A/B і C/D є результат скорочення дробу (AD+BC)/BD. Напишемо програму, у якій описується читання двох дробів, їх додавання зі скороченням результату й виведення у вигляді нескоротного дробу. Будемо припускати, що знаменники дробів, що читаються, не рівні 0. Опис дій із дробами оформимо у вигляді підпрограм: читання readfr, додавання plusfr і друкування writefr. Закінчення fr в іменах процедур – це скорочене fraction, тобто "дріб". У програмі означимо цілі змінні a, b, c, d, r1, r2 для зберігання прочитаних дробів a/b і c/d та результату операції r1/r2. Не уточнюючи самих підпрограм, запишемо поки лише тіло програми з їх використанням: begin writeln('задайте дріб (два цілих, друге не 0):'); readfr(a, b); writeln('задайте дріб (два цілих, друге не 0):'); readfr(c, d); plusfr(a, b, c, d, r1, r2); writefr(r1, r2); end. А тепер почнемо уточнювати підпрограми. Процедура читання очевидна: procedure readfr(var x, y : integer); begin readln(x); readln(y) end; Передбачається, що при виконанні програми користувач набере дві цілі сталі. Процедура додавання повинна повертати результат додавання у вигляді нескоротного дробу. Нехай її параметри x1, y1, x2, y2 задають два дроби, що додаються, а x3, y3 – результат. Скорочення дробу задамо процедурою redufr. Її параметри-змінні задають дріб (скорочений спочатку і в результаті). Тоді додавання задається такою процедурою: procedure plusfr(x1, y1, x2, y2 : integer; var x3, y3 : integer); begin x3:=x1*y2+x2*y1; y3:=y1*y2; redufr(x3, y3) end; Щоб скоротити дріб, треба його чисельник і знаменник розділити на їх найбільший спільний дільник (задача 4.5). Нехай його обчислення задає функція gcd, яку ми тут не уточнюємо. Тоді процедура скорочення redufr очевидна: procedure redufr(var x1, y1 : integer); var t : integer; begin