Сравнения, условия и выбор
В языке C++ есть такое понятие, как логическая величина, которая принимает два значения: правда и ложь, true
и false
, 1
и 0
. Для хранения логических величин у нас есть тип данных boolean
(bool
), который может принимать значения 0
(false
) или 1
(true
). Логические переменные часто называют флагами: если переменная равна true
- флаг поднят, если false
- опущен.
Сравнение
Два числа можно сравнить при помощи операторов сравнения:
==
равенство (a == b
)!=
неравенство (a != b
)>=
больше или равно (a >= b
)<=
меньше или равно (a <= b
)>
больше (a > b
)<
меньше (a < b
)
В рассмотренных выше абстрактных примерах с a
и b
мы получаем логическое значение, которое является результатом сравнения чисел. Пусть a = 10
и b = 20
, тогда скобка (a > b)
вернёт значение false
, потому что a
меньше b
. А вот (a != b)
вернёт true
, т.к. а
действительно не равно b
.
Для связи нескольких логических величин используются логические операторы:
!
логическое НЕ, отрицание. Есть аналог - операторnot
&&
логическое И. Есть аналог - операторand
||
логическое ИЛИ. Есть аналог - операторor
byte a = 10, b = 20; (a > b); // false (a != b); // true boolean flag = true; flag; // true !flag; // false !(a > b); // true //flagA = true, flagB = false; (flagA && flagB); // false, т.к. B false //flagA = true, flagB = true; (flagA and flagB); // true, т.к. оба true //flagA = true, flagB = false; (flagA || flagB); // true, т.к. хотя бы А true //flagA = false, flagB = false; (flagA or flagB); // false, т.к. ни А ни В не true
Двойное неравенство
В языке Си не предусмотрено "двойных неравенств", то есть запись вида 0 < a < 10
приведёт к ошибке компиляции. Для записи таких условий нужно разделить неравенство на одиночные и соединить их оператором И, вот исправленный пример 0 < a && a < 10
.
Сравнение float
Со сравнением float
чисел всё не так просто из за особенности самой модели "чисел с плавающей точкой" - вычисления иногда производятся с небольшой погрешностью, из за этого сравнение может работать неверно! Пример из урока про вычисления:
float val1 = 0.1; // val1 == 0.100000000 float val2 = 1.1 - 1.0; // val2 == 0.100000023 !!! // казалось бы, val1 == val2 // но сравнение вернёт false if (val1 == val2); // false
Будьте внимательны при сравнении float чисел, особенно со строгими операциями <=
: результат может быть некорректным и нелогичным!
true/false и 1/0
Для программы ключевое слово true
это 1
, а false
это 0
, и их можно равноценно друг на друга заменять. Зачем тогда их придумали, если можно использовать 1 и 0 и получить более короткую запись? Эти обозначения нужны в первую очередь для удобства программиста, чтобы в большой программе сориентироваться по типам данных, т.к. true
и false
принято присваивать только к логическим переменным типа bool
. Например, если в коде встретится var = 0;
- то var может быть любым численным типом данных, а если var = false;
- то мы сразу понимаем, что это флаг, и это понимание помогает быстрее сориентироваться в чужом и своём коде.
Условный оператор if
Условный оператор if
(англ. "если") позволяет разветвлять выполнение программы в зависимости от логических величин, т.е. результатов работы операторов сравнения и логических переменных.
if (лог. величина) { // выполняется, если лог. величина - true }
Оператор else
(англ. "иначе") работает в паре с оператором if
и позволяет предусмотреть действие на случай невыполнения if
:
if (лог. величина) { // выполняется, если лог. величина - true } else { // выполняется, если лог. величина - false }
Также есть третья конструкция, позволяющая ещё больше разветвить код, называется она else if
:
if (лог. величина 1) { // выполняется, если лог. величина 1 - true } else if (лог. величина 2) { // выполняется, если лог. величина 2 - true } else { // выполняется иначе }
Посмотрим на все эти операторы в действии в большом примере:
// при выполнения одного действия // внутри условия, {} не обязательны if (a > b) c = 10; // если a больше b, то c = 10 else c = 20; // если нет, то с = 20 // вместо сравнения можно использовать лог. переменную boolean myFlag, myFlag2; // если myFlag true, то c присвоить 10 if (myFlag) c = 10; // сложные условия // если оба флага true, то c присвоить 10 if (myflag && myFlag2) c = 10; // при выполнении двух и более действий // внутри условия, {} обязательны! if (myFlag) { с = 10; b = c; } else { с = 20; b = a; } byte buttonState; if (buttonState == 1) a = 10; // если buttonState 1 else if (buttonState == 2) a = 20; // если нет, но если buttonState 2 else a = 30; // если и это не верно, то вот
Оператор if
позволяет управлять программой и создавать разветвлённые действия в зависимости от разных условий. Обратите внимание на последний блок в примере выше, там где используется else if
для выбора действия в зависимости от значения одной и той же переменной. Существует оператор выбора switch
, позволяющий сделать код более красивым. О нём поговорим чуть ниже.
Особенность bool
В уроке о типах данных я упоминал о том, что bool
принимает значение true
, если присвоить ему отличное от нуля число, то есть оператору if
можно скормить любое число, и он вернёт true в любом случае, кроме нуля. Это бывает удобно в некоторых случаях, но также может и приводить к ошибкам, которые трудно отловить. if (50) {}
- код в фигурных скобках будет выполнен.
Порядок условий
Порядок условий играет очень большую роль: логические выражения и переменные проверяются слева направо, и если результат всего выражения в скобках будет однозначно определён после проверки первого выражения - остальные выражения проверяться не будут. Например если в выражении if (a && b && c)
хотя бы а
имеет значение false
, проверка остальных выражений (b
и c
) уже не выполняется, потому что всё выражение заведомо будет false
.
Или наоборот: если в выражении if (a || b || c)
хотя бы а
будет true
- всё выражение также будет true
и b
с c
не будут проверяться.
Это может помочь в оптимизации кода: например, есть какой-то флаг и выражение, которое вычисляется прямо в условии и сразу проверяется. Если флаг опущен, микроконтроллер не будет тратить время на лишние вычисления и сразу покинет условие. Например:
if (flag && analogRead(0) > 500) { // делать что то }
Тернарный оператор ?
Оператор знак вопроса ?
, или тернарный оператор, является более коротким аналогом для записи конструкции if else
. Действие с оператором ?
имеет следующий вид:
условие ? выражение1 : выражение2
Это работает так: вычисляется условие, если оно истинно, то всё действие возвращает значение выражения 1, а если оно ложно, то всё действие возвращает значение выражения 2. Пример:
byte a, b; a = 10; // если а > 9, b получает значение 100 // иначе b получает значение 200 b = (a > 9) ? 100 : 200;
Аналогичная конструкция на if-else
a = 10; if (a > 9) b = 100; else b = 200;
Ещё вариант с вычислением:
byte a = 10, b = 5; // прибавим к b результат выражения a*10 если а > 10 // иначе прибавим a+10 b += (a > 9) ? (a * 10) : (a + 10);
Аналогичным образом можно использовать оператор ?
для вывода данных и текста в последовательный порт (подробнее о нём в другом уроке):
Serial.println((a > 9) ? "больше 9" : "меньше 9");
Важный момент: если результат присваивается или передаётся в функцию, тип данных должен быть одинаковый! То есть код Serial.println((a > 9) ? 9 : "меньше 9");
приведёт к ошибке, так как мы пытаемся передать или int
или char*
, что не будет работать.
Также можно получать значения из функций, если они имеют одинаковый возвращаемый тип данных:
a = condition ? func1() : func2();
А можно ли сделать на операторе ?
более сложную конструкцию, типа else if
? Можно!
void setup() { Serial.begin(9600); // код выводит "размер" переменной value byte value = 5; // конструкция на if-else if (value > 19) Serial.println("> 19"); else if (value > 9) Serial.println("> 9"); else Serial.println("< 9"); // на операторах ? Serial.println(( (value > 9) ? ( (value > 19) ? "> 19" : "> 9" ) : "< 9" )); }
Оператор выбора
Оператор выбора switch
позволяет создать разветвление кода в зависимости от значения одной переменной. Синтаксис такой:
switch (значение) { case 0: // выполнить, если значение == 0 break; case 1: // выполнить, если значение == 1 break; case 2: case 3: case 4: // выполнить, если значение == 2, 3 или 4 break; default: // выполнить, если значение не совпадает ни с одним из case break; }
Наличие оператора default
необязательно. Наличие оператора break
обязательно, иначе сравнение пойдёт дальше, как показано для case 2, 3 и 4.
При помощи условных операторов и операторов выбора строится логика работы программы. Условный оператор поможет сравнить значение с датчика и принять решение, что делать дальше. Оператор выбора отлично справится с изменяющимися режимами работы программы или опросом кнопок, нажатых на ИК пульте.
Также конструкция на switch
работает быстрее, чем аналогичная на else if
: происходит выбор варианта и моментальный переход на нужный блок кода, а в else if
в каждой строке производится проверка логического выражения.
ВАЖНОЕ ЗАМЕЧАНИЕ
Нужно быть крайне внимательным при работе с оператором switch
, потому что код, находящийся внутри фигурных скобок switch() { }
, является одним блоком кода для всех кейсов. Соответственно кейсы case
- всего лишь ярлыки перехода между участками этого блока. Почему это так важно: все кейсы находятся в одной области видимости, то есть внутри switch
не могут быть объявлены локальные переменные с одинаковыми именами:
switch (mode) { case 0: long val = 100; break; case 1: long val = 100; // приведёт к ошибке break; case 2: break; }
Более того, крайне не рекомендуется создавать локальные переменные внутри кейсов, так как это может сломать код!
switch (mode) { case 0: break; case 1: // локальная переменная long val = 100; break; case 2: // при mode == 2 будет выводиться кек // только если убрать локальную переменную выше! Serial.println("kek"); break; }
Что делать, если очень хочется? Обернуть содержимое кейса в блок при помощи фигурных скобок:
switch (mode) { case 0: break; case 1: { long val = 100; } break; case 2: Serial.println("kek"); break; }
Диапазон значений
В switch
также можно задавать диапазон значений для кейсов через ...
, всё будет понятно из примера:
switch (a) { case 0 ... 100: b = 1; break; case 101 ... 200: b = 2; break; case 201 ... 300: b = 3; break; case 301 ... 400: b = 4; break; case 401 ... 500: b = 5; break; case 501 ... 600: b = 6; break; }
По сути данный код равносилен вот такой конструкции на else if
:
if (a > 0 && a < 100) b = 1; else if (a < 200) b = 2; else if (a < 300) b = 3; else if (a < 400) b = 4; else if (a < 500) b = 5; else if (a < 600) b = 6;
Вариант со свитчем выполняется быстрее (доли микросекунды), т.к. программа опять же будет выбирать значения, а не сравнивать их.
Видео
Полезные страницы
- Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
- Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
- Полная документация по языку Ардуино, все встроенные функции и макросы, все доступные типы данных
- Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
- Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
- Поддержать автора за работу над уроками
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])