Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

osn_progr_final

.pdf
Скачиваний:
37
Добавлен:
12.02.2016
Размер:
3.27 Mб
Скачать

чотириногим улюбленцем, ми передаємо йому команди повідомлення: “стояти”, голос”,”до мене”. Чим розумніша істота, тим різноманітніші повідомлення вона розуміє і може на них відреагувати. Всередині класів ховаються всі особливості програмної реалізації, суть алгоритмів взаємодії між об’єктами. Тому і розмір main() - функції в таких програмах буває скороченим до мінімуму. Вона складається лише з повідомлень, що передаються об’єктам. Розглянемо, наприклад, моделювання екологічної системи Океан. В роботі [3 ] main()-функція має приблизно такий вигляд:

main()

{Ocean* myOcean=new Ocean; myOcean->initialize(); myOcean->run(); }

Функція run() містить цикл виду

for(int i=0; i<N; i++) елемент. process();

Тут process() - це деяка функція-член класу , яка запускає процес “життєдіяльності” для кожного елемента океану. Бачимо, що всі особливості програмної реалізації моделі заховані у класах. Завдяки цьому створюється ідеальна модель . Елементи можуть “жити”, “розмножуватись”, вести себе по відношенню до сусідів згідно з заданим алгоритмом їх поведінки. Причому програміст абсолютно не управляє цим процесом. Система самоорганізується , функціонує самостійно згідно з законами, заданими у описах класів. В main-функції немає ніякого аналізу ситуацій, ніяких викликів функцій. Лише функція process(), яка еквівалентна народженню об’єкта, втіленню його у віртуальний світ океану. Окремі елементи океану, об’єкти певних класів, ніби стають реальними живими об’єктами. Уявімо собі, що хочемо написати таку саму програму без використання об’єктного підходу, користуючись лише засобами процедурного програмування. Тоді ми просто заплутаємось у описах та аналізі величезної кількості взаємозв’язків між елементами даних та функціями їх обробки, у описі властивостей елементів океану та їх взаємодії. Очевидно, що створити аналогічну програму без використання засобів ООП буде значно важче.

Відмітимо, що при постановці задач в об’єктно-орієнтованих мовах можна використовувати термінологію реальних об’єктів, які моделюються. Підзадачі в завданнях по класах можна переформулювати так: “навчимо літак рухатись”, “навчимо літак бачити” і т д. Оскільки мова йде про реальні об’єкти, властивості яких добре відомі , то

311

конкретизувати постановку задач , можливо, і непотрібно. Важливо лише, щоб програміст розумів кінцеву ціль створення даної моделі. Тоді він вже сам може вибрати чи створити собі такі типи даних, які максимально підходять для розв’язання саме його задачі.

Легкість у створенні та використанні типів даних користувача в об’єктно-орієнтованих мовах робить сам процес програмування дуже захоплюючим. Програміст відчуває себе творцем свого власного віртуального світу. Як відмічає професор В.А.Сухомлин , за допомогою об’єктно-орієнтованого програмування можна досягнути “ефекту занурення користувача у його ж семантичні світи, де він може почувати себе істинним творцем”. Давайте замислимося над тезою: людина - істинний творець. Виходячи з такого положення, людина може робити з своїм творінням все, що їй заманеться. Адже вона сама - творець. У віртуальному світі все просто: захотів - натиснув клавішу комп’ютера і знищив об’єкт, захотів - створив його. Але цей віртуальний світ часто є відображенням реального, є моделлю тих чи інших процесів , що відбуваються у реальному світі. Тоді відчуття творця, наділеного безмежними можливостями у віртуальному світі, може в якійсь мірі підсвідомо перенестись у реальний світ.

Комп’ютер виховує людину, яка працює з ним, навіть самою організацією цієї роботи. Адже в процесі роботи з комп’ютером постійно існує єфект “чарівної палички”: одного легкого поруху руки з маніпулятором достатньо для того, щоб об”єкт, який тебе цікавить, з’явився на екрані чи зник. А це , в свою чергу, поступово може породити такі стереотипи мислення, і особливо у дітей, коли їх бажання повинні виконуватись . Людина надзвичайно легко звикає до цього. Легко уявити собі, що відбувається, якщо такий стереотип мислення переноситься у людське суспільство.

Постійно сприймаючи замість реального світу лише його комп’ютерне відображення, світ через екран комп’ютера, людина поступово може заповнити свою область несвідомого спрощеним “віртуальним” світом”. Але ж якими далекими від реальності є його закони та об’єкти! Скільки мільйонів і мільярдів “полів даних” повинен мати справжній літак, не говорячи вже про океан! Хіба ж можна порівняти його з реальним об’єктом ? І якою елементарною та немічноювиглядає будь-яка комп’ютерна модель , яким би геніальним програмістом вона не була створена, перед величчю реального світу, створеного Творцем. Лише мізерну частину цього реального світу ми можемо усвідомити. Інше ж ми ”бачимо” на рівні підсвідомості, не ро-

312

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

Ідеологія особистого домінування, знищення всього , існуючого

вінших формах, насильства та жорстокості ( яка нерідко пропагується в комп’ютерних іграх) знаходить підтримку у підсвідомості людини, оскільки вона притаманна тілесній стороні людської сутності на даному етапі людського існування. Виходячи з концепції “людина - творець” можна прийти до виправдання всього найганебнішого, що є

внашій цивілізації. Тому дуже важливо, щоб в процесі творення цих “семантичних світів” не породжувалася ілюзія всемогутності людини, людського розуму, особистої геніальності. Адже необмеженість приводить до обмеженості, а обмеженість - до необмеженості. Відчуття своєї досконалості та неперевершеності призведе , безсумнівно, з часом до обмеженості і відсталості, а відчуття обмеженості та недосконалості призводить , як правило, до максимального вдосконалення та прогресу.

9 АВТОМАТНА ТЕХНОЛОГІЯ ПРОГРАМУВАННЯ.

Найважливішими технологіями останніх десятиліть є методології структурного і об'єктно-орієнтованого програмування. Паралельно з ними використовується і розвивається метод програмування заснований на скінченних автоматах (СА). Легко можна переконатися, що ООП успадковувало у СА такі позиції як об'єкт і виконання функцій властивих даному стану об'єкту.

Ми розглядатимемо не загальну теорію скінченних автоматів (хоча і вивчимо ряд властивостей і визначень), а швидше автоматний стиль мислення для розв’язання задач деякого класу.

Модель кінцевого автомата - така ж алгоритмічна модель, як і блок-схема (БС). Проте автомат володіє якостями, яких немає у БС. Принциповою суттю СА є наявність станів, на основі яких і відбуваються всі дискретні перетворення.

Перед вивченням цієї технології, розглянемо коло задач, до яких можна її застосувати.

При розв’язанні задач компіляції доводиться стикатися з проблемою розділення рядків на деякі осмислені частини, перевірки правильності введення значень і т.д. Для подібних випадків використовується формальні описи структур - формальні граматики.

313

9.1 ЛЕКСИЧНИЙ АНАЛІЗ.

Тепер перейдемо до задачі виділення лексем з послідовності символів деякої мови програмування. Таким чином, ми вирішимо одну з підзадач теорії конструювання компіляторів.

Основна задача лексичного аналізу - розбити вхідний текст, що складається з послідовності одинарних символів, на послідовність слів, чи лексем, тобто виділити ці слова з неперервної послідовності символів. Усі символи вхідної послідовності з цього погляду розділяються на символи, що належать яким-небудь лексемам, і символи, що розділяють лексеми (розділювачі). У деяких випадках між лексемами може і не бути розділювачів. У С розділове значення символіврозділювачів може блокуватися ('\' наприкінці рядка, усередині "...").

Розпізнавачем є процедура, що дозволяє визначити належність ланцюжка символів деякій мові.

9.1.1 Формалізм лексичного аналізатора.

Як правило, для опису лексичних аналізаторів, використовують формалізм контекстно-вільних граматик, а саме підкласу автоматних, чи регулярних, граматик.

Як приклад, розглянемо таку регулярну граматику G[Z]: <Z>::=<U>0|<V>1

U::=<Z>1|1

V::= Z0|0

Легко бачити, що мова, яка породжується нею, складається з послідовностей, утворених парами 01 чи 10, тобто

L(G) = {У | n > 0}, де В == {01, 10}.

Для розпізнавання речень граматики G, намалюємо діаграму станів (рис. 1а). У цій діаграмі кожен нетермінал граматики G представлений вузлом чи станом; крім того, є початковий стан S (передбачається, що граматика не містить нетермінала S). Кожному правилу Q ::= Т в G відповідає дуга з позначкою Т, що спрямована від початкового стану S до стану Q. Кожному правилу Q ::= RT відповідає дуга з позначкою Т, що спрямована від стану R до стану Q.

Ми використовуємо діаграми станів, щоб розпізнати ланцюжок х у такий спосіб:

314

1.Першим поточним станом вважати початковий стан S. Почати із самої лівої літери в ланцюжку х і повторювати крок 2 доти, доки не буде досягнутий правий кінець х.

2.Сканувати наступну літеру х, просунутися по дузі, позначеної цією літерою, переходячи до наступного поточному стану.

3.Якщо при якомусь повторенні кроку 2 такої дуги не виявиться, то ланцюжок х не є реченням і відбувається зупинка. Якщо ми досягаємо кінця х, то х – речення (допустима конструкція) тоді і тільки тоді, коли останній поточний стан є Z.

Рис. 1. Діаграма станів.

9.1.2 ПРЕДСТАВЛЕННЯ В ЕОМ

Діаграму станів, що показана на рис. 1, можна записати в таблицю відповідностей.

M (S, 0) = V M (S, 1) = U

M (V, 0) = F M (V, 1) = Z

M (U, 0) = ZM (U, 1) = F

M (Z, 0) = VM (Z, 1) = U

M (F, 0) = F M (F, 1) = F

315

Ця таблиця фактично є формальним представленням послідовностей дій, які необхідно було виконувати для розпізнавання ланцюжка символів.

Тобто цю послідовність дій зі станами S1, ... , Sn і вхідними літерами Т1, ... , Тm можна представити матрицею В, що складається з n*m елементів. Елемент В[i, j] містить число k - номер стану Sk, такого, що М[Si, Tj] = Sk. Можна домовитися, що стан S1 - початковий, а список заключних станів представлений вектором. Така матриця іноді називається матрицею переходів, оскільки вона вказує, яким чином відбувається переключення з одного стану в інший.

Іншим способом представлення може бути спискова структура. Представлення кожного стану з k дугами, що виходять з нього, займає 2*k + 2 слів. Перше слово - ім'я стану, друге - значення k. Кожна наступна пара слів містить термінальний символ із вхідного алфавіту і вказівник на початок представлення стану, у яке треба перейти по цьому символі.

9.2 ПРОГРАМУВАННЯ ЗА ДОПОМОГОЮ СКІНЧЕННИХ АВТОМАТІВ.

9.2.1 Скінченний автомат як формалізм.

Роботу лексичного аналізатора краще описувати формалізмом скінченних автоматів.

Визначення 1. Скінченний автомат - це п'ятірка (К, VT , F, H, S),

де

К - скінченна множина станів;

VT - скінченна множина допустимих вхідних символів;

F - відображення множини K × VT → K, що визначає поведінку автомата; відображення F часто називають функцією переходів;

H К - початковий стан;

S K - завершальний стан (або скінченна множина завершальних станів).

F(А, t)= B означає, що із стану А по вхідному символу t відбувається перехід в стан B.

Укожен поточний момент часу скінченний автомат може знаходитися в одному з можливих станів (число станів, в яких може знаходитися кінцевий автомат, – кінцево). Автомат послідовно прочитує

316

символи вхідного тексту (рядки). Кожен прочитаний символ або переводить автомат в новий стан, або залишає його в поточному стані. Формально, автомат можна описати за допомогою функції переходів. Аргументами цієї функції є попередній стан автомата і поточний прочитаний символ, а значенням – новий стан автомата.

Позначимо через l( ) кількість тактів обробки слова a кінцевим автоматом. Нехай ai1 ai2 ....aip – вхідне слово, l( )=р. Через q (t) позна-

чимо стан, в якому опиняється автомат К через t тактів роботи над цим словом (тут t=0, 1, 2, ..., р):

q (0) q0

q (1) g(q (0), ai1 )

q (2) g(q (1), ai2 )

q ( p) g(q ( p 1), aip )

. . .

Визначення 2. Говоритимемо, що слово приймається автома-

том К, якщо q (р) S.

Визначення 3. Слово належить мові L(К), якщо дане слово приймається автоматом К. Тоді мову L(К) іменуємо мовою, що розпізнається даним кінцевим автоматом.

Визначення 4. Мову L назвемо регулярною, якщо для неї можна побудувати кінцевий автомат, що її розпізнає.

Скінченний автомат зручно задавати діаграмою його переходів або станів. Діаграма є орієнтований граф, вершини якого однойменні станам автомата; дуга з вершини qi, у вершину qk з надписаною над нею буквою аj проводиться тоді і тільки тоді, коли F(qij)=qk, тобто, коли автомат із стану qi під впливом букви аj повинен перейти в стан qk. У разі, коли перехід з qi в qk здійснюється під впливом будь-якої з букв деякої підмножини S, S А, всі букви цієї підмножини підписуються над відповідною дугою. Якщо довільний стан qi входить в S, то даний факт на діаграмі наголошується жирним кружком, що виділяє вершину qi. Це означає, що автомат знаходиться в одному з своїх завершальних станів.

Очевидно, що будь-який скінченний автомат повністю визначається своєю діаграмою переходів. Під задачею побудови скінченного автомата, що володіє тими або іншими властивостями, розумітимемо задачу побудови діаграми його станів.

Основною властивістю стану є те, що в ньому СА чекає обмежений набір вхідних дій. Будь-який алгоритм має вхідну і вихідну інфо-

317

рмацію. Вихідні дії можна розділити на два види: змінні (наприклад, операції над властивостями об'єктів) і функції. Друга властивість стану - видача набору фіксованих значень вихідних змінних. Це означає, що - у будь-який момент часу визначені значення всіх вихідних змінних, оскільки алгоритм завжди знаходиться в якомусь стані.

На рис. 1. показана діаграма автомата К, що працює із словами алфавіту A={а,b,c}. Автомат має два стани, q0 і q1, завершальним є стан q1. Почавши роботу в стані q0, автомат при отриманні на вхід букв а, b в новий стан не переходить; при отриманні букви с, здійснюється перехід в стан q1; далі наступна буква залишає автомат в тому ж стані. Таким чином, автомат K розпізнає мову L1, що складається із слів, що мають в своєму складі букву с і тому запропонована мова є регулярною.

a,b,c

c

q0

q1

q1

Рис. 1.

Природньо, що при виконанні дій можливі виклики вкладених скінченних автоматів.

Число станів і наборів значень вихідних змінних обмежене. Діаграми станів, що ілюструють роботу скінченного автомата, ввійшли невід'ємною частиною в основні принципи об'єктно-орієнтованого програмування.

Основною особливістю програмної реалізації скінченних автоматів є обов'язкова наявність циклу. Як ми вже могли помітити, на відміну від традиційних алгоритмів, що включають два види компонент: умова і дія, cкінченний автомат має додаткову компоненту - стан.

9.2.2 ОСНОВИ КОНСТРУЮВАННЯ ПРОГРАМ З ДОПОМОГОЮ СА.

Одним з інструментів розбору рядків символів є скінченні автомати. Як правило, вони застосуються для розбиття початкового рядка на набір деяких лексичних одиниць.

Приклад 1. Нехай дані два масиви, що закінчуються нулем. Другий відрізняється від першого, додатковими вставками з чисел, відсутніх в першому масиві. Наприклад:

M = 1 2 3 4 5 6 7 8 9 0

318

N = 1 2 3 4 5 3 2 4 43 6 7 8 9 0

Програма повинна порівняти два масиви і надрукувати вставки, що зустрічаються.

Масиви позначимо M і N, а номери їх елементів, відповідно, i і j. Необхідно написати програму знаходження і друку вставок. Побудуємо діаграму станів. Позначимо початковий стан як А.

Перебиратимемо елементи масиву, використовуючи цикл

(NonStop=true). Вихід з циклу (NonStop=false) обробки масивів здійс-

нюється після закінчення елементів або в обох масивах або в одному. З початкового стану здійснюється перехід в поки невідомий стан і виконання певних дій. Зобразимо цей етап наших уявлень про розв’язання задачі.

While(NonStop)

?

 

 

A

 

B

?

NonStop=false

 

 

 

 

q1

Розглянемо новий стан В. У ньому порівнюватимемо елементи двох масивів. Поки порівнювані елементи рівні - послідовності співпадають. Тому стан В назвемо станом очікування появи вставки, або нерівності поточних елементів масивів. У цьому стані виконуватиметься приріст номерів елементів масивів на одиницю (i=i+1; j=j+1). Оскільки починаємо роботу з першого елементу масивів, то i=1; j=1. Це буде перша дія при виході з початкового стану.

В стані В порівнюватимемо елементи M[i] і N[j]. У разі нерівності (локальна подія M[i]<> N[j]) переходимо в невідомий поки стан (позначимо його С) із запам'ятовуванням поточних номерів елементів масивів в додаткових змінних i1 = i; j1 = j.

While(NonStop)

i=j=0

 

 

A

EndN&EndM:NonStop=false

B

=: i=i+1; j=j+1

 

 

 

 

 

EndN: Write(’error’);

q1

 

 

NonStop=false

 

 

NonStop=false

M[i]<>N[j]:

 

 

 

i1=i; j1=j

 

 

C 319

В стані В може наступити закінчення списку елементів масивів - M[i]= 0 і N[j]= 0. Якщо досягнутий кінець другого масиву і не досягнутий кінець першого (подія EndN, або N[j]= 0) – формується повідомлення про помилку і здійснюється вихід. Для цього вводиться перехід із стану В в стан А по локальній події ЕndN з дією на переході

Write(‘Error’) і NonStop = false, де Write(‘Error’) – друк повідомлення про помилку. Якщо обидва чергові елементи масивів – нульові - локальна подія ЕndМ & EndN, то програма переходить в стан А з дією коректного завершення NonStop= false.

Таким чином, в стані В вибираємо чергову пару елементів порівнюваних масивів і чекаємо появи однієї з трьох локальних подій:

1.EndM & not EndN – закінчилися елементи в другому масиві

іне закінчилися в першому;

2.EndM & EndN - закінчилися елементи в обох масивах;

3.M[i]<> N[j] – знайшли не співпадаючі елементи.

По кожній з цих подій здійснюється перехід із стану В або в стан А - по подіях 1 і 2, або поки в невідомий стан С - по події 3. Якщо жодна з цих подій не трапилася в поточному циклі, то програма залишається в змозі В, що відображається дугою з В у В.

Розглянемо події, які відбуватимуться в стані С. В цьому стані відбувається очікування появи елементу N[j1] співпадаючого з M[i1]. Наступ цієї події говорить про завершення вставки. В стані С необхідно здійснити приріст індексу j1 елементу другого масиву і друк відповідного елементу.

While(NonStop)

i=j=0

 

 

 

A

 

 

B

=: i=i+1; j=j+1

EndN&EndM:NonStop=false

 

 

 

 

 

EndN: Write(’error’);

 

q1

 

 

NonStop=false

 

 

 

 

M[i1]=N[j1]:

M[i]<>N[j]:

 

 

EndN:

j=j+1

 

 

NonStop=false

 

i1=i; j1=j

 

 

 

 

 

 

320

<>: C

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]