Можно было бы уточнить содержание блоков try
и catch
, но это внесет в программу еще большую путаницу. Ошибки в принципе трудно обрабатывать, а ошибки, возникающие при обработке других ошибок, обрабатывать еще труднее. Поэтому стоит попытаться найти способ удалять из потока Token_stream
символы, которые могут породить исключение. Единственный путь для ввода данных в калькулятор пролегает через функцию get()
, и он может, как мы только что выяснили, порождать исключения. Таким образом, необходима новая операция. Очевидно, что ее целесообразно поместить в класс Token_stream
.
class Token_stream {
public:
Token_stream(); // создает поток Token_stream, считывающий
// данные из потока cin
Token get(); // считывает лексему
void putback(Token t); // возвращает лексему
void ignore(char c); // отбрасывает символы,
// предшествующие символу с включительно
private:
bool full; // есть лексема в буфере?
Token buffer; // здесь хранится лексема, которая возвращается
// назад с помощью функции putback()
};
Функция ignore()
должна быть членом класса Token_stream
, так как она должна иметь доступ к его буферу. Мы выбрали в качестве искомого символа аргумент функции ignore()
. Помимо всего прочего, объект класса Token_stream
не обязан знать, что калькулятор считает хорошим символом для исправления ошибок. Мы решили, что этот аргумент должен быть символом, потому что не хотим рисковать, работая с составными лексемами (мы уже видели, что при этом происходит). Итак, мы получаем следующую функцию:
void Token_stream::ignore(char c)
// символ c обозначает разновидность лексем
{
// сначала проверяем буфер:
if (full && c==buffer.kind) {
full = false;
return;
}
full = false;
// теперь проверяем входные данные:
char ch = 0;
while (cin>>ch)
if (ch==c) return;
}
В этом коде сначала происходит проверка буфера. Если в буфере есть символ c
, прекращаем работу, отбрасывая этот символ c
; в противном случае необходимо считывать символы из потока cin
, пока не встретится символ c
. Теперь функцию clean_up_mess()
можно написать следующим образом:
void clean_up_mess()
{
ts.ignore(print);
}
Обработка ошибок всегда является сложной. Она требует постоянного экспериментирования и тестирования, поскольку крайне трудно представить заранее, какая ошибка может возникнуть в ходе выполнения программы. Защита программы от неправильного использования всегда представляет собой очень сложную задачу. Дилетанты об этом никогда не беспокоятся. Качественная обработка ошибок — один из признаков профессионализма.
7.8. Переменные
Поработав над стилем и обработкой ошибок, можем вернуться к попыткам улучшить функциональные возможности калькулятора. Мы получили вполне работоспособную программу; как же ее улучшить? Во-первых, необходимо ввести переменные. Использование переменных позволяет лучше выражать более длинные вычисления.
Аналогично для научных вычислений хотелось бы иметь встроенные имена, такие как 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
.