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

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

Ветвление 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
}
}

Изменено: