Jaki warunek wpisać do pętli?
Takie pytanie pojawia się, gdy ktoś zna narzędzia - wie jak działają pętle (
do..)
while, podstawowy
for, ma w głowie ogólne pojęcie, co pętla powinna robić, ale nie potrafi tego zapisać formalnie w formie kodu. Niektórzy początkujący mają złudne odczucie, że potrafią posługiwać się pętlami, bo potrafią kopiować takie popularne idiomy jak, przykładowo, przejście po tablicy
for( int i = 0; i < 10; ++i )
std::cout << tablica[ i ] << ' ';
Taki kod jest oczywisty. Tablice indeksuje się od zera, do
Rozmiar - 1. Wypisujemy wszystko po kolei, więc zwiększamy o 1. Taki kod byłby podany w kursie, w lekcji o tablicach, gdyby pętla
for była wcześniej omówiona, ale to nie ma tu znaczenia, bo wszystkie pętle są między sobą zamienne.
Taka pętla jest dobra do powielania, bo jedyne co się w niej wymienia, to rozmiar tablicy i może nazwę zmiennej. Ale co dokładnie dzieje się w
i < 10?
Dla liczb i mniejszych od rozmiaru tablicy, a więc dążymy do rozmiaru tablicy, ale sam rozmiar tablicy już nie jest poprawną wartością. To samo można by zapisać warunkiem
i <= Rozmiar - 1, co jest zapisem który czasem można spotkać na forum w tym kontekście, ale jest to niepotrzebne wydłużenie kodu.
W drugą stronę
Wypiszmy teraz elementy tablicy w przeciwną stronę:
for( int i = Rozmiar - 1; i >= 0; --i )
std::cout << tablica[ i ] << ' ';
Teraz kod przypomina trochę dłuższą wersję poprzedniego przykładu, tylko wszystko jest na opak. Zwróć uwagę, że teraz
i nie może być typu
unsigned, bo dla typów bez znaku każda liczba jest większa równa zeru i pętla byłaby nieskończona. Nie ma tego ograniczenia, jeśli dodasz 1 'do obu stron równania'.
for( int i = Rozmiar; i > 0; --i )
std::cout << tablica[ i - 1 ] << ' ';
Zaczynamy od
i o jeden większego, kończymy na
i o jeden większym i jest to skorygowane w odwołaniu do tablicy.
Co w bardziej skomplikowanym przypadku?
Aby odpowiedzieć na to pytanie, pomimo tego jak bardzo jest ogólne, trzeba zrozumieć absolutną podstawę działania pętli:
powtarzaj, jeśli warunek jest prawdziwy, lub inaczej:
powtarzaj, aż warunek będzie fałszywy. Wszystko sprowadza się do określenia albo warunku
kontynuacji pętli, albo warunku
przerwania pętli - jedno jest po prostu negacją drugiego, ale czasem łatwiej jest opisać problem z przeciwnej strony.
Samo sedno sprawy
Choćby wszystkie utrudnienia były sprzątnięte z drogi, nie da się dać uniwersalnej podpowiedzi do ostatecznego problemu przy pisaniu pętli -
Czy ja wiem, co ta pętla ma robić? Wiesz, że chcesz użyć pętli (inaczej zmarnowałeś czas czytając te, powoli pełznące do meritum, wprowadzenie - mam nadzieję, że jakoś pomogło), a więc chcesz jakiś fragment kodu zapętlić, i to skończoną ilość razy - gdyby chodziło o pętlę nieskończoną, to nie byłoby problemu, a skoro wiesz że ma być koniec, to zdefiniuj okoliczności tego końca. W formie warunku. "Moja pętla ma się powtarzać gdy..", "Moja pętla ma się przerwać gdy.." - jeśli potrafisz się wyrazić w języku naturalnym, to możesz to przełożyć jeden do jednego na warunek w C++. Jest "i", jest "lub", jest negacja i spora dowolność w tym, co można wpisać pomiędzy nie. Do tego trzeba zanegować wszystko razem, jeśli poszedłeś w kierunku "pętla ma się przerwać", a nie "pętla ma się powtarzać".
Ostatnia prosta
Warunek już jest, działa, ale wygląda tragicznie. Nie dałeś rady opisać go bez sporej ilości negacji. Teraz jest czas, żeby go trochę uprościć i przeredagować. Powiedzmy, że masz "
i nie jest mniejsze od 10", co zapisałeś jako
!(i < 10). Można to skrócić i wyrzucić negację, przez odwrócenie znaku porównania, "
i jest większe
równe 10"
i >= 10. Często zdarza się błąd, że zapomina się o tym znaku równości. Bez niego, warunek inaczej się zachowa dla
i równego 10 i do kodu dostanie się "błąd o jedynkę".
Teraz coś bardziej skomplikowanego: "pętla ma się przerwać, gdy albo
i > 10, albo
j == 0". Mówimy o przerwaniu pętli, więc warunek kontynuacji pętli będzie przeciwny:
while( !( i > 10 || j == 0 ) )
Tu też można usunąć negację i warunek uprościć:
while( i <= 10 && i != 0 )
A tu co się stało..? Mieliśmy 2 warunki, zajście dowolnego z nich zrywa pętlę:
i > 10,
j == 0. A więc, pętla wykonuje się, gdy
żaden z nich nie zachodzi. A więc, "pętla powtarza się, gdy
!(i > 10) i !(j == 0)". Nazywa się to
prawem De Morgana.