W zaprezentowanym fragmencie kodu występuje błąd logiczny. Na czym on polega?
int x = 0; while (x != 0 || x != 5) { std::cout << x << " "; x++; }
A. braku inicjalizacji zmiennej x, który sprawia, że zmienna nie ma wartości początkowej
B. nieprawidłowym warunku pętli, który sprawia, że pętla jest nieskończona
C. nieprawidłowym warunku pętli, który sprawia, że pętla nigdy się nie wykona
D. błędnym zastosowaniu funkcji cout, który sprawia, że zmienna jest wczytywana w pętli
Sedno problemu jest w warunku pętli: while (x != 0 || x != 5). Operator || oznacza alternatywę logiczną, czyli wystarczy, że jedna część warunku będzie prawdziwa. A tutaj dla każdej możliwej wartości x przynajmniej jeden z warunków zawsze będzie prawdziwy. Gdy x wynosi 0, wyrażenie x != 0 jest fałszywe, ale x != 5 jest prawdziwe, więc całość daje true. Gdy x wynosi 5, odwrotnie: x != 0 jest prawdziwe, a x != 5 fałszywe. Dla innych wartości oba warunki są prawdziwe. W efekcie pętla nie ma normalnego logicznego momentu zakończenia. Moim zdaniem to klasyczny przykład błędu, który dobrze pokazuje, że przy warunkach z != trzeba bardzo uważać na ||. W praktyce programistycznej taki błąd może zawiesić program, zablokować wątek, zużyć CPU albo doprowadzić do problemów w aplikacji serwerowej. Dobra praktyka to rozpisanie sobie tabeli prawdy albo sprawdzenie warunków brzegowych: x = 0, x = 5 i jakaś inna wartość. Jeśli celem było zatrzymanie przy 5, wystarczyłoby np. while (x != 5). Jeśli celem było wykonywanie pętli tylko wtedy, gdy x nie jest ani 0, ani 5, należałoby użyć &&: x != 0 && x != 5, choć przy początkowym x = 0 taka pętla nie ruszy. W C++ warto też pamiętać, że przy ciągłym zwiększaniu zmiennej int może dojść do przepełnienia typu ze znakiem, co według standardu C++ prowadzi do niezdefiniowanego zachowania, więc technicznie program jest jeszcze bardziej ryzykowny niż zwykła nieskończona pętla.
W tym fragmencie zmienna x jest zainicjalizowana poprawnie, bo instrukcja int x = 0; nadaje jej konkretną wartość początkową. Nie ma tu więc problemu z niezainicjalizowaną zmienną, który w C++ faktycznie bywa groźny, bo odczyt śmieciowej wartości może dawać losowe wyniki. Tutaj start programu jest jasny: x zaczyna od zera. Również std::cout nie służy do wczytywania danych, tylko do wypisywania ich na standardowe wyjście. Do pobierania danych od użytkownika używa się zwykle std::cin. Linia z cout jedynie pokazuje aktualną wartość x i spację, a potem x jest zwiększane przez x++. Błąd nie polega też na tym, że pętla nigdy się nie wykona. Przy x = 0 warunek x != 0 || x != 5 daje wynik true, ponieważ druga część, czyli x != 5, jest prawdziwa. Typowa pułapka myślowa polega na tym, że ktoś czyta ten warunek jak zdanie: x nie jest 0 lub 5. Komputer tak tego nie rozumie. On sprawdza dwa osobne porównania połączone operatorem ||. Ponieważ żadna liczba nie może być jednocześnie równa 0 i równa 5, zawsze przynajmniej jedno porównanie z != będzie prawdziwe. Dlatego pętla jest logicznie nieskończona. Z mojego doświadczenia najlepiej takie warunki testować na kilku konkretnych wartościach i nie ufać intuicji, bo operatory logiczne potrafią robić małe, ale bardzo kosztowne zamieszanie w kodzie.