11.05 Оператор switch и разбор ДКА
Это проект занятия, которого нет в планах
Switch
Когда в алгоритме требуется цепочка операторов вида «если целое число равно A — сделать то-то; если равно B — сделать то-то, …», лучше применять оператор switch:
Оператор switch() 1 #include <stdio.h> 2 3 int main(void) { 4 char *ans; 5 for (int i=0; i<6; i++) { 6 printf("%d. Are you sure (Yes/No/Cancel)? ", i); 7 switch(getchar()) { 8 case 'Y': 9 case 'y': ans = "Yes"; break; 10 case 'N': 11 case 'n': ans = "No"; break; 12 case 'C': 13 case 'c': ans = "Cancel"; i=6; break; 14 case EOF: i=6; 15 default : ans = "UNKNOWN"; i--; break; 16 case '\n': i--; continue; 17 } 18 getchar(); /* перевод строки после ввода символа */ 19 puts(ans); 20 } 21 }
Клаузы case …: — это метки (как в goto), только вместо идентификаторов используются целые числа — возможные значения выражения внутри switch()
Поскольку это метки, все операторы выполняются подряд — для выхода из switch надо ставить break
Где в примере несколько case выполняются подряд
Метка default отвечает за значения switch(), не упомянутые в case
- Может быть не последней!
Что вычисляет вот такое выражение: -(a<0) + (a>0)?
Написать функцию classify(число), которая определяет, какому интервалу принадлежит число: ]-∞…-10], ]-10…0[, [0], ]0…10[ или [10…∞[, и возвращает -100500, -10, 0, 10 и 42 соответственно. Условие: обработать в switch() целочисленное выражение, определяющее принадлежность интервалу (по аналогии с -(a<0) + (a>0))
Enum
Константы в switch() — целочисленные (в виде числе или char-литералов). Во избежание «магических чисел» можно использовать #define. Но если есть необходимость превратить последовательность числе в идентификаторы, надо пользоваться enum.
1 enum { A, B, C = 10, D, E = 1, F, G = F + C };- Задаёт константы с указанными имена, прибавляя 1 к предыдущей, если значение не указано
- Здесь: A=0, B=1, C=10, D=11, E=1, F=2, G=12
- TODO: падаванка на вывод элементов енума через printf
Классификация животных (и по этой ссылке слабонервным ходить не рекомендуется) 1 enum {ROACH, KALUSHA, WHALE, HIPPO, FLY, COW, DRAKE};напишите функцию classify_animal(unsigned int a), которая при помощи switch(a) и приведенного выше enum'a разделяет животных на классы и выводит через printf() название класса (известные классы — "insect" и "mammal", для прочих животных выводить "unknown").
в switch'e используйте case без break (для одинаковой обработки вариантов из одного класса), а также default
вызовите написанную функцию для всех значений enum'a
Именованный enum Задаёт целочисленный тип enum color
Добавить в пример другой тип enum и проверить (gcc -Wall -Wextra): можно ли в переменную типа enum color присвоить любое целое вне диапазона
можно ли в переменную типа enum color присвоить значение из другого enum
Детерминированные конечные автоматы
( https://madebyevan.com/fsm/ https://fsmd.moritzliedtke.com/ TODO захостить + доделать autocrop?) )
- Разбор конечным автоматом входной строки
- Автомат "распознает" строку, если в результате ее посимвольной обработки он для каждого символа находит переход (из текущего состояния), и по завершении строки останавливается в финальном состоянии. Конец строки может "просто" наступить по исчерпанию символов, а может — по символу EOS (end of string).
какие строки распознает этот автомат?
какие строки распознает этот автомат?
Построение простого парсера:
С помощью enum задаём состояния
- В переменной храним состояние, начинаем со стартового
- В цикле обрабатываем строку посимвольно
switch по состоянию
Внутри состояния — switch по символу
- Переход в новое состояние
Ошибка, если символ неожиданный (default)
- Финальное состояние — разбор успешен
Yes / No
Предлагаю выпеднриться и написать всё руками, без копипасты отсюда (но с копипастой в редакторе, конечно)
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 enum { BASE, Y, Ye, Yes, N, No };
5
6 int ynparser(char *str) {
7 int state = BASE;
8 for(char *c=str; *c; c++)
9 switch(state) {
10 case BASE:
11 switch(*c) {
12 case 'Y': state=Y; break;
13 case 'N': state=N; break;
14 default : return -1;
15 }
16 break;
17 case Y:
18 switch(*c) {
19 case 'e': state=Ye; break;
20 default : return -1;
21 }
22 break;
23 case Ye:
24 switch(*c) {
25 case 's': state=Yes; break;
26 default : return -1;
27 }
28 break;
29 case Yes:
30 return 1;
31 case N:
32 switch(*c) {
33 case 'o': state=No; break;
34 default : return -1;
35 }
36 break;
37 case No:
38 return 0;
39 default: /* Не должно случиться! */
40 abort();
41 }
42 return -1;
43 }
44
45 #define SZ 12
46 int main(void) {
47 char yn[SZ];
48 fgets(yn, SZ, stdin);
49 printf("%d: %s", ynparser(yn), yn);
50 return 0;
51 }
Модифицировать пример выше, чтобы парсер распознавал Yeeee…eees
Транслятор
В момент разбора строки можно выполнять разнообразные действия.
- TODO: заменить задачу. В имеющемся автомате состояния по существу НЕРАЗЛИЧИМЫ. Нужна задача с различимыми (по выполняемым на выходе из них действиям) состояниями.
Написать парсер двоичного представления числа (с использованием двойного switch()). Действия, которые выполняются при получении того или иного символа, указаны на рёбрах после «|»
Д/З
«Большая» задача
В цикле вводить строки, предположительно содержащие вещественное число в любом из двух форматов (экспоненциальном и с фиксированной точкой). Конец ввода — EOF. Написать функцию int checkfloat(char *str, float *num), распознающую число. Функция записывает введённое число в *num и возвращает 0 в случае, если строка распознана. В противном случае возвращается -1, а *num не меняется. Буква 'e' может быть как большой, так и маленькой; в начале числа и порядка может стоять '+', '-' и ничего. Программа выводит введённое число в формате "%f", если ввод корректен, и NaN, если нет. Переводы строки игнорируются, пробелы — нет.
