Обсуждая проектирование и анализ, мы будем называть такие примеры прецедентами использования (use cases). Впервые сталкиваясь с разработкой калькулятора, большинство людей сразу приходят к следующей логике программы:
read_a_line
calculate // выполните работу
write_result
Этот набросок, конечно, не программа; он называется псевдокодом (pseudo code). Псевдокоды обычно используются на ранних этапах проектирования, когда
еще не совсем ясно, какой смысл мы вкладываем в обозначения. Например, является ли слово “calculate” вызовом функции? Если да, то каковы его аргументы? Для ответа на этот вопрос просто еще не настало время.
6.3.1. Первое приближение
На этом этапе мы действительно еще не готовы написать программу, имитирующую функции калькулятора. Мы просто мало думали об этом, но размышления — трудная работа, а, как большинство программистов, мы стремимся сразу писать какой-то код. Итак, попробуем написать простую программу-калькулятор и посмотрим, к чему это приведет. Первое приближение может выглядеть примерно так:
#include "std_lib_facilities.h"
int main
{
cout << "Пожалуйста, введите выражение (допускаются + и –): ";
int lval = 0;
int rval;
char op;
int res;
cin>>lval>>op>>rval; // считываем что-то вроде 1 + 3
if (op=='+')
res = lval + rval; // сложение
else if (op=='–')
res = lval – rval; // вычитание
cout << "Результат: " << res << '\n';
keep_window_open;
return 0;
}
Иначе говоря, программа считывает пару значений, разделенных оператором, например
2+2
, вычисляет результат (в данном случае
4
) и выводит его на печать. Здесь переменная, стоящая слева от оператора, обозначена как
lval
, а переменная, стоящая справа от оператора, — как
rval
.
Эта программа работает! Ну и что, если программа довольно простая? Очень хорошо получить что-то работающее! Возможно, программирование и компьютерные науки проще, чем о них говорят. Может быть, но не стоит слишком увлекаться ранним успехом. Давайте сделаем кое-что.
1. Несколько упростим код.
2. Добавим операции умножения и деления (например,
2*3
).
3. Добавим возможность выполнять несколько операторов (например,
1+2+3
).
В частности, известно, что корректность входной информации следует проверять (в нашем варианте мы “забыли” это сделать) и что сравнивать значения с несколькими константами лучше всего с помощью инструкции
switch
, а не
if
.
Цепочку операций, например
1+2+3+4
, будем выполнять по мере считывания значений; иначе говоря, начнем с
1
, потом увидим
+2
и добавим
2
к
1
(получим промежуточный результат, равный
3
), увидим
+3
и добавим
3
к промежуточному результату, равному
3
, и т.д.
После нескольких неудачных попыток и исправления синтаксических и логических ошибок получим следующий код:
#include "std_lib_facilities.h"
int main
{
cout <<
<< "Пожалуйста, введите выражение (допускаются +, –, * и /): ";
int lval = 0;
int rval;
char op;
cin>>lval; // считываем самый левый операнд
if (!cin) error("нет первого операнда");
while (cin>>op) { // считываем оператор и правый операнд в цикле
cin>>rval;
if (!cin) error("нет второго операнда ");
switch(op) {
case '+':
lval += rval; // сложение: lval = lval + rval
break;
case '–':
lval –= rval; // вычитание: lval = lval – rval
break;
case '*':
lval *= rval; // умножение: lval = lval * rval
break;
case '/':
lval /= rval; // деление: lval = lval / rval
break;
default: // нет другого оператора: выводим результат
cout << "Результат: " << lval << '\n';
keep_window_open;
return 0;
}
}
error("неверное выражение");
}
Это неплохо, но попытайтесь вычислить выражение
1+2*3
, и вы увидите, что результат равен
9
, а не
7
, как утверждают учителя математики. Аналогично,
1–2*3
равно
–3
, а не
–5
, как мы думали. Мы выполняем операции в неправильном порядке:
1+2*3
вычисляется как
(1+2)*3
, а не
1+(2*3)
, как обычно. Аналогично,
1–2*3
вычисляется как
(1–2)*3
, а не
1–(2*3)
, как обычно. Лентяи! Мы можем считать правило, согласно которому умножение выполняется раньше, чем сложение, устаревшим, но не стоит отменять многовековые правила просто для того, чтобы упростить себе программирование.