Алгоритмические конструкции языка Си

Предисловие

Этот раздел — почти точная копия соответствующей краткой справки из первого блока упражнений в тестирующей системе.

Составная команда

{ команда_1 команда_2 ... команда_n }

Выполнение такой команды эквивалентно последовательному выполнению команд, заключённых в фигурные скобки, за тем исключением, что все переменные, определённые внутри составной команды, вне этой команды использовать нельзя.

Пример

int sum = 0;
{   
    // Внутри составной команды принято делать отступ, 
    // единый для всех подкоманд этой команды. 
    int temp = 0;
    scanf("%d", &temp); sum += temp;
    scanf("%d", &temp); sum += temp;
}

printf("Сумма введённых чисел равна %d\n", sum);
// А вот переменной temp здесь уже нет!

Ветвление

if (формула) команда1

или

if (формула) команда1 else команда2

Вычисляет формулу, значением которой должно быть число. Если получился результат, отличный от 0, то выполняется команда1. В противном случае выполняется команда2.

Вариант

if (A) B

эквивалентен

if (A) B else {}

Настоятельно рекомендуется с if (и прочими подобными конструкциями) использовать только составные команды: это позволяет избежать многих трудноуловимых ошибок.

Пример

if      (x > 5) { printf("Очень много!\n"); }
else if (x > 3) { printf("Больше тройки!\n"); }
else if (x > 0) { printf("Чуть-чуть...\n"); }
else            { printf("Ноль или меньше.\n"); }

Прыжки

Перед большей частью команд (за исключением определений переменных) можно поставить метку: слово, за которым идёт двоеточие (не обязательно слитно).

Перейти к исполнению команды, следующей за меткой, можно при помощи команды

goto имя_метки;

Не рекомендуется использовать goto без надобности: чаще всего эта команда помогает внутри подпрограмм, занимающих какие-то ресурсы, а в конце своей работы освобождающих их.

Пример

int main(void) {
    int status = 0;

    FILE *file = fopen("words.txt", "w");
    if (!file) {
        // не удалось открыть файл на запись
        return 1;  // (число 1 было выбрано произвольно: это не стандарт)
    }

    if (0 > fprintf(file, "Вася ест грибы...\n")) {
        // произошла ошибка записи в файл
        status = 2; // (число 2 было выбрано произвольно: это не стандарт)
        goto close_file;
    }

    fseek(file, 0, SEEK_SET);
    if (0 > fprintf(file, "Петя")) { status = 2; goto close_file; }

    printf("Теперь грибы ест Петя!\n");

close_file:
    fclose(file);

    return status;
}

Цикл while

Конструкция

while (A) команда

эквивалентна

метка: if (A) { команда goto метка; }

Пример

int A = a;
int B = b;

while (b != 0) {
    int rem = a % b;
    a = b;
    b = rem;
}

if (a < 0) { a = -a; }

printf("НОД чисел %d и %d равен %d\n", A, B, a);

Цикл for

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

Конструкция

for(A; B; C) D

эквивалентна

{ A; while(B) { D C; } }

Пример

int sum = 0;

for (int i = 1; i < 10; i += 1) {
    sum += i;
}

printf("Сумма чисел от 1 до 9 равна %d\n", sum);

Управление циклами

Внутри циклов допустимы команды continue; и break;. Первая прыгает к концу тела цикла, а вторая — за его пределы.

Говоря более формально, рассмотрим два цикла с метками:

while(...) { ... A: ; } B: ;

for(...) { ... A: ; } B: ;

Команда continue; эквивалентна goto A;, а команда break; эквивалентна goto B;.

Арифметика

В заключение приведём список полезных арифметических операторов:

  • +, -, * — сумма, разность, произведение
  • /, % — частное (для целых чисел — неполное) и остаток (при неотрицательной левой части)
  • ==, >, <, >=, <= — равно, больше, меньше, больше-или-равно, меньше-или-равно
  • &, |, ^, ~поразрядные конъюнкция, дизъюнкция, xor и отрицание
  • ! — отрицание (превращает неноль в ноль, а ноль — в единицу)
  • && — ленивая конъюнкция; вычисляет свою правую подформулу тогда и только тогда, когда значение левой отлично от нуля
  • || — ленивая дизъюнкция; вычисляет свою правую подформулу тогда и только тогда, когда значение левой равно нулю

Также к большинству арифметических операторов есть соответствующая разновидность оператора присваивания. Например:

foo += bar; baz |= quuz;

эквивалентно

foo = foo + bar; baz = baz | quuz;

Пример

В примере продемонстрируем, насколько полезной бывает ленивая логика:

if (b != 0 && a % b == 0) {
    //        ^^^^^^^^^^  нельзя вычислять, не убедившись ПРЕДВАРИТЕЛЬНО
    //                    в том, что b не ноль: это приведёт к ошибке
    printf("Число %a делится на число %b\n", a, b);
}