Поработав над стилем и обработкой ошибок, можем вернуться к попыткам улучшить функциональные возможности калькулятора. Мы получили вполне работоспособную программу; как же ее улучшить? Во-первых, необходимо ввести переменные. Использование переменных позволяет лучше выражать более длинные вычисления.
Аналогично для научных вычислений хотелось бы иметь встроенные имена, такие как
pi
и
e
, как в научных калькуляторах. Переменные и константы — основные новшества, которые мы внесем в калькулятор. Это коснется многих частей кода. Такие действия не следует предпринимать без весомых причин и
без достаточного времени на работу. В данном случае мы вносим переменные и константы, поскольку это дает возможность еще раз проанализировать код и освоить новые методы программирования.
7.8.1. Переменные и определения
Очевидно, что для работы с переменными и константами программа-калькулятор должна хранить пары (имя, значение) так, чтобы мы имели доступ к значению по имени. Класс
Variable
можно определить следующим образом:
class Variable {
public:
string name;
double value;
Variable (string n, double v) :name(n), value(v) { }
};
Член класса name используется для идентификации объекта класса
Variable
, а член
value
— для хранения значения, соответствующего члену
name
. Конструктор добавлен просто для удобства.
Как хранить объекты класса
Variable
так, чтобы их значение можно было найти или изменить по строке
name
? Оглядываясь назад, видим, что на этот вопрос есть только один правильный ответ: в виде вектора объектов класса
Variable
.
vector<Variable> var_table;
В вектор
var_table
можно записать сколько угодно объектов класса
Variable
, а найти их можно, просматривая элементы вектора один за другим. Теперь можно написать функцию
get_value
, которая ищет заданную строку
name
и возвращает соответствующее ей значение
value
.
double get_value(string s)
// возвращает значение переменной с именем s
{
for (int i = 0; i<var_table.size; ++i)
if (var_table[i].name == s) return var_table[i].value;
error("get: неопределенная переменная", s);
}
Этот код действительно прост: он перебирает объекты класса
Variable
в векторе
var_table
(начиная с первого элемента и продолжая до последнего включительно) и проверяет, совпадает ли их член name c аргументом
s
. Если строки name и
s
совпадают, функция возвращает член
value
соответствующего объекта. Аналогично можно определить функцию
set_value
, присваивающую новое значение члену
value
объекта класса
Variable
.
void set_value(string s, double d)
//
присваивает объекту класса Variable с именем s значение d
{
for (int i = 0; i<var_table.size; ++i)
if (var_table[i].name == s) {
var_table[i].value = d;
return;
}
error("set: неопределенная переменная", s);
}
Теперь можем считать и записывать переменные, представленные в виде объектов класса
Variable
в векторе
var_table
. Как поместить новый объект класса
Variable
в вектор
var_table
? Как пользователь калькулятора должен сначала записать переменную, а затем присвоить ей значения? Можно сослаться на обозначения, принятые в языке С++.
double var = 7.2;
Это работает, но все переменные в данном калькулятора и так хранят значения типа
double
, поэтому явно указывать этот тип совершенно не обязательно. Можно было бы написать проще.
var = 7.2;
Что ж, возможно, но теперь мы не можем отличить определение новой переменной от синтаксической ошибки.
var1 = 7.2; // определение новой переменной с именем var1
var1 = 3.2; // определение новой переменной с именем var2
Ой! Очевидно, что мы имели в виду
var2 = 3.2;
но не сказали об этом явно (за исключением комментария). Это не катастрофа, но будем следовать традициям языков программирования, в частности языка С++, в которых объявления переменных с их инициализацией отличаются от присваивания. Мы можем использовать ключевое слово
double
, но для калькулятора нужно что-нибудь покороче, поэтому — следуя другой старой традиции — выбрали ключевое слово
let
.
let var = 7.2;
Грамматика принимает следующий вид:
Вычисление:
Инструкция
Печать
Выход
Инструкция вычисления
Инструкция:
Объявление
Выражение
Объявление:
"let" Имя "=" Выражение
Вычисление — это новое правило вывода в грамматике. Оно выражает цикл (в функции
calculate
), который позволяет выполнять несколько вычислений в ходе одного сеанса работы программы. При обработке выражений и объявлений это правило опирается на правило Инструкция. Например, инструкцию можно обработать следующим образом: