Операторы

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

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

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

ОператорОписаниеСинтаксисПример, результат
=Присваивание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();

Изменено:

Язык Си: 3 комментария

  1. >Вместо нуля может быть почти любой другой символ. Плюс и минус работают по-другому. Для выравнивания числа по по левому краю, а не по правому, перед числом нужно поставить знак минус.
    Лишне по

  2. Термин «регистр ядра» довольно специфичен. Звучит не привычно. А почему не использовать термин CPU или процессор или на худой конец микроконтроллер?

  3. Смысл статических переменных вне функции мне кажется не раскрыт. Такой модификатор делает переменную недоступной из других единиц трансляции. Т.е. не будет конфликта имен между разными .с файлами.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.