5.8 Математические функции — Math |
Предыдущая Содержание Следующая |
ANSI-C определяет ряд математических функций, такие как sin(), sqrt(), exp() и так далее. В качестве другого упражнения в наследовании, мы собираемся добавить в наш калькулятор библиотечные функции с одним параметром типа double и результатом типа double. Эти функции работают в значительной степени подобно унарным операторам. Можно было бы определить новый тип узла для каждой функции и собрать большую часть функциональности из классов Minus и Name, но есть более простой способ. Мы расширим struct Name в struct Math следующим образом:
struct Math { struct Name _; double (* funct) (double); };
#define funct(tree) (((struct Math *) left(tree)) -> funct)
В дополнение к имени функции, которое будет использоваться при вводе, и маркера для распознавания сохраняем адрес библиотечной функции, подобной sin(), в записи таблицы символов. Для добавления всех описаний функций в таблицу символов во время инициализации вызываем следующую функцию:
#include <math.h>
void initMath (void) { static const struct Math functions [] = { { &_Math, "sqrt", MATH, sqrt }, ... 0 };
const struct Math * mp;
for (mp = functions; mp -> _.name; ++ mp) install(mp); }
Вызов функции является коэффициентом, так же как и использование знака минус. Для присвоения коэффициентов надо расширить нашу грамматику:
factor : NUMBER | - factor | ... | MATH ( sum )
MATH является общим маркером для всех функций, добавляемых initMath(). Это превращается в следующее дополнение к factor() в распознавателе:
static void * factor (void) { void * result; ... switch (token) { case MATH: { const struct Name * fp = symbol;
if (scan(0) != ’(’) error("expecting ("); scan(0); result = new(Math, fp, sum()); if (token != ’)’) error("expecting )"); break; }
symbol сначала содержит элемент таблицы символов для функции подобной sin(). Мы сохраняем этот указатель и строим дерево выражений для аргумента функции вызовом sum(). Затем используем Math, описание типа для функции, и предоставляем new() построить следующий узел для дерева выражений:
Левой стороне бинарного узла предоставляем указывать на элемент таблицы символов для этой функции и подключаем дерево аргументов справа. Бинарный узел имеет Math в качестве описания типа, то есть для обработки и удаления узла будут вызываться методы doMath() и freeMath() соответственно. Узел Math по-прежнему конструируется с помощью mkBin(), так как эту функцию не заботит, какие указатели введены в качестве потомков. freeMath(), тем не менее, может удалить только поддерево правой стороны:
static void freeMath (void * tree) { delete(right(tree)); free(tree); }
Если внимательно посмотреть на картинку, можно увидеть, что работа узла Math очень проста. doMath() требуется вызвать какую-то функцию, хранящуюся в таблице символов элемента, доступного в виде левого потомка бинарного узла, от которого она вызывается:
#include <errno.h>
static double doMath (const void * tree) { double result = exec(right(tree));
errno = 0; result = funct(tree)(result); if (errno) error("error in %s: %s", ((struct Math *) left(tree)) -> _.name, strerror(errno)); return result; }
Единственная проблема заключается отлове числовых ошибок мониторингом переменной errno, задекларированной в заголовочном файле errno.h ANSI-C. Это завершает реализацию математических функций калькулятора.
|
Предыдущая Содержание Следующая |