5.5 – Оператор запятая и условный оператор
Оператор запятая
Оператор | Обозначение | Пример использования | Операция |
---|---|---|---|
Запятая | , | x, y | Вычисляет x , затем y , возвращает значение y |
Оператор запятая (,
) позволяет вычислять несколько выражений везде, где разрешено одно выражение. Оператор запятая вычисляет левый операнд, затем правый операнд, а затем возвращает результат правого операнда.
Например:
#include <iostream>
int main()
{
int x{ 1 };
int y{ 2 };
std::cout << (++x, ++y); // увеличиваем x и y,
// вычисляется как значение правого операнда
return 0;
}
Сначала вычисляется левый операнд оператора запятой, который увеличивает x
с 1 до 2. Затем вычисляется правый операнд, который увеличивает y
с 2 до 3. Оператор запятой возвращает результат правого операнда (3), который впоследствии выводится в консоль.
Обратите внимание, что запятая имеет самый низкий приоритет среди всех операторов, даже ниже, чем присваивание. Из-за этого следующие две строки кода делают разные вещи:
// сначала вычисляем (a, b), чтобы получить результат b,
// затем присваиваем это значение переменной z.
z = (a, b);
// вычисляется как "(z = a), b", поэтому z присваивается значение a,
// после чего b вычисляется и отбрасывается.
z = a, b;
Это делает использование оператора запятой несколько опасным.
Практически в каждом случае инструкцию, написанную с использованием оператора запятой, лучше записать в виде отдельных инструкций. Например, приведенный выше код можно записать как:
#include <iostream>
int main()
{
int x{ 1 };
int y{ 2 };
++x;
std::cout << ++y;
return 0;
}
Большинство программистов вообще не используют оператор запятой, за единственным исключением внутри циклов for
, где его использование довольно распространено. Мы обсудим циклы for
в следующем уроке «7.9 – Инструкции for
».
Лучшая практика
Избегайте использования оператора запятой, за исключением циклов for
.
Запятая как разделитель
В C++ символ запятой часто используется в качестве разделителя, и это использование не вызывает оператора запятой. Некоторые примеры разделителей запятыми:
// Запятая используется для разделения параметров в определении функции
void foo(int x, int y)
{
// Запятая используется для разделения аргументов при вызове функции
add(x, y);
// Запятая используется для разделения нескольких переменных, определенных
// в одной строке (не делайте так)
constexpr int z{ 3 }, w{ 5 };
}
Избегать разделительных запятых нет необходимости (кроме случаев объявления нескольких переменных, чего делать не следует).
Условный оператор
Оператор | Обозначение | Пример использования | Операция |
---|---|---|---|
Условный | ?: | c ? x : y | Если c не равно нулю (т.е. вычисляется как true ), то вычисляется x , в противном случае вычисляется y |
Условный оператор (?:
) (также иногда называемый оператором «арифметическое если») является тернарным оператором (он принимает 3 операнда). Поскольку исторически это единственный тернарный оператор C++, его также иногда называют «тернарным оператором».
Оператор ?:
предоставляет сокращенный метод для выполнения определенного типа инструкции if
/else
. Если вам нужно освежить в памяти if
/else
, то прежде чем продолжить просмотрите урок «4.10 – Знакомство с операторами if
».
Оператор if
/else
имеет следующую форму:
if (условие)
инструкция_1;
else
инструкция_2;
Если условие истинно, то выполняется инструкция_1, в противном случае выполняется инструкция_2.
Оператор ?:
имеет следующую форму:
(условие) ? выражение_1 : выражение_2;
Если условие истинно, то выполняется выражение_1, в противном случае выполняется выражение_2. Обратите внимание, что выражение_2 не является необязательным.
Рассмотрим оператор if
/else
, который выглядит так:
if (x > y)
larger = x;
else
larger = y;
Его можно переписать следующим образом:
larger = (x > y) ? x : y;
В таких случаях условный оператор может помочь сделать код более компактным без потери читабельности.
Заключение условного оператора в скобки
Условная часть оператора обычно помещается в круглые скобки, чтобы облегчить чтение, а также, чтобы убедиться в правильности приоритетов. Остальные операнды вычисляются так, как если бы они были заключены в круглые скобки, поэтому явные скобки для них не требуются.
Обратите внимание, что оператор ?:
имеет очень низкий приоритет. Если вы делаете что-либо, кроме присваивания результата переменной, весь оператор ?:
также должен быть заключен в круглые скобки.
Например, чтобы вывести на экран большее из значений x
и y
, мы могли бы сделать это:
if (x > y)
std::cout << x;
else
std::cout << y;
Или мы могли бы использовать для этого условный оператор:
std::cout << ((x > y) ? x : y);
Давайте посмотрим, что произойдет, если в приведенном выше случае мы не заключим в скобки весь условный оператор.
Поскольку оператор <<
имеет более высокий приоритет, чем оператор ?:
, следующая инструкция:
std::cout << (x > y) ? x : y;
будет вычисляться как:
(std::cout << (x > y)) ? x : y;
Она напечатает 1 (true
), если x
> y
, или 0 (false
) в противном случае!
Лучшая практика
Всегда заключайте в скобки условную часть условного оператора, а также подумайте о том, чтобы заключить его в скобки полностью.
Условный оператор вычисляется как выражение
Поскольку операнды условного оператора являются выражениями, а не операторами, условный оператор может использоваться в некоторых местах, где нельзя использовать if
/else
.
Например, при инициализации константной переменной:
#include <iostream>
int main()
{
constexpr bool inBigClassroom { false };
constexpr int classSize { inBigClassroom ? 30 : 20 };
std::cout << "The class size is: " << classSize << '\n';
return 0;
}
Для этого нет удовлетворительного оператора if
/else
. Вы можете подумать попробовать что-то вроде этого:
#include <iostream>
int main()
{
constexpr bool inBigClassroom { false };
if (inBigClassroom)
constexpr int classSize { 30 };
else
constexpr int classSize { 20 };
std::cout << "The class size is: " << classSize;
return 0;
}
Однако это не скомпилируется, и вы получите сообщение об ошибке, что classSize
не определен. Подобно тому, как переменные, определенные внутри функций, умирают в конце функции, переменные, определенные внутри инструкции if
или else
, умирают в конце инструкции if
или else
. Таким образом, classSize
уже был уничтожен к тому моменту, когда мы пытаемся напечатать его значение.
Если вы хотите использовать if
/else
, вам нужно будет сделать что-то вроде этого:
#include <iostream>
int getClassSize(bool inBigClassroom)
{
if (inBigClassroom)
return 30;
else
return 20;
}
int main()
{
const int classSize { getClassSize(false) };
std::cout << "The class size is: " << classSize;
return 0;
}
Это работает, потому что мы не определяем переменные внутри if
или else
, мы просто возвращаем значение обратно вызывающей функции, которое затем можно использовать в качестве инициализатора.
А это много лишней работы!
Тип выражений должен быть одинаковым или конвертируемым
Чтобы соответствовать проверке типов C++, оба выражения в условном операторе должны либо быть одинакового типа, либо второе выражение должно преобразовываться в тип первого выражения.
Итак, хотя вы могли ожидать, что можете написать что-то вроде этого:
#include <iostream>
int main()
{
constexpr int x{ 5 };
std::cout << (x != 5 ? x : "x is 5"); // не компилируется
return 0;
}
Приведенный выше пример не компилируется. Одно из выражений является значением целочисленного типа, а другое – строковым литералом. Компилятор попытается найти способ преобразовать строковый литерал в целочисленное значение, но, поскольку он не знает, как это сделать, выдаст ошибку. В таких случаях вам придется использовать if
/else
.
Итак, когда следует использовать условный оператор?
Условный оператор дает нам удобный способ сделать более компактными некоторые операторы if
/else
. Он наиболее полезен, когда нам нужен условный инициализатор (или присваивание) для переменной или для передачи условного значения функции.
Его не следует использовать для сложных операторов if
/else
, поскольку он быстро становится нечитаемым и подверженным ошибкам.
Лучшая практика
Используйте условный оператор только для простых условных выражений там, где вы используете результат, и где он улучшает читаемость.