Чи може цикл 'for' всередині циклу 'for' використовувати те саме ім’я змінної лічильника?


107

Чи можу я використовувати ту саму змінну лічильника для forциклу всередині forциклу?

Або змінні впливатимуть одна на одну? Чи повинен наступний код використовувати іншу змінну для другого циклу, наприклад j, або iдобре?

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

72
Це заплутано - воно не омине мене в огляді коду. Але це законно. Є дві різні змінні, обидва названі i, з різними сферами застосування. Використовуйте -Wshadowразом із GCC, щоб автоматично повідомляти про такі проблеми.
Джонатан Леффлер

15
Я здивований, що -Wshadowне включається -Wall.
Ліворуч близько

5
@leftaroundabout також -Wshadowпопереджає про затінення глобальних змінних, що може легко набриднути у великих проектах.
Кубічний

9
@leftaroundabout ще дивніше, навіть -Wextraне включає -Wshadow. Я думаю, що це досить часто в деяких проектах, або який-небудь розробник gcc любить затінення як стиль кодування, щоб гарантувати, що він залишився таким.
Гайд

5
@leftaroundabout Наголос у тому, що сказав Кубік, -Wshadowмає жахливу помилкову швидкість, що робить його абсолютно марним. Сфера існування є причиною, і тінірування апріорі не є проблематичною. Зараз -Wshadow-local(зауважте: ні -Wshadow=local ) дуже відрізняється. Але, на жаль, GCC поки що відмовилася включати його в багажник (хоча, мабуть, є і вилки GCC, які включають його).
Конрад Рудольф

Відповіді:


140

Ви можете використовувати те саме ім’я (ідентифікатор). Це буде інший об’єкт. Вони не вплинуть один на одного. Всередині внутрішнього циклу немає ніякого способу посилання на об'єкт, що використовується у зовнішньому циклі (якщо ви не зробите для цього спеціальних положень, як наведення покажчика на нього).

Це, як правило, поганий стиль, схильний до плутанини, і його слід уникати.

Об'єкти відрізняються лише в тому випадку, якщо внутрішній визначений окремо, як із int iпоказаним вами. Якщо те саме ім’я використовується без визначення нового об’єкта, петлі будуть використовувати той самий об’єкт і будуть заважати один одному.


3
використання для (i) та для (j) вкладених, а всередині i ++, збільшить змінну зовнішнього циклу. Однак те, що ви говорите, є правильним, якщо ви використовуєте один і той же ідентифікатор в обох циклах, оскільки вони є різними масштабами.
KYL3R

3
@BloodGain: "Об'єкт" - це технічний термін, що використовується в стандарті C. Тут я це навмисно використав.
Eric Postpischil

1
@EricPostpischil: Ах, бачу, так. Я не знав про це визначення в стандарті, і боявся, що це буде вводити в оману нових програмістів (оскільки це дуже очевидно питання для початківців), оскільки у C немає "об'єктів" в тому сенсі, що ми зазвичай використовуємо цей термін. Я бачу це в стандарті C11, і зараз мені цікаво, чи було воно визначено таким чином до C11.
Кривавий продаж

1
Це було. Це 3,14 в стандарті C99, замість 3,15. Тож жодного виправдання з мого боку. Це навчить мене ставити під сумнів вас <: - |
Bloodgain

1
Більш загально: нічого не заважає повторно використовувати ім’я змінної в будь-якій вкладеній області. За винятком, звичайно, страху Божого покарання за написання плутаного коду.
Ісаак Рабінович

56

По-перше, це абсолютно законно: код буде компілюватися і запускатися, повторюючи тіло вкладеного циклу 10 × 10 = 100 разів. Лічильник циклу iвсередині вкладеного циклу приховає лічильник зовнішньої петлі, тому два лічильника будуть збільшуватися незалежно один від одного.

Оскільки зовнішня iприхована, код усередині вкладеного циклу має доступ лише до значення iвкладеного циклу, а не iдо зовнішнього циклу. У ситуаціях, коли вкладеному циклу не потрібен доступ до зовнішнього iкоду, такий код може бути абсолютно виправданим. Однак це, швидше за все, створить більше плутанини у його читачів, тому не варто писати такий код, щоб уникнути "зобов'язань з обслуговування".

Примітка. Хоча лічильні змінні обох циклів мають однаковий ідентифікатор i, вони залишаються двома незалежними змінними, тобто ви не використовуєте однакову змінну в обох циклах. Використання однакової змінної в обох циклах також можливо, але код важко буде прочитати. Ось приклад:

for (int i = 1 ; i < 100 ; i++) {
    for ( ; i % 10 != 0 ; i++) {
        printf("%02d ", i);
    }
    printf("%d\n", i);
}

Тепер обидві петлі використовують одну і ту ж змінну. Однак потрібно визначити, що робить цей код, не складаючи його ( демо );


4
Оскільки питання є фразовим як "використання тієї самої змінної лічильника", я також хотів би зазначити, що затінення відбувається лише тоді, коли відбувається перевизначення. Якщо intувімкнути цикл внутрішнього циклу, тобто фактично використовувати ту саму змінну лічильника, це призведе до того, що зовнішня петля запуститься лише один раз, коли внутрішня петля залишиться i == 10. Це банально, але, на його думку, це дає роз’яснення з огляду на те, як було задано питання
Істон Борнемейер,

@EastonBornemeier Ви праві, я вважав, що я повинен вирішити питання "тієї самої змінної" в тілі відповіді. Дякую!
dasblinkenlight

@EricPostpischil "Змінна тініровка" - це офіційний термін, доповнений власною сторінкою у Вікіпедії . Хоча я оновив відповідь, щоб відповідати формулюванню стандарту. Дякую!
dasblinkenlight

2
@dasblinkenlight: Насправді у мене був спазм мозку щодо напрямку, а внутрішня назва затінює зовнішню назву. Мій попередній коментар був неправильним у цьому відношенні. Мої вибачення. (Однак це в англійському сенсі, а не в офіційному розумінні. Вікіпедія не є офіційною публікацією для С або програмування взагалі. Я не знаю жодного офісу чи авторитетного органу, який би визначав цей термін.) Стандарт C використовує "Сховати", так що це краще.
Eric Postpischil

Приємно, особливо з прикладом "тієї ж змінної". Однак я думаю, що " код буде компілюватися та запускатися так, як очікувалося ", було б краще, як "код буде компілюватись і працювати як той, хто уважно його прочитав і розуміє всі очікувані наслідки " ... як ви кажете, такий код " ймовірно, це створить більше плутанини у своїх читачів ", і проблема в тому, що розгублений читач може очікувати чогось іншого, ніж те, що робить.
TripeHound

26

Ти можеш. Але вам слід знати про сферу застосування is. якщо ми будемо називати зовнішній iз i_1і внутрішній iз i_2, то сфера дії is така:

for(int i = 0; i < 10; i++)
{
     // i means i_1
     for(int i = 0; i < 10; i++)
     {
        // i means i_2
     }
     // i means i_1
}

Ви повинні помітити, що вони не впливають один на одного, і просто сфера їх визначення різна.


17

Це цілком можливо, але майте на увазі, ви не зможете звернутися до першого заявленого я

for(int i = 0; i < 10; i++)//I MEAN THE ONE HERE
{

  for(int i = 0; i < 10; i++)
    {

    }
}

у другій петлі в межах другої дочірньої петлі

for(int i = 0; i < 10; i++)
{

  for(int i = 0; i < 10; i++)//the new i
    {
        // i cant see the i thats before this new i here
    }
}

якщо вам потрібно відкоригувати або отримати значення першого i, використовуйте j у другому циклі

for(int i = 0; i < 10; i++)
{

  for(int j = 0; j < 10; j++)
    {

    }
}

і якщо ваша творчість достатня, ви можете зробити їх обох в одному циклі

for(int i ,j= 0; i < 10; (j>9) ? (i++,j=0) : 0 ,j++)
{
    printf("%d %d\n",i,j);
}

6
Якщо я під час перегляду коду я вловив тіньові i змінні у вкладених циклах, я б бачив це як можливість коучингу. Якщо я зловив когось, що замикає внутрішню петлю, як ваш останній приклад (це НЕ одна петля), я можу викинути їх у вікно.
Кривавий продаж

це один цикл, він має тільки один для циклу, якщо він був 2 він буде мати два для ключових слів або два, ключових слів або для ключових слів і в той час
Dodo

3
Тому я сказав, що ви перекрутили петлю. Ви все ще цикли, ви просто приховали це менш очевидним синтаксисом. І гірше всіляко для цього.
Bloodgain

12

Так, ви можете використовувати те саме ім’я змінної лічильника для внутрішнього forциклу, що і для зовнішнього forциклу.

З для циклу :

for ( init_clause ; cond_expression ; iteration_expression ) loop_statement
Вираз виразу, що використовується як loop_statement, встановлює власну область блоку, відмінна від області дії init_clause .

for (int i = 0; ; ) {
    long i = 1;   // valid C, invalid C++
    // ...
}  

Обсяг loop_statement буде вкладено в межах обсягу init_clause .

Із стандартів C # 6.8.5p5 Заяви про ітерації [наголос мій]

Ітераційний оператор - це блок, сфера дії якого є суворим підмножиною області його блоку, що вкладається. Тіло циклу - це також блок, сфера дії якого є строгим підмножиною області дії заяви про ітерацію .

Із стандартів C # 6.2.1p4 Область ідентифікаторів [акцент мій]

.... У внутрішній області ідентифікатор позначає сутність, заявлену у внутрішній області; особа оголошується в зовнішній області приховано (і не видно) в межах внутрішнього обсягу.


10

З точки зору коду / компілятора, це було б абсолютно дійсним і законним. int iОголошено у внутрішньому for(int i = 0; i < 10; i++)циклі знаходиться в новому і меншому обсязі, так що заява тінь декларації про int iциркуляцію по зовнішньому контуру (або, іншими словами: У внутрішньому обсязі всіх звернення до змінних iйти до int iоголошено у внутрішньому обсязі, залишаючи int iв зовнішній області недоторканою).

Однак з точки зору якості коду це надзвичайно жахливо. Це важко читати, важко зрозуміти і легко зрозуміти. Не робіть цього.


8

Так, ви можете використовувати його, але це досить заплутано. Найголовніше - це область локальної змінної всередині циклу. Що стосується, якщо змінна оголошена всередині функції, то сферою цієї змінної є ця функція.

int a = 5;
// scope of a that has value 5
int func(){
    int a = 10;
   // scope of a that has value 10
}
// scope of a that has value 5

Аналогічно випадку з петлями, змінна оголошена всередині внутрішньої петлі має різну область застосування, а змінна оголошена зовнішня петля має різну область застосування.

for(int i = 0; i < 10; i++){
    // In first iteration, value of i is 0

    for(int i = 1; i < 10; i++){
        // In first iteration, value of i is 1
    }
    // In first iteration, value of i is 0
}

Кращий підхід - використовувати різні змінні для внутрішніх і зовнішніх циклів.

for(int i = 0; i < 10; i++){

    for(int j = 1; j < 10; j++){

    }

}

8

Так, безумовно, ви можете використовувати однойменну змінну.

Змінні програмування на C можуть бути оголошені в трьох місцях:
локальні змінні: -Замістити функцію або блок.
Глобальні змінні: -Всі функції.
Формальні параметри: -У параметрах функції.

Але у вашому випадку i scopeдоведеться пам’ятати нижче про речі

for(int i = 0; i < 10; i++)
{
     // i means 1st for loop variable
     for(int i = 0; i < 10; i++)
     {
        // but here i means 2nd for loop  variable
     }
     //interesting thing here i means 1st for loop variable
}

Примітка. Найкраще було б використовувати різні змінні для внутрішніх і зовнішніх циклів


6

Так - і ще цікавіше, що ви можете повторно використовувати ім'я змінної кожного разу, коли відкриєте набір дужок. Це часто зручно при вставці діагностичного коду. Введіть відкриту дужку '{' з подальшим оголошенням та використанням змінних, потім закрийте дужку і змінні відійдуть. Це гарантує, що ви нічого не будете втручатися в основний корпус, зберігаючи при цьому перевагу будь-яких змінних, класів та методів, оголошених поза дужками.


3

Правило діапазону: Змінна, оголошена у операторі for, може використовуватися лише в цьому операторі та тілі циклу.

Якщо у вашому коді ви визначили кілька примірників i у внутрішніх циклах, кожен екземпляр займе власний простір пам’яті. Тож нічого не варто турбуватися про результати, все одно було б однаково.

int main(void) {

    int i = 2; //defined with file global scope outside of a function and will remain 2
    if(1)
    {       //new scope, variables created here with same name are different
        int i = 5;//will remain == 5
        for(int i = 0; i < 10; i++)
        {   //new scope for "i"

            printf("i value in first loop: %d \n", i); // Will print 0 in first iteration
            for(int i = 8; i < 15; i++) 
            {   //new scope again for "i", variable with same name is not the same
                printf("i value in nested loop: %d \n", i); // Will print 8 in first iteration
            }
        }

    }

    return 0;
}

Але не рекомендується використовувати одне і те ж ім'я змінної, оскільки це важко зрозуміти, і він стає невідповідним кодом пізніше.


1

Важлива частина полягає в тому, що параметр внутрішнього циклу містить int i. Оскільки iперероблено таким чином, дві змінні не впливають одна на одну; їх сфери дії різні. Ось два приклади, щоб показати це:

for(int i = 0; i < 10; i++) // This code will print "Test" 100 times
{
 for(int i = 0; i < 10; i++)
 {
  puts("Test");
 }
}

Зауважте, що наведений вище код включає int iпараметр внутрішнього циклу, а код нижче включає лише i.

for(int i = 0; i < 10; i++) // This code will print "Test" 10 times
{
 for(i = 0; i < 10; i++)
 {
  puts("Test");
 }
}

0

Ну, ви можете зробити це без проблем зі своїми сценаріями, але вам слід уникати цієї структури. Зазвичай це призводить до плутанини

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.