Czy pętla „for” wewnątrz pętli „for” może używać tej samej nazwy zmiennej licznika?

107

Czy mogę użyć tej samej zmiennej licznika dla forpętli wewnątrz forpętli?

A może zmienne będą miały na siebie wpływ? Czy poniższy kod powinien używać innej zmiennej dla drugiej pętli, na przykład j, czy jest w iporządku?

for(int i = 0; i < 10; i++)
{
  for(int i = 0; i < 10; i++)
  {
  }
}
Uclydde
źródło
72
To jest mylące - nie ominie mnie podczas przeglądu kodu. Ale to jest uzasadnione. Wywołane są dwie różne zmienne io różnych zakresach. Używaj -Wshadowz GCC, aby automatycznie zgłaszać takie problemy.
Jonathan Leffler,
15
Dziwię się, że -Wshadownie ma tego w -Wall.
leftaround około
5
@leftaroundabout -Wshadowostrzega również o cieniowaniu zmiennych globalnych, co może łatwo irytować w przypadku większych projektów.
Sześcienny
9
@leftaroundabout jeszcze bardziej zaskakujące, nawet -Wextranie zawiera -Wshadow. Wydaje mi się, że jest to dość powszechne w niektórych projektach lub niektórzy deweloperzy gcc uwielbiają shadowing jako styl kodowania, aby zagwarantować, że zostanie w ten sposób pominięty.
hyde
5
@leftaroundabout Powtórzenie tego, co powiedział Cubic, -Wshadowma horrendalny wskaźnik fałszywie dodatnich, co czyni go całkowicie bezużytecznym. Zakres istnieje z jakiegoś powodu, a cieniowanie nie jest a priori problematyczne. Teraz -Wshadow-local(uwaga: nie -Wshadow=local ) jest zupełnie inaczej. Ale niestety GCC jak dotąd odmówił włączenia go do bagażnika (chociaż wydaje się, że istnieją rozwidlenia GCC, które go zawierają).
Konrad Rudolph

Odpowiedzi:

140

Możesz użyć tej samej nazwy (identyfikatora). To będzie inny obiekt. Nie będą na siebie wpływać. Wewnątrz pętli wewnętrznej nie ma możliwości odniesienia się do obiektu używanego w pętli zewnętrznej (chyba że poczynisz na to specjalne przepisy, np. Przez podanie do niego wskaźnika).

Jest to ogólnie zły styl, jest podatny na pomyłki i należy go unikać.

Obiekty różnią się tylko wtedy, gdy wewnętrzny jest zdefiniowany osobno, tak jak int ipokazałeś. Jeśli ta sama nazwa zostanie użyta bez definiowania nowego obiektu, pętle będą używać tego samego obiektu i będą ze sobą kolidować.

Eric Postpischil
źródło
3
użycie zagnieżdżonych for (i) i for (j) oraz wewnątrz i ++ zwiększy zmienną pętli zewnętrznej. Jednak to, co mówisz, jest poprawne, jeśli używasz tego samego identyfikatora w obu pętlach, ponieważ są to zmienne o różnym zakresie.
KYL3R
3
@BloodGain: „Obiekt” to termin techniczny używany w standardzie C. Użyłem go tutaj celowo.
Eric Postpischil
1
@EricPostpischil: Ach, rozumiem, tak. Nie byłem świadomy tej definicji w standardzie i obawiałem się, że wprowadzi ona w błąd nowych programistów (ponieważ jest to bardzo wyraźnie pytanie dla początkujących), ponieważ C nie ma „obiektów” w tym sensie, że zwykle używamy tego terminu. Widzę to w standardzie C11, a teraz jestem ciekawy, czy zostało to zdefiniowane w ten sposób przed C11.
Bloodgain
1
To było. To 3,14 w standardzie C99 zamiast 3,15. Więc nie ma wymówki z mojej strony. To nauczy mnie zadawać ci pytania <: - |
Bloodgain
1
Mówiąc bardziej ogólnie: nic nie stoi na przeszkodzie, abyś ponownie używał nazwy zmiennej w jakimkolwiek zagnieżdżonym zakresie. Z wyjątkiem oczywiście strachu przed karą Bożą za napisanie mylącego kodu.
Izaak Rabinowicz
56

Po pierwsze, jest to całkowicie legalne: kod skompiluje się i uruchomi, powtarzając treść zagnieżdżonej pętli 10 × 10 = 100 razy. Licznik pętli iwewnątrz zagnieżdżonej pętli ukryje licznik pętli zewnętrznej, więc dwa liczniki będą zwiększane niezależnie od siebie.

Ponieważ zewnętrzna ijest ukryta, kod wewnątrz treści zagnieżdżonej pętli miałby dostęp tylko do wartości izagnieżdżonej pętli, a nie iz zewnętrznej pętli. W sytuacjach, gdy zagnieżdżona pętla nie potrzebuje dostępu do zewnętrznego ikodu, taki kod może być całkowicie uzasadniony. Jednak może to spowodować większe zamieszanie wśród czytelników, więc dobrym pomysłem jest unikanie pisania takiego kodu, aby uniknąć „zobowiązań alimentacyjnych”.

Uwaga: Mimo że zmienne licznikowe obu pętli mają ten sam identyfikator i, pozostają dwiema zmiennymi niezależnymi, tj. Nie używasz tej samej zmiennej w obu pętlach. Możliwe jest również użycie tej samej zmiennej w obu pętlach, ale kod byłby trudny do odczytania. Oto przykład:

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

Teraz obie pętle używają tej samej zmiennej. Jednak zajmie trochę czasu, aby dowiedzieć się, co robi ten kod bez kompilowania go ( demo );

dasblinkenlight
źródło
4
Ponieważ pytanie jest sformułowane jako „używanie tej samej zmiennej licznikowej”, chciałbym również zwrócić uwagę, że cienie ma miejsce tylko wtedy, gdy następuje redefinicja. Pominięcie w intwewnętrznej pętli for, tj. Faktyczne użycie tej samej zmiennej licznika, spowoduje, że zewnętrzna pętla uruchomi się tylko raz, ponieważ wewnętrzna pętla opuści i == 10. Jest to trywialne, ale myślę, że zapewnia wyjaśnienie, biorąc pod uwagę sposób, w jaki zostało postawione pytanie
Easton Bornemeier
@EastonBornemeier Masz rację, pomyślałem, że powinienem odnieść się do kwestii „tej samej zmiennej” w treści odpowiedzi. Dziękuję Ci!
dasblinkenlight
@EricPostpischil "Variable shadowing" to oficjalny termin, wraz z własną stroną na Wikipedii . Zaktualizowałem odpowiedź, aby była zgodna ze sformułowaniem standardu. Dziękuję Ci!
dasblinkenlight
2
@dasblinkenlight: Właściwie to miałem skurcz mózgu związany z kierunkiem, a nazwa wewnętrzna przesłania cień nazwy zewnętrznej. Mój poprzedni komentarz był pod tym względem błędny. Przepraszam. (Jednak jest to w sensie angielskim, a nie oficjalnym - Wikipedia nie jest oficjalną publikacją dla C lub ogólnie programowania i nie znam żadnego urzędu ani autorytatywnego organu, który definiowałby ten termin.) Standard C używa „Ukryj”, więc to jest lepsze.
Eric Postpischil
Fajnie, zwłaszcza z przykładem „tej samej zmiennej”. Jednak myślę, że „ kod skompiluje się i będzie działał zgodnie z oczekiwaniami ” byłoby lepsze, ponieważ „kod skompiluje się i będzie działał jako ktoś, kto uważnie go przeczytał i zrozumie wszystkie oczekiwane konsekwencje ”… jak mówisz, kod taki jak ten ” prawdopodobnie spowoduje większe zamieszanie u swoich czytelników ”, a problem polega na tym, że zdezorientowany czytelnik może oczekiwać czegoś innego niż to, co robi.
TripeHound
26

Możesz. Ale powinieneś zdawać sobie sprawę z zakresu is. Jeżeli nazywamy zewnętrznej iz i_1a wewnętrzną iz i_2, zakres iS jest następujący:

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
}

Należy zauważyć, że nie oddziałują one na siebie, a tylko ich zakres definicji jest inny.

O mój Boże
źródło
17

Jest to całkowicie możliwe, ale pamiętaj, że nie będziesz w stanie zająć się pierwszym zadeklarowanym i

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

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

    }
}

w drugiej pętli w drugiej pętli potomnej

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

jeśli chcesz dostosować lub uzyskać wartość pierwszego i, użyj j w drugiej pętli

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

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

    }
}

a jeśli jesteś wystarczająco kreatywny, możesz wykonać je obie w jednej pętli

for(int i ,j= 0; i < 10; (j>9) ? (i++,j=0) : 0 ,j++)
{
    printf("%d %d\n",i,j);
}
Dront
źródło
6
Gdybym podczas przeglądu kodu złapał shadowane zmienne i w zagnieżdżonych pętlach, uznałbym to za okazję do coachingu. Gdybym przyłapał kogoś na zaciemnianiu wewnętrznej pętli, tak jak w poprzednim przykładzie (to NIE jest jedna pętla), mógłbym wyrzucić go przez okno.
Bloodgain
jest to jedna pętla, ma tylko jedną pętlę for , gdyby była 2, miałby dwa słowa kluczowe lub dwa słowa kluczowe while lub słowa kluczowe for i while
Dodo
3
Dlatego powiedziałem, że zaciemniłeś pętlę. Nadal zapętlasz, po prostu ukryłeś to za pomocą mniej oczywistej składni. I jest gorzej pod każdym względem.
Bloodgain,
12

Tak, możesz użyć tej samej nazwy zmiennej licznika dla forpętli wewnętrznej , jak dla forpętli zewnętrznej .

Od pętli for :

for ( init_clause ; cond_expression ; iteration_expression ) loop_statement
Stwierdzenie ekspresji stosuje się jako loop_statement określa swój własny zakres, odrębny blok z zakresu od init_clause .

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

Zakres loop_statement jest zagnieżdżony w zakresie init_clause .

Ze standardów C # 6.8.5p5 Instrukcje iteracyjne [ wyróżnienie moje]

Instrukcja iteracji to blok, którego zakres jest ścisłym podzbiorem zakresu otaczającego go bloku. Treść pętli jest również blokiem, którego zakres jest ścisłym podzbiorem zakresu instrukcji iteracji .

Ze standardów C # 6.2.1p4 Zakresy identyfikatorów [ wyróżnienie moje]

.... W zakresie wewnętrznym identyfikator określa jednostkę zadeklarowaną w zakresie wewnętrznym; jednostka uznane w zakresie zewnętrznej jest ukryty (nie widoczne) w zakresie wewnętrznego.

HS
źródło
10

Z punktu widzenia kodu / kompilatora byłoby to całkowicie uzasadnione i zgodne z prawem. int iUznane w wewnętrznej for(int i = 0; i < 10; i++)pętli jest nowy i mniejszym zakresie, tak, że deklaracja cieni przyjęcia zgłoszenia o int iw zewnętrznej pętli (lub innymi słowy, w ramach wewnętrznej wszystkich wejść do zmiennej iprzejść do int iuznane w zakresie wewnętrznego, pozostawiając int inietknięty zewnętrzny zakres).

To powiedziawszy, z punktu widzenia jakości kodu jest to całkowicie okropne. Jest trudny do odczytania, trudny do zrozumienia i łatwy do niezrozumienia. Nie rób tego.

CharonX
źródło
8

Tak, możesz go używać, ale jest to dość zagmatwane. Najważniejszy jest zasięg zmiennej lokalnej wewnątrz pętli. O ile zmienna jest zadeklarowana wewnątrz funkcji, zakresem tej zmiennej jest ta funkcja.

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

Podobnie jest w przypadku pętli, zmienna zadeklarowana wewnątrz pętli wewnętrznej ma inny zasięg, a zmienna zadeklarowana w pętli zewnętrznej ma inny zasięg.

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
}

Lepszym podejściem jest użycie różnych zmiennych dla pętli wewnętrznej i zewnętrznej.

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

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

    }

}
Safwan Shaikh
źródło
8

Tak, zdecydowanie możesz użyć tej samej zmiennej nazwy.

Zmienne programowania C można zadeklarować w trzech miejscach:
zmienne lokalne: -Wewnątrz funkcji lub bloku.
Zmienne globalne: - Spoza wszystkich funkcji.
Parametry formalne: -W parametrach funkcji.

Ale w twoim przypadku i scopebędziesz musiał pamiętać o poniższych rzeczach

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
}

Uwaga: Najlepszym rozwiązaniem byłoby użycie różnych zmiennych dla pętli wewnętrznej i zewnętrznej

Zaynul Abadin Tuhin
źródło
6

Tak - a co jeszcze bardziej interesujące, możesz ponownie użyć nazwy zmiennej za każdym razem, gdy otwierasz zestaw nawiasów klamrowych. Jest to często przydatne podczas wstawiania kodu diagnostycznego. Wpisz otwarty nawias „{”, po którym następuje deklaracja i użycie zmiennych, a następnie zamknij nawias, a zmienne znikną. Gwarantuje to, że nie będziesz ingerować w nic w głównym ciele, zachowując jednocześnie przewagę wszelkich zmiennych, klas i metod zadeklarowanych poza nawiasami klamrowymi.

SuwaneeCreek
źródło
3

Zasada zakresu: Zmienna zadeklarowana w instrukcji for może być używana tylko w tej instrukcji i w treści pętli.

Jeśli w swoim kodzie zdefiniowałeś wiele instancji i w wewnętrznych pętlach, każda instancja będzie zajmować własną przestrzeń pamięci. Więc nie ma się co martwić o wyniki i tak byłoby tak samo.

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;
}

Nie zaleca się jednak używania tej samej nazwy zmiennej, ponieważ jest ona trudna do zrozumienia i później staje się kodem niemożliwym do utrzymania.

Subash J.
źródło
1

Ważną częścią jest to, że zawiera parametr pętli wewnętrznej int i. Ponieważ ijest przedefiniowany w ten sposób, dwie zmienne nie wpływają na siebie; ich zakresy są różne. Oto dwa przykłady, które to pokazują:

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

Zauważ, że powyższy kod zawiera int iparametr pętli wewnętrznej, a poniższy kod zawiera tylko i.

for(int i = 0; i < 10; i++) // This code will print "Test" 10 times
{
 for(i = 0; i < 10; i++)
 {
  puts("Test");
 }
}
Uclydde
źródło
0

Cóż, możesz to zrobić bez problemu ze skryptami, ale powinieneś unikać takiej struktury. Zwykle prowadzi to do zamieszania

Ognisko
źródło