Операторы

Язык Си поддерживает различные операторы: арифметические, сравнения, логические, побитовые и другие.

Арифметические

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

ОператорОписаниеСинтаксисПример, результат
=Присваиваниеa = 10; b = a;b = 10
+Складывает два числаa + b20 + 5 = 25
-Вычитает одно число из другого либо изменяет знакa - b, -a20 — 5 = 15, -(20) = -20
*Перемножает два числаa * b20 * 5 = 100
/Делит числитель на знаменательa / b20 / 5 = 4
%Возвращает остаток от целочисленного деленияa % b20 % 5 = 0
++Инкремент числа, т.е. увеличивает значение на 1a++, ++aa = 21; b = a++; a = 21; b = ++a; // b = 21; a = 22; b = 22; a = 22;
--Декремент числа, т.е. уменьшает значение на 1a--, --aАналогично операции ++

При программировании часто приходится увеличивать или уменьшать значение переменной на единицу. Такая запись обычно выглядит следующим образом:

value = value + 1;

Например, такая операция нужна в цикле.

for (uint32_t i = 0; i < 10; i = i + 1) {
// do something
}

Операция встречается настолько часто, что в синтаксис были введены операторы инкремента и декремента. Записываются они как ++ и -- соответственно. При этом они могут быть как суффиксами (прибавлять / вычитать значение после считывания) и префиксными (т.е. прибавлять / вычитать перед считыванием).

uint32_t value = 0;
if (value++ == 0) { // value is 0
   // the code here will execute if the value is 0. The value inside the code block becomes to 1
}
// ...
value = 0;
if (++value == 0) { // value is 1
   // at the comparison time, the value is 1, hence the program won't enter the block body
}

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

В управляющих конструкциях используются «логические выражения», т.е. те, которые принимают значения истина или ложь. Причем в языке Си любое отличное от нуля значение считается истиной, а сам ноль ложью. Результат работы оператора сравнения является логическим. Допустим, a = 10 и b = 10.

ОператорОписаниеСинтаксисПример, результат
==Равенствоa == bИстина, a равно b
!=Неравенствоa != bЛожь, a равно b
>Большеa > bЛожь, a равно b
<Меньшеa < bЛожь, a равно b
>=Больше или равноa >= bИстина, a больше или равно b
<=Меньше или равноa <= bИстина, a меньше или равно b

В рассмотренных выше примерах с циклом for и условным оператором if можно увидеть такие выражения.

Логические операторы

Следующим классом операторов являются логические. Они, так же как и операторы сравнения, в результате дадут истину или ложь. Допустим, a = 0, b = 5.

ОператорОписаниеСинтаксисПример, результат
&&Логическое умножение, Иa && b0 && 5 = 0
||Логическое сложение, ИЛИa || b0 || 5 = 1

Обратите внимание: неважно, какие значения хранятся в операндах, — результат будет либо 1, либо 0.

Побитовые операции

При работе с регистрами, как мы уже говорили в начале книги, требуются побитовые операции, чтобы выставлять и сбрасывать определенные биты в регистрах.

ОператорОписаниеСинтаксис
~Побитовая инверсия~a
&Побитовое Иa & b
|Побитовое ИЛИa | b
^Побитовое исключающееa ^ b
<<Побитовый сдвиг влевоa << b
>>Побитовый сдвиг вправоa >> b

Некоторые из них мы уже рассмотрели (~, &, |), пробежимся только по оставшимся. Таблица истинности для исключающего ИЛИ (XOR) приведена ниже.

ABA ^ B
000
011
101
110

Данную операцию удобно использовать для переключения состояния бита. Например, данный код

GPIOB->ODR |= GPIO_ODR_ODR0;  // turn on the LED
delay(1000); // 1s
GPIOB->ODR &= ~GPIO_ODR_ODR0; // turn off the LED
delay(1000); // 1s

можно сократить в два раза:

GPIOB->ODR ^= GPIO_ODR_ODR0;
delay(1000);

К слову, мы уже рассматривали такую возможность выше, можно включить два светодиода одной строчкой (если, скажем, к ножке 7 он подключён):

GPIOB->ODR |= GPIO_ODR_ODR0 | GPIO_ODR_ODR7;

И аналогичным способом их выключить:

GPIOB->ODR &= ~(GPIO_ODR_ODR0 | GPIO_ODR_ODR7);

Оставшиеся две операции — это побитовое смещение в левую и в правую сторону. С его помощью удобно создавать маски. Допустим, нам нужно создать маску для 4-го бита (скажем, ножки порта ввода-вывода). Сделать это можно так:

#define GPIO_PIN_3              0b1000

Выглядит не очень удобно: что если этот бит будет стоять на 12-й позиции? Придется отсчитывать эти самые позиции, чтобы понять, действительно ли маска соответствует тому, о чём говорит ее название. Можно поступить проще: так как система двоичная, логично использовать десятичное число для записи того же самого, ведь восемь — это третья степень двойки.

#define GPIO_PIN_3              8

Запомнить степени двойки проще, однако всё так же не очень хорошо для восприятия. На помощь приходит оператор смещения.

#define GPIO_PIN_3              (1 << 3)

Данной записью мы берем 1 и смещаем ее на три позиции, получая заветную восьмерку. При помощи такой операции мы можем в одно слово поместить четыре 8-битных значения.

uint32_t values = 0xFF000000 + 0x00CD0000 + 0x0000B200 + 0x000000A0;
uint8_t a = (uint8_t)(values >> 24); // 0xFF
uint8_t b = (uint8_t)(values >> 16); // 0xCD
uint8_t c = (uint8_t)(values >>  8); // 0xB2
uint8_t d = (uint8_t)(values >>  0); // 0xA0

Либо, что лучше, можно использовать битовые поля, благо в ARM присутствуют инструкции для работы с ними.

Составное присваивание

Почти все вышеперечисленные операторы могут быть использованы как составная часть оператора присваивания =. Допустим, у нас имеется следующий код:

int a, b;
a = 10;     // a == 10
a = a + 10; // a == 20

Такой код легко можно упростить, заменив строчку a = a + 10 на a += 10. Вот список этих операторов: +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=.

Другие операторы

Кроме унарных операций (требующих одну переменную) и бинарных (требующих две) существуют и тернарная условная операция. По сути она заменяет конструкцию ifelse, о которой мы еще поговорим. Общий вид такого оператора можно представить так:

[условие] ? [действие] : [альтернативное действие];

Воспользуемся этой конструкцией, чтобы реализовать функцию включения-отключения светодиода.

STATE_t led_swtich(void) {
return (GPIOA->IDR & LED_PIN) ? ON : OFF;
}

Имеется и его бинарная вариация, которую иногда называют оператором Элвиса.

Допустим, у нас имеется два температурных датчика, один из которых основной, второй — резервный. Если первый работает некорректно, то функция get_primary_sensor_data() возвращает 0, и нужно взять данные со второго сенсора. При помощи оператора Элвиса делается это в одну строчку.

uint32_t temperature = get_primary_sensor_data() ?: get_secondary_sensor_data();

Изменено: