Kurs C++: Pętle i Iteracje

Instrukcja goto w C++ pozwala na skok do innego miejsca w kodzie, oznaczonego etykietą. Może wydawać się użyteczna, ale jej stosowanie jest odradzane, ponieważ utrudnia czytelność kodu i prowadzi do tzw. „spaghetti code”.


1. Składnia instrukcji goto

goto etykieta;  // Skok do etykiety
// ... (kod, który zostanie pominięty)
etykieta: 
// Kod, który zostanie wykonany po skoku

🔹 Przykład użycia goto w pętli:

#include <iostream>
using namespace std;

int main() {
    int liczba;

    poczatek:  // Etykieta
    cout << "Podaj liczbę większą niż 10: ";
    cin >> liczba;

    if (liczba <= 10) {
        cout << "Liczba za mała! Spróbuj ponownie." << endl;
        goto poczatek;  // Skok do początku
    }

    cout << "Dziękuję! Podano poprawną liczbę." << endl;
    return 0;
}

Działa poprawnie, ale kod jest trudniejszy do śledzenia niż w przypadku użycia pętli.


2. Dlaczego należy unikać goto?

2.1. Tworzy „spaghetti code”

Kod staje się chaotyczny, jeśli goto jest używane wiele razy, ponieważ trudno śledzić, skąd i dokąd następują skoki.

🔴 Przykład złego użycia goto:

#include <iostream>
using namespace std;

int main() {
    int x = 1;

start:
    cout << x << " ";
    x++;
    
    if (x <= 5) goto start;  // Skok do etykiety

    return 0;
}

Problem: Kod jest nieczytelny, zamiast goto lepiej użyć pętli while:

int x = 1;
while (x <= 5) {
    cout << x << " ";
    x++;
}

Efekt ten sam, ale kod czytelniejszy!


2.2. Utrudnia debugowanie

Jeśli program jest skomplikowany, a goto skacze do wielu miejsc, trudno znaleźć błąd i zrozumieć, jak kod działa.

🔴 Problem:

  • goto omija naturalny przepływ sterowania.
  • Debugowanie wymaga skakania między etykietami, co utrudnia analizę kodu.

2.3. Powoduje wycieki pamięci

Jeśli goto omija zwalnianie pamięci lub zamykanie plików, może prowadzić do błędów.

🔴 Zły przykład (pomija delete):

int* ptr = new int(5);
goto koniec;  // Pomija zwolnienie pamięci
delete ptr;  

koniec:
cout << "Program zakończony.";

Pamięć nie zostaje zwolniona, co powoduje wyciek!
Lepiej użyć if lub return, by nie pominąć delete.


3. Kiedy można używać goto?

Mimo wad goto może być użyteczne w nielicznych przypadkach, np. do awaryjnego wyjścia z wielu zagnieżdżonych pętli.

Przykład – wyjście z wielu pętli naraz:

#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            cout << "i = " << i << ", j = " << j << endl;
            if (i == 2 && j == 2) {
                goto koniec;  // Przerywa obie pętle
            }
        }
    }

koniec:
    cout << "Pętla przerwana!" << endl;
    return 0;
}

Działa, ale lepiej użyć break z flagą kontrolną.


4. Jak unikać goto?

Zamień goto na pętle (for, while) lub instrukcje warunkowe (if-else).
Zamiast goto w obsłudze błędów używaj wyjątków (try-catch).
Aby wyjść z wielu pętli, użyj flagi (bool przerwij = true).

Przykład – zamiast goto, użycie flagi:

#include <iostream>
using namespace std;

int main() {
    bool przerwij = false;

    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            cout << "i = " << i << ", j = " << j << endl;
            if (i == 2 && j == 2) {
                przerwij = true;
                break; // Przerywa wewnętrzną pętlę
            }
        }
        if (przerwij) break; // Przerywa także zewnętrzną pętlę
    }

    cout << "Pętla przerwana!" << endl;
    return 0;
}

Efekt ten sam, ale kod bardziej czytelny!


5. Podsumowanie – dlaczego unikać goto?

goto prowadzi do „spaghetti code” – kod jest trudny do zrozumienia.
Debugowanie skoków w kodzie jest męczące.
Może prowadzić do błędów i wycieków pamięci.
Zamiast goto, lepiej używać pętli, if-else, wyjątków lub flag sterujących.
Jedyny sensowny przypadek użycia goto to awaryjne wyjście z kilku zagnieżdżonych pętli – ale lepiej użyć flagi.