Управляющие конструкции

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

Ветвление if

Для создания условной конструкции используют ключевое слово if.

if (statement_1) {
// do something
} else if (statement_2) {
// ...
} else if (statement_3) {
// ...
} else {
// ...
}

Строчки с else if и else не обязательны. Вы можете использовать их по отдельности или вместе.

Вспомним тернарный условный оператор.

older = (alice_age > bob_age) ? 1 : 0;

Тот же самый код можно переписать с использованием конструкции ifelse.

if (alice_age > bob_age) {
older = 1;
} else {
older = 0;
}

Переключатель switch

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

switch([statement]) {
case 0:
// do something useful
break;
case 1:
// do something useful
break;
default:
// do something useful
}

В отличие от случая с if, значения, которые можно поставить после case, строго детерминированы — это четкие значения, а не диапазоны. Ниже приведен код, который позволяет выбрать, какой светодиод нужно переключить:

void led_toggle(LED_t led, uint32_t ms) {
if (ms > 10000)
return;
switch (led) {
case LED_GREEN:
GPIOA->IDR ^= LED_GREEN_PIN;
break;
case LED_RED:
GPIOA->IDR ^= LED_RED_PIN;
break;
case LED_BLUE:
default:
GPIOA->IDR ^= LED_BLUE_PIN;
break;
}
delay(ms);
}

default является необязательным, его выполнение произойдет в том случае, если ни один из case не будет выполнен. В коде, приведенном выше, он сочленяется с LED_BLUE.

Циклы

Когда необходимо совершать множество однотипных операций, лучше всего использовать циклы. В языке Си есть три типа циклов: for, while и dowhile.

Цикл for можно записать следующим образом:

for(uint32_t i = 0; i < threshold; i++) {
// do something useful
}

Такую конструкцию удобно использовать, когда количество итераций известно заранее. Код в фигурных скобках будет выполнен threshold раз. При этом параметры в цикле не обязательны, их можно опустить при необходимости. Для того чтобы создать бесконечный цикл, можно записать цикл так:

for(;;);

В случае, если количество операций заранее не известно, лучше использовать цикл while.

while (SPI_I2S_GetFlagStatus(SPI_MASTER, SPI_I2S_FLAG_TXE) == RESET);

Данной строчкой мы заставляем ожидать появление флага SPI_I2S_FLAG_TXE. Пока он не будет установлен, программа не будет ничего делать.

Для создания бесконечного цикла необходимо просто создать условие, которое будет всегда истинным, например, вот так:

while(1) {
// main loop
}

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

У цикла while имеется модификация.

do {
// do something useful
} while([statement]);

В отличие от просто while, здесь сначала выполняется код, а затем идет проверка, нужно ли код повторить. В некоторых случаях можно сэкономить одну операцию сравнения.

Оператор ,

С помощью запятой в Си можно связать два выражения. Они будут вычисляться последовательно, слева направо, при этом результатом операции будет последнее вычисление. Код ниже,

uint32_t a, b;
b = 10;
a = b + 5;

можно переписать следующим образом:

uint32_t a, b;
a = (b = 10, b + 5);

Всегда нужно помнить о читаемости кода, поэтому писать так скорее не следует, но данное свойство запятой часто используют в циклах. Код ниже,

while (value < THRESHOLD) {
   value = func(i++);
}

переписывается в одну строчку:

while (value = func(i++), value < THRESHOLD);

В цикле for можно задать начальные значения двум (и более) переменным и изменять их одновременно:

for (uint32_t i = 0, j = SIZE; i < SIZE / 2; i++, j--) {
 result += array[i] + array[j];
}

Ключевые слова break и continue

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

while(1) {
if (GPIOA->IDR & BUTTON_PIN)
break;
led_toggle();
delay(1000);
}

Если кнопка будет отжата, мы попадем внутрь условия и завершим работу цикла while.

Аналогичным образом работает другое ключевое слово continue. Оно не завершает работу цикла полностью, а позволяет перейти к следующей итерации.

// sum = 0
for (uint32_t i = 0; i < 5; i++) {
if (i == 2)
continue;
sum += i;
}
// sum = 0 + 1 + 3 + 4 = 8

Оператор goto

В языке Си существует еще одна управляющая конструкция, которая позволяет переходить из одного участка кода в другой. Ее использование считается дурным тоном, так как она затрудняет анализ кода. К тому же любую конструкцию можно переписать без ее использования. Если совсем кратко: не используйте ее.

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

В феврале 2014 года в реализации SSL/TLS от Apple была обнаружена ошибка в использовании goto, см. goto fail bug. Случайно продублированная строчка создавала секцию недостижимого кода.

Тем не менее, привести пример ее использования всё же стоит. Допустим, у нас есть два массива (arr_1[] и arr_2[]), и нам необходимо знать, существует ли в них хотя бы одно совпадающее значение.

for (uint32_t i = 0; i < ARR_SIZE; i++)
for (uint32_t j = 0; j < ARR_SIZE; j++)
if (arr_1[i] == arr_2[j])
goto found;
found:
// do something

Если мы вместо goto напишем break, то выйдем из внутреннего цикла (j), в то время как внешний цикл (i) пойдет на следующую итерацию. Придется ввести дополнительную переменную и проверять ее каждый раз, когда управление переходит к внешнему циклу.

uint32_t flag = 0;
for (uint32_t i = 0; i < ARR_SIZE; i++) {
if (flag != 0)
break;
for (uint32_t j = 0; j < ARR_SIZE; j++)
if (arr_1[i] == arr_2[j]) {
flag = 1;
// do something
}
}

Изменено:

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

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

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

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

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

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

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