8.7 — Instrukcje Goto

Następnym rodzajem instrukcji przepływu sterowania, który omówimy, jest skok bezwarunkowy. Bezwarunkowy skok powoduje, że wykonanie przeskakuje do innego miejsca w kodzie. Termin „bezwarunkowy” oznacza, że ​​skok zawsze następuje (w przeciwieństwie do instrukcji if lub instrukcji switch, gdzie skok następuje tylko warunkowo na podstawie wyniku wyrażenia).

W C++ bezwarunkowe skoki są implementowane za pomocą instrukcji goto, a miejsce, do którego należy wykonać skok, jest identyfikowane poprzez użycie etykiety instrukcji. Podobnie jak w przypadku etykiet przełączników, etykiety instrukcji zwykle nie są wcięte.

Poniżej znajduje się przykład instrukcji goto i etykiety instrukcji:

#include <iostream>
#include <cmath> // for sqrt() function

int main()
{
    double x{};
tryAgain: // this is a statement label
    std::cout << "Enter a non-negative number: "; 
    std::cin >> x;

    if (x < 0.0)
        goto tryAgain; // this is the goto statement

    std::cout << "The square root of " << x << " is " << std::sqrt(x) << '\n';
    return 0;
}

W tym programie użytkownik proszony jest o wprowadzenie liczby nieujemnej. Jeśli jednak zostanie wprowadzona liczba ujemna, program użyje instrukcji goto, aby przejść z powrotem do etykiety tryAgain . Następnie użytkownik jest ponownie proszony o wprowadzenie nowego numeru. W ten sposób możemy w sposób ciągły prosić użytkownika o wprowadzenie danych, dopóki nie wprowadzi czegoś prawidłowego.

Oto przykładowe uruchomienie tego programu:

Enter a non-negative number: -4
Enter a non-negative number: 4
The square root of 4 is 2

Etykiety instrukcji mają zakres funkcji

W rozdziale poświęconym zakresowi obiektowemu (rozdział 7) omówiliśmy dwa rodzaje zasięgu: zasięg lokalny (blokowy) i zakres plikowy (globalny). Etykiety instrukcji wykorzystują trzeci rodzaj zakresu: zakres funkcji, co oznacza, że ​​etykieta jest widoczna w całej funkcji, nawet przed momentem jej deklaracji. Instrukcja goto i odpowiadająca jej statement label muszą występować w tej samej funkcji.

Chociaż powyższy przykład pokazuje instrukcję goto, która przeskakuje do tyłu (do poprzedniego punktu funkcji), instrukcje goto mogą również przeskakiwać do przodu:

#include <iostream>

void printCats(bool skip)
{
    if (skip)
        goto end; // jump forward; statement label 'end' is visible here due to it having function scope
    
    std::cout << "cats\n";
end:
    ; // statement labels must be associated with a statement
}

int main()
{
    printCats(true);  // jumps over the print statement and doesn't print anything
    printCats(false); // prints "cats"

    return 0;
}

Wypisuje:

cats

Poza skokiem do przodu jest w powyższym programie kilka interesujących rzeczy, o których warto wspomnieć.

Po pierwsze należy pamiętać, że etykiety instrukcji muszą być powiązane z oświadczeniem (stąd ich nazwa: oznaczają oświadczenie). Ponieważ koniec funkcji nie zawierał instrukcji, musieliśmy użyć instrukcji o wartości null, więc mieliśmy instrukcję do oznaczenia. Po drugie, mogliśmy przejść do instrukcji oznaczonej przez end mimo że nie zadeklarowaliśmy jeszcze end ze względu na to, że etykiety instrukcji mają zakres funkcji. Nie jest wymagana żadna deklaracja przekazania etykiet wyciągów. Po trzecie, warto wyraźnie wspomnieć, że powyższy program jest kiepskiej formy - lepiej byłoby użyć instrukcji if do pominięcia instrukcji print niż instrukcji goto do przeskoczenia jej.

Istnieją dwa podstawowe ograniczenia dotyczące przeskakiwania: możesz skakać tylko w granicach pojedynczej funkcji (nie możesz przeskoczyć z jednej funkcji do drugiej), a jeśli przeskoczysz do przodu, nie będziesz mógł przeskoczyć do przodu przez inicjalizację żadnej zmiennej, która wciąż jest objęta zakresem w lokalizacji, w której się znajdujesz skoczył do. Na przykład:

int main()
{
    goto skip;   // error: this jump is illegal because...
    int x { 5 }; // this initialized variable is still in scope at statement label 'skip'
skip:
    x += 3;      // what would this even evaluate to if x wasn't initialized?
    return 0;
}

Zauważ, że możesz skoczyć wstecz po inicjalizacji zmiennej, a zmienna zostanie ponownie zainicjowana po wykonaniu inicjalizacji.

Unikaj używania goto

Używanie goto jest unikane w C++ (i innych współczesnych językach wysokiego poziomu). Edsger W. Dijkstra, znany informatyk, przedstawił argumenty za unikaniem goto w słynnym, ale trudnym do odczytania artykule zatytułowanym Przejdź do oświadczenia uważanego za szkodliwe. Podstawowym problemem goto jest to, że pozwala programiście na dowolne przeskakiwanie po kodzie. Tworzy to coś, co nie jest tak pieszczotliwie zwane kodem spaghetti. Kod spaghetti to kod, którego ścieżka wykonania przypomina miskę spaghetti (wszystkie splątane i poskręcane), co niezwykle utrudnia przestrzeganie logiki takiego kodu.

Jak Dijkstra ujął to nieco humorystycznie, „jakość programistów to malejąca funkcja gęstości instrukcji go to w tworzonych przez nich programach”.

Prawie każdy kod napisany przy użyciu instrukcji goto można zapisać w bardziej przejrzysty sposób przy użyciu innych konstrukcji w C++, takich jak instrukcje if i pętle. Godnym uwagi wyjątkiem jest sytuacja, gdy trzeba wyjść z zagnieżdżonej pętli, ale nie z całej funkcji — w takim przypadku prawdopodobnie najczystszym rozwiązaniem jest przejście goto tuż za pętlami.

Dla zaawansowanych czytelników

Oto wymyślony przykład użycia goto do wyjścia z zagnieżdżonej pętli bez wychodzenia z funkcji:

#include <iostream>

int main()
{
    for (int i = 1; i < 5; ++i)
    {        
        for (int j = 1; j < 5; ++j)
        {
            std::cout << i << " * " << j << " is " << i*j << '\n';
            
            // If the product is divisible by 9, jump to the "end" label
            if (i*j % 9 == 0)
            {
                std::cout << "Found product divisible by 9.  Ending early.\n";
                goto end;
            }
        }

        std::cout << "Incrementing the first factor.\n";
    }

end:
    std::cout << "And we're done." << '\n';

    return 0;
}

Nota autora

Od naszych przyjaciół z xkcd:

https://imgs.xkcd.com/comics/goto.png

Najlepsza praktyka

Unikaj instrukcji goto (chyba że alternatywy są znacznie gorsze dla kodu czytelność).

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
180 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze