- •Київський національний університет імені Тараса Шевченка
- •Гриф надано Міністерством освіти і науки України (лист № 1.4/18-г-1523 від 20.09.07)
- •Основи алгоритмізації
- •1.1. Поняття алгоритму
- •1.2. Класифікація внутрішніх структур алгоритмів
- •1. Ввести а,b.
- •3. Вивести значення s.
- •4. Кінець.
- •6. Закінчити роботу.
- •1. Ввести число n.
- •6. Перехід до п.3.
- •7. Друк к.
- •8. Кінець.
- •1.3. Складність алгоритмів
- •1.4. Складність задач
- •Завдання для самостійної роботи
- •Форма Бекуса – Наура
- •Завдання для самостійної роботи
- •Void main(){
- •3.2. Структура с-програми
- •3.3. Описувачі
- •3.4. Основні операції мови с
- •If(!inword)
- •Void main()
- •Int rozmir;
- •3.5. Оператори мови с
- •3.5.1. Прості оператори
- •3.5.2. Умовний оператор
- •3.5.3. Оператор циклу for
- •3.5.4. Оператори do-while, while
- •3.5.5. Оператор continue
- •3.5.6. Оператор-перемикач switch
- •3.5.7. Оператор break
- •3.5.8. Оператор goto
- •If(error(I,j,k)) goto exit;
- •3.5.9. Оператор return
- •3.6. Директиви препроцесору та вказівки компілятору
- •3.6.1. Директива препроцесору #define
- •1. Макровизначення:
- •3.6.3. Директива #include
- •3.6.4. Директиви умовної компіляції #if, #elif, #else, #endif
- •3.6.5. Директива #line
- •If(!cond)
- •3.7. Описувачі з модифікаторами
- •3.7.1. Моделі пам'яті
- •3.7.2. Модифікатори типу доступу в пам'яті
- •Int huge*near X;
- •3.7.3. Модифікатори const, volatile, cdecl, pascal, interrupt
- •Volatile int t;
- •Void interrupt timer()
- •Void wait(int interval)
- •Завдання для самостійної роботи
- •Принципи типізації даних
- •4.1. Прості типи даних
- •4.2. Похідні типи
- •4.3. Еквівалентність типів
- •4.4. Успадкування атрибутів
- •4.5. Перераховні типи
- •4.6. Логічні типи
- •4.7. Символьні типи
- •4.8. Числові типи
- •4.9. Структурні типи даних
- •4.9.1. Масиви
- •4.9.2. Структури
- •Int year;
- •4.10. Деякі особливості типів даних c
- •4.10.1. Базові типи даних
- •4.10.2. Перетворення типів
- •Int atoi(char s[]) /*char* s*/
- •4.10.3. Засіб typedef
- •Int curs;
- •4.10.4. Покажчики та масиви
- •Void * p;
- •Int array[12];
- •Void f(int a[])
- •Int f(char * s)
- •Наведемо деякі приклади розв'язання задач.
- •Int shift; /*відступ*/
- •Int count[n]; /*кількість монет даного типу (коефіцієнти ai)*/
- •Int coin;
- •Int sum; /*монета, яку міняємо*/
- •Int maxcoin; /*індекс по масиву cost[] монети максимальної вартості, допустимої при даному розміні.*/
- •If(count[I])
- •If(maxcoin)
- •Int* ctranspon (int *a,int n,int m)
- •Void dobutok(int* a, int* b, int** c, int n, int m)
- •Int n,m,I,size;
- •Int main()
- •4.10.5. Структури та об'єднання
- •Розглянемо деякі приклади розв'язання задач.
- •Int hashfunc(key); int eqkey(key, key);
- •Void freeval(val); void setval(val, val);
- •Void freekey(key); void setkey(key, key);
- •Int hashfunc(key key){
- •Val val; /*значення*/
- •Void set(key key, val val){
- •Void printcell(struct cell *ptr){
- •Void main(void)
- •Завдання для самостійної роботи
- •Зображення чисел у комп'ютері
- •Int main(void)
- •5.1. Системи числення
- •5.2. Правила переведення чисел з однієї системи числення в іншу
- •5.3. Правило визначення точності зображення
- •5.4. Двійкова арифметика
- •5.4.1. Додавання двійкових чисел
- •5.4.2. Зображення від'ємних чисел
- •XXXXXXXX 00000001 00000000.
- •5.4.3. Віднімання двійкових чисел
- •5.4.4. Множення двійкових чисел
- •5.4.5. Ділення двійкових чисел
- •5.5. Ознака переповнення розрядної сітки при арифметичних операціях
- •5.6. Зображення цілих чисел
- •5.7. Зображення дійсних чисел
- •5.8. Керування машинним зображенням чисел та особливості виконання арифметичних операцій
- •Завдання для самостійної роботи
- •Реалізація концепції структурного програмування
- •6.1. Оголошення та визначення функцій
- •Int d;} people;
- •6.2. Формальні та фактичні параметри
- •Void swap(int a,int b)
- •Void swap(int a,int*b)
- •6.3. Функції зі змінною кількістю параметрів
- •Void sum(char *msg,...)
- •6.5. Параметри функції main
- •6.6. Лiтернi покажчики та функцiї
- •Void strcpy(char*s,char*t)
- •Void f(void)
- •6.8. Класи пам'яті
- •Розглянемо деякі приклади розв'язання задач.
- •I, power(2,I),power(-3,I));
- •Void main() { choturukyt b; tochka *a; float s; long n,in; srand(time(null));
- •6.9. Введення–виведення с. Файли та потоки
- •6.9.1. Функції введення–виведення верхнього рівня
- •6.9.2. Функції введення–виведення консольного термінала та порту
- •Int main(void)
- •6.9.3. Функції введення–виведення нижнього рівня
- •Int main(void)
- •Int handle;
- •Розглянемо приклади розв'язання задач.
- •Void main(void)
- •Void main(argc,argv)
- •If(c& masks[I])
- •If (цей рядок довший за найдовший з попередніх)
- •Int max; /*максимальна довжина*/
- •Int len; /*довжиною цього рядка*/
- •Int nwords; /*кількість слів у рядку*/
- •If(!*s) /*рядок закінчився*/
- •Int ctr; /*кількість входжень слова*/
- •If(!strcmp(word,w[I].Wrd)){
- •If(alert){
- •Void main() { float X,y,z,t,s; int I,j,flag,n,k; m1: clrscr();
- •InitBase (void){
- •Int key, /*новий ключ*/
- •InitBase();
- •Завдання для самостійної роботи
- •7.1. Елементи концепції обєктно-орієнтованого програмування
- •Int year;
- •Int year;
- •7.3. Опис протоколу класу
- •7.4.1. Коментарі
- •7.4.2. Прототипи функцій
- •Void f();
- •7.4.5. Перевантаження функцій
- •Int Name (int first)
- •Int Name (unsigned first)
- •Int Name (int first,char*second)
- •7.4.6. Значення формальних параметрів за умовчанням
- •7.4.7. Посилання й покажчики
- •Void increment(int& X)
- •Int anotherint;
- •7.4.10. Покажчик на void
- •Void*void_ptr;
- •Void swap(void*&item1,void*&item2)
- •7.4.11. Зв'язування зі збереженням типів
- •7.4.12. Про структури та об'єднання
- •7.5. Функції-члени класу
- •X *this;
- •Int year;
- •7.6. Конструктори та деструктори
- •7.6.1. Поняття про конструктори
- •Int*data;
- •Int size;
- •7.6.3. Конструктор копіювання
- •Int data[large];
- •Inline Large1 Large1::fast(const Large1 & b)
- •7.7. Глобальні та локальні об'єкти
- •Void main(void)
- •7.8. Статична пам'ять і класи
- •Int statpol::I;
- •Vоid draw()
- •Int large;
- •Int bigwant;
- •Void f() {
- •Void g(int a)
- •7.9. Успадкування
- •7.9.1. Синтаксична реалізація успадкування
- •7.9.2. Правила доступу до полів даних
- •Void f(void)
- •Void g(void){}//...}
- •7.9.3. Конструктори та деструктори в похідних класах
- •7.9.4. Використання заміщуючих функцій-членів
- •Void Display (void); //замiщувальна функцiя
- •Void Region::Display(void)
- •Void Display(void);};
- •Void Population::Display(void)
- •7.9.5. Похідні класи й покажчики
- •7.9.6. Ієрархія типів
- •XyValue(int_x,int_y):X(_x),y(_y)
- •XyData(int_x,int_y)
- •7.9.7. Множинне успадкування
- •Void SetLoc(int_x,int_y);};
- •Int data;
- •7.10. Віртуальні функції та класи
- •7.10.1. Віртуальні функції
- •Int value;
- •Virtual int GetValue();
- •Int Value::GetValue(){return value;}
- •7.10.2. Чисті віртуальні функції. Абстрактні класи
- •Virtual void f1(void);
- •Virtual void f2(void);//...}
- •Int index;
- •7.10.3. Віртуальні деструктори
- •7.10.4. Посилання як засіб для реалізації поліморфізму
- •7.10.5. Дещо про механізм віртуальних функцій
- •Virtual int method1(float r);
- •Int data;
- •Void func(void){//тіло}};
- •Virtual public CocaCola {
- •Int size;
- •Void ShowValue(void)
- •Void ShowValues(void);};
- •Void Two::ShowValues(void)
- •7.11.2. Дружні функції
- •Void Show(One &c1,Two &c2)
- •Void Show(One &c1);
- •Void Two::Show(One &c1)
- •7.12. Перевантаження операцій
- •7.12.1. Загальний підхід
- •Void main()
- •7.12.2. Перетворення типів
- •X::operator т();
- •7.12.3. Перевантаження операції індексування масиву
- •Int znach;
- •7.12.4. Перевантаження операції виклику функції
- •Int operator()(void);
- •Int FuncClass::operator()(void)
- •Vidnosh*vec;
- •7.12.5. Перевантаження операції доступу до члена класу
- •7.12.6. Перевантаження операцій інкремента й декремента
- •Int index;
- •Void*operator new(size_t)
- •Void*operator new(size_t);
- •8.1. Функціональні шаблони
- •8.1.1. Визначення й використання шаблонів функцій
- •Void func(t t)
- •Int main(void)
- •8.1.2. Перевантаження шаблонів функції
- •Int main(void)
- •8.1.3. Cпецiалiзованi функцiї шаблона
- •Int main(void){
- •8.2. Шаблони класів
- •8.2.1. Визначення шаблонів класу
- •Void push(t t);
- •Int numitems;
- •8.2.2. Константи й типи як параметри шаблона
- •8.2.3. Використання шаблонних класів
- •Int main(void)
- •8.2.4. Спеціалізація шаблонів класу
- •Void add(t item);
- •Int main(void)
- •IArray.Add(i1);
- •Int main(void)
- •IList.Add(i1);
- •Завдання для самостійної роботи
- •Автоматна технологія програмування
- •If(!stop)printf("не входити");
- •Завдання для самостійної роботи
- •Список літератури
- •Передмова 3
Автоматна технологія програмування
Основи сучасного програмування Основи сучасного програмування Основи сучасного програмування Основи сучасного програмування Основи сучасного програмування Основи сучасного
Найважливішими технологіями останніх десятиріч є методології структурного та об'єктно-орієнтованого програмування. Паралельно з ними використовується й розвивається метод програмування, заснований на скінченних автоматах (СА). Легко можна переконатися, що ООП успадковувало у СА такі позиції, як об'єкт і виконання функцій, властивих даному стану об'єкта.
Ми розглядатимемо не загальну теорію СА (хоча й вивчимо деякі властивості й визначення), а "автоматний" стиль мислення для розв’язання задач певного класу.
Модель СА – така сама алгоритмічна модель, як і блок-схема. Проте автомат має якості, яких у ній немає. Принциповою суттю СА є наявність станів, на основі яких відбуваються всі дискретні перетворення.
Скінченний автомат як формалізм. Роботу лексичного аналізатора краще описувати формалізмом СА.
Означення 9.1. СА – це п'ятірка (К, VT, F, H, S), де
К – скінченна множина станів; VT – скінченна множина допустимих вхідних символів; F – відображення множини K × VT → K, що визначає поведінку автомата; відображення F часто називають функцією переходів; HК – початковий стан; SK – завершальний стан (або скінченна SK завершальних станів). F(А, t) B означає, що зі стану А по вхідному символу t відбувається перехід у стан B.
У кожен поточний момент часу СА може перебувати в одному з можливих станів (кількість станів, у яких може знаходитися СА, скінченна). Автомат послідовно прочитує символи вхідного тексту (рядки). Кожен прочитаний символ або переводить автомат у новий стан, або залишає його в поточному. Формально автомат можна описати за допомогою функції переходів. Її аргументами є попередній стан автомата і поточний прочитаний символ, а значенням – новий стан автомата.
СА зручно задавати діаграмою його переходів або станів. Діаграма є орієнтованим графом, вершинами якого є однойменні стани автомата; дуга з вершини qi у вершину qk з надписаною над нею літерою аj проводиться тоді й тільки тоді, коли F(qi,аj) qk, тобто коли автомат зі стану qi під впливом аj має перейти в стан qk. У разі, коли перехід з qi у qk здійснюється під впливом будь-якої з літер деякої підмножини S, SА, усі літери цієї підмножини підписуються над відповідною дугою. Якщо довільний стан qi входить у S, то даний факт на діаграмі наголошується жирним кружком, що виділяє вершину qi. Це означає, що автомат перебуває в одному зі своїх завершальних станів.
Очевидно, що будь-який СА повністю визначається своєю діаграмою переходів. Під задачею побудови СА, що має ті чи інші властивості, розумітимемо задачу побудови діаграми його станів.
Основною властивістю стану є те, що в ньому СА чекає обмежений набір вхідних дій. Будь-який алгоритм має вхідну й вихідну інформацію. Її можна розділити на два типи: змінні (напр., операції над властивостями об'єктів) і функції. Друга властивість стану – видача набору фіксованих значень вихідних змінних. Це означає, що в будь-який момент часу визначені значення всіх вихідних змінних, оскільки алгоритм завжди перебуває в якомусь стані.
На рис. 9.1. подано діаграму автомата К, що працює зі словами алфавіту A {а, b, c}. Автомат має два стани, q0 і q1, завершальним є стан q1. Почавши роботу в стані q0, автомат при отриманні на вхід літер а, b у новий стан не переходить; при отриманні літери с здійснюється перехід у стан q1; далі | |
Рис. 9.1 |
наступна літера залишає автомат у тому самому стані. Таким чином, автомат K розпізнає мову L1, що складається зі слів, які мають у своєму складі літеру с, тому запропонована мова є регулярною.
Природно, що при виконанні дій можливі виклики вкладених СА.
Кількість станів і наборів значень вихідних змінних обмежена. Діаграми станів, що ілюструють роботу СА, стали невід'ємною частиною основних принципів об'єктно-орієнтованого програмування.
Основною особливістю програмної реалізації СА є обов'язкова наявність циклу. На відміну від традиційних алгоритмів, що включають два типи компонент – умову й дію, – cкінченний автомат має додаткову компоненту – стан.
Основи конструювання програм за допомогою СА. Одним з інструментів розгляду рядків символів є СА. Зазвичай вони застосуються для розбиття початкового рядка на набір деяких лексичних одиниць.
Нехай дано два масиви, що закінчуються нулем. Другий відрізняється від першого додатковими вставками з чисел. Наприклад:
M = 1 2 3 4 5 6 7 8 9 0
N = 1 2 3 4 5 3 2 4 43 6 7 8 9 0
Програма має порівняти два масиви й надрукувати вставки, що зустрічаються.
Масиви позначимо M і N, а номери їх елементів – відповідно i і j.
Необхідно написати програму знаходження й друку вставок.
Побудуємо діаграму станів. Позначимо початковий стан як А. Перебиратимемо елементи масиву, використовуючи цикл (NonStop=true). Вихід із циклу (NonStop=false) обробки масивів здійснюється після закінчення елементів або в обох масивах, або в одному. Із початкового стану здійснюється перехід до поки що невідомого стану й виконання певних дій. Цей етап подано на рис. 9.2.
Рис. 9.2
Розглянемо новий стан В. У ньому порівнюватимемо елементи двох масивів. Поки порівнювані елементи рівні – послідовності збігаються. Тому стан В назвемо станом очікування появи вставки, або нерівності поточних елементів масивів. У цьому стані виконуватиметься приріст номерів елементів масивів на одиницю (i=i+1;j=j+1). Оскільки робота починається з першого елемента масивів, то i=1; j=1. Це перша дія при виході з початкового стану.
У стані В порівнюватимемо елементи M[i] і N[j]. У разі їх нерівності (локальна подія M[i]<>N[j]) переходимо в невідомий поки стан (позначимо його С) із запам'ятовуванням поточних номерів елементів масивів у додаткових змінних i1=i;j1=j (рис. 9.3).
У стані В може закінчитися список елементів масивів – M[i]=0 і N[j]=0. Якщо досягнутий кінець другого масиву й не досягнутий першого (подія EndN, або N[j]=0), то формується повідомлення про помилку і здійснюється вихід. Для цього вводиться перехід зі стану В у стан А за локальною подією ЕndN з дією на переході Write('Error') і NonStop=false, де Write('Error') – друк повідомлення про помилку. Якщо обидва чергові елементи масивів нульові, то відбувається локальна подія ЕndМ&EndN і програма переходить у стан А з дією коректного завершення NonStop=false.
Рис. 9.3
Таким чином, у стані В вибираємо чергову пару елементів порівнюваних масивів і чекаємо появи однієї з трьох локальних подій:
1. EndM & not EndN – закінчилися елементи в другому масиві й не закінчилися в першому.
2. EndM & EndN – закінчилися елементи в обох масивах.
3. M[i]<>N[j] – знайшли незбіжні елементи.
За кожною із цих подій здійснюється перехід зі стану В або до стану А – за подіями 1 і 2, або до невідомого поки що стану С – за подією 3. Якщо жодна з цих подій не трапилася в поточному циклі, то програма залишається в стані В, що відображається дугою з В у В.
Розглянемо події, які відбуватимуться в стані С. У цьому стані відбувається очікування появи елемента N[j1], збіжного з M[i1]. Настання цієї події свідчить про завершення вставки. У стані С необхідно здійснити приріст індексу j1 елемента другого масиву і друк відповідного елемента (рис. 9.4).
Іншою локальною подією є кінець послідовності другого масиву, тобто ЕndN(N[j1]=0). За цією подією здійснюється перехід у початковий стан А і формується код виходу з циклу – NonStop=false.
Поки не будуть знайдені збіжні елементи, знаходимося в стані С. Задачу розв'язано.
Рис. 9.4
Введемо такі означення для реалізації СА у вигляді програми.
Означення 9.2. Говоритимемо, що локальна подія відбулася, якщо маємо позитивний результат деякого логічного виразу.
Якщо локальних подій у програмі більше одної, то кожній з них ставиться у відповідність свій логічний вираз.
Пронумеруємо стани СА (за допомогою літер або цифр). Тоді локальні події позначимо двома індексами, перший з яких позначає поточний стан, а другий – стан, у який має перейти програма в наступному циклі при настанні даної локальної події.
Означення 9.3. Стан є фрагментом програми, у якому очікується локальна подія за наслідками обчислень логічних виразів, що входять у даний фрагмент програми.
Якщо жодна локальна подія при поточному проходженні циклу не відбулася, то програма залишається в тому самому стані, тобто обчислює той самий фрагмент і в наступному проходженні циклу. Якщо локальна подія настала, то до наступного проходження циклу програма переходить у новий стан – до іншого фрагмента, де мають очікуватися інші локальні події.
Іншими словами: стан програми, що реалізує СА – це зациклення на одному й тому самому фрагменті програми до настання локальної події. Позначимо стан як where.
Означення 9.4. Переходом назвемо зміну поточного стану на інший.
При цьому змінна стану where змінює своє значення.
Кожен перехід супроводжується деякою дією. Це означає, що при настанні локальної події потрібно не тільки змінити змінну where, але й виконати задану послідовність операторів. Тому загальний вигляд розв'язання задачі за допомогою СА такий:
int NonStop=1;
while(NonStop)
{
//тіло автомата;
}
де NonStop – ознака продовження циклу, який перед передаванням керування оператору while має бути встановлений на значення true.
Уся логіка роботи програми полягає в циклічній обробці станів. Вихід із циклу здійснюється за виконання умови закінчення обробки. У тілі циклу відбувається обробка кожного зі станів. СА може бути реалізований так:
While (NonStop)
switch(where)//Змінна стану автомата.
{
case А:
//перевірка умов на дугах і петлях і;
//виконання переходів і дій
break;
...
case Z:
//перевірка умов на дугах і петлях і;
//виконання переходів і дій
break;
};
Розглянемо процес програмної реалізації СА детальніше.
Розв'яжемо задачу про видалення з тексту коментаря програми, що написана мовою Паскаль. Нагадаємо, що коментарем називається послідовність символів, вміщена в спеціальні дужки: (*…*).
Видалення коментаря з тексту проводиться шляхом збереження основного тексту й незбереження тексту коментаря. Символи читаються й записуються, поки не зустрінуть початок коментаря. Потім початок коментаря та його символи просто прочитуються без збереження. Після досягнення закінчення коментаря знову читаємо та зберігаємо символи. Розберемо цей процес детальніше.
Якщо поточним прочитаним символом є "(", то далі, можливо, ітимуть символи, що утворюватимуть коментар, тобто можливий перехід у стан початку коментаря. Це залежить від наступного прочитаного символу. Якщо прочитаний символ "*", то "(" і "*" не зберігаються, і процес читання продовжується без збереження поточних символів. Інакше "(" зберігаємо й читаємо (зі збереженням) символи далі. Читання триває до появи символу "*". Це означає, що, можливо, далі йтиме стан закінчення коментаря. Якщо наступний прочитаний символ ")", то коментар закінчений, інакше продовжуємо читати й не зберігати прочитаний текст.
Можна виділити стани, у яких перебуває наша програма.
Стан "поза коментарем". У цьому стані читаємо й записуємо символи тексту.
Стан можливого початку коментаря. Прочитано символ "(". При читанні наступного символу можливі такі ситуації: поточним є прочитаний символ "*". У цьому випадку "(" і "*" не зберігаються, і програма переходить у стан "усередині коментаря"; поточним є будь-який інший символ. У цьому випадку зберігаємо символ "(" і переходимо в стан "поза коментарем".
Стан "усередині коментаря". Якщо прочитано поточний символ "*", то переходимо в стан можливого закінчення коментаря, інакше залишаємося в початковому стані, тобто продовжуємо читати без збереження тексту.
Стан можливого закінчення коментаря. Прочитано символ "*". При читанні наступного символу можливі такі ситуації: поточним є прочитаний символ "*". У цьому випадку необхідно зберегти попередній символ і залишитися в стані можливого закінчення коментаря; поточним є будь-який інший символ. У цьому випадку здійснюємо перехід у стан "усередині коментаря".
Складемо діаграму станів (рис. 9.5).
Рис.
9.5
Позначимо через а читання будь-якого символу, окрім "("; b –будь-якого символу, окрім "(" і "*"; d – будь-якого символу, окрім "*" і ")"; А – стан "поза коментарем"; B – стан можливого початку коментаря; С – стан "усередині коментаря"; D – стан можливого кінця коментаря.
Деталізуємо автоматний алгоритм:
static char where='A';
int NonStop=1;
while(NonStop)
{
switch(where)
{
case 'A':
//обробка стану А;
break;
case 'B':
//обробка стану B;
break;
case 'C':
//обробка стану З;
break;
case 'D':
//обробка стану D;
break;
}
}
Локальній події взаємно однозначно відповідає перехід з поточного стану в інший. Якщо жодна з обчислюваних у даному стані локальних подій не настала, то зберігається поточний стан. Цьому відповідає логічна умова, що заперечує умову переходів в інші стани.
За локальною подією програма переходить у наступному циклі в новий або залишається в старому стані. Однак перш ніж перейти в новий стан, вона має здійснити деяку дію на переході.
Правильна програма, що реалізовує СА, має містити дію закінчення циклу, щоб уникнути зациклення в одному стані.
Введемо такі позначення:
поза коментарем – out;
можливий початок коментаря – beg_com;
усередині коментаря – in_com;
можливий кінець коментаря – end_com.
При читанні першого символу СА знаходиться в початковому стані – where=out. Подальші дії здійснюються залежно від значення where. Наприклад, якщо where=out і current='/', то переходимо в стан "початок коментаря" – змінюється стан СА, тобто треба змінній where надати значення beg_com. Якщо читається будь-який інший символ, то необхідно його записати у вихідний файл, і СА залишиться в поточному стані.
out:switch(current)
{
case '/':where=beg_com;break;
default:putc(current,f_out);
}
Нехай, наприклад, СА перебуває у стані "початок коментаря". Якщо поточний прочитаний символ '/', то записуємо його у вихідний файл і не змінюємо стан СА; якщо поточний символ '*', то переходимо в стан "усередині коментаря", і where=in_com. При читанні будь-якого іншого символу друкуємо його у вихідний файл і переходимо у стан "поза коментарем" where=out.
beg_com:switch(current)
{
case'/':putc(current,f_out);break;
case'*':where=in_com;break;
default:
putc('/',f_out);
putc(current,f_out);
where=out;
}
Аналогічно програмуються всі інші стани СА.
Тепер легко можна написати програму, що реалізує СА, наведений на рис. 9.5:
#include <stdio.h>
enum states{out,beg_com, in_com, end_com};
char current;
states where;
FILE*f_in,*f_out;
main(){
where=out;
f_in=fopen("c:\\text_in.txt","r");
f_out=fopen("c:\\text_out.txt","w");
while(!feof(f_in))
{
current=getc(f_in);
switch(where) {
case out:
switch(current)
{
case '/':where=beg_com;break;
default:putc(current,f_out);
}
break;
case beg_com:
switch(current)
{
case'/':putc(current,f_out);break;
case'*':where=in_com;break;
default:
putc('/',f_out);
putc(current,f_out);
where=out;
}
break;
case in_com:switch(current){
case'*':where=end_com;
}
case end_com:switch(current)
{
case'*':;
case'/':where=out;
default:where=in_com;
}
}
}
fclose(f_in);
fclose(f_out);
}
Розглянемо ще кілька прикладів.
1. Нехай необхідно зі вхідного файла виділити ідентифікатори, що містять цифри. Припустимо для простоти, що синтаксично ідентифікатори записані правильно.
Для класифікації послідовностей символів визначимо такі множини:
L – 'A'..'Z', 'a'..'z', '_';
D – '0'..'9';
R – роздільники ' ', '.', ',', ':', ';', '-', '+', EOL;
Визначимо стани, у яких знаходитиметься СА:
I – очікування появи ідентифікатора;
L – обробка ідентифікатора. Очікування появи цифри;
LD – обробка ідентифікатора з цифрою. Очікування кінця ідентифікатора.
Автомат має виконувати такі дії:
M – пропустити символ;
А – записати символ у буфер;
W – вивести буфер у вихідний файл і очистити;
С – очистити буфер.
Складемо діаграму станів СА (рис. 9.6).
Рис. 9.6
2. Нехай маємо послідовність символів x1...xn. Визначити, чи є в ній символи "abcd", що йдуть один за одним. Іншими словами, вимагається з'ясувати, чи міститься в слові Х підслово "abcd".
Проглядатимемо слово Х зліва направо в очікуванні появи символу 'a'. Як тільки він з'явиться, чекаємо появи за ним символу 'b', потім 'c' і, нарешті, 'd'.
Таким чином, ми в кожен момент знаходимося в одному з таких станів: початковий (0), очікування b після а (A), очікування с після ab (B), очікування d після abc (C) і вихід після abcd (D).
Складемо діаграму станів (рис. 9.7).
Рис. 9.7
Стан Exit означає закінчення роботи. Напишемо відповідну програму:
enum states{A,B,C,D};
enum boolean{false,true};
char current;
boolean stop;
states where;
FILE*f_in;
main(){
where=A;
stop=false;
f_in=fopen("c:\\text_in.txt");
while(!feof(f)||stop)
{
current=getc(f_in);
switch (where)
{
case A:switch(current)
{
case'a':where=B;break;
default:;
}
break;
case B:switch(current)
{
case'a':;break;
case'b':where=C;break;
default:where=A;
}
break;
case C:switch(current)
{
case'c':where=D;break;
case'a':where=B;break;
default:where=A;
}
break;
case D:switch(current)
{
case'd':
printf("входить\n");
stop=true;
break;
case'a':where=B;break;
default:where=A;
}
}
}