вызывает expr(). Этот цикл необходимо как-то разорвать, для чего вполне подходит заданное до
определения prim() описание:
double expr(); // это описание необходимо
Функция term() справляется с умножением и делением аналогично тому, как функция expr() со
сложением и вычитанием:
double term()
// умножает и складывает
{
double left = prim();
for(;;)
switch(curr_tok) {
case
MUL:
get_token();
//
случай '*'
left
*=
prim();
break;
case
DIV:
get_token();
//
случай '/'
double
d
=
prim();
if (d == 0) return error("
деление на 0");
left
/=
d;
break;
default:
return
left;
}
}
Проверка отсутствия деления на нуль необходима, поскольку результат деления на нуль неопределен
и, как правило, приводит к катастрофе.
Функция error() будет рассмотрена позже. Переменная d появляется в программе там, где она
действительно нужна, и сразу же инициализируется. Во многих языках описание может находиться
только в начале блока. Но такое ограничение может искажать естественную структуру программы и
способствовать появлению ошибок. Чаще всего не инициализированные локальные переменные
свидетельствуют о плохом стиле программирования. Исключение составляют те переменные, которые
инициализируются операторами ввода, и переменные типа массива или структуры, для которых нет
традиционной инициализации с помощью одиночных присваиваний. Следует напомнить, что = является
Бьерн Страуструп.
Язык программирования С++
73
операцией присваивания, тогда как == есть операция сравнения.
Функция prim, обрабатывающая первичное, во многом похожа на функции expr и term(). Но раз мы
дошли до низа в иерархии вызовов, то в ней кое-что придется сделать. Цикл для нее не нужен:
double number_value;
char name_string[256];
double prim() // обрабатывает первичное
{
switch (curr_tok) {
case NUMBER: // константа с плавающей точкой
get_token();
return
number_value;
case
NAME:
if (get_token() == ASSIGN) {
name*
n
=
insert(name_string);
get_token();
n->value
=
expr();
return
n->value;
}
return
look(name_string)->value;
case MINUS: // унарный минус
get_token();
return
-prim();
case
LP:
get_token();
double
e
=
expr();
if (curr_tok != RP) return error("
требуется )");
get_token();
return
e;
case
END:
return
1;
default:
return error("требуется первичное");
}
}
Когда появляется NUMBER (то есть константа с плавающей точкой), возвращается ее значение.
Функция ввода get_token() помещает значение константы в глобальную переменную number_value. Если
в программе используются глобальные переменные, то часто это указывает на то, что структура не до
конца проработана, и поэтому требуется некоторая оптимизация. Именно так обстоит дело в данном
случае. В идеале лексема должна состоять из двух частей: значения, определяющего вид лексемы (в
данной программе это token_value), и (если необходимо) собственно значения лексемы. Здесь же
имеется только одна простая переменная curr_tok, поэтому для хранения последнего прочитанного
значения NUMBER требуется глобальная переменная number_value. Такое решение проходит потому,
что калькулятор во всех вычислениях вначале выбирает одно число, а затем считывает другое из
входного потока. В качестве упражнения предлагается избавиться от этой излишней глобальной
переменной ($$3.5 [15]).
Если последнее значение NUMBER хранится в глобальной переменной number_value, то строковое
представление последнего значения NAME хранится в name_string. Перед тем, как что-либо делать с
именем, калькулятор должен заглянуть вперед, чтобы выяснить, будет ли ему присваиваться значение,
или же будет только использоваться существующее его значение. В обоих случаях надо обратиться к
таблице имен. Эта таблица рассматривается в $$3.1.3; а здесь достаточно только знать, что она
состоит из записей, имеющих вид:
struct name {
char*
string;
name*
next;
double
value;
};
Бьерн Страуструп.
Язык программирования С++
74
Член next используется только служебными функциями, работающими с таблицей:
name* look(const char*);
name* insert(const char*);
Обе функции возвращают указатель на ту запись name, которая соответствует их параметру-строке.
Функция look() "ругается", если имя не было занесено в таблицу. Это означает, что в калькуляторе
можно использовать имя без предварительного описания, но в первый раз оно может появиться только
в левой части присваивания.
Достарыңызбен бөлісу: |