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.