Приоритет операторов и порядок вычислений
Когда компьютер встречает уравнение, он выполняет математические действия не просто слева направо. Прежде всего он просматривает строку и определяет порядок выполнения операций, основываясь на приоритете операторов. Приоритет операторов означает, что одни операторы выполняются раньше других независимо от того, в какой последовтельности они записаны в уравнении. Приоритет операторов умножения и деления выше, чем приоритет сложения и вычитания, что совпадает с последовательностью выполнения математических действий в уравнении (вспомните школьные примеры на «порядок действий»).
Компьютер просматривает уравнение и выполняет все операции умножения и деления в том порядке, в каком они записаны в инструкции, не отдавая предпочтения ни одному из двух действий. Если оператор деления стоит перед оператором умножения, деление будет выполнено первым. Промежуточные результаты помещаются в специальной, временно отведенной для этого области памяти*. После выполнения всех операций умножения и деления, компьютер возвращается к началу выражения и выполняет сложение и вычитание в порядке, соответствующем расположению операторов.
Рассмотрим простое уравнение:
a = 1 + 4 / 2 * 5 + 3;Если выполнять действия просто слева направо, не отдавая предпочтения ни одному из них, полученный результат будет равен 15.5 (рис.6.5). Однако компьютер учитывает порядок выполнения математических операций, и результат поэтому получится равным 14 (на рис.6.6 показано, как вычисляется этот результат). Когда вы пишете программу, следует внимательно следить за тем, выполняются ли математические операции в желаемом порядке.
Рис. 6.5. Выполнение математических операций без учета приоритета
Рис. 6.6. Компьютер выполняет математические операциис учетом приоритета операторов
____________________________________
* Автор имел в виду машинный стек, но на самом деле современные компиляторы чаще сохраняют промежуточные результаты вычислений на регистрах центрального процессора, а значения выражений, не содержащих имен переменных, вычисляют еще на стадии компиляции. (Прим.перев.)
Очень часто начинающие программисты допускают ошибку при расчетах средних значений. В Листинге 6.3 приведен текст программы, в которой сделана попытка ввести три числа и вычислить их среднее арифметическое. Мы говорим «попытка», так как полученное в результате работы этой программы значение будет неправильным. Проверьте, сможете ли вы определить, почему?
Листинг 6.3. Неправильная программа расчета среднего арифметического значения трех чисел.
/*average.c*/main() { float number_1, number_2, number_3, average; printf("Введите первое число: "); scanf("%f", &number_1); printf("Введите второе число: "); scanf("%f", &number_2); printf("Введите третье число: "); scanf("%f", &number_3); average = number_1 + number_2 + number_3 / 3; printf("Среднее арифметическое равно %f", average); }Ошибка содержится в инструкции
average = number_1 + number_2 + number_3 / 3;Дойдя до этой строки программы, компьютер будет выполнять математические действия в следующем порядке:
- Поделит значение переменной number_3 на 3.
- Сложит значения переменных number_1 и number_2 и прибавит к ним результат деления.
Если вы присвоите каждой переменной значение 100, выведенное на экран среднее арифметическое будет равняться 233.33. Вот правильная запись этой строки инструкций:
average = (number_1 + number_2 + number_3) / 3;Круглые скобки изменяют порядок приоритетов. Просматривая эту строку, компьютер прежде всего выполнит действия в скобках, затем вернется к началу строки и выполнит остальные операции в соответствии с обычным приоритетом операторов, о котором мы только что говорили. Таким образом, в нашей программе будет сперва выполнено сложение трех чисел в скобках, а потом результат поделен на 3 (рис.6.7). Вуаля! Получился правильный ответ.
Рис. 6.7. Правильная формула расчета среднего арифметического значения
Рис. 6.8. Использование нескольких уровней круглых скобок
Для того чтобы установить нужный порядок выполнения операций, можно использовать несколько уровней скобок (рис.6.8). Порядок выполнения операций установлен от самых внутренних скобок к наружным.
Рассмотрим пример. Допустим, работник получает оплату в двойном размере за каждый час, проработанный сверх 40-часовой рабочей недели. Полагая, что он работает как минимум 40 часов и, соответственно, получает плату как минимум за 40 рабочих часов, расчет недельного заработка состоит из следующих частей:
40 * rate /* обычный недельный заработок */hours – 40 /* сверхурочные часы */rate * 2 /* оплата сверхурочных */
где rate — плата за 1 час работы, а hours — общее количество проработанных часов. В этом уравнении надо умножить количество сверхурочных часов на оплату в двойном размере, а затем прибавить полученный результат к обычной недельной оплате. Если вы напишете уравнение без учета приоритета операторов, оно будет выглядеть так:
total = 40 * rate + hours - 40 * rate * 2Допустим теперь, некто проработал 48 часов в неделю, причем его обычный заработок составляет 10 долларов в час. В приведенном уравнении операции будут выполняться в следующем порядке:
Операция | Результат |
40 * rate | |
40 * rate | |
400 * 2 | |
400 + 48 | |
448 – 800 | –352 |
Согласитесь, что получить недельный заработок в размере –352 доллара несколько обидно. Чтобы написать правильное уравнение, используйте скобки: total = (40 * rate) +((hours - 40) * (rate * 2)) Теперь уравнение состоит из двух логических частей (полный текст программы приведен в Листинге6.4). В первых скобках вычисляется обычная заработная плата, во вторых — оплата сверхурочных. Расчет сверхурочных тоже состоит из двух частей, заключенных в собственные скобки. Си прежде всего выполнит операции во внутренних скобках. Порядок выполнения действий теперь станет таким:
Операция | Результат |
40 * rate | |
hours – 40 | |
rate * 2 | |
8 * 20 | |
400 + 160 |
Листинг 6.4. Программа расчета недельного заработка с учетом сверхурочных.
/*payroll1.c*/main() { float rate, hours, total; printf("Введите оплату одного часа работы: "); scanf("%f", &rate); printf("Введите количество отработанных часов: "); scanf("%f", &hours); total = (40 * rate) + ((hours -40) * (rate*2)); printf("Ваш недельный заработок: %f", total); }Если вы находите, что использование скобок сбивает вас с толку, разбейте уравнение на несколько отдельных частей. В Листинге6.5 приведен текст программы, которая выполняет тот же расчет недельного заработка, но проводит его в несколько последовательных этапов, определяя для этого дополнительные переменные. Результат каждого отдельного вычисления присваивается конкретной переменной и может быть выведен на экран, что делает получаемую информацию даже несколько более наглядной. Этот прием позволяет полностью контролировать весь процесс вычисления и с большей легкостью находить ошибки.
Листинг 6.5. Программа, осуществляющая вычисление в несколько этапов.
/*payroll2.c*/main(){float rate, hours, total, regular, extra, d_time, overtime;printf("Введите оплату одного часа работы: ");scanf("%f", &rate);printf("Введите количество отработанных часов: ");scanf("%f", &hours);regular = 40 * rate;extra = hours - 40;d_time = rate * 2;overtime = extra * d_time;total = regular + overtime;printf("Ваш недельный заработок: %f", total);}