Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
c++book1.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
2 Мб
Скачать
☆

3.1.2 Функция ввода

Чтение ввода - часто самая запутанная часть программы. Причина в том, что если программа должна общаться с челове­ком, то она должна справляться с его причудами, условностями и внешне случайными ошибками. Попытки заставить человека вес­ти себя более удобным для машины образом часто (и справедли­во) рассматриваются как оскорбительные. Задача низкоуровневой программы ввода состоит в том, чтобы читать символы по одному и составлять из них лексические символы более высокого уров­ня. Далее эти лексемы служат вводом для программ более высо­кого уровня. У нас ввод низкого уровня осуществляется get_token(). Обнадеживает то, что написание программ ввода низкого уровня не является ежедневной работой; в хорошей сис­теме для этого будут стандартные функции.

Для калькулятора правила сознательно были выбраны таки­ми, чтобы функциям по работе с потоками было неудобно эти правила обрабатывать; незначительные изменения в определении лексем сделали бы get_token() обманчиво простой.

Первая сложность состоит в том, что символ новой строки '\n' является для калькулятора существенным, а функции работы с потоками считают его символом пропуска. То есть, для этих функций '\n' значим только как ограничитель лексемы. Чтобы преодолеть это, надо проверять пропуски (пробел, символы та­буляции и т.п.):

char ch

do (* // пропускает пропуски за исключением '\n'

if(!cin.get(ch)) return curr_tok = END;

*) while (ch!='\n' && isspace(ch));

Вызов cin.get(ch) считывает один символ из стандартного потока ввода в ch. Проверка if(!cin.get(ch)) не проходит в случае, если из cin нельзя считать ни одного символа. В этом случае возвращается END, чтобы завершить сеанс работы кальку­лятора. Используется операция ! (НЕ), поскольку get() возвра­щает в случае успеха ненулевое значение.

Функция (inline) isspace() из <ctype.h> обеспечивает стандартную проверку на то, является ли символ пропуском (#8.4.1); isspace(c) возвращает ненулевое значение, если c является символом пропуска, и ноль в противном случае. Про­верка реализуется в виде поиска в таблице, поэтому использо­вание isspace() намного быстрее, чем проверка на отдельные символы пропуска; это же относится и к функциям isalpha(), isdigit() и isalnum(), которые используются в get_token().

После того, как пустое место пропущено, следующий символ используется для определения того, какого вида какого вида лексема приходит. Давайте сначала рассмотрим некоторые случаи отдельно, прежде чем приводить всю функцию. Ограничители лек­сем '\n' и ';' обрабатываются так:

switch (ch) (*

case ';':

case '\n':

cin >> WS; // пропустить пропуск

return curr_tok=PRINT;

Пропуск пустого места делать необязательно, но он позво­ляет избежать повторных обращений к get_token(). WS - это стандартный пропусковый объект, описанный в <stream.h>; он используется только для сброса пропуска. Ошибка во вводе или конец ввода не будут обнаружены до следующего обращения к get _token(). Обратите внимание на то, как можно использовать несколько меток case (случаев) для одной и той же последова­тельности операторов, обрабатывающих эти случаи. В обоих слу­чаях возвращается лексема PRINT и помещается в curr_tok.

Числа обрабатываются так:

case '0': case '1': case '2': case '3': case '4':

case '5': case '6': case '7': case '8': case '9':

case '.':

cin.putback(ch);

cin >> number_value;

return curr_tok=NUMBER;

Располагать метки случаев case горизонтально, а не вер­тикально, не очень хорошая мысль, поскольку читать это гораздо труднее, но отводить по одной строке на каждую цифру нудно.

Поскольку операция >> определена также и для чтения констант с плавающей точкой в double, программирование этого не составляет труда: сперва начальный символ (цифра или точ­ка) помещается обратно в cin, а затем можно считывать конс­танту в number_value.

Имя, то есть лексема NAME, определяется как буква, за которой возможно следует несколько букв или цифр:

if (isalpha(ch)) (*

char* p = name_string;

*p++ = ch;

while (cin.get(ch) && isalnum(ch)) *p++ = ch;

cin.putback(ch);

*p = 0;

return curr_tok=NAME;

*)

Эта часть строит в name_string строку, заканчивающуюся нулем. Функции isalpha() и isalnum() заданы в <ctype.h>; isalnum(c) не ноль, если c буква или цифра, ноль в противном случае.

Вот, наконец, функция ввода полностью:

token_value get_token()

(*

char ch;

do (* // пропускает пропуски за исключением '\n'

if(!cin.get(ch)) return curr_tok = END;

*) while (ch!='\n' && isspace(ch));

switch (ch) (*

case ';':

case '\n':

cin >> WS; // пропустить пропуск

return curr_tok=PRINT;

case '*':

case '/':

case '+':

case '-':

case '(':

case ')':

case '=':

return curr_tok=ch;

case '0': case '1': case '2': case '3': case '4':

case '5': case '6': case '7': case '8': case '9':

case '.':

cin.putback(ch);

cin >> number_value;

return curr_tok=NUMBER;

default: // NAME, NAME= или ошибка

if (isalpha(ch)) (*

char* p = name_string;

*p++ = ch;

while (cin.get(ch) && isalnum(ch)) *p++ = ch;

cin.putback(ch);

*p = 0;

return curr_tok=NAME;

*)

error("плохая лексема");

return curr_tok=PRINT;

*)

*)

Поскольку token_value (значение лексемы) операции было определено как целое значение этой операции*, обработка всех операций тривиальна.

--------------------

* знака этой операции. (прим. перев.)

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