1.2. Операторы - StriderAJR/StudentCpp GitHub Wiki

Оператор - некая фраза в языке программирования, определяющая законченный этап обработки данных одного или двух операндов.

  • Операторы выражения
    • Приоритет операторов
  • Приведение типов
  • Условные операторы
    • Оператор if
    • Оператор switch
    • Тернарный оператор
  • Оператор ,
  • Операторы цикла
  • Ссылки на исходники

Операторы выражения

Если упростить еще сильнее, то оператор - это какое-то действие, которое может иметь один или два параметра.

int a = 5;

= - это оператор. Оператор присваивания. Его операндами (параметрами) являются число 5 и переменная a. Этот оператор инициализирует переменную a числом 5 (в область памяти, помеченную именем a, записывается число 5).

Операторы могут состоять не только из одного символа. Существуют такие операторы, как if, switch, new, delete и т.д. Это тоже операторы. Но обо всем в свое время.

Оператор расширения (уточнения) видимости ::

Это достаточно простой оператор, с которым мы уже работали, когда писали первую программу:

std::cout << "Hello, World!";
  • Левый операнд оператора :: - это верхний в иерархии объект, внутри которого что-то есть.
  • Правый операнд оператора :: - это нижний в иерархии объект, который мы специализируем

namespace std
|-- cout
|-- cin
|-- что-то-еще

Если сформулировать еще проще: оператор :: говорит в какой "коробке" мы ищем нужную нам вещь. И запись std::cout расшифровывается как "внутри пространства std найди объект cout".

Все. Не больше и не меньше. Оператор :: позволяет нам сказать точно, где находится тот или иной объект/функция.

Арифметические операторы +, -, *, /, %

Это бинарные операторы, т.е. им нужно 2 операнда (параметра) для работы, левый и правый.

  • + - сложение (внезапно!)
  • - - вычитание (невероятно!)
  • * - умножение (кто бы мог подумать!)
  • / - целочисленное деление, дробная часть результата будет отброшена, даже если она должна была бы быть
  • % - остаток от деления
int a = 10;
cout << a + 1; // 11
cout << a - 1; // 9
cout << a * 2; // 20
cout << a / 3; // 3
cout << a % 3; // 1

Постфиксный и префиксный инкремент ++ и декремент --

Операторы инкремента и декремента содержат в себе оператор присваивания. Выражения

a++;
b--;

полностью эквивалентно записи

a = a + 1;
b = b - 1;

Но инкремент и декремент еще могут быть постфиксные и инфиксные. Постфиксный - оператор стоит после операнда. Инфиксиный - оператор стоит до операнда. Разницу между постфиксном и инфиксном проще показать на примере.

int a = 0;
int b = 0;
cout << a++ << endl; // выведется... 0
cout << ++b << endl; // выведется... 1

Сложно сказать, что вам покажется более странным, что в первом случае вывелся 0, или что во втором случае вывелось 1, или что вообще есть разница в результате для этих двух строк.

Разница между инфиксом и постфиксом в том, что произойдет раньше: инкремент/декремент или вы получите значение переменной. Давайте я просто распишу верхний алгоритм на составляющие:

int a = 0;
int b = 0;

cout << a << endl;
a = a + 1;

b = b + 1;
cout << b << endl;

Вот теперь отлично видно, что в случае с постфиксом, мы сначала используем операнд в своем выражении и только затем происходит инкремент.

В случае с инфиксом сначала происходит инкремент и только затем операнд участвует в выражении.

Эта разница бывает критичной в алгоритмах, поэтому стоит об этом помнить.

Логические операторы ==, !=, >, <, >=, <=

Логические операторы возвращают результатом своей работы логический тип данных bool.

  • == - проверка на равенство
  • != - проверка на неравенство Остальные операторы, кажется, даже пояснять нужды нет.

Логические и побитовые операторы && и &, || и |, ! и ~, ^

  • & - конъюнкция
  • | - дизъюнкция
  • ! и ~ - отрицание
  • ^ - исключающая дизъюнкция

Есть такие "веселые" парные операторы, которые вечно начинающие забывают разделять в своей голове на 2 разные подгруппы: логические и побитовые. У логической группы результатом будет тип bool - истина или ложь, побитовая же группа операторов в качестве результата выдает число.

  • Логическая группа это операторы &&, ||, !
int a = 10;
int b = 0;
bool c = true;

cout << (a && b) << endl; // 0
cout << (a && c) << endl; // 1
cout << (b && c) << endl; // 0

// Выражение пришлось записать в круглых скобках из-за приоритета операторов
// У оператора && приоритет ниже, чем у <<

Надеюсь, вы помните, почему вывод получился 0 и 1 - потому что в С++ логический тип данных все равно числа. Автоматически красивые слова true и false вам никто не выведет, это прерогатива уже более высокоуровневых языков типа С#.

  • Побитовая группа это операторы &, |, ~ Особенность побитовых арифметических операторов, что они работают с двоичным представлением числа и причем с каждым отдельным битом, а не со всем числом в целом. Результат получается целочисленное число.
int a = 3;
int b = 4;
bool c = 1;

cout << (a & b) << endl;
cout << (a | b) << endl;
cout << (a ^ b) << endl;
cout << (~c) << endl;

Если записать побитого каждое число, то

a = 0011
b = 0100
c = 0001

0011 & 0100 = 0000 = 0
0011 | 0100 = 0111 = 7
0011 ^ 0100 = 0111 = 7

Так что не нужно путать пары: & и &&, | и || - это разные операторы с разным результатом и даже разным типом данных результата.

Побитовый сдвиг >>, <<

Это тоже побитовые операторы. Они сдвигают число на нужное кол-во разрядов вправо или влево.

int a = 1;
cout << (a << 2) << endl; // 4
0001 << 2 = 0100 = 4

И помните, что cout << "Hello" и a << 2 - это один и тот же оператор побитого сдвига. Но в дальнейшем мы изучим, что поведение оператора можно менять для разных типов данных. Поэтому оператор << для потока ввода/вывода не сдвигает что-то там влево, а записывает или читает из потока данные. Но оператор и там, и там - это оператор побитого сдвига, но его поведение изменили именно для потока. Это называется "перегрузить оператор" и будет изучаться в дальнейшем.

Комбинированные операторы с присваиванием

В С++ есть комбинированные операторы, которые образовались как краткая запись самого оператора вместе с присваиванием.

a = a+5;

можно записать также как

a += 5;

Эти строчки будут полностью эквивалентны.

И таких "сокращенных" операторов очень много:

a -= 1; // a = a-1;
a *= 2; // a = a*2;
a /= 3; // a = a / 3;
a %= 4; // надеюсь, дальше мне уже не нужно расписывать?
a <<= 5;
a >>= 6;
a &= 7;
a ^= 8;
a |= 9;

Оператор sizeof

Оператор ,

Приведение типов

TODO

Приоритет операторов

int a = 5;
int b = 7;
int c = a+b;

В последней строке применяется сразу 2 оператора. Сначала выполняется оператор +, а затем оператор =. Почему именно в таком порядке? Потому что в любом языке программирования, также как в математических выражениях, у операторов есть приоритет.

Например, оператор + имеет больший приоритет чем оператор =. Именно поэтому сначала вычислится сумма двух переменных: a и b и только затем результат присвоится переменной c.

int a = 5;
int b = 7;
int c = a+b*a; // c будет равно 40, а не 60

Полный перечень операторов с их приоритетами можно найти в любом справочнике или тематической сайте, например, CppReference - Приоритет операций

Условные операторы

Условные операторы еще назваются операторами ветвления, потому что они позволяют "разветвить" алгоритм - создать несколько веток, которые будут выполняться при выполнении определенного условия.

Например, если заданное условие будет истинным, то выполнить алгоритм №1, а если ложно, то выполнить алгоритм №2.

Некоторые операторы ветвлений позволяют создать только 2 ветки алгоритмов: истинную и ложную (оператор if), а некоторые операторы могут создать сразу множество веток (оператор switch).

Оператор if

Как уже было сказано оператор if проверяет заданное условие и выполняет либо одну, либо другую ветку алгоритма в зависимости от истинности условия.

if(условие)
{
	// код, который выполняется, если условие было истинным
}
else
{
	// код, который выполняется, если условие было ложным
}

Обратите внимание, что условие это не обязательно какое-то просто выражение типа

a == 10

или

a > 0

Это могут и сложные конструкции, состоящие из нескольких действий.

if( ((a > 10) && (b == 3)) || (a % 2 == 0) )
	cout << "True";
else
	cout << "False";

Обратите внимание, в языке С++ истиной является все, что отлично от нуля, а ложью является только 0. Так что вполне себе можно писать так:

if(10) cout << "Ok";
if(5 & 0) cout << 0;

Как видно, условие не обязательно должно иметь тип данных bool.

А еще оператор if как несложно догадаться можно быть вложенным.

if(a == 5) cout << "Excellent!";
else if(a == 4) cout << "Good";
else if(a == 3) cout << "Not so good";
else if(a == 2) cout << "Bad";
else if(a == 1) cout << "You are blunt or what?";
else if(a == 0) cout << "WTF?";
else cout << "0_o";

В коде выше в зависимости от значения переменной a будет выведено только одно сообщение. Если же переменная a не будет задана ни одному из предустановленных значений, то выведется конечный вариант-реакция на неизвестное значение. Так можно организовать такой алгоритм с помощью вложенных операторов if.

Но записать тот же самый алгоритм можно и с помощью другого оператора switch - множественного ветвления.

Оператор switch

Тот же самый код выше можно записать и другим образом.

switch(a)
{
	case 5: cout << "Excellent!"; break;
	case 4: cout << "Good"; break;
	case 3: cout << "Not so good"; break;
	case 2: cout << "Bad"; break;
	case 1: cout << "You are blunt or what?"; break;
	case 0: cout << "WTF?"; break;
	default: cout << "0_o";
}

Этот код полностью идентичен по логике выполнения тому, что был записан с помощью вложенных операторов if. Считается, что в случае множественного ветвления оператор switch более компактен и предпочтителен к применению.

В целом синтаксис оператора switch такой:

switch(переменная_значение_которой_проверяется)
{
	case вариант_1:
		// код который нужно выполнить для варианта 1
		break;
	case вариант_2:
		// код который нужно выполнить для варианта 1
		break;
	// таких вариантов может быть сколько угодно
	case вариант_n:
		// код который нужно выполнить для варианта n
		break;
	default:
		// код, который нужно выполнить, если ни один из вариантов не подойдет
}

Ветка default может и отстутствовать, ее писать не обязательно. Тогда в случае отсутствия совпадений, в рамках оператора switch ничего не будет выполонено.

И последний важный момент про оператор switch: это оператор прерывания выполнения break. Если не писать этот оператор в конце алгоритма для каждой ветки, то особенность работы switch такова, что будет выполнен код, который идет сразу следом за выполняющимся.

int a = 2;
switch(a)
{
	case 2:
		cout << "Two";
	case 1: 
		cout << "One";
}

Если вы забыли про break, то ожидаете, что на экран выведется

Two

Но только это не так. На самом деле выведется

TwoOne

Потому что такова особенность switch. Без break начнется выполняться следующая ветка.

Казалось, зачем было так тупо делать, сделали бы разработчики С++, чтобы ветка прерывалась автоматически. Но на самом деле это бывает полезно, если вам нужно собирать предустановленные значение в группы.

switch(a)
{
	case 5:
	case 4:
		cout << "You are a good student!";
		break;
	case 3:
		cout << "Bad student!"
		break;
	case 2:
	case 1:
		cout << "You're not a student anymore.";
}

Если бы не особенность работы switch, то вывод сообщения пришлось бы повторять для вариантов 5 и 2. А это дублирование кода. Создавать функцию для такой ерунды тоже не лучшее решение. Так что такая организация работы switch не так уж и плоха.

Тернарный оператор

Тернарный оператор это краткий вариант записи оператора if. Тернарным его зовут, потому что он состоит из трех частей: условия, выражения для истины и выражения для лжи. Общая запись такая:

условие ? выражение_если_истина : выражение_ксли_ложь;

Возьмем пример оператора if:

if(a > 0) cout << "Positive";
else cout << "Negative";

Его можно записать через тернарный оператор, например, вот так:

a > 0 ? cout << "Positive" : cout << "Negative";

Суть работы тернарного оператора в том, что после проверки условия вместо всего оператора останется либо выражение для истины, либо выражение для лжи. В приведенном коде выше, если переменная а будет равна, скажем, 1, то вместо всего тернарного оператора останется только выражение cout << "Positive";

Именно поэтому тернарный оператор можно использовать в качестве inline проверки - проверки прямо на строке с исполняемым кодом. Например, тот же код выше, который мы рассматриваем, можно записать и так:

cout << (a > 0 ? "Positive" : "Negative");

Тут мы прямо внутри команды cout << делаем проверку на положительность переменной a и подсовываем либо константу Positive, либо Negative. Вместо всего тернарного оператора останется только одна, нужная, константа.

Примечание: пришлось взять тернарный оператор в круглые скобки, потому что приоритет у оператора > выше, чем у <<.

Так что развлекаться с тернарным оператором можно много.

int a = 10;
int b = a > 0 ? a / 2 : -a;

А еще тернарный оператор может быть вложенным. :)

cout << (a > 0 
			? "Positive"
			: a == 0
				? "Zero"
				: "Negative");

Оператор ,

Еще более веселый оператор - это оператор ,. Его особенность в том, что он разделяет несколько выражений, но итогом (т.е. тем, что остается после его выполнения как результат) является только последнее выражение.

Это как в случае с тернарным оператором. После выполнения оператора, что-то должно "остаться" в строке кода. Лучше всего будет смотреть на примерах:

int a = 0;
int b = a+1, a+2, a++, a;

Этот же код можно было бы записать следующим образом:

int a = 0;
a+1; // Здесь нет сохранения данных. Число 1 будет "висеть в воздухе", сохранения в память нет
a+2; // То же самое, только с числом 2
a++; // А вот это краткая запись выражения a = a+1 - тут есть сохранение, переменная а стала равно 1
int b = a; // В итоге b будет присвоено значени 1.

Самое главное, что вместо всех вариантов в операторе , остался на строчке с присваиванием переменной b значения последнее из перечисленных значений.

int b = a;

А теперь подумайте что выведется на экран:

int a = 10;
cout << (a /= 2, a++, a-5, a*3);

Правильный ответ: 18.

Операторы цикла

Операторов циклов есть 3 варианта: for, while и do while. Для циклов будет выделен отдельный раздел в следующих заметках.

Ссылки на исходники

⚠️ **GitHub.com Fallback** ⚠️