Почему уже установленное значение сбрасывается на новое значение в условии цикла for в C?

Я учусь на курсе программирования, и в одном из вопросов последнего теста есть код, который нужно «вычислить».

#include <stdio.h>

int main(void) {
    short a[4] = {66, 066, 0x66};
    int i = 0, s = 0, p = 15;

    for (++a[i]; !i, p = a[i]; s += a[i++]) {
        p = a[i] >> 2 | p;
        a[i] &= p;
    }

    printf("%d", s);
}

Исходный вопрос состоит в том, чтобы найти значение s, но у меня есть проблема с пониманием, почему p = 67 находится «до первой итерации», если оно было объявлено как p = 15. Разве p = a[i] не является условием цикла, а не способом объявления значения?

Я попробовал «вычислить» p = 15, где я не получил правильный результат, и p = 67, где я получил правильный результат.

!i, p = a[i]; делает не то, что вы себе представляете. Первое условие игнорируется, а вторая часть устанавливает p в a[i]; и проверяет его значение.
Weather Vane 10.04.2024 01:06

«почему p = 67 перед первой итерацией?» ++a[i] устанавливает a[0] на 67. Затем p = a[i] устанавливает p на 67.

Weather Vane 10.04.2024 01:12

Необходимо оценить среднюю часть цикла for, чтобы решить, следует ли входить в цикл. Если это имеет побочные эффекты, то они произойдут до первой итерации, даже если результат был ложным и цикл не был введен.

Retired Ninja 10.04.2024 01:13

Отвечает ли это на ваш вопрос? Что делает оператор запятая?

phuclv 10.04.2024 03:30

@phuclv Хотя я нашел дополнительную информацию об операторе , полезной, кое-что из этого я уже узнал. Больше всего мне помогли ответы: первый комментарий Уэзера Вейна и оба ответа Джона Байко и Лундина. Я не уверен, как я могу их отблагодарить, поскольку я впервые использую переполнение стека, но эти ответы были конкретными, короткими и понятными.

Uros Lalic 10.04.2024 13:16
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
126
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

В тестовом условии цикла for у вас есть команда с запятой.

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

например

int x = (1,2,a[i]=4);

В этом случае 1 и 2 просто игнорируются, а 4 оба будут присвоены a[I] и возвращены для присвоения x.

Взгляните на это объяснение.

Ответ принят как подходящий

Я понимаю, что это намеренно плохо написано для теста, но надеюсь, вы понимаете, насколько это плохо и почему так не должно быть. Деконструкция его может помочь прояснить, что он на самом деле делает.

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

    for (++a[i]; !i, p = a[i]; s += a[i++]) {
        ...
    }

Становится это:

    ++a[i];
    while (!i, p = a[i]) {
        ...
        s += a[i++];
    }

Оператор , выполняет две операции, но возвращает результаты только второй. Это может быть полезно, если вы хотите, чтобы побочные эффекты первой операции влияли на вторую, но !i не имеет побочных эффектов, поэтому от него можно отказаться. Итак, цикл теперь:

    ++a[i];
    while (p = a[i]) {
        ...
        s += a[i++];
    }

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

    ++a[i];
    p = a[i];  // Update p here first.
    while (p) {
        ...
        s += a[i++];
        p = a[i];  // Update p before looping again.
    }

Это должно прояснить цикл. Внутри p находится в выражении по обе стороны от знака =. Хотя это разрешено, это означает, что одна переменная используется для двух вещей, и когда это произойдет, вам нужно переключить свое мышление — если вы пропустите ее, вы будете думать о неправильном значении переменной. Добавляйте новую переменную каждый раз, когда значение меняется:

    short a[4] = {66, 066, 0x66};
    int i = 0, s = 0, p = 15;

    ++a[i];
    p = a[i];
    while (p) {
        int m = a[i] >> 2 | p;
        a[i] &= m;
        s += a[i++];
        p = a[i];
    }

Должно быть понятно, что p можно заменить на a[i] везде, где он используется, но я оставлю все как есть. Это должно прояснить, что именно делает код сейчас и почему. Если хотите, вы можете преобразовать цикл while обратно в цикл for:

    for (p = a[i];p;p = a[i]) {

Это не кажется полезным, но в конечном итоге так оно и есть, поэтому я бы оставил это как цикл while.

Излишне говорить, что это «запутанный» код: код, намеренно написанный плохими способами, чтобы запутать читателя.

  • ++a[i] Это захватывает содержимое a[0] и увеличивает его на 1. a[0] теперь равно 67. Поскольку это первое предложение цикла for, оно выполняется только один раз, перед запуском цикла.
  • !i, ... это «недействительно»; это ничего не делает. Первым вычисляется левый операнд оператора запятой, но возвращаемое значение соответствует значению правого операнда, в данном случае p = a[i]. p станет 67 или, если хотите, 0x43. Цикл прервется, когда a[i] станет нулем.
  • a[i] >> 2 сдвиг вправо на 2, что тоже самое, что разделить на 2, в 2 раза. Обратите внимание, что << имеет более высокий приоритет, чем |. 0x43 >> 2 дает 0x10
  • 0x10 | 0x42 дает 0x53.
  • a[i] &= p; — это 0x43 & 0x53, что снова возвращает нам значение 0x43.
  • Все идет по кругу, делая побитовую ерунду и с другими значениями в массиве, а затем восстанавливая их исходные значения.
  • s += a[i++] просто суммирует значения массива, а затем увеличивает i на 1.
  • Поскольку был массив из 4 значений, но последнее не было инициализировано программистом явно, оно было неявно инициализировано нулем. Таким образом, цикл прерывается на 4-м значении, когда i==3.

Таким образом, весь код, который хорош на практике, — это увеличить значение первого элемента массива на 1, а затем суммировать:

66+1 (десятеричный) + 066 (восьмеричный) + 0x66 (шестнадцатеричный) = 223


Чтобы понять подобный код, было бы полезно добавить множество операторов печати:

#include <stdio.h>

int main(void) {
    short a[4] = {66, 066, 0x66};
    int i = 0, s = 0, p = 15;

    for (++a[i]; printf("i:%d ",i), !i, p = a[i]; printf("s:%d \n", s += a[i++])) {
        printf("p:%.2X ", p);
        p = a[i] >> 2 | p;
        printf("p:%.2X ", p);
        a[i] &= p;
        printf("a[%d]:%.2X ", i, a[i]);
    }

    printf("\n\ns:%d", s);
}

Выход:

i:0 p:43 p:53 a[0]:43 s:67 
i:1 p:36 p:3F a[1]:36 s:121 
i:2 p:66 p:7F a[2]:66 s:223 
i:3 

s:223

Или, еще лучше, просто выполните код в отладчике за один шаг и просмотрите все значения переменных.

Другие вопросы по теме