BAZA WIEDZY
KURSY
Bazy danych w PHP
Kurs AdvancedAJAX
Kurs ASP
Kurs ASP.NET
Kurs C++
Kurs CSS
Kurs HTML
Kurs HTML drugi
Kurs JavaScript
Kurs MySQL
Kurs PHP
Kurs RSS
Kurs XHTML
Obiekty DOM
MANUALE
CSS1 - W3C
DOM - w budowie
PHP 2005
PHP 2006
Wyrażenia regularne
SHOUTBOX
STAT
Online: 5 | UU: 228

Pętle

Pętle (ang. loops ), zwane też instrukcjami iteracyjnymi , stanowią podstawę prawie wszystkich algorytmów. Lwia część zadań wykonywanych przez programy komputerowe opiera się w całości lub częściowo właśnie na pętlach.

Pętla to element języka programowania, pozwalający na wielokrotne, kontrolowane wykonywanie wybranego fragmentu kodu.

Liczba takich powtórzeń (zwanych cyklami lub iteracjami pętli) jest przy tym ograniczona w zasadzie tylko inwencją i rozsądkiem programisty. Te potężne narzędzia dają więc możliwość zrealizowania niemal każdego algorytmu.

Pętle są też niewątpliwie jednym z atutów C++: ich elastyczność i prostota jest większa niż w wielu innych językach programowania. Jeżeli zatem będziesz kiedyś kodował jakąś złożoną funkcję przy użyciu skomplikowanych pętli, z pewnością przypomnisz sobie i docenisz te zalety :)

Pętle warunkowe do i while

Na początek poznamy dwie konstrukcje, które zwane są pętlami warunkowymi . Miano to określa całkiem dobrze ich zastosowanie: ciągłe wykonywanie kodu, dopóki spełniony jest określony warunek . Pętla sprawdza go przy każdym swoim cyklu - jeżeli stwierdzi jego fałszywość, natychmiast kończy działanie.

Pętla do

Prosty przykład obrazujący ten mechanizm prezentuje się następująco:

// do - pierwsza pętla warunkowa
#include <iostream>
#include <conio.h>
void main()
{
int nLiczba;
do
{
std::cout << "Wprowadz liczbe wieksza od 10: " ;
std::cin >> nLiczba;
} while (nLiczba <= 10 );
std::cout << "Dziekuje za wspolprace :)" ;
getch();
}

Program ten, podobnie jak jeden z poprzednich, oczekuje od nas o liczby większej niż dziesięć. Tym razem jednak nie daje się zbyć byle czym - jeżeli nie będziemy skłonni od razu przychylić się do jego prośby, będzie ją niezłomnie powtarzał aż do skutku (lub do użycia Ctrl+Alt+Del ;D).


Screen 15. Nieugięty program przeciwko krnąbrnemu użytkownikowi :)

Upór naszej aplikacji bierze się oczywiście z umieszczonej wewnątrz niej pętli do ('czyń') . Wykonuje ona kod odpowiedzialny za prośbę do użytkownika tak długo, jak długo ten jest konsekwentny w ignorowaniu jej :) Przejawia się to rzecz jasna wprowadzaniem liczb, które nie są większe od 10, lecz mniejsze lub równe tej wartości - odpowiada to warunkowi pętli nLiczba <= 10 . Instrukcja niniejsza wykonuje się więc dopóty, dopóki (ang. while ) zmienna nLiczba , która przechowuje liczbę pobraną od użytkownika, nie przekracza granicznej wartości dziesięciu. Przedstawia to poglądowo poniższy diagram:


Schemat 4. Działanie przykładowej pętli do

Co się jednak dzieje przy pierwszym "obrocie" pętli, gdy program nie zdążył jeszcze pobrać od użytkownika żadnej liczby? Jak można porównywać wartość zmiennej nLiczba , która na samym początku jest przecież nieokreślona?. Tajemnica tkwi w fakcie, iż pętla do dokonuje sprawdzenia swojego warunku na końcu każdego cyklu - dotyczy to także pierwszego z nich. Wynika z tego dość oczywisty wniosek:

Pętla do wykona zawsze co najmniej jeden przebieg.

Fakt ten sprawia, że nadaje się ona znakomicie do uzyskiwania jakichś danych od użytkownika przy jednoczesnym sprawdzaniu ich poprawności. Naturalnie, w prawdziwym programie należałoby zapewnić swobodę zakończenia aplikacji bez wpisywania czegokolwiek. Nasz obrazowy przykład jest jednak wolny od takich fanaberii - to wszak tylko kod pomocny w nauce, więc pisząc go nie musimy przejmować się takimi błahostkami ;))

Podsumowaniem naszego spotkania z pętlą do będzie jej składnia:

do
{
instrukcje
} while ( warunek )

Wystarczy przyjrzeć się jej choć przez chwilę, by odkryć cały sens. Samo tłumaczenie wyjaśnia właściwie wszystko: "Wykonuj (ang. do ) instrukcje , dopóki (ang. while ) zachodzi warunek ". I to jest właśnie spiritus movens całej tej konstrukcji.

Pętla while

Przyszła pora na poznanie drugiego typu pętli warunkowych, czyli while . Słówko będące jej nazwą widziałeś już wcześniej, przy okazji pętli do - nie jest to bynajmniej przypadek, gdyż obydwie konstrukcje są do siebie bardzo podobne.

Działanie pętli while prześledzimy zatem na poniższym ciekawym przykładzie:

// while - druga pętla warunkowa
#include <iostream>
#include <ctime>
#include <conio.h>
void main()
{
// wylosowanie liczby
srand (( int ) time(NULL));
int nWylosowana = rand() % 100 + 1 ;
std::cout << "Wylosowano liczbe z przedzialu 1-100." << std::endl;
// pierwsza próba odgadnięcia liczby
int nWprowadzona;
std::cout << "Sprobuj ja odgadnac: " ;
std::cin >> nWprowadzona;
// kolejne próby, aż do skutku - przy użyciu pętli while
while (nWprowadzona != nWylosowana)
{
if (nWprowadzona < nWylosowana)
std::cout << "Liczba jest zbyt mala." ;
else
std::cout << "Za duza liczba." ;
std::cout << " Sprobuj jeszcze raz: " ;
std::cin >> nWprowadzona;
}
std::cout << "Celny strzal :) Brawo!" << std::endl;
getch();
}

Jest to nic innego, jak prosta. gra :) Twoim zadaniem jest w niej odgadnięcie "pomyślanej" przez komputer liczby (z przedziału od jedności do stu). Przy każdej próbie otrzymujesz wskazówkę, mówiącą czy wpisana przez ciebie wartość jest za duża, czy za mała.


Screen 16. Wystarczyło tylko 8 prób :)

Tak przedstawia się to w działaniu. Jako programiści chcemy jednak zajrzeć do kodu źródłowego i przekonać się, w jaki sposób można było taki efekt osiągnąć. Czym prędzej więc ziśćmy te pragnienia :D

Pierwszą czynnością podjętą przez nasz program jest wylosowanie liczby, którą użytkownik będzie odgadywał. Zasadniczo odpowiadają za to dwie początkowe linijki:

srand (( int ) time(NULL));
int nWylosowana = rand() % 100 + 1 ;

Nie będziemy obecnie zagłębiać się w szczegóły ich funkcjonowania, gdyż te zostaną omówione w następnym rozdziale. Teraz możesz jedynie zapamiętać, iż pierwszy wiersz, zawierający funkcję srand() (i jej osobliwy parametr), jest czymś w rodzaju zakręcenia kołem ruletki. Jego obecność sprawia, że aplikacja za każdym razem losuje nam inną liczbę.

Za samo losowanie odpowiada natomiast wyrażenie z funkcją rand() . Obliczona wartość tegoż jest od razu przypisywana do zmiennej nWylosowana i to o nią toczy bój niestrudzony gracz :)

Kolejny pakiet kodu pozwala na wykonanie pierwszej próby odgadnięcia właściwego wyniku. Nie widać tu żadnych nowości - z podobnymi fragmentami spotykaliśmy się już wielokrotnie i wyjaśniliśmy je dogłębnie. Zauważmy tylko, że liczba wpisana przez użytkownika jest zapamiętywana w zmiennej nWprowadzona .

O wiele bardziej interesująca jest dla nas pętla while , występująca dalej. To na niej spoczywa zadanie wyświetlania graczowi wskazówek, umożliwiania mu kolejnych prób i sprawdzania wpisanych wartości.

Podobnie jak w przypadku do , wykonywanie tej pętli uzależnione jest spełnieniem określonego kryterium. Tutaj jest nim niezgodność między liczbą wylosowaną na początku (zawartą w zmiennej nWylosowana ), a wprowadzoną przez użytkownika (zmienna nWprowadzona ). Zapisujemy to w postaci warunku nWprowadzona != nWylosowana . Oczywiście pętla wykonuje się do chwili, w której założenie to przestaje być prawdziwe, a użytkownik poda właściwą liczbę.

Wewnątrz bloku pętli podejmowane zaś są dwie czynności. Najpierw wyświetlana jest podpowiedź dla użytkownika. Mówi mu ona, czy wpisana przed chwilą liczba jest większa czy mniejsza od szukanej. Gracz otrzymuje następnie kolejną szansę na odgadnięcie pożądanej wartości.

Gdy wreszcie uda mu się ta sztuka, raczony jest w nagrodę odpowiednim komunikatem :)

Tak oto przedstawia się funkcjonowanie powyższego programu przykładowego, którego witalną częścią jest pętla while . Wcześniej natomiast zdążyliśmy się dowiedzieć i przekonać, iż konstrukcja ta bardzo przypomina poznaną poprzednio pętlę do . Na czym więc polega różnica między nimi?.

Jest nią mianowicie moment sprawdzania warunku pętli. Jak pamiętamy, do czyni to na końcu każdego cyklu. Analogicznie, while dokonuje tego zawsze na początku swego przebiegu. Determinuje to dość oczywiste następstwo:

Pętla while może nie wykonać się ani razu , jeżeli jej warunek będzie od początku nieprawdziwy.

W naszym przykładowym programie odpowiada to sytuacji, gdy gracz od razu trafia we właściwą liczbę. Naturalnie, jest to bardzo mało prawdopodobne (rzędu 1%), lecz jednak możliwe. Trzeba zatem przewidzieć i odpowiednio zareagować na taki przypadek, zaś pętla while rozwiązuje nam ten problem praktycznie sama :)

Na koniec tradycyjnie już przyjrzymy się składni omawianej konstrukcji:

while ( warunek )
{
instrukcje
}

Ponownie wynika z niej praktycznie wszystko: "dopóki ( while ) zachodzi warunek , wykonuj instrukcje ". Czyż nie jest to wyjątkowo intuicyjne? ;)

***

Tak oto poznaliśmy dwa typy pętli warunkowych - ich działanie, składnię i sposób używania. Tym samym dostałeś do ręki narzędzia, które pozwolą ci tworzyć lepsze i bardziej skomplikowane programy.

Jakkolwiek oba te mechanizmy mają bardzo duże możliwości, korzystanie z nich może być w niektórych wypadkach nieco niewygodne. Na podobne okazje obmyślono trzeci rodzaj pętli, z którym właśnie teraz się zaznajomimy.

Pętla krokowa for

do tej pory spotykaliśmy się z sytuacjami, w których należało wykonywać określony kod aż do spełnienia pewnego warunku. Równie często jednak znamy wymaganą ilość "obrotów" pętli jeszcze przed jej rozpoczęciem - chcemy ją podać w kodzie explicite lub obliczyć wcześniej jako wartość zmiennej.

Co wtedy zrobić? Możemy oczywiście użyć odpowiednio spreparowanej pętli while , chociażby w takiej postaci:

int nLicznik = 1 ;
// wypisanie dziesięciu liczb całkowitych w osobnych linijkach
while (nLicznik <= 10 )
{
std::cout << nLicznik << std::endl;
nLicznik++;
}

Powyższe rozwiązanie jest z pewnością poprawne, aczkolwiek istnieje jeszcze lepsze :) W przypadku, gdy znamy z góry liczbę przebiegów pętli, bardziej naturalne staje się użycie instrukcji for ('dla'). Została ona bowiem stworzona specjalnie na takie okazje i sprawdza się w nich o wiele lepiej niż uniwersalna while . Korzystający z niej ekwiwalent powyższego kodu może wyglądać na przykład tak:

for ( int i = 1 ; i <= 10 ; i++)
{
std::cout << i << std::endl;
}

Jeżeli uważnie przyjrzysz się obu jego wersjom, z pewnością zdołasz domyśleć się ogólnej zasady działania pętli for . Zanim dokładnie ją wyjaśnię, posłużę się bardziej wyrafinowanym przykładem do jej ilustracji:

// for - pętla krokowa
int Suma( int nLiczba)
{
int nSuma = 0 ;
for ( int i = 1 ; i <= nLiczba; i++)
nSuma += i;
return nSuma;
}
void main()
{
int nLiczba;
std::cout << "Program oblicza sume od 1 do podanej liczby."
<< std::endl;
std::cout << "Podaj ja: " ;
std::cin >> nLiczba;
std::cout << "Suma liczb od 1 do " << nLiczba << " wynosi "
<< Suma(nLiczba) << "." ;
getch();
}

Mamy zatem kolejny superużyteczny programik do przeanalizowania ;) Bezzwłocznie więc przystąpmy do wykonania tego pożytecznego zadania.

Rzut oka na kod tudzież kompilacja i uruchomienie aplikacji prowadzi do słusznego wniosku, iż przeznaczeniem programu jest obliczanie sumy kilku początkowych liczb naturalnych. Zakres dodawania ustala przy tym sam użytkownik programu.

Czynnością sumowania zajmuje się tu odrębna funkcja Suma() , na której skupimy obecnie całą naszą uwagę.

Pierwsza linijka tej funkcji to znana już nam deklaracja zmiennej, połączona z jej inicjalizacją wartością 0 . Owa zmienna, nSuma , będzie przechowywać obliczony wynik dodawania, który zostanie zwrócony jako rezultat całej funkcji.

Najbardziej interesującym fragmentem jest występująca dalej pętla for :

for ( int i = 1 ; i <= nLiczba; i++)
nSuma += i;

Wykonuje ona zasadnicze obliczenia: dodaje do zmiennej nSuma kolejne liczby naturalne, zatrzymując się na podanym w funkcji parametrze. Całość odbywa się w następujący, dość prosty sposób:

  • Instrukcja int i = 1 jest wykonywana raz na samym początku. Jak widać, jest to deklaracja i inicjalizacja zmiennej i . Nazywamy ją licznikiem pętli . W kolejnych cyklach będzie ona przyjmować wartości 1 , 2 , 3 , itd.

  • Kod nSuma += i; stanowi blok pętli i jest uruchamiany przy każdym jej przebiegu. Skoro zaś licznik i jest po kolei ustawiany na następujące po sobie liczby naturalne, pętla for staje się odpowiednikiem sekwencji instrukcji nSuma += 1 ; nSuma += 2 ; nSuma += 3 ; nSuma += 4 ; itd.

  • Warunek i <= nLiczba określa górną granicę sumowania. Jego obecność sprawia, że pętla jest wykonywana tylko wtedy, gdy licznik i jest mniejszy lub równy zmiennej nLiczba . Zgadza się to oczywiście z naszym zamysłem.

  • Wreszcie, na koniec każdego cyklu instrukcja i++ powoduje zwiększenie wartości licznika o jeden.

Po dłuższym zastanowieniu nad powyższym opisem można niewątpliwie dojść do wniosku, że nie jest on wcale taki skomplikowany, prawda? :) Zrozumienie go nie powinno nastręczać ci zbyt wielu trudności. Gdyby jednak tak było, przypomnij sobie podaną w tytule nazwę pętli for - krokowa .

To całkiem trafne określenie dla tej konstrukcji. Jej zadaniem jest bowiem przebycie pewnej "drogi" (u nas są to liczby od 1 do wartości zmiennej nLiczba ) poprzez serię małych kroków i wykonanie po drodze jakichś działań. Klarownie przedstawia to tenże rysunek:


Schemat 5. "Droga" przykładowej pętli for

Mam nadzieję, że teraz nie masz już żadnych kłopotów ze zrozumieniem zasady działania naszego programu.

Przyszedł czas na zaprezentowanie składni omawianej przez nas pętli:

for ( [ początek ] ; [ warunek ] ; [ cykl ] )
{
instrukcje
}

Na jej podstawie możemy dogłębnie poznać funkcjonowanie tego ważnego tworu programistycznego. dowiemy się też, dlaczego konstrukcja for jest uważana za jedną z mocnych stron języka C++.

Zaczniemy od początku, czyli komendy oznaczonej jako. początek :) Wykonuje się ona jeden raz, jeszcze przed wejściem we właściwy krąg pętli. Zazwyczaj umieszczamy tu instrukcję, która ustawia licznik na wartość początkową (może to być połączone z jego deklaracją).

warunek jest sprawdzany przed każdym cyklem instrukcji . Jeżeli nie jest on spełniony, pętla natychmiast kończy się. Zwykle więc wpisujemy w jego miejsce kod porównujący licznik z wartością końcową.

W każdym przebiegu, po wykonaniu instrukcji , pętla uruchamia jeszcze fragment zaznaczony jako cykl . Naturalną jego treścią będzie zatem zwiększenie lub zmniejszenie licznika (w zależności od tego, czy liczymy w górę czy w dół).

Inkrementacja czy dekrementacja nie jest bynajmniej jedyną czynnością, jaką możemy tutaj wykonać na liczniku. Posłużenie się choćby mnożeniem, dzieleniem czy nawet bardziej zaawansowanymi funkcjami jest jak najbardziej dopuszczalne.
Wpisując na przykład i *= 2 otrzymamy kolejne potęgi dwójki (2, 4, 8, 16 itd.), i += 10 - wielokrotności dziesięciu, itp. Jest to znaczna przewaga nad wieloma innymi językami programowania, w których liczniki analogicznych pętli mogą się zmieniać jedynie w postępie arytmetycznym (o stałą wartość - niekiedy nawet dopuszczalna jest tu wyłącznie jedynka!).

Elastyczność pętli for polega między innymi na fakcie, iż żaden z trzech podanych w nawiasie "parametrów" nie jest obowiązkowy! Wprawdzie na pierwszy rzut oka obecność każdego wydaje się tu absolutnie niezbędna, jednakże pominięcie któregoś (czasem nawet wszystkich) może mieć swoje logiczne uzasadnienie.

Brak początku lub cyklu powoduje dość przewidywalny skutek - w chwili, gdy miałyby zostać wykonane, program nie podejmie po prostu żadnych akcji. O ile nieobecność instrukcji ustawiającej licznik na wartość początkową jest okolicznością rzadko spotykaną, o tyle pominięcie frazy cykl jest konieczne, jeżeli nie chcemy zmieniać licznika przy każdym przebiegu pętli. Możemy to osiągnąć, umieszczając odpowiedni kod np. wewnątrz zagnieżdżonego bloku if .

Gdy natomiast opuścimy warunek , iteracja nie będzie miała czego weryfikować przy każdym swym "obrocie", więc zapętli się w nieskończoność. Przerwanie tego błędnego koła będzie możliwe tylko poprzez instrukcję break , którą już za chwilę poznamy bliżej.

***

W ten oto sposób zawarliśmy bliższą znajomość z pętla krokową for . Nie jest to może łatwa konstrukcja, ale do wielu zastosowań zdaje się być bardzo wygodna. Z tego względu będziemy jej często używali - tak też robią wszyscy programiści C++.

Instrukcje break i continue

Z pętlami związane są jeszcze dwie instrukcje pomocnicze. Nierzadko ułatwiają one rozwiązywanie pewnych problemów, a czasem wręcz są do tego niezbędne. Mowa tu o tytułowych break i continue .

Z instrukcją break ('przerwij') spotkaliśmy się już przy okazji konstrukcji switch . Korzystaliśmy z niej, aby zagwarantować wykonanie kodu odpowiadającego tylko jednemu wariantowi case . break powodowała bowiem przerwanie bloku switch i przejście do następnej linijki po nim.

Rola tej instrukcji w kontekście pętli nie zmienia się ani na jotę: jej wystąpienie wewnątrz bloku do , while lub for powoduje dokładnie ten sam efekt. Bez względu na prawdziwość lub nieprawdziwość warunku pętli jest ona błyskawicznie przerywana, a punkt wykonania programu przesuwa się do kolejnego wiersza za nią.

Przy pomocy break możemy teraz nieco poprawić nasz program demonstrujący pętlę do :

// break - przerwanie pętli
void main()
{
int nLiczba;
do
{
std::cout << "Wprowadz liczbe wieksza od 10" << std::endl;
std::cout << "lub zero, by zakonczyc program: " ;
std::cin >> nLiczba;
if (nLiczba == 0 ) break ;
} while (nLiczba <= 10 );
std::cout << "Nacisnij dowolny klawisz." ;
getch();
}

Mankament niemożności zakończenia aplikacji bez spełnienia jej prośby został tutaj skutecznie usunięty. Mianowicie, gdy wprowadzimy liczbę zero, instrukcja if skieruje program ku komendzie break , która natychmiast zakończy pętlę i uwolni użytkownika od irytującego żądania :)

Podobny skutek (przerwanie pętli po wpisaniu przez użytkownika zera) osiągnęlibyśmy zmieniając warunek pętli tak, by stawał się prawdziwy również wtedy, gdy zmienna nLiczba miałaby wartość 0 . W następnym rozdziale dowiemy się, jak poczynić podobną modyfikację.

Instrukcja continue jest używana nieco rzadziej. Gdy program natrafi na nią wewnątrz bloku pętli, wtedy automatycznie kończy bieżący cykl i rozpoczyna nowy przebieg iteracji. Z instrukcji tej korzystamy najczęściej wtedy, kiedy część (zwykle większość) kodu pętli ma być wykonywana tylko pod określonym, dodatkowym warunkiem.

Zakończyliśmy właśnie poznawanie bardzo ważnych elementów języka C++, czyli pętli. dowiedzieliśmy się o zasadach ich działania, składni oraz przykładowych zastosowaniach. Tych ostatnich będzie nam systematycznie przybywało wraz z postępami w sztuce programowania, gdyż pętle to bardzo intensywnie wykorzystywany mechanizm - nie tylko zresztą w C++.

1 for nie jest tylko wymysłem twórców C++. Podobne konstrukcje spotkać można właściwie w każdym języku programowania, istnieją też nawet bardziej wyspecjalizowane ich odmiany. Trudno więc uznać tę poczciwą pętlę za zbędne udziwnienie :)

2 Jak zapewne pamiętasz, jedną linijkę w bloku kodu możemy zapisać bez nawiasów klamrowych {} - dowiedzieliśmy się tego przy okazji instrukcji if :)

| | | |
Copyright © 2006-2013 egrafik.pl | Kontakt | Reklama | Projekty domów
jocker