Операторы
Язык Си поддерживает различные операторы: арифметические, сравнения, логические, побитовые и другие.
Арифметические
Арифметические операторы используются для выполнения математических операций: сложения, вычитания, умножения, деления и получения остатка от целочисленного деления двух чисел.
Оператор | Описание | Синтаксис | Пример, результат |
---|---|---|---|
= | Присваивание | a = 10; b = a; | b = 10 |
+ | Складывает два числа | a + b | 20 + 5 = 25 |
- | Вычитает одно число из другого либо изменяет знак | a - b , -a | 20 — 5 = 15, -(20) = -20 |
* | Перемножает два числа | a * b | 20 * 5 = 100 |
/ | Делит числитель на знаменатель | a / b | 20 / 5 = 4 |
% | Возвращает остаток от целочисленного деления | a % b | 20 % 5 = 0 |
++ | Инкремент числа, т.е. увеличивает значение на 1 | a++ , ++a | a = 21; b = a++; a = 21; b = ++a; // b = 21; a = 22; b = 22; a = 22; |
-- | Декремент числа, т.е. уменьшает значение на 1 | a-- , --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 && b | 0 && 5 = 0 |
|| | Логическое сложение, ИЛИ | a || b | 0 || 5 = 1 |
Обратите внимание: неважно, какие значения хранятся в операндах, — результат будет либо 1
, либо 0
.
Побитовые операции
При работе с регистрами, как мы уже говорили в начале книги, требуются побитовые операции, чтобы выставлять и сбрасывать определенные биты в регистрах.
Оператор | Описание | Синтаксис |
---|---|---|
~ | Побитовая инверсия | ~a |
& | Побитовое И | a & b |
| | Побитовое ИЛИ | a | b |
^ | Побитовое исключающее | a ^ b |
<< | Побитовый сдвиг влево | a << b |
>> | Побитовый сдвиг вправо | a >> b |
Некоторые из них мы уже рассмотрели (~
, &
, |
), пробежимся только по оставшимся. Таблица истинности для исключающего ИЛИ (XOR) приведена ниже.
A | B | A ^ B |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
Данную операцию удобно использовать для переключения состояния бита. Например, данный код
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
. Вот список этих операторов: +=
, -=
, *=
, /=
, %=
, &=
, |=
, ^=
, <<=
, >>=
.
Другие операторы
Кроме унарных операций (требующих одну переменную) и бинарных (требующих две) существуют и тернарная условная операция. По сути она заменяет конструкцию if
—else
, о которой мы еще поговорим. Общий вид такого оператора можно представить так:
[условие] ? [действие] : [альтернативное действие];
Воспользуемся этой конструкцией, чтобы реализовать функцию включения-отключения светодиода.
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();