Как поместить два оператора приращения в цикл for C ++?


93

Я хотел бы forувеличить две переменные в условии -loop вместо одной.

Так что-то вроде:

for (int i = 0; i != 5; ++i and ++j) 
    do_something(i, j);

Какой у этого синтаксис?

Ответы:


154

Распространенной идиомой является использование оператора запятой, который оценивает оба операнда и возвращает второй операнд. Таким образом:

for(int i = 0; i != 5; ++i,++j) 
    do_something(i,j);

Но действительно ли это оператор запятой?

Теперь, написав это, комментатор предположил, что на самом деле это какой-то особый синтаксический сахар в операторе for, а не оператор запятой вообще. Я проверил это в GCC следующим образом:

int i=0;
int a=5;
int x=0;

for(i; i<5; x=i++,a++){
    printf("i=%d a=%d x=%d\n",i,a,x);
}

Я ожидал, что x подберет исходное значение a, поэтому должно было отображаться 5,6,7 .. для x. У меня было это

i=0 a=5 x=0
i=1 a=6 x=0
i=2 a=7 x=1
i=3 a=8 x=2
i=4 a=9 x=3

Однако, если я заключил выражение в скобки, чтобы синтаксический анализатор действительно увидел оператор запятой, я получу следующее

int main(){
    int i=0;
    int a=5;
    int x=0;

    for(i=0; i<5; x=(i++,a++)){
        printf("i=%d a=%d x=%d\n",i,a,x);
    }
}

i=0 a=5 x=0
i=1 a=6 x=5
i=2 a=7 x=6
i=3 a=8 x=7
i=4 a=9 x=8

Первоначально я думал, что это показывает, что он вообще не ведет себя как оператор запятой, но, как оказалось, это просто проблема приоритета - оператор запятой имеет минимально возможный приоритет , поэтому выражение x = i ++, a ++ эффективно анализируется как (x = i ++), a ++

Спасибо за все комментарии, это был интересный опыт обучения, и я использую C много лет!


1
Я несколько раз читал, что запятая в первой или третьей части цикла for - это не оператор запятой, а просто разделитель запятой. (Однако мне не удалось найти официального источника для этого, поскольку я особенно плохо разбираюсь в стандарте языка C ++.)
Даниэль Даранас,

Сначала я подумал, что вы ошиблись, но я написал тестовый код, и вы правы - он не работает как оператор запятой. Поправлю свой ответ!
Пол Диксон

19
Это является оператором запятой в этом контексте. Причина, по которой вы не получаете того, чего ожидаете, заключается в том, что оператор команды имеет более низкий приоритет, чем оператор присваивания, поэтому без скобок он анализирует как (x = i ++), j ++.
caf

6
Это оператор запятой. Присваивание связывает сильнее, чем оператор запятой, поэтому x = i ++, a ++ анализируется (x = i ++), a ++, а не x = (i ++, a ++). Некоторые библиотеки неправильно используют эту характеристику, так что v = 1,2,3; делает интуитивно понятные вещи, но только потому, что v = 1 возвращает прокси-объект, для которого перегруженный оператор запятой выполняет добавление.
АПрограммист

3
ОК. В разделе 6.5.3 open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2857.pdf последняя часть - это «выражение». (Хотя 1.6 # 2 определяет «список-выражений» как «список выражений, разделенных запятыми», эта конструкция не появляется в 6.5.3.). Это означает, что когда мы пишем «++ i, ++ j», оно должно быть выражением само по себе, и, следовательно, «,» должно быть оператором запятой (5.18). (Это не «список инициализаторов» или «список аргументов функций», которые являются примерами, где «запятая имеет особое значение», как говорится в 5.18 # 2.). Хотя я нахожу это немного запутанным.
Даниэль Даранас, 05

56

Попробуй это

for(int i = 0; i != 5; ++i, ++j)
    do_something(i,j);

18
+1 Вы также можете объявить j в первой части. for (int i = 0, j = 0; i! = 5; ++ i, ++ j) {...}
Дэниел Даранас,

1
+1 В качестве примечания: тот же синтаксис работает в C # (я попал сюда из поиска Google по запросу «C # for loop incrament 2 counters», поэтому подумал, что упомяну об этом).
CodingWithSpike

@CodingWithSpike: Ну, в C # запятой является особенной, это на самом деле не законно выражение оператора запятая появляться там. Пример легального использования оператора запятой в C ++, но отклоненный C #:for( ; ; ((++i), (++j)) )
Бен Фойгт

@BenVoigt не имеет ничего общего с запятой. Это также недопустимо для C #: for(int i = 0; i != 5; (++i)) {лишние скобки заставляют компилятор думать, что это больше не операция «приращения».
CodingWithSpike

@CodingWithSpike: это правда, но круглые скобки также меняют то, как C # видит запятую, и предотвращает особый смысл внутри действия for.
Бен Войт

6

Постарайтесь этого не делать!

С http://www.research.att.com/~bs/JSF-AV-rules.pdf :

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

Обоснование: читаемость.


4
Это правда, но, честно говоря, я почти уверен, что стандарт правил был написан для встроенного программного обеспечения в истребитель, а не для программы C (++), разработанной в саду. При этом, вероятно, это хорошая привычка к удобочитаемости, и кто знает, может быть, вы будете разрабатывать программное обеспечение для F-35, и от этой привычки будет на одну меньше избавляться.
Galois


2

Я пришел сюда, чтобы напомнить себе, как закодировать второй индекс в предложении приращения цикла FOR, что, как я знал, можно было сделать в основном, наблюдая за ним в образце, который я включил в другой проект, написанный на C ++.

Сегодня я работаю на C #, но был уверен, что он будет подчиняться тем же правилам в этом отношении, поскольку оператор FOR является одной из старейших управляющих структур во всем программировании. К счастью, недавно я потратил несколько дней на точное документирование поведения цикла FOR в одной из моих старых программ на C, и я быстро понял, что эти исследования содержат уроки, применимые к сегодняшней проблеме C #, в частности к поведению второй индексной переменной. .

Для неосторожных ниже приводится краткое изложение моих наблюдений. Все, что я видел сегодня, внимательно наблюдая за переменными в окне Locals, подтвердило мое ожидание, что оператор C # FOR ведет себя точно так же, как оператор FOR C или C ++.

  1. При первом выполнении цикла FOR предложение инкремента (третье из трех) пропускается. В Visual C и C ++ приращение генерируется в виде трех машинных инструкций в середине блока, реализующего цикл, так что начальный проход запускает код инициализации только один раз, а затем перескакивает через блок приращения для выполнения теста завершения. Это реализует функцию, заключающуюся в том, что цикл FOR выполняется ноль или более раз, в зависимости от состояния его индексных и предельных переменных.
  2. Если тело цикла выполняется, его последняя инструкция представляет собой переход к первой из трех инструкций увеличения, которые были пропущены первой итерацией. После их выполнения управление естественным образом попадает в код проверки пределов, реализующий среднее предложение. Результат этого теста определяет, выполняется ли тело цикла FOR или управление передается следующей инструкции после перехода в нижней части его области видимости.
  3. Поскольку управление передается от нижней части блока цикла FOR к блоку приращения, индексная переменная увеличивается до выполнения теста. Такое поведение не только объясняет, почему вы должны кодировать свои предложения limit так, как вы узнали, но оно влияет на любое дополнительное приращение, которое вы добавляете с помощью оператора запятой, поскольку оно становится частью третьего предложения. Следовательно, он не изменяется на первой итерации, но находится на последней итерации, которая никогда не выполняет тело.

Если какая-либо из ваших индексных переменных остается в области действия после завершения цикла, их значение будет на единицу выше порога, который останавливает цикл, в случае истинной индексной переменной. Точно так же, если, например, вторая переменная инициализируется нулем перед входом в цикл, ее значение в конце будет счетчиком итераций, предполагая, что это приращение (++), а не декремент, и что ничего в тело цикла меняет свое значение.


1

Я согласен со squelart. Увеличение двух переменных подвержено ошибкам, особенно если вы проверяете только одну из них.

Это читаемый способ сделать это:

int j = 0;
for(int i = 0; i < 5; ++i) {
    do_something(i, j);
    ++j;
}

ForЦиклы предназначены для случаев, когда ваш цикл работает с одной увеличивающейся / убывающей переменной. Для любой другой переменной измените ее в цикле.

Если вам нужно jбыть привязанным i, почему бы не оставить исходную переменную как есть и добавить i?

for(int i = 0; i < 5; ++i) {
    do_something(i,a+i);
}

Если ваша логика более сложна (например, вам действительно нужно отслеживать более одной переменной), я бы использовал whileцикл.


2
В первом примере j увеличивается на единицу больше, чем i! А как насчет итератора, над которым нужно выполнить какие-то действия для первых x шагов? (И коллекция всегда достаточно длинная) Вы можете подниматься по итератору на каждой итерации, но это намного чище, imho.
Питер Смит

0
int main(){
    int i=0;
    int a=0;
    for(i;i<5;i++,a++){
        printf("%d %d\n",a,i);
    } 
}

1
Какой смысл в том, чтобы не делать петлю iи быть aлокальным?
sbi 05

2
Нет, просто показано, как выполнять оба приращения в for, это просто пример синтаксиса
Аркаитц Хименес

0

Используйте математику. Если две операции математически зависят от итерации цикла, почему бы не сделать математику?

int i, j;//That have some meaningful values in them?
for( int counter = 0; counter < count_max; ++counter )
    do_something (counter+i, counter+j);

Или, более конкретно, ссылаясь на пример OP:

for(int i = 0; i != 5; ++i)
    do_something(i, j+i);

Особенно, если вы переходите в функцию по значению, тогда вы должны получить что-то, что делает именно то, что вы хотите.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.