Что делают операторы
Оператор - это символ, выполняющий действие над одним или несколькими значениями, которые называются операндами. Один из них вы уже использовали: =, который связывает значение с переменной. C++ поставляется с богатым набором операторов для математики, сравнения, логики и работы с битами - и с несколькими острыми углами, о которые спотыкаются новички.
На предыдущей странице вы видели, как const фиксирует значение. Операторы - это то, чем вы вычисляете значения, которые сохраняете, будь они константными или нет. Пройдёмся по семействам, которые вы будете использовать каждый день.
Арифметические операторы
Пять арифметических операторов - это +, -, *, / и % (остаток от деления, modulo):
Главный подвох прячется в этом частном. 17 / 5 выводит 3, а не 3.4. Когда оба операнда целые, / выполняет целочисленное деление и отбрасывает дробную часть - оно усекает в сторону нуля, а не округляет. Если вам нужна настоящая дробь, хотя бы один операнд должен быть с плавающей точкой:
Оператор % работает только с целыми числами. Применение % к double - это ошибка компиляции; для остатка от деления чисел с плавающей точкой используйте std::fmod из <cmath>.
Присваивание и составные операторы
Одиночный = присваивает; он не сравнивает. C++ даёт вам составные формы, которые объединяют операцию с присваиванием, чтобы вам не приходилось повторять имя переменной:
Классическая болезненная ошибка - написать = там, где вы имели в виду ==, внутри условия. if (x = 0) присваивает x значение 0, а затем проверяет результат (который ложен), вместо того чтобы сравнивать. Современные компиляторы предупреждают об этом, когда вы включаете предупреждения - держите -Wall включённым и относитесь к предупреждению серьёзно.
Операторы сравнения
Операторы сравнения задают вопрос «да/нет» и выдают bool (true или false):
По умолчанию cout печатает bool как 1 или 0. Вставьте boolalpha один раз - и до конца этого потока он переключится на слова true/false.
Тонкая ловушка: не объединяйте сравнения в цепочку вроде 1 < x < 10. Это разбирается как (1 < x) < 10 - первое сравнение даёт bool (0 или 1), который затем сравнивается с 10, поэтому всё выражение почти всегда true. Вместо этого пишите 1 < x && x < 10.
Логические операторы и короткое замыкание
&& (и), || (или) и ! (не) объединяют булевы выражения. Первые два используют короткое замыкание: вычисление прекращается, как только результат известен.
Ни check("A"), ни check("B") так и не выполняются - это и есть короткое замыкание. Это не просто оптимизация; это инструмент. Вы можете безопасно написать if (ptr != nullptr && ptr->ready), потому что часть ptr->ready достигается только тогда, когда ptr не равен null, что предотвращает разыменование висячего указателя.
Инкремент и декремент
++ прибавляет единицу; -- вычитает единицу. У каждого есть префиксная и постфиксная форма, и разница важна, когда вы используете результат:
Когда вам нужен только побочный эффект (например, в цикле for), предпочитайте ++i. Для обычного int это идентично, но для более тяжёлых типов вроде итераторов постфиксный инкремент вынужден сначала скопировать старое значение, а это напрасная работа.
Ещё одно предупреждение: не изменяйте одну и ту же переменную дважды в одном выражении, например i = i++ + 1; или arr[i] = i++;. Порядок этих обновлений не определён, и результат - неопределённое поведение. Ограничивайте каждую переменную одним изменением на инструкцию.
Побитовые операторы и приоритет
Для низкоуровневой работы существуют побитовые операторы: & (и), | (или), ^ (исключающее или, xor), ~ (не) и сдвиги << и >>.
Будьте внимательны: << и >> - это также операторы потока для cout. Внутри строки cout вам обычно нужны скобки вокруг битового сдвига, иначе компилятор прочтёт его как вставку в поток.
Наконец, приоритет определяет, что связывается первым, когда вы смешиваете операторы. * и / связываются крепче, чем + и -, точно как в математике, поэтому 2 + 3 * 4 равно 14. Сравнение связывается слабее арифметики, а логические &&/|| - ещё слабее. В случае сомнений не заучивайте всю таблицу - добавьте скобки. (a + b) * c понятнее, чем рассчитывать на то, что читатель вспомнит правила.
Далее: приведение типов
Выше вы видели, что (double)a / b заставило целое число участвовать в делении с плавающей точкой. Это приведение (cast) - намеренное преобразование значения из одного типа в другой. На следующей странице мы рассмотрим инструменты преобразования C++, от неявных повышений до static_cast, и когда каждое из них безопасно.
Часто задаваемые вопросы
Какие операторы есть в C++?
C++ делит операторы на арифметические (+ - * / %), сравнения (== != < > <= >=), логические (&& || !), присваивания (= += -= ...), инкремента/декремента (++ --) и побитовые (& | ^ ~ << >>). Есть также тернарный ?: и несколько других, например sizeof.
Почему 5 / 2 равно 2 в C++?
Потому что оба операнда имеют тип int, поэтому / выполняет целочисленное деление, и дробная часть отбрасывается, а не округляется. Чтобы получить 2.5, сделайте хотя бы один операнд числом с плавающей точкой: 5.0 / 2 или 5 / 2.0.
В чём разница между ++i и i++ в C++?
Оба прибавляют 1 к i. ++i (префиксный инкремент) сначала увеличивает и возвращает новое значение; i++ (постфиксный инкремент) возвращает старое значение, а затем увеличивает. Когда вам важен только побочный эффект, предпочитайте ++i.