Я учусь на курсе программирования, и в одном из вопросов последнего теста есть код, который нужно «вычислить».
#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
, где я получил правильный результат.
«почему p = 67 перед первой итерацией?» ++a[i]
устанавливает a[0]
на 67
. Затем p = a[i]
устанавливает p
на 67
.
Необходимо оценить среднюю часть цикла for, чтобы решить, следует ли входить в цикл. Если это имеет побочные эффекты, то они произойдут до первой итерации, даже если результат был ложным и цикл не был введен.
Отвечает ли это на ваш вопрос? Что делает оператор запятая?
@phuclv Хотя я нашел дополнительную информацию об операторе , полезной, кое-что из этого я уже узнал. Больше всего мне помогли ответы: первый комментарий Уэзера Вейна и оба ответа Джона Байко и Лундина. Я не уверен, как я могу их отблагодарить, поскольку я впервые использую переполнение стека, но эти ответы были конкретными, короткими и понятными.
В тестовом условии цикла 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.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
Или, еще лучше, просто выполните код в отладчике за один шаг и просмотрите все значения переменных.
!i, p = a[i];
делает не то, что вы себе представляете. Первое условие игнорируется, а вторая часть устанавливаетp
вa[i];
и проверяет его значение.