- Приоритеты операций
- Работа
Приоритеты операций
Каждая операция имеет свой приоритет, который определяет порядок вычисления операции в выражении: если слева направо следуют подряд несколько операций одинакового приоритета, порядок их вычисления не определен, но результат вычисления гарантированно будет верным, в противном случае вычисляются прежде операции с наибольшим приоритетом, а затем - с меньшим. Порядок вычислений может быть изменен при помощи круглых скобок: выражение в скобках вычисляется всегда прежде остальных, т.е. имеет наивысший приоритет.
В таблице перечислены все операции с указанием их приоритетов:
Операция | Приоритет |
! ~ «унарные плюс и минус» ++ -- ? операция разименования указателя * | 13 |
* / % | 12 |
+ - ( | 11 |
>> и << | 10 |
< <= > >= | 9 |
== != | 8 |
& | 7 |
^ | 6 |
| | 5 |
&& | 4 |
|| | 3 |
? | 2 |
= *= /= += -= &= ^= |= <<= >>= | 1 |
Так как запомнить все приоритеты довольно непросто, желательно на первых порах (и не возбраняется вообще всегда) указывать порядок вычислений при помощи скобок.
Использование операторов ++ и -- совместно с указателями приводит к необходимости помнить о порядке выполнения операций:
// вариант 1
ptr = &temp;
var = *ptr++;
// вариант 2
ptr = &temp;
var = (*ptr)++;
Оба варианта дадут одно и то же значение var, но совсем разные значений для temp и ptr: в первом случае переменная temp не изменится, но увеличится на 1 значение указателя ptr; во втором случае неизменным останется значение указателя, но изменится содержимое переменной, на которую он указывает, т.е. temp увеличится на 1.
Операторы ++ и -- с указателями работают особым образом: они изменяют значение указателя на величину, равную размеру типа элемента, на который указатель указывает. То есть если указатель объявлен как int *ptr, то ptr++ изменит значение указателя на 2, если long *ptr, то ptr++ увеличит указатель на 4 и т.д. Увеличение указателя на 1 происходит только для типа указателя (void *) («абстрактный указатель», просто адрес байта) или (char *).
В любом выражении допустимо комбинировать логические и арифметические выражения, в этом случае следует считать, что логическому значению ЛОЖЬ соответствует арифметическое значение ноль, а значению ИСТИНА - арифметическое значение 1:
var = (5 < 2) + 2; // переменной var будет присвоено значение 2
var = (5 >= 2) + 2; // переменной var будет присвоено значение 3
Особое внимание следует обращать на приоритет операций:
var = (5 > 2) + 2; // var = 3
var = 5 > 2 + 2; // var = 0
Во втором выражении более высокий приоритет имеет арифметическая операция сложения, поэтому переменной var будет присвоено значение выражения 5 > 4, что ЛОЖНО, т.е. var будет равна 0.
Наиболее простым и универсальным советом будет использование скобок везде, где необходимо точно гарантировать порядок вычислений - в этом случае можно считать (конечно, лишь условно!) все операторы равноприоритетными, а порядок вычислений определять лишь с помощью скобок. Возможно, это приведет к некоторой излишней «скобочности» программы, зато гарантирует, что планы программиста не разойдутся с мнением компилятора.
Упомянутые ранее унарные операторы инкремента и декремента - одна из интересных особенностей языка Си. Эти операторы могут применяться только к операнду-переменной, и ни в коем случае не к операнду-константе. Но главное, они могут быть префиксными и постфиксными, т.е. записываться либо слева от переменной, либо справа:
int var s = 1;
var++; // теперь var = 2
--var; // теперь var = 1
var--; // var = 0
Казалось бы, к чему два способа выполнить одно и то же? Все дело в том, как будет использован результат оператора при вычислении выражений. Вот пример:
int var = 5;
int b = var++ - 5; // var становится равным 6
int с = ++var - 5; // var становится равным 7
Переменная Ь окажется равной 0, а переменная с будет равна 2. Все дело в том, что при постфиксном операторе ++ сначала используется значение переменной в выражении, а потом выполняется оператор инкремента, а при префиксной записи - сначала выполняется оператор инкремента, а потом уже обновленное значение переменной используется при вычислении выражения.
Операторы побитового сдвига « и » выполняют перемещение битов операнда слева на количество разрядов, заданное выражением справа. Как уже было сказано, эти операторы сохраняют знак левого операнда, если операнд - число со знаком. Заполнение «освобождаемых» разрядов для беззнаковых чисел происходит нулями.
Унарные логические операции, и операция побитовой инверсии имеют лишь один вариант вычисления. Но, к сожалению, на этом простые операторы заканчиваются, все прочие операторы имеют более сложную форму записи и, соответственно, выполняют более сложные действия, заслуживая отдельного рассмотрения.