Головна » Статті » Інформатика | [ Додати статтю ] |
Область дії означень та побічний ефект
Програма може містити кілька підпрограм, і в них самих можуть бути підпрограми тощо. Виникає питання, чи можна в одній підпрограмі використовувати імена, означені в іншій? І зокрема, чи можна в підпрограмах користуватися іменами з програми, і навпаки? У цьому параграфі ми дамо відповіді на ці та деякі інші питання.
Означення імені задає позначення деякого об'єкта, наприклад, змінної або підпрограми. Так, після означення var aaa : integer; ім'я aaa позначає змінну цілого типу, а не сталу або щось інше. Таке завдання позначення об'єкта називається його іменуванням. Виникає питання, чи можна різні об'єкти іменувати однаково, тобто давати їм те саме ім'я? Виявляється, можна, але не завжди. Уточнимо це, увівши нове поняття. Область дії означення імені, або область дії іменування – це сукупність місць у програмі, де ім'я позначає об'єкт, указаний саме в цьому означенні. Область дії іменування часто називають "область дії імені", що не зовсім точно. За правилами мови Паскаль, означення імені діє від того місця програми або підпрограми, де воно записано, до кінця її блоку. Якщо в цій області є підпрограми, то означення діє й у них. Але якщо вони містять своє власне означення цього імені, то за тими самими правилами до кінця їх блоків діють їх власні означення. Можна сказати, що власне означення в підпрограмі "перекриває" означення, записане вище. Приклад 7.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 t:=gcd(x1, y1); x1:=x1 div t; y1:=y1 div t end; Нарешті, наведемо розташування наведених підпрограм у блоці програми. Функція gcd повинна бути записана перед процедурою redufr, оскільки в ній визивається. Так само ця процедура повинна бути записана перед процедурою plusfr. Взаємне розташування plusfr, writefr і readfr не має значення: … function gcd(x, y : integer) : integer; … begin … end; procedure redufr(var x1, y1 : integer); … begin … gcd … end; procedure plusfr(x1, y1, x2, y2 : integer; var x3, y3 : integer); begin … redufr … end; procedure readfr(var x, y : integer); begin … end; procedure writefr(x, y : integer); begin write(x, '/', y) end; Запис програми в остаточному вигляді залишаємо як вправу. Переозначати ім'я підпрограми в ній самій мова Паскаль забороняє. Але дозволяє використовувати його, тобто позначати ним виклики цієї ж підпрограми. Такі виклики підпрограми усередині її самої називаються рекурсивними; ми познайомимося з ними в розділі 9. Ім'я, означене в підпрограмі (або програмі), називається локальним у ній. Ім'я, записане, але не означене в підпрограмі, називається глобальним у ній. Воно може бути означеним у одній з підпрограм, що охоплюють, або програмі. У прикладі 7.1 ім'я t є глобальним в процедурі minus і в процедурі plus, але тільки до означення {2}; далі воно є локальним і позначає зовсім іншу змінну, хоча й того ж типу. У наступному підрозділі ми розглянемо модулі – спеціальні "збірники означень", які дозволяють використовувати імена взагалі без означення в програмі. Втім, такі імена вже знайомі – наприклад, імена математичних функцій або підпрограм readln і writeln. Зміна змінної з глобальним ім'ям у підпрограмі називається побічним ефектом підпрограми. Наприклад, у програмі twovars побічний ефект процедури minus полягає в зміні значень змінної з ім'ям t. Побічний ефект називається явним, якщо він заданий операторами присвоювання. Такий побічний ефект процедури minus. Неявний побічний ефект задається, якщо глобальне ім'я вказати у виклику підпрограми як аргумент, що відповідає параметру-змінній. Наприклад, у виклику процедури читання. Програміст може задати побічний ефект, іноді навіть не бажаючи цього, через неуважність. Результати такої помилки можуть виявитися цілком несподіваними й навіть дуже сумними для автора програми. Тому використання глобальних імен вимагає особливої уваги. Втім, це аж ніяк не означає, що користуватися ними не варто. Як і побічним ефектом. Усе добре в міру й за своїм призначенням. При імітації виконання програми можна ввести додаткові позначення для локальних імен у підпрограмах, щоб явно відрізняти їх від глобальних. Наприклад, якщо в підпрограмі S означено ім'я N, то позначимо його S.N, а якщо підпрограма S2 вкладена в підпрограму S1 і містить означення імені N, то позначимо його S1.S2.N тощо. Зокрема, у прикладі 7.1 ім'я t у підпрограмі minus є глобальним і додаткового позначення не одержує, а в підпрограмі plus позначається plus.t. Як відомо з розд.2, при виконанні виклику підпрограми її параметрам-змінним зіставляється пам'ять аргументів. При імітації виконання програми можна "сумістити" параметр-змінну з аргументом. Іншим іменам змінних, означеним у підпрограмі, зіставляються свої власні ділянки пам'яті. Наприклад, виконання програми program qq(input, output); var a, b, c : integer; procedure ps(a : integer; var b : integer); var t : integer; begin t := a+b; b := t-b; a := t-b; c := t end; begin a := 1; b := 5; c := 2; ps(b, a); writeln(a, b, c) end. можна відбити такою таблицею: Виконувані дії a b c a:=1; b:=5; c:=3 1 5 2 Виклик ps ps.b ps.a ps.t Неявне ps.a:=b 1 5 2 5 ? ps.t:=ps.a+ps.b 1 5 2 5 6 ps.b:=ps.t-ps.b 5 5 2 5 6 ps.a:=ps.t-ps.b 5 5 2 1 6 c:=ps.t 5 5 6 1 6 writeln(a, b, c) 5 5 6 Суміщення імен a і ps.b в одній колонці вказує, що цим іменам зіставлена та сама ділянка пам'яті. У результаті виконання буде надруковано 5 5 6. Задачі 7.1.* Укажіть помилкове використання імен у програмі: program AB(input, output); function A : integer; function B : integer; function A : integer; begin A:=1 end begin A := 2; B := A end;begin A := 3 end; begin writeln(A); writeln(B) end. 7.2. Імітувати виконання програми: program (input, output); var a, b : integer; procedure badswap(var a : integer; t : integer); var d : integer; begin d := t; t := a; a := d end; begin a := 1; b := 3; badswap(a, b); writeln(a, b) end. 7.3.* Написати програму, за допомогою якої можна встановити, чи завжди обчислюються праві операнди бульових операцій and і or. 7.4.* Дописати необхідні означення до тіла програми, щоб при її виконанні було надруковано не "0", а "1": begin writeln(b*c-c*b) end. | |
Переглядів: 498 | |
Всього коментарів: 0 | |