Управляющие конструкции
Редко когда программа работает линейно. В ней встречаются ветвления и повторяющиеся операции. Для их реализации введены специальные конструкции.
Ветвление 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;
Тот же самый код можно переписать с использованием конструкции if
—else
.
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
и do
—while
.
Цикл 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
}
}