Як помістити два оператори приросту в цикл C ++ 'for'?


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 ++

Дякую за всі коментарі, це був цікавий досвід навчання, і я використовую С вже багато років!


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

Спочатку я подумав, що ти помилився, але я написав тестовий код, і ти маєш рацію - він не поводиться як оператор коми. Зміню мою відповідь!
Пол Діксон,

19
У цьому контексті він є оператором коми. Причиною того, що ви не отримуєте того, що ви очікуєте, є те, що оператор команди має нижчий пріоритет, ніж оператор присвоєння, тому без парентезів він аналізується як (x = i ++), j ++.
кафе

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.). Мені це здається трохи заплутаним.
Даніель Даранас,

55

Спробуйте це

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

17
+1 Ви також можете оголосити j у першій частині. for (int i = 0, j = 0; i! = 5; ++ i, ++ j) {...}
Даніель Даранас

1
+1 Як додаткова примітка, цей самий синтаксис працює в C # (я потрапив сюди з пошуку Google за запитом "C # для циклічного збільшення кількості лічильників 2", тому подумав, що згадаю про це).
CodingWithSpike

@CodingWithSpike: Ну, в C # коми є особливою, це на самому ділі не законно вираз оператора кома з'являтися там. Приклад легального використання оператора коми в C ++, але відхилений C #:for( ; ; ((++i), (++j)) )
Бен Войгт

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

@CodingWithSpike: Це правда, але дужки також змінюють те, як C # бачить кому і запобігає спеціальному значенню всередині дії.
Ben Voigt

6

Намагайся цього не робити!

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

Правило AV AV
. Вираз збільшення в циклі for не виконуватиме жодної дії, крім зміни параметра одного циклу на наступне значення для циклу.

Обґрунтування: читабельність.


4
Це правда, але заради справедливості я майже впевнений, що стандарт правил був написаний для вбудованого програмного забезпечення у винищувачі, а не для садової програми С (++). З огляду на це, це, мабуть, хороша звичка для читабельності, і хто знає, можливо, ви будете розробляти програмне забезпечення F-35, і це буде одна звичка менше позбуватися.
галоа


2

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

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

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

  1. Під час першого запуску циклу FOR пропозиція про збільшення (третя з трьох) пропускається. У Visual C та C ++ приріст генерується як три машинні інструкції в середині блоку, що реалізує цикл, так що початковий прохід запускає код ініціалізації лише один раз, а потім перескакує через приріст блоку для виконання тесту завершення. Це реалізує функцію, що цикл FOR виконує нуль або більше разів, залежно від стану його індексу та граничних змінних.
  2. Якщо тіло циклу виконується, його останній оператор - це перехід до першої з трьох інструкцій приросту, які були пропущені першою ітерацією. Після їх виконання керування природно потрапляє в граничний тестовий код, який реалізує середнє речення. Результат цього тесту визначає, чи виконується тіло циклу FOR, чи керування переходить до наступної інструкції, яка переходить до стрибка внизу області дії.
  3. Оскільки керування переходить із нижньої частини блоку циклу FOR до блоку приросту, змінна індексу збільшується перед виконанням тесту. Ця поведінка не тільки пояснює, чому ви повинні кодувати свої обмежувальні речення так, як ви вивчили, але це впливає на будь-який вторинний приріст, який ви додаєте за допомогою оператора коми, оскільки він стає частиною третього речення. Отже, він не змінюється на першій ітерації, але є на останній ітерації, яка ніколи не виконує тіло.

Якщо будь-яка з ваших змінних індексу залишається в області дії, коли цикл закінчується, їх значення буде на одне вище порога, який зупиняє цикл, у випадку істинної змінної індексу. Подібним чином, якщо, наприклад, друга змінна ініціалізується нулем до введення циклу, її значення в кінці буде підраховувати кількість ітерацій, припускаючи, що це приріст (++), а не зменшення, і що нічого в тіло циклу змінює своє значення.


1

Я погоджуюся з віскатом. Збільшення двох змінних схильне до помилок, особливо якщо ви перевіряєте лише одну з них.

Це можна прочитати так:

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

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.