9. Instrukcje pętli
Instrukcje pętli służą do wielokrotnego, uzależnionego od jakiegoś warunku, wykonywania danego fragmentu kodu. Język C posiada trzy podstawowe konstrukcje pętli. W tym punkcie zostaną one przedstawione wraz z przykładami ich użycia.
9.1. Pętla „while”
Zaczniemy od razu od przykładowego programu:
#include <stdio.h>
void main(void)
{
int licznik = 10;
printf(„Poczatek petli
„);
while(licznik 0)
{
printf(„Zmienna licznik = %d
„, licznik);
licznik–;
}
printf(„Koniec petli
„);
}
Skompiluj go proszę i uruchom. Na ekranie pojawi się dziesięć linii tekstu o treści: „Zmienna licznik = xx”, gdzie xx symbolizuje kolejne numerki od dziesięć do jeden. Zapewne już się domyślasz jak działa pętla oparta na instrukcji while , lecz dla dopełnienia formalności opiszę to pokrótce. Pętla rozpoczyna się od słowa kluczowego while , po którym, podobnie jak w przypadku instrukcji if , podajemy warunek kontynuowania pętli. Pętla będzie wykonywana dopóty, dopóki warunek ten jest spełniony. Następnie podajemy instrukcję, która będzie wykonywana w pętli. Jeśli chcesz wykonywać w pętli kilka instrukcji, musisz zawrzeć je w bloku ograniczonym znakami { i } (podobnie jest w wszystkich konstrukcjach języka C). W naszym przykładowym programie wykonujemy w pętli dwie instrukcje – wywołujemy funkcję printf i zmniejszamy zawartość zmiennej licznik o jeden. Algorytm działania tego programu wygląda następująco:
1. Wpisz wartość dziesięć do zmiennej licznik.
2. Wyświetl na ekranie tekst informujący o rozpoczęciu działania pętli.
3. Napotykamy się na instrukcję „while” – sprawdza ona czy wartość
zmiennej licznik jest większa od zera (na początku programu jest,
bo wpisaliśmy do niej dziesięć) i jeśli tak to przechodzi do linijki
z wywołaniem funkcji „printf”, wewnątrz bloku. Zauważ, że gdybyśmy
zainicjalizowali zmienna licznik wartością np. minus jeden, to od razu
skoczylibyśmy do kroku numer siedem i na ekranie pokazałby się napis o
zakończeniu działania pętli ! Warunek przy instrukcji „while” byłby
bowiem od razu fałszywy, w związku z tym działanie programu byłoby
kontynuowane od pierwszej linijki za blokiem instrukcji dla pętli.
Jak zobaczysz w następnym punkcie, istnieje także pętla, która sprawdza
warunek na końcu pętli i w wyniku tego wykona się ona, w przeciwieństwie
do „while”, przynajmniej jeden raz.
4. Jesteśmy teraz wewnątrz bloku i za pomocą instrukcji „printf”
wyświetlamy informację o aktualnym stanie licznika.
5. Zmniejszamy wartość zmiennej licznik o jeden.
6. Skaczemy do kroku numer trzy – dokonuje się tam znowu sprawdzenie
warunku i w zależności od tego, czy zmienna licznik osiągnęła już zero,
czy nie, program będzie kontynuował swe działanie od kroku numer cztery,
albo siedem.
7. Wyświetlamy informację o zakończeniu działania pętli i program kończy
swe działanie.
Jeszcze jedna uwaga dla znających Pascala – oczywiście odpowiednikiem while w języku C jest instrukcja Pascala o tej samej nazwie.
9.2. Pętla „do-while”
Pętla „do-while” jest bardzo podobna do poznanej w poprzednim punkcie „while”. Jedyną różnicą jest fakt, że o ile pętla while mogła w ogóle się nie wykonać, jeśli warunek nie był spełniony, o tyle pętla do-while wykona się przynajmniej jeden raz, bo warunek sprawdzany jest dopiero na samym końcu. A oto jak będzie wyglądał nasz program z poprzedniego punktu przy użyciu nowej konstrukcji:
#include <stdio.h>
void main(void)
{
int licznik = 10;
printf(„Poczatek petli
„);
do
{
printf(„Zmienna licznik = %d
„, licznik);
licznik–;
} while(licznik 0);
printf(„Koniec petli
„);
}
Jak widzisz różnica jest minimalna. Zwróć tylko uwagę, że po while tym razem stawiamy średnik. Odpowiednikiem tej konstrukcji w Pascalu jest pętla repeat-until . Występuje tu jednak różnica – pętla repeat-until wykonuje się do momentu, gdy warunek _stanie się_ prawdziwy, natomiast do-while wykonuje się _dopóki_ warunek jest prawdziwy.
9.3. Pętla „for”
Najbardziej rozbudowaną konstrukcją pętli jest pętla „for”. Ogólny jej zapis wygląda następująco:
for(inicjalizacja; warunek; inkrementacja)
{
instrukcje do wykonania
}
Pętla ta działa na podstawie takiego oto algorytmu:
1. Wykonywane są instrukcje zawarte w części „inicjalizacja”. Jest to wykonywane
tylko jeden raz, na samym początku pętli.
2. Sprawdzany jest warunek – jeśli jest fałszywy to następuje skok do kroku
numer sześć.
3. Wykonywane są „instrukcje do wykonania”.
4. Wykonywane są instrukcje zawarte w części „inkrementacja”.
5. Następuje skok do kroku numer dwa.
6. Pętla kończy się – wykonywane są instrukcje poza pętlą.
Żaden z podanych parametrów nie jest wymagany, tak więc najprostszą pętlą przy użyciu „for” jest:
for(;;)
{
instrukcje do wykonania
}
Taką konstrukcję nazywa się czasem „forever” ponieważ jest to pętla nieskończona (będzie wykonywała się aż do momentu, gdy użytkownik zresetuje komputer lub gdy napotka instrukcję break lub goto, o których to powiemy później).
Pokażmy wreszcie jak będzie wyglądał nasz przykładowy program z poprzednich punktów przy użyciu pętli „for”:
#include <stdio.h>
void main(void)
{
int licznik;
printf(„Poczatek petli
„);
for(licznik=10; licznik 0; licznik–)
{
printf(„Zmienna licznik = %d
„, licznik);
}
printf(„Koniec petli
„);
}
Jak widzisz konstrukcja ta jest już nieco inna od poznanych wcześniej. W części inicjalizacyjnej dokonujemy ustawienia zmiennej licznik na dziesięć. Moglibyśmy to zrobić tak jak poprzednio (od razu ją ustawić przy deklaracji) i część tą zostawić pustą, ale napisałem to w ten sposób, żeby pokazać, że można coś takiego zrobić. Następnie widzimy warunek – tu nic nowego, wygląda on dokładnie tak samo jak poprzednio. Zmniejszenia licznika nie dokonujemy jednak wewnątrz pętli, lecz w części „inkrementacja”. Oczywiście moglibyśmy to zrobić wewnątrz pętli, a tą cześć pozostawić pustą, ale zostało to wykonane w ten sposób, aby zaprezentować sposób użycia.
Części „inicjalizacja” i „inkrementacja” mogą zawierać także po kilka instrukcji – oddzielamy je wtedy przecinkami. Dla zobrazowania takiej konstrukcji zamieściłem nieco zmodyfikowaną wersję poprzedniego programu – została dodana jeszcze jedna zmienna, która jest zwiększana z każdym przejściem pętli o dwa:
#include <stdio.h>
void main(void)
{
int licznik, a;
printf(„Poczatek petli
„);
for(licznik=10, a=5; licznik 0; licznik–, a+=2)
{
printf(„Zmienna licznik = %d
„, licznik);
printf(„Zmienna a = %d
„, a);
}
printf(„Koniec petli
„);
}
9.4. Break, continue i goto
Czasem może się zdarzyć potrzeba pominięcia jednego przebiegu pętli lub wcześniejszego jej przerwania. Służą do tego celu dwie wspomniane instrukcje. Instrukcja break jest wręcz konieczna w przypadku zastosowania pętli nieskończonej (pokazanej w poprzednim punkcie). Przyjrzyjmy się nieco zmodyfikowanemu programowi z poprzedniego punktu:
#include <stdio.h>
void main(void)
{
int licznik;
printf(„Poczatek petli
„);
for(licznik=10; licznik 0; licznik–)
{
if(licznik == 5) continue;
if(licznik == 2) break;
printf(„Zmienna licznik = %d
„, licznik);
}
printf(„Koniec petli
„);
}
Jak widzimy program niewiele różni się od poprzedniego – zostały tylko dodane dwie linijki z instrukcją warunkową if . W przypadku, gdy zmienna licznik jest równa pięć wykonywana jest instrukcja continue , a gdy jest równa dwa, wykonywana jest instrukcja break . Skompiluj teraz, proszę, program i uruchom go. Widzisz jaka jest różnica ? Z początku program działa identycznie z poprzednim – do wartości zmiennej licznik równej sześć jest wyświetlana odpowiednia linia. Jednak linijka informująca, że zmienna licznik jest równa pięć w ogóle nie została wyświetlona – odpowiedzialna jest za to instrukcja continue . Na początku nie jest ona wykonywana, jednak gdy zmienna licznik dojdzie do wartości pięć, zostaje ona wykonana. Powoduje to, że pętla for pomija wszystkie następujące po continue instrukcje wewnątrz pętli (dla danego przebiegu) i przechodzi od razu do następnej iteracji. W następnych dwóch przebiegach żaden z warunków występujących po instrukcjach if nie jest spełniony i program wyświetla informacje, że zmienna licznik jest równa, kolejno, cztery i trzy. Dochodzimy teraz do momentu, gdy zmienna licznik osiąga wartość dwa. W takim wypadku spełniony jest warunek drugiej z instrukcji if i wykonywana jest instrukcja break . Jaki jest tego rezultat widzisz na ekranie – pętla kończy swe działanie i zostaje wyświetlony komunikat o tym informujący.
Alternatywą instrukcji break jest instrukcja goto . Mimo, że w przypadku języków strukturalnych (jakim jest język C, jak również Pascal) jej stosowanie jest wręcz tępione (szczególnie na poziomie nauczania języka), to jest jednak sytuacja, gdy można ją zastosować ponieważ nie istnieje żaden inny prosty sposób osiągnięcia celu. Sytuacją tą jest wyjście z zagnieżdżonej pętli. Od razu to zilustrujemy odpowiednim przykładem – załóżmy, że mamy do czynienia z takim przypadkiem:
#include <stdio.h>
void main(void)
{
int a, b;
printf(„Poczatek petli
„);
for(a=0; a < 4; a++)
{
for(b=0; b < 4; b++)
{
// if((a==2) && (b==1)) break;
printf(„a = %d, b = %d
„, a, b);
}
}
printf(„Koniec petli
„);
}
Jak widzimy mamy tu zagnieżdżoną pętlę, tzn. jedna pętla jest wykonywana w drugiej. W sumie otrzymamy więc na ekranie 4*4=16 linijek tekstu. Załóżmy teraz, że chcielibyśmy zakończyć wykonywanie obu pętli przy zmiennej a równej dwa i zmiennej b równej jeden. Jeśli użylibyśmy instrukcji break (tak jak to pokazano w komentarzu) to przerwalibyśmy tylko pętlę, w której jest zwiększana wartość zmiennej b. W efekcie na ekranie pojawiłoby się:
Poczatek petli
a=0, b=0
a=0, b=1
a=0, b=2
a=0, b=3
a=1, b=0
a=1, b=1
a=1, b=2
a=1, b=3
a=2, b=0
a=3, b=0
a=3, b=1
a=3, b=2
a=3, b=3
Koniec petli
Czyli jak widzimy pętla „b” rzeczywiście została przerwana przy a równym dwa, ale następnie została ona ponownie wykonana dla a równego trzy. A my chcieliśmy zakończyć obie pętle ! Rozwiązaniem jest tutaj użycie instrukcji goto:
#include <stdio.h>
void main(void)
{
int a, b;
printf(„Poczatek petli
„);
for(a=0; a < 4; a++)
{
for(b=0; b < 4; b++)
{
if((a==2) && (b==1)) goto koniec;
printf(„a = %d, b = %d
„, a, b);
}
}
koniec:
printf(„Koniec petli
„);
}
W programie tym instrukcja break została zastąpiona przez goto koniec . „koniec” jest to tzw. etykieta. Zadeklarowaliśmy ją przed ostatnią instrukcją programu – jest to po prostu dowolna nazwa zakończona dwukropkiem. W programie możemy zadeklarować dowolną liczbę etykiet (oczywiście każda musi mieć inną nazwę). Aby przenieść wykonywanie programu do innego miejsca należy wykonać instrukcję goto podając jej nazwę etykiety, do której ma nastąpić skok. W naszym przypadku po wykonaniu instrukcji goto koniec program wyświetli informację o końcu pętli i zakończy swe działanie. Po uruchomieniu powyższego programu na ekranie uzyskamy:
Poczatek petli
a=0, b=0
a=0, b=1
a=0, b=2
a=0, b=3
a=1, b=0
a=1, b=1
a=1, b=2
a=1, b=3
a=2, b=0
Koniec petli
czyli dokładnie to, o co nam chodziło.