Операции и операторы
Вспомним, что мы говорили о типах данных. Тип данных в первую очередь определяет то, какие операции мы можем совершать над данными.
А операции над данными - это одна из самых базовых (в плане простых и в плане необходимых) вещей в программировании в целом.
Давайте посмотрим на то, какие операции в принципе есть в С++.
Операции и операторы
Операция - это конкретное действие, которые производится над данными. Сложение, умножение, и так далее.
Операторы - это же специальные символы, с помощью которых мы создаём операции.
Например, оператор + задаёт для целых чисел операцию сложения.
Обратите внимание: каждая операция в 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 += '!';
С помощью оператора += можно добавить в конец строки символ или строку.