Операции и операторы
Вспомним, что мы говорили о типах данных. Тип данных в первую очередь определяет то, какие операции мы можем совершать над данными.
А операции над данными - это одна из самых базовых (в плане простых и в плане необходимых) вещей в программировании в целом.
Давайте посмотрим на то, какие операции в принципе есть в С++.
Операции и операторы
Операция - это конкретное действие, которые производится над данными. Сложение, умножение, и так далее.
Операторы - это же специальные символы, с помощью которых мы создаём операции.
Например, оператор +
задаёт для целых чисел операцию сложения.
Обратите внимание: каждая операция в C++ определена для конкретных типов, а не для всех сразу.
Операторы в С++ могут для разных типов обозначать разные операции - например, +
обозначает сложение для целых чисел и конкатенацию для строк (об этом далее).
У каждой операции есть возвращаемый тип данных
Стоит помнить, что каждая операция в C++ возвращает какое-то значение, привязанное к конкретному типу данных.
Самое очевидное - это арифметические операции, по типу +
и -
. Но стоит помнить, что операции логического сравнения, присваивания, и любые другие тоже возвращают значение какого-то определённого типа данных.
Присваивание
Самая базовая операция.
int a = 1;
a = 8;
Выполняется при помощи оператора присваивания =
.
Оператор =
возвращает значение, которое было с помощью него записано в переменную, поэтому возможна следующая запись:
int a;
int b;
a = b = 4;
Здесь сначала выполнится b = 4
, вернёт 4, и потом выполнится a = 4
.
Базовая арифметика
Здесь всё просто. С целыми и дробными числами мы можем выполнять базовые арифметические операции:
int a = 9 + 2;
int b = 9 - 2;
int c = 9 / 2;
int d = 9 * 2;
Это сложение, вычитание, деление и умножение.
Порой нам нужно к уже имеющейся переменной добавить число, или поделить переменную на число, или сделать с ней ещё какую-то базовую арифметическую операцию.
Писать это через операции сложения, деления, и т.д. напрямую достаточно неудобно:
int a = a + 1;
Поэтому, для таких случаев в C++ есть специальные операторы:
a += 9; // a = a + 9;
b -= 9; // a = b - 9;
c /= 9; // c = c / 9;
d *= 9; // d = d * 9;
Остаток от деления
Очень часто в программировании нам нужно использовать такую операцию, как остаток от деления. Записывается она так:
int a = 5 % 2;
Что такое остаток от деления, я надеюсь, вы знаете из школьного курса - грубо говоря, это тот остаток, который у нас остаётся от числа, когда мы пытаемся его нацело поделить в столбик.
Остаток от деления зачастую используется, например, для проверки того, что одно число делится на другое - в случае, если число a
делится на число b
, остаток от деления a
на b
(a % b
) должен равняться 0, и какому-то числу от 0
До b
в противном случае.
Для остатка от деления также есть оператор со знаком равенства:
a %= 5;
Префиксное увеличение
Для того, чтобы добавить к числу 1, можно использовать оператор префиксного увеличения. Выглядит это примерно так:
++a;
Префиксное - потому что оператор ++
записывается перед увеличиваемым значением.
Постфиксное увеличение
Также в C++ доступно и постфиксное увеличение переменной:
a++;
В этом случае к переменной a
тоже будет добавлена единица
Различие префиксного и постфиксного увеличения
Как конечный итог выполнения, и a++
и ++a
прибавляют к a
единицу. Но всё таки у этих операторов есть чёткое различие, и оно заключается в возвращаемом значении.
Если выполнить следующий код:
int a = 1;
std::cout << a++ << std::endl;
...на экран выведется 1. То есть, при обращении к a++
, мы получаем число до прибавления 1.
Если же поменять a++
на ++a
, на экран выведется 2.
Префиксное и постфиксное уменьшение
Здесь всё абсолютно аналогично увеличению, но вместо прибавления единицы, происходит вычитание единицы, а операнд ++
заменяется на операнд --
.
Операции сравнения
Здесь, в принципе, тоже нет ничего нового.
Основные операции сравнения в C++ записываются как:
>
, >=
, <
, <=
.
Единственные особенности касаются операций проверки равенства двух чисел (==
) и неравенства (!=
).
Все эти операторы возвращают значение типа bool
.
С оператором сравнения чисел ==
существует классическая ошибка, которую нам нужно обязательно рассмотреть:
int b;
std::cin >> b;
if (b = 5)
{
std::cout << "You guessed the number!\n";
}
На первый взгляд, может быть, ошибку и сложно заметить, но она заключается в том, что в if
мы вместо оператора сравнения ==
использовали оператор присваивания =
.
Причём этот код будет успешно скомпилирован и запущен, но условие if
будет выполняться для любых введённых значений b. Почему?
Как уже говорилось ранее, каждая операция возвращает какое-то значение. Операция присваивания не исключение - она будет возвращать присвоенное значение.
В данном случае, операция b = 5
вернёт 5
, которое потом преобразуется в bool
по цепочке преобразований (см. урок о приведении типов). Из-за этого тело условного оператора будет выполняться всегда, и порой такую ошибку бывает очень сложно заметить.
Логические операции
Для составления логических условий так же используются логические операции. Все они возвращают значение типа bool
.
Логические операторы:
||
- или. Принимает на вход два аргумента типаbool
. Возвращаетtrue
, если хотя бы один из аргументовtrue
.&&
- и. Принимает на вход для аргумента типаbool
. Возвращаетtrue
только если оба аргумента тожеtrue
.!
- не. Принимает на вход один аргумент и возвращает его противоположное значение.
Простой пример:
int age = 17;
if (age > 16 && age < 18)
{
std::cout << "Вам можно только ездить на мопеде.\n";
}
Что здесь происходит:
- Сначала вычисляется
age > 16
- операция сравнения возвращает булевое значение - Аналогично справа
- Потом оператор
&&
подхватывает эти два значения и как-то обрабатывает
Другой пример - проверка того, что число делится на 3 или на 5:
int number = 18;
if (!(number % 3) || !(number % 5))
{
std::cout << "Делится на 3 или на 5.\n";
}
Что происходит здесь?
- Компилятор видит выражение с или, и начинает разбирать левую его часть. Для начала вычисляется выражение в скобках. Как "промежуточный" результат получаем:
if (!0 || !(number %5))
- Оператор
!
умеет работать только с булевыми значениями, поэтому компилятор выполняет цепочку преобразований для целого значения 0, и мы получаем:
if (!false || !(number % 5))
- Применяем отрицание:
if (true || !(number % 5))
- И здесь наступает очень важный момент. Оператор
||
видит, что справа от него уже стоит значениеtrue
. Зачем ему тогда проверять значение справа, если результат или уже гарантированно будетtrue
? На этом шаге оператор сразу возвращаетtrue
, без вычисления!(number % 5)
И в этой "оптимизированной" проверке заключается особенность логических операций сравнения. Нагляднее это можно продемонстрировать, используя отдельную функцию:
#include <iostream>
int f()
{
std::cout << "f()\n";
return 5;
}
int main()
{
if (!(false && f()))
{
std::cout << "Obviously false statement is false.\n";
}
}
Если запустить этот код, в терминал не выведется строка f()
, что значит, что функция f()
даже не вызвалась, так как левый аргумент оператора &&
уже был false
.
Бинарные операции
x & y // бинарное И
x | y // бинарное ИЛИ
x ^ y // бинарное НЕ-ИЛИ (XOR)
~x // бинарное НЕ
В случае этих операторов, над входящими аргументами производятся именно бинарные операции, то есть честно будут выполнятся все перемещения, сложения (и т.п.) битов входящих чисел.
Про то, что такое в теории бинарные операции, рассказано в отдельной статье.
Здесь очень важно понимать различие x & y
и x | y
от x && y
и x || y
.
Как мы уже обсуждали, логические операторы просто проверяют два булевых значения и возвращают такое же булевое значение, выполняя по возможности простые оптимизации.
Бинарные же операторы принимают на вход два числа (или 1 в случае отрицания) и возвращают как результат своих действий такое же число - результат применения ко входным данным конкретной бинарной операции. В этом случае вне зависимости от значений аргументов при любых обстоятельствах будут происходится просто вычисления над битами.
Унарные операции
Частично мы уже затронули унарные операции - это операции, которые принимают на вход только один аргумент. К ним можно также добавить:
-
- возвращаетчисло * (-1)
+
- используется для того, чтобы намеренно указать на положительность какого-то числа. Используется не так часто, так что пока можно сильно в смысл унарного+
не вникать.
Операции над строками
Над строками тоже можно делать базовые операции. Бегло рассмотрим два примера.
Конкатенация - объединение двух строк:
std::cout << "Hello " + "world\n" << std::endl;
В данном случае оператор +
вернёт строку Hello world\n
.
Обратите внимание, что в этом случае мы не говорим об операции сложения - мы говорим именно об операции конкатенации, потому что объединение двух строк и сложение двух чисел - это совершенно разные операции как на уровне идеи, так и на уровне реализации.
Добавление в конец
std::string greetings = "Hello";
greetings += '!';
С помощью оператора +=
можно добавить в конец строки символ или строку.