Czy dobrze jest zdefiniować zmienną wewnątrz pętli? [Zamknięte]

15

Mój instruktor powiedział mi kiedyś, że nie powinienem definiować zmiennej wewnątrz pętli , ale szczerze mówiąc nadal nie rozumiem, dlaczego.

Jakie są wady tego?

Czy ktokolwiek mógłby mi to wyjaśnić?

użytkownik3260672
źródło
7
Jakiego języka programowania uczył twój instruktor?
Brian
2
Jeśli zdefiniujesz zmienną z nieprymitywnym typem w pętli, twój program może niepotrzebnie wywoływać swojego konstruktora za każdym razem przez pętlę. Jeśli musisz zdefiniować go tylko raz poza pętlą, zrób to.
Brandin,
17
Kiedy masz wątpliwości co do tego, co mówi instruktor, najlepszym źródłem informacji jest instruktor. Mogą zapewnić ci gęstą komunikację tam iz powrotem, której strona Q&A nie może zapewnić.
1
Duplikat między witrynami: różnica między deklarowaniem zmiennych przed lub w pętli? (i oczywiście wiele, wiele duplikatów na tej stronie dla takiego podstawowego pytania (w tym te, które dotyczą tylko C ++)).
Peter Mortensen,
2
Ta rada była specyficzna dla danego kontekstu. Ze względu na osobisty styl wolę deklarować moje zmienne, constchyba że istnieje ku temu powód (nawyk programowania funkcjonalnego). Albo ich nie zmodyfikuję, a optymalizator powinien wykryć, kiedy nie są potrzebne, albo zrobię to i zapobiegłem poważnemu błędowi. Gdy te stałe wartości pośrednie są specyficzne dla iteracji pętli, oznacza to deklarowanie ich wewnątrz pętli. Innym razem, gdy musisz zadeklarować zmienne poza pętlą, jest to, że będziesz odwoływał się do nich poza pętlą; na przykład przechowywane wyniki.
Davislor,

Odpowiedzi:

42

Zdefiniowanie zmiennej w pętli nie stanowi problemu . W rzeczywistości jest to dobra praktyka, ponieważ identyfikatory powinny być ograniczone do możliwie najmniejszego zakresu.

Złe jest przypisanie zmiennej w pętli, jeśli równie dobrze można ją przypisać raz przed uruchomieniem pętli. W zależności od tego, jak skomplikowana jest prawostronna strona zadania, może to być dość kosztowne, a nawet zdominować czas wykonywania pętli. Jeśli napiszesz pętlę, która używa tej samej wartości obliczeniowej we wszystkich iteracjach, zdecydowanie powinieneś obliczyć ją powyżej pętli - jest to ważniejsze niż minimalizowanie jej zakresu.

Aby wyjaśnić: tak długo, jak compute()zawsze zwraca tę samą wartość, to

int value = compute();
while (something) {
    doSomething(value);
}

jest mądrzejszy niż to:

while (something) {
    int value = compute();
    doSomething(value);
}
Kilian Foth
źródło
2
Jak zdefiniowałbyś zmienną w pętli i przypisałeś ją przed pętlą?
Masked Man
6
@MaskedMan, myślę, że nie rozumiesz. Co Kilian miał na myśli, jeśli masz zmienną, która ma tę samą wartość podczas każdej iteracji pętli, np. Ta sama zmienna daty jest ustawiona 1/1/1900, zmienna powinna zostać zadeklarowana, a wartość powinna być przypisana przed pętlą.
ps2goat
2
Nie sądzę, żeby w ciągu ostatnich dwudziestu lat napisano kompilator (poza kursem kompilatora), który nie zorientowałby się, że przypisujesz tę samą wartość przy każdej iteracji i wyprowadzasz to zadanie z pętli.
TMN
14
@tmn: Nigdy nie pozwól kompilatorowi robić tego, co możesz trywialnie zrobić z większą przejrzystością kodu.
Robert Harvey
10
@TMN, niekoniecznie. Ta optymalizacja jest możliwa tylko wtedy, gdy kompilator może udowodnić, że obliczenia nie powodują skutków ubocznych.
Paul Draper,
16

Typy złożone mają nietrywialne konstruktory i destruktory.

Będą one wywoływane na początku i na końcu korpusu pętli (ponieważ jest on inicjowany i wychodzi poza zakres). Jeśli inicjalizacja jest kosztowna, ponieważ wymaga przydzielenia pamięci, należy tego uniknąć.

Jednak w przypadku trywialnych typów nie stanowi to problemu. Sama alokacja i dezalokacja polega jedynie na dodawaniu i odejmowaniu wartości od wskaźnika stosu. (które zostaną zoptymalizowane)

maniak zapadkowy
źródło
dzięki, dokładnie takiej odpowiedzi, której szukałem!
gebbissimo
6

Cóż, jego rada jest nieco zbyt prosta (to mało powiedziane).
Następujące zakresy to aż z dobrym pomysłem na kogo to obchodzi i zły pomysł do niemożliwe .

  1. Powinieneś postępować zgodnie z nim, ilekroć ponowne użycie jest tańsze niż niszczenie starego i tworzenie nowego.

    #include <iostream>
    #include <string>
    
    int main() {
        std::string s; // Don't needlessly free the buffer
        while ((std::cin >> s))
            std::cout << s;
    }
  2. Powinieneś unikać go ze względu na styl, gdy nie ma to znaczenia dla wydajności.

    #include <stdio.h>
    #include <stdlib.h>
    int f(int, int);
    
    int main() {
        for (int i = 0; i < 100; ++i) {
            int x = rand(); // Declared here so you don't need to hunt it down.
            printf("%d => %d\n", x, f(x-1, x+i));
        }
    }
  3. Ty naprawdę powinny unikać go, kiedy ma gorszą wydajność lub niewłaściwych semantykę.

    #include <iostream>
    #include <string>
    std::string generate(int);
    
    int main() {
        for(int i = 0; i < 100; ++i) {
            std::string s = generate(i); // Using copy-ellision here
            std::cout << s;
        }
    }
  4. Nie można tego zrobić, gdy użyty typ nie pozwala ani na zamianę, ani na przeniesienie, ani na kopiowanie.

    #include <iostream>
    #include <puzzle>
    
    int main() {
        for (int i = 0; i < 100; ++i) {
            Puzzle x(i); // Puzzle is an immutable class. For whatever reasons.
            std::cout << x;
        }
    }
Deduplikator
źródło
2
W zależności od definicji „w pętli” 1 można zmienić na for (std::string s; std::cin >> s;) ...i nadal być „na zewnątrz”
Caleth