Может ли цикл 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.
leftaround около

5
@leftaroundabout также -Wshadowпредупреждает о затенении глобальных переменных, что может легко раздражать в больших проектах.
Cubic

9
@leftaroundabout что еще более удивительно, даже -Wextraне включает -Wshadow. Я думаю, что это достаточно распространено в некоторых проектах, или какой-то разработчик gcc любит затенение как стиль кодирования, чтобы гарантировать, что его так упускают.
hyde

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

Ответы:


140

Вы можете использовать то же имя (идентификатор). Это будет другой объект. Они не повлияют друг на друга. Внутри внутреннего цикла нет возможности ссылаться на объект, используемый во внешнем цикле (если вы не сделаете для этого специальные условия, например, указав на него указатель).

Как правило, это плохой стиль, поэтому его следует избегать.

Объекты отличаются только в том случае, если внутренний определяется отдельно, как в случае с int iпоказанным вами. Если то же имя используется без определения нового объекта, циклы будут использовать тот же объект и будут мешать друг другу.


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

3
@BloodGain: «Объект» - это технический термин, используемый в стандарте C. Я специально использовал это здесь.
Эрик Постпищил

1
@EricPostpischil: Ах, понятно, да. Я не знал об этом определении в стандарте и боялся, что оно может ввести в заблуждение новых программистов (поскольку это явно вопрос новичка), поскольку C не имеет «объектов» в том смысле, в котором мы обычно используем этот термин. Я вижу это в стандарте C11, и теперь мне любопытно, было ли это определено таким образом до C11.
Bloodgain

1
Это было. В стандарте C99 это 3,14 вместо 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внутреннем цикле for, т.е. фактически использовать одну и ту же переменную счетчика, внешний цикл будет выполняться только один раз, так как внутренний цикл уйдет i == 10. Это тривиально, но я подумал, что он дает разъяснение, учитывая, как был задан вопрос
Истон Борнемайер

@EastonBornemeier Вы правы, я подумал, что мне следует затронуть проблему «той же переменной» в теле ответа. Спасибо!
dasblinkenlight

@EricPostpischil «Затенение переменных» - официальный термин, имеющий отдельную страницу в Википедии . Однако я обновил ответ, чтобы он соответствовал формулировке стандарта. Спасибо!
dasblinkenlight

2
@dasblinkenlight: На самом деле у меня был спазм мозга по поводу направления, а внутреннее имя затмевает внешнее имя. В этом отношении мой предыдущий комментарий был неправильным. Мои извинения. (Однако это в английском смысле, а не в официальном - Википедия не является официальной публикацией для C или программирования в целом, и я не знаю ни одного офиса или авторитетного органа, который определяет этот термин.) Стандарт 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

Это вполне возможно, но имейте в виду, что вы не сможете обратиться к первому объявленному i

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 во вложенных циклах во время проверки кода, я бы увидел в этом возможность для обучения. Если я поймаю, что кто-то запутывает внутренний цикл, как в вашем последнем примере (это НЕ один цикл), я могу выбросить его в окно.
Bloodgain

это один цикл, у него только один цикл for , если бы он был 2, у него было бы два ключевых слова или два ключевых слова while или ключевые слова for и while
Додо

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.