Чтобы правильно вычислять выражения (например, 4 + 2 * 3
), мы должны знать, что делают определенные операторы и в каком порядке они выполняются. Последовательность, в которой они выполняются, называется приоритетом операций. Следуя обычным правилам математики (в которой умножение следует перед сложением), выражение, приведенное выше, обрабатывается следующим образом: 4 + (2 * 3) = 10
.
В языке C++ все операторы (операции) имеют свой уровень приоритета. Те, в которых он выше, выполняются первыми. В таблице, приведенной ниже, можно увидеть, что приоритет операций умножения и деления (5) выше, чем в операциях сложения и вычитания (6). Компилятор использует приоритет операторов для определения порядка обработки выражений.
А что делать, если у двух операторов в выражении одинаковый уровень приоритета, и они размещены рядом? Какую операцию компилятор выполнит первой? А здесь уже компилятор будет использовать правила ассоциативности, которые указывают направление выполнения операций: слева направо или справа налево. Например, в выражении 3 * 4 / 2
операции умножения и деления имеют одинаковый уровень приоритета (5-й уровень). А ассоциативность пятого уровня соответствует выполнению операций слева направо, таким образом: (3 * 4) / 2 = 6
.
Таблица приоритета и ассоциативности операций
Несколько примечаний:
1 означает самый высокий уровень приоритета, а 17 — самый низкий. Операции с более высоким уровнем приоритета выполняются первыми.
L -> R
означает слева направо.
R -> L
означает справа налево.
Ассоциативность | Оператор | Описание | Пример |
1. Нет | :: | Глобальная область видимости (унарный) | ::имя |
:: | Область видимости класса (бинарный) | имя_класса::имя_члена | |
2. L -> R | () | Круглые скобки | (выражение) |
() | Вызов функции | имя_функции(parameters) | |
() | Инициализация | имя_типа(выражение) | |
{} | uniform-инициализация (C++11) | имя_типа{выражение} | |
type() | Конвертация типа | новый_тип(выражение) | |
type{} | Конвертация типа (C++11) | новый_тип{выражение} | |
[] | Индекс массива | указатель[выражение] | |
. | Доступ к члену объекта | объект.имя_члена | |
-> | Доступ к члену объекта через указатель | указатель_объекта->имя_члена | |
++ | Пост-инкремент | lvalue++ | |
–– | Пост-декремент | lvalue–– | |
typeid | Информация о типе во время выполнения | typeid(тип) или typeid(выражение) | |
const_cast | Cast away const | const_cast(выражение) | |
dynamic_cast | Type-checked cast во время выполнения | dynamic_cast(выражение) | |
reinterpret_cast | Конвертация одного типа в другой | reinterpret_cast(выражение) | |
static_cast | Type-checked cast во время компиляции | static_cast(выражение) | |
3. R -> L | + | Унарный плюс | +выражение |
— | Унарный минус | -выражение | |
++ | Пре-инкремент | ++lvalue | |
–– | Пре-декремент | ––lvalue | |
! | Логическое НЕ (NOT) | !выражение | |
~ | Побитовое НЕ (NOT) | ~выражение | |
(type) | C-style cast | (новый_тип)выражение | |
sizeof | Размер в байтах | sizeof(type) или sizeof(выражение) | |
& | Адрес | &lvalue | |
* | Разыменование | *выражение | |
new | Динамическое выделение памяти | new тип | |
new[] | Динамическое выделение массива | new тип[выражение] | |
delete | Динамическое удаление памяти | delete указатель | |
delete[] | Динамическое удаление массива | delete[] указатель | |
4. L -> R | ->* | Выбор члена через указатель | указатель_объекта->*указатель_на_член |
.* | Выбор члена объекта | объект.*указатель_на_член | |
5. L -> R | * | Умножение | выражение * выражение |
/ | Деление | выражение / выражение | |
% | Деление с остатком | выражение % выражение | |
6. L -> R | + | Сложение | выражение + выражение |
— | Вычитание | выражение — выражение | |
7. L -> R | << | Побитовый сдвиг влево | выражение << выражение |
>> | Побитовый сдвиг вправо | выражение >> выражение | |
8. L -> R | < | Сравнение: меньше чем | выражение < выражение |
<= | Сравнение: меньше чем или равно | выражение <= выражение | |
> | Сравнение: больше чем | выражение > выражение | |
>= | Сравнение: больше чем или равно | выражение >= выражение | |
9. L -> R | == | Равно | выражение == выражение |
!= | Не равно | выражение != выражение | |
10. L -> R | & | Побитовое И (AND) | выражение & выражение |
11. L -> R | ^ | Побитовое исключающее ИЛИ (XOR) | выражение ^ выражение |
12. L -> R | | | Побитовое ИЛИ (OR) | выражение | выражение |
13. L -> R | && | Логическое И (AND) | выражение && выражение |
14. L -> R | || | Логическое ИЛИ (OR) | выражение || выражение |
15. R -> L | ?: | Тернарный условный оператор | выражение ? выражение : выражение |
= | Присваивание | lvalue = выражение | |
*= | Умножение с присваиванием | lvalue *= выражение | |
/= | Деление с присваиванием | lvalue /= выражение | |
%= | Деление с остатком и с присваиванием | lvalue %= выражение | |
+= | Сложение с присваиванием | lvalue += выражение | |
-= | Вычитание с присваиванием | lvalue -= выражение | |
<<= | Присваивание с побитовым сдвигом влево | lvalue <<= выражение | |
>>= | Присваивание с побитовым сдвигом вправо | lvalue >>= выражение | |
&= | Присваивание с побитовой операцией И (AND) | lvalue &= выражение | |
|= | Присваивание с побитовой операцией ИЛИ (OR) | lvalue |= выражение | |
^= | Присваивание с побитовой операцией «Исключающее ИЛИ» (XOR) | lvalue ^= выражение | |
16. R -> L | throw | Генерация исключения | throw выражение |
17. L -> R | , | Оператор Запятая | выражение, выражение |
Некоторые операторы вы уже знаете из предыдущих уроков: +
, -
, *
, /
, ()
, =
, <
и >
. Их значения одинаковы как в математике, так и в языке C++.
Однако, если у вас нет опыта работы с другими языками программирования, то большинство из этих операторов вам сейчас могут быть непонятны. Это нормально. Мы рассмотрим большую их часть на уроках этой главы, а об остальных расскажем по мере необходимости.
Эта таблица предназначена в первую очередь для того, чтобы вы могли в любой момент обратиться к ней для решения возможных проблем приоритета или ассоциативности.
Как возвести число в степень в C++?
Вы уже должны были заметить, что оператор ^
, который обычно используется для обозначения операции возведения в степень в обычной математике, не является таковым в языке C++. В языке С++ это побитовая операция XOR. А для возведения числа в степень в языке C++ используется функция pow(), которая находится в заголовочном файле cmath:
1 2 3 |
#include <cmath> double x = pow(3.0, 4.0); // 3 в степени 4 |
Обратите внимание, параметры и возвращаемые значения функции pow() являются типа double. А поскольку типы с плавающей точкой известны ошибками округления, то результаты pow() могут быть неточными (чуть меньше или чуть больше).
Если вам нужно возвести в степень целое число, то лучше использовать собственную функцию, например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Примечание: Экспонент не должен быть отрицательным int pow(int base, int exp) { int result = 1; while (exp) { if (exp & 1) result *= base; exp >>= 1; base *= base; } return result; } |
Не переживайте, если здесь что-то не понятно. Просто помните о проблеме переполнения, которая может произойти, если один из аргументов будет слишком большим.
Тест
Из школьной математики нам известно, что выражения внутри скобок выполняются первыми. Например, в выражении (2 + 3) * 4
часть (2 + 3)
выполняется первой.
В этом задании есть 4 выражения, в которых отсутствуют какие-либо скобки. Используя приоритет операций и правила ассоциативности, приведенные выше, добавьте скобки в каждое выражение так, как если бы их обрабатывал компилятор.
Подсказка: Используйте колонку «Пример» в таблице приоритета и ассоциативности операций, чтобы определить, является ли оператор унарным (имеет один операнд) или бинарным (имеет два операнда).
Например: х = 2 + 3 % 4
Бинарный оператор %
имеет более высокий приоритет, чем оператор +
или =
, поэтому он выполняется первым: х = 2 + (3 % 4)
. Затем выполняется бинарный оператор +
, так как он имеет более высокий приоритет, чем оператор =
.
Ответ: х = (2 + (3 % 4))
.
Дальше нам уже не нужна таблица, чтобы понять ход обработки этого выражения компилятором.
Задания:
Выражение №1: x = 3 + 4 + 5
Выражение №2: x = y = z
Выражение №3: z *= ++y + 5
Выражение №4: a || b && c || d
Ответ
Выражение №1: x = 3 + 4 + 5
Уровень приоритета бинарного оператора +
выше, чем оператора =
, поэтому: х = (3 + 4 + 5)
. Ассоциативность бинарного оператора +
слева направо, поэтому ответ: х = ((3 + 4) + 5)
.
Выражение №2: x = y = z
Ассоциативность бинарного оператора =
справа налево, поэтому ответ: x = (y = z)
.
Выражение №3: z *= ++y + 5
Унарный оператор ++
имеет наивысший приоритет, поэтому: z *= (++y) + 5
. Затем идет бинарный оператор +
, поэтому ответ: z *= ((++y) + 5)
.
Выражение №4: a || b && c || d
Бинарный оператор &&
имеет приоритет выше, чем ||
, поэтому: a || (b && c) || d
. Ассоциативность бинарного оператора ||
слева направо, поэтому ответ: (a || (b && c)) || d
.
Чё-то нифига не понятно.
while (exp)
1. exp==false(0), цикл не выполняется и сразу return. Тут понятно.
2. exp==true(1), то получаем бесконечный цикл. Не увидел условие выхода из цикла.
3. exp==2, 3 и т.д , то есть при другом возможном значении?
Если выражение exp >>= 1; вызывать много раз то оно в любом случае, когда то станет 0 т.е. FALSE. Но до тех пор пока оно не 0 оно TRUE.
Это так называемое "быстрое возведение в степень". Его ещё называют RPA(russian peasant algorithm(или алгоритм русского крестьянина)). На данном примере выигрыш во времени около нулевой, а вот если бы мы писали длинную арифметику, то там бы это нам серьёзно помогло.
Я попытаюсь объяснить.
Как это работает? По умолчанию, результат равен 1. Попытаемся возвести 2 в степень 1. Проверяем условие: если 1 по битам сходится с 1 (буквально равны, ибо это 0001 в двоичном представлении), то result = 1 * 2 = 2. Дальше осуществляем сдвиг экспоненты вправо: exp = 1>>1 = 0. То есть сдвигаем вправо на 1 бит число 0001 – остается 0000 (1 отбрасывается без возможности восстановления), то есть 0. И так как экспонента не используется в цикле (нулевое значение – это автоматически false), то инструкция base = 2 * 2 не будет работать, потому что цикл больше не использует экспоненту, ибо равна нулю. Тогда результат равен 2. Коротко говоря, любое число в степени единицы равно само по себе без изменений.
Теперь возведем двойку в квадрат. Проверяем условие и по битам сравним 2 и 1 (это 0010 и 0001 в двоичной системе соответственно). Условие ложно, значит пропускаем и продолжаем цикл. Далее идет смещение: exp = 2 >> 1 = 0001 = 1, то есть в 0010 отбросили последний 0, отсюда и 0001. Цикл все еще использует этот параметр (ненулевое значение – автоматически true), потому base = 2 * 2 = 4. Цикл начинается по новой, но exp = 1. Условие в этот раз истинно, то есть result = 1 * base = 1 * 4 = 4, так как base уже равен 4. Сдвигаем 1 на 1 бит – это 0. Цикл не использует переменную, потому последний стейтмент цикла не срабатывает. Выведется результат 4.
Попытаемся возвести 2 в кубе. Проверяем условие побитового сравнения 3 и 1 (0011 и 0001), которое становится истинным. Значит result = 1 * 2 = 2. Далее сдвигаем 3 на 1 бит: exp = 3 >> 1 = 1. Параметр используется, значит: base = 2 * 2 = 4. Возобновляется цикл, где exp уже не 3, а 1. Условие вновь истинно, значит: result = result * base (полученные в прошлом цикле) = 2 * 4 = 8. Сдвиг 1 на 1 бит равен 0, значит цикл прекращается. Результат возвращает 8.
Попытаемся возвести 2 в четвертой степени. Проверяем условие побитового сравнения 4 и 1 (0100 и 0001). Оно ложно. Идем дальше. Идет сдвиг вправо: exp = 4 >> 1 = 0100 >> 1 = 0010 = 2. Умножаем число само на себя: base = 2 * 2 = 4. Возобновляем цикл и снова проверяем условие побитового сравнения 2 и 1, что снова ложно. Далее сдвиг: exp = 2 >> 1 = 1. Параметр все еще используется, следовательно: base = base (полученный) на самого себя = 4 * 4 = 16. Возобновляем цикл снова, но на этот раз условие истинно. Значит: result = 1 * 16 = 16. Сдвиг 1 на 1 бит равен 0. Цикл прекращается. Возвращает результат 16.
«Если вам нужно возвести в степень целое число, то лучше использовать собственную функцию»
Что-то я не понял, а зачем нам pow() тогда вообще? Нам привели пример как пользоваться pow, но сказали использовать какой то не понятный вариант. Вообще ничего не понял в этой главе с возведением в степень. Можно объяснить? Почему мы не можем возвести в степень целочисленный тип через pow()? Если через double лучше не делать, то зачем pow()?
Использование собственной функции нужно для возведения целых чисел в степень. Например, степени десятки для перевода в различные системы счисления.
Pow() применяется для чисел с плавающей точкой: он может принять аргументы int, но вернет их в возможно не точном виде типа double. При этом, даже такое неточное значение может пригодиться в математических уравнениях.
Элементарно, Ватсон! Целые числа возводить в целую же степень — проще простого, поэтому легко реализовать такую функцию самому с целочисленными типами, где гарантированно всё будет без ошибок округления. А вот pow() корректно возводит в степень любые числа, даже дробные и отрицательные (как основание степени, так и показатель). Огромная разница.
Возвести целое число в целую степень разве не проще через обычный цикл?
Зачем такая витиеватость?
Понятно. Количество умножений сократили.
Количество умножений не может сократится. Иначе будет не верный результат. Сдается мне что если дисасемблировать ваш вариант и тот что в уроке, они будут практически одинаковыми.
И скорость схожения у них одинаковая.
Пример. база 10, степень 20. В уроке степень считается битами, 20 это 10100, пять умножений плюс ещё два умножения на единичках, в цикле 20. Худший вариант это пять единиц(степень 31), тогда в урочном примере 10 умножений, а комментовом 31.
Для степеней больше 31 количество умножений ещё больше различается, а на малых числах одинакова.
Или я не так что-то понял?
Мы работаем с нашим числом степеней в двоичной системе, считаем сколько единиц, двоек, четверок….. в степени. И это сокращает количество операций во много раз. А степени складываются умножением. base *= base — это и есть удвоение степеней, новый разряд в двоичной системе. И мы смотрим на крайнюю правую цифру в двоичном коде. Если разряд есть, то добавляем умножением его в сумму степеней. Затем убираем крайнюю правую цифру сдвигом, а слева появляется 0, и проверяем новую крайнюю цифру. Проверяем побитовым И, которое означает: " есть и первое, и второе" . Одно из них — это единица ( …0001). Там где есть 0, первого и второго точно не будет, получим 0, а у крайней правой может быть единица, если в нашем числе этот разряд тоже есть. И так сдвигаем, пока не выйдем на все нули,
На самом деле это вина исключительно отсталой математики, что люди не видят параллели между системами счисления. Привыкли, что круглое число называется не 8, а 10, и именно столько чисел между разрядами должно быть. 10-ичная система порочна тем, что кроме основы счисления в целых единицах, состоит из двух множителей, друг над руга не делящихся! То есть из 2 и 5. И это не позволяет отслеживать связь степеней. С простыми числами, тем более двойками легко работать — это обычный литерал, под которым хоть что можем понимать или подставлять, как и сделали в данном случае. Делаем мы там всегда! Если бы мы считали в жизни циклами, то миллиард возводили бы в квадрат не умножением, а сложением по единице. Экономия в целом не очевидна. Понятно, что когда нам нужно совершить определённое число действий, мы используем циклы, но сложение часто заменяем умножениям, а тут была двойная замена — и степени складывали умноженными на 2 и в степени отсчитывали через умножение. С единицей складывали, так как в единицах считали, считали бы в других количествах, в другие систему счисления переводили, могли бы и другое занулять по трафарету. А в десятках считали, могли бы просто нули нарисовать. Мне понравилось. Неожиданно концовка
Хм…. А и правда…
При малых степенях нет смысла в извращении понятной логики, а при больших степенях и базах вероятность переполнения гораздо выше производительности функции.
Но при последовательном умноженнии проще отследить owerflow, чем при перемножении хитроумных четных степеней базы.
здравствуйте, может, где то упоминалось, но не найду: операторы ввода вывода << и >> (к примеру в std::cout<<) — это и есть побитовый сдвиг влево вправо?
Если нет, то какое место они занимают во всей этой системе операторов?
<< и >> это всё те же операции битового сдвига влева и вправо соответсвенно, НО, в пространстве имён std эти две операции ПЕРЕОПРЕДЕЛЕНЫ. Думаю, о переопределении, речь пойдёт в след. уроках и Вы узнаете, что это такое. Но можете сейчас погуглить )))
Спасибо за ответ! "Гуглить сейчас" не стоило)