2. Iteracje
Bardzo często pewne instrukcje lub grupy instrukcji będziemy
chcieli wykonywać wielokrotnie. do licznik = wart_pocz to wart_konc [ by krok ] ins1; ins2; ... insN; end gdzie: Pętla ta wykonywana jest w następujący sposób: Zmienne oznaczające
liczniki pętli nazywamy zwykle krótko: i, j, k, l
Rozpatrzmy przykłady. Poniższy program wypisuje na konsoli kwadraty
liczb całkowitych od 1 do 100. /* Kwadraty liczb całkowitych od 1 do 100 */ do i = 1 to 100 say i i*i; end Zmiennej i nadawana jest wartość 1. Wartość ta mniejsza jest od 100, zatem wykonywana jest instrukcja w pętli (say ...), wyprowadzająca na konsolę wartość zmiennej i oraz - po spacji - jej kwadrat. Przy napotkaniu słowa end sprawdzany jest warunek kontynuacji pętli (czy i <= 100). Warunek jest prawdziwy, zatem i zwiększa się o 1 a instrukcje pętli wykonywane są znowu (wypisywana jest wartość i oraz jej kwadrat: 2 4. Terminem sterowanie określamy kolejność wykonania instrukcji
programu. Sformułowanie "sterowanie przekazywane jest do instrukcji
ins " oznacza, że kolejną wykonywaną instrukcją w programie będzie
ins .
I tak dalej: dopóki wartość i nie osiągnie 101. W tym momencie warunek kontynuacji pętli (i <= 100) nie jest spełniony i sterowanie przekazywane jest do instrukcji po słowie end. Ponieważ nie ma już żadnych instrukcji - program kończy działanie. Inny przykładowy program symuluje spłaty kredytu. Podajemy na wejściu: wielkość kredytu, liczbę lat kredytu oraz jego oprocentowanie (w %). Oprocentowanie pobierane jest wraz z miesięczną ratą i obliczane jako miesięczne oprocentowanie niespłaconej części kredytu. Program wyprowadza dla kolejnych miesięcy okresu kredytowania należne spłaty, a w podsumowaniu sumę spłat i informację o ile (na skutek oprocentowania) przekroczy ona wielkość kredytu. /* Symulacja splaty kredytu */ /*---------------------- Wprowadzanie danych */ say "Kredyt?"; kredyt = linein(); say "Liczba lat kredytu?"; lata = linein(); say "Oprocentowanie w skali rocznej?" proc = linein(); /*---------------------- Obliczenia i wyniki */ proc = (proc/100)/12; /* odsetki miesięczne */ lrat = lata * 12; /* liczba rat */ rata = kredyt / lrat; /* wielkość raty */ doSplaty = kredyt; /* ile pozostało do spłaty */ sumaSplat = 0; /* ile splacono: raty + oprocentowanie */ do i = 1 to lrat splata = rata + proc*doSplaty; /* splata = rata + nalezne odsetki */ doSplaty = doSplaty - rata; sumaSplat = sumaSplat + splata; say i ':' splata; /* informacja o splacie dla i-go miesiąca */ end say "Suma spłat =" sumaSplat; say "Ponad kredyt =" sumaSplat - kredyt; W tym programie pętla do...
wykonywana jest tyle razy ile wynosi liczba rat do spłacenia. Zmienna
i oznacza numer raty (miesiąca spłaty). Za każdym razem wyliczana
jest spłata (która wynosi wielkość raty + miesięczna wielkość odsetek od
niespłacanej jeszcze części kredytu). Na konsolę wyprowadzany jest numer
miesiąca oraz przypadająca na ten miesiąc spłata. Gdy i = lrat
(oststni miesiąc spłaty) po raz ostatni obliczana i wyprowadzana jest
informacja o spłacie. Następnie i zwiększa się o 1 i wynosi
lrat +1. Warunek kontynuacji pętli nie jest spełniony, zatem
sterowanie opuszcza pętlę (przekazywane jest do instrukcji po słowie kluczowym
end - czyli say "Suma splat..."). Na konsolę wyprowadzane są informacje
wyjściowe i program kończy działanie. Wydruk działania programu:
Kredyt? 10000 Liczba lat kredytu? 1 Oprocentowanie w skali rocznej? 20 1 : 1000.00000 2 : 986.111111 3 : 972.222222 4 : 958.333333 5 : 944.444445 6 : 930.555556 7 : 916.666667 8 : 902.777778 9 : 888.888889 10 : 875.000000 11 : 861.111111 12 : 847.222223 Suma spłat = 11083.3333 Ponad kredyt = 1083.3333 Zwróćmy uwagę na zastosowany w tym programie sposób sumowania spłat. Sumę spłat uzyskamy w zmiennej sumaSplat. Na początku jej wartość równa jest 0. W pierwszej iteracji do sumaSplat dodawana jest wartośc pierwszej spłaty (w tej chwili sumaSplat równa jest pierwszej spłacie). W każdej następnej iteracji poprzednia suma spłat jest zwiększana o bieżącą spłatę, a wynik znowu zapisywany w zmiennej sumaSplat. Jest to powszechny sposób sumowania (akumulowania) różnych wartości. Może on być zastosowany nie tylko do wartości numerycznych, ale również w innych przypadkach, np. do kumulatywnego łączenia łańcuchów znakowych. Np. w poniższym programiku wypisujemy napis składający się z n-razy
kolejno łączonych słów "Ala" i "kot". /* Łączenie napisów w pętli */ word1 = "Ala"; word2 = "kot"; n = 8; string = ""; do i = 1 to n if ( i // 2 = 0 ) then string = string||word2; else string = string||word1; end say string; Wydruk działania programu
AlakotAlakotAlakotAlakot Na początku zapisujemy do zmiennej string pusty łańcuch znakowy (""). Następnie w pętli na przemian dodajemy do bieżącej zawartości zmiennej string albo słowo word1, albo słowo word2. Sposób naprzemiennego dodawania jest następujący: jeśli numer iteracji
(wartość zmiennej i) jest parzysty, dodajemy zmienną word2, a jeśli jest
nieparzysty - zmienną word1. Uwaga! W Javie operatorem reszty z dzielenia jest %
Parzystość sprawdzamy za pomocą użycia operatora reszty z dzielenia (który w REXXie oznaczany jest //). Reszta z dzielenia przez 2 dla liczb parzystych równa jest 0, a dla nieparzystych ma inną wartość niż 0. Zwróćmy też uwagę, że wykorzystaliśmy priorytety operatorów: priorytety operatorów arytmetycznych (w tym: reszty z dzielania) są wyższe niż priorytet operatora równości =, dlatego w wyrażenie i // 2 = 0 najpierw jest stosowany operator //, a później dopiero operator =, porównujący wynik wyrażenia i // 2 z zerem. Proszę zapisać i uruchomić omawiane wyżej programy na własnym komputerze. Drugą, przedstawianą tu, formę pętli iteracyjnej realizuje instrukcja
do while
do while ( warunek )
ins1; ins2; ... insN; end Instrukcje w pętli (ins1, ins2, ... insN) wykonywane są o ile i dopóki warunek jest prawdziwy. Poniższy program służy do testowania działania pętli do while. say "Podaj a: "; a = linein(); say "Podaj b: "; b = linein(); say "a = " a "b = " b; n = 0 do while ( a > b) a = a - 1; n = n + 1 end say "Pętla została wykonana: " n "razy." say "a = " a "b = " b; Przykładowy wydruk działania programu
Podaj a: 1078.1 Podaj b: 23.5 a = 1078.1 b = 23.5 Pętla zostala wykonana: 1055 razy. a = 23.1 b = 23.5 Podajemy w nim wartości zmiennych a i b. Pętla do while zawiera warunek (a > b) zatem:
Proszę zapisać i uruchomić omawiany wyżej program na własnym komputerze. Na przykład, całkiem dopuszczalny jest taki schemat: do while (a > b) do i = 1 to N .... do j = 1 to M ..... do while (s = "") ... end end end end Istotną sprawą przy zagnieżdżaniu instrukcji sterujących jest "dopasowanie"
ich początków i końców (w naszym przypadku każdemu otwierającemu do musi odpowiadać zamykające end). Wcięcia w kodzie programu zwiększają jego czytelność i pozwalają łatwiej odnajdywać błędy.
Wcięcia są jednym z elementów tzw. stylu programowania, czyli sposobu formatowania kodu tak by był przejrzysty. Należy jednak pamiętać, że sposób formatowania kodu programu nie ma nic wspólnego
z jego poprawnością składniową i semantyką. Żaden kompilator i żaden interpreter
nigdy nie stara się odczytać intencji programisty na podstawie zastosowanych
wcięć. Wcięcia nie mają nic wspólnego z semantyką programu.
Możemy zmieniać naturalne działanie pętli iteracyjnych za pomocą instrukcji: W Javie służy temu instrukcja break
Przerwanie działania pętli uzyskujemy w REXXie za pomocą instrukcji leave (opuść pętlę). Przerywana jest najbardziej wewnętrzna pętla, lub jeśli w instrukcji leave użyjemy etykiety (leave jakasEtykieta) - pętla oznaczona tą etykietą.
Np. do i = 1 to 100000 n = i * i; if (n > 20000) then leave; end say i n; Wynik
142 20164 Pętla wykonała się 142 razy (a nie 100000 jak wskazuje górna wartość
licznika), ponieważ gdy n jest większe od 20000 przerywamy działanie pętli
za pomocą instrukcji leave (sterowanie zostaje przekazane poza end kończące
pętlę). W Javie mamy tu instrukcję continue
W Rexxie służy temu instrukcja iterate (tak samo jak leave może dotyczyć najbardziej wewnętrznej z zagnieżdżonych pętli lub pętli oznaczonej podaną etykietą). Na przykład w poniższym programie wczytujemy z konsoli wszystkie wprowadzone
przez użytkownika napisy, pomijając puste wiersze (np. spacje lub samo ENTER),
łączymy je w jeden łańcuch znakowy i wyprowadzamy po wprowadzeniu przez użytkownika
słowa quit, kończąc na tym działanie programu. out = ''; do forever txt = linein(); if ( txt = "" ) then iterate; if ( txt = "quit" ) then leave; out = out txt; end say out; W Javie taką nieskończoną pętlę możemy zapisać jako while ( true ) {...}
Warto tu zwrócić uwagę na składniową konstrukcję do forever, oznaczającą pętlę nieskończoną (wykonuje się wiecznie; oczywiście zawsze trzeba mieć jakiś sposób, warunek przerwania takiej pętli). Generalnie, warto szczególnie podkreślić problem właściwego kończenia wykonywania pętli iteracyjnych. Niezwykle często bowiem na skutek błędów w programie pętle nie kończą działania i program wykonuje się bez końca. Czasami jedynym sposobem jego zatrzymania jest zakończenie procesu z poziomu systemu operacyjnego.
|