Wprowadzenie
Pisząc swoje pierwsze programy i podążając przy tym kolejnymi rozdziałami kursu, rzadko kiedy zastanawiamy się 'czy da się poznane narzędzia wykorzystywać na inne sposoby'. Ucząc się nowych rzeczy zakładamy bowiem, że autor opowie nam o wszystkim co powinniśmy wiedzieć i najczęściej takie założenie istotnie ogranicza nasze możliwości stworzenia czegokolwiek fajnego w programowaniu. Celem niniejszego rozdziału jest uświadomienie Ci, że omawiane narzędzia są niczym innym jak klockami, które można łączyć ze sobą w dowolny sposób, co w praktyce daje programiście nieograniczone możliwości.
Wspomnienia z nauki programowania
Kiedy uczyłem się programowania to pamiętam, że co jakiś czas natrafiałem na różnego rodzaju 'bariery', których nie mogłem przez dłuższy czas 'przeskoczyć'. Jedną z tych barier było zrozumienie jak działają zagnieżdżone pętle, co istotnie ograniczało moje 'przygody' z programowaniem. Problem był na pozór banalny, jednak sięgał on znacznie głębiej, ponieważ nie rozumiałem wówczas zbyt dobrze czym jest zmienna, jak jej należy używać, natomiast pętla 'for' była regułką, którą należy napisać w określony sposób i jakoś to wtedy zadziała. Po kilku godzinach patrzenia w kod składający się z kilku linijek, wracałem do książki, później z książki do kodu i tak w kółko nie posuwając się przy tym ani o krok do przodu. Później programowanie sobie odpuściłem na jakiś czas. Po powrocie do programowania z nowymi kursami znów utknąłem w tym samym miejscu, jednak miałem więcej przykładów do analizowania. Po kolejnych dniach analizowania prostych problemów - w końcu się udało zrozumieć zagnieżdżone pętle i świat programowania w jednej chwili stał się prosty.
Popełnione błędy
Powyższa historia nie miałaby sensu gdyby nie zawierała wniosków w postaci popełnionych błędów podczas nauki programowania. W tym miejscu wymienię kilka z nich, które uważam, że przyczyniły się do wielu moich porażek podczas nauki programowania. Oto one:
Wnioski
Patrząc z perspektywy czasu oraz wiedząc czym jest to owe 'i', występujące w licznych przykładach, zrozumienie wszelkiego kodu zawierającego pętle nie stanowi problemu ani dla mnie ani dla żadnego innego programisty, który samodzielnie potrafi pisać kod. Problem w tym, że żaden kurs z którym się spotkałem nie zaakcentował dobitnie czym jest to owe 'i', a każdy programista, który rozumie problem, odeśle taką osobę do podstaw programowania tj. do zapoznania się z pętlą for. Początkujący programista z kolei namiętnie będzie twierdził, że 'wie co to jest pętla for oraz jak działa' i na tym się skończy współpraca początkującego programisty z doświadczonym programistą. Celem tego rozdziału jest więc uświadomienie programiście czym są te magiczne literki 'i' oraz 'j', pokazując tym samym jaką pełnią one rolę w programowaniu.
Krótkie przypomnienie pętli for
Oto prosty przykład pętli:
for( int i = 1; i <= 10; i++ )
{
}
Jak już wiesz, zapis
int i = 1
to nic innego jak utworzenie zmiennej o nazwie '
i' oraz nadanie jej wartości początkowej
1. Drugi zapis to warunek określający kiedy ta konkretna pętla ma się zakończyć. W tym przypadku jest to
i <= 10
. Ostatnie pole pętli
for jest przeznaczone na instrukcję, która ma się wykonać po wykonaniu bloku instrukcji pętli
for, a będąc precyzyjnym to bezpośrednio przed każdym kolejnym sprawdzeniem warunku kończącego pętlę. Jeżeli bez niniejszego wyjaśnienia wiedziałeś co dany zapis oznacza to bez problemu powinieneś zrozumieć przykłady jakie się pojawią w niniejszym rozdziale - w przeciwnym wypadku zalecam powrót do rozdziałów poświęconych pętlom zanim będziesz kontynuował naukę.
Zagnieżdżanie pętli
Zagnieżdżanie pętli sprowadza się do umieszczania jednej pętli wewnątrz drugiej (czy też kolejnej) pętli. Na początek prosty przykład zagnieżdżonych pętli:
#include <iostream>
void wypiszJeden( int a )
{
std::cout << "x = " << a << "; ";
std::cout << std::endl;
}
void wypiszDwa( int a, int b )
{
std::cout << "y = " << b << "; ";
std::cout << "{ ";
std::cout << "x = " << a << "; ";
std::cout << "}" << std::endl;
}
void wypiszTrzy( int a, int b, int c )
{
std::cout << "z = " << c << "; ";
std::cout << "{ ";
std::cout << "x = " << a << "; ";
std::cout << "y = " << b << "; ";
std::cout << "}" << std::endl;
}
int main()
{
for( int x = 4; x <= 7; x += 3 )
{
wypiszJeden( x );
for( int y = 3; y <= 7; y += 2 )
{
wypiszDwa( x, y );
for( int z = 1; z <= 3; z += 1 )
wypiszTrzy( x, y, z );
}
}
return 0;
}
Po skompilowaniu i uruchomieniu powyższego programu, na standardowym wyjściu programu (czyli w oknie konsoli) pojawi się następująca treść:
x = 4;
y = 3; { x = 4; }
z = 1; { x = 4; y = 3; }
z = 2; { x = 4; y = 3; }
z = 3; { x = 4; y = 3; }
y = 5; { x = 4; }
z = 1; { x = 4; y = 5; }
z = 2; { x = 4; y = 5; }
z = 3; { x = 4; y = 5; }
y = 7; { x = 4; }
z = 1; { x = 4; y = 7; }
z = 2; { x = 4; y = 7; }
z = 3; { x = 4; y = 7; }
x = 7;
y = 3; { x = 7; }
z = 1; { x = 7; y = 3; }
z = 2; { x = 7; y = 3; }
z = 3; { x = 7; y = 3; }
y = 5; { x = 7; }
z = 1; { x = 7; y = 5; }
z = 2; { x = 7; y = 5; }
z = 3; { x = 7; y = 5; }
y = 7; { x = 7; }
z = 1; { x = 7; y = 7; }
z = 2; { x = 7; y = 7; }
z = 3; { x = 7; y = 7; }
Jeżeli uważnie przeanalizujesz otrzymane wyniki oraz kod, który je wygenerował to zauważysz, że wszystkie instrukcje wykonały się po kolei, czyli:
Zagnieżdżone pętle i ich zmienne
Korzystając z zagnieżdżania pętli należy mieć świadomość, że to
nie jest żadna magiczna sztuczka. Cały czas korzystasz z podstawowego narzędzia jakim jest pętla. Innymi słowy: nawet jeżeli masz klocek, który wygląda i działa zawsze tak samo, to nie oznacza to, że wielu klocków nie da rady łączyć na różne sposoby :) Oto kolejny przykład demonstrujący możliwości jakie daje nam stosowanie zagnieżdżonych pętli. Zwróć uwagę na fakt, że zmienne utworzone przy pomocy pętli można wykorzystywać wszędzie tam gdzie się nam podoba (oczywiście pod warunkiem, że zmienna istnieje w bloku instrukcji w którym chcesz dopisywać kod). Przykład:
#include <iostream>
int main()
{
for( int pierwszaZmienna = 1; pierwszaZmienna <= 10; ++pierwszaZmienna )
{
for( int x = 1; x <= pierwszaZmienna; ++x )
std::cout << "*";
std::cout << std::endl;
}
return 0;
}
Standardowe wyjście programu:
*
**
***
****
*****
******
*******
********
*********
**********
Na zakończenie przeanalizuj działanie powyższego kodu, rozpisując sobie ten przykład na kartce, co kompilator robi w kolejnych krokach tego programu, aż do jego zakończenia.
Praktyczne zastosowanie zagnieżdżania pętli
Możliwość zagnieżdżania pętli daje programistom nieograniczone możliwości. W praktyce więc jedynym problemem z jakim muszą mierzyć się programiści to ograniczona moc obliczeniowa komputerów, która de-facto wpływa tylko i wyłącznie na czas wykonywania napisanego kodu. Z Twojego punktu widzenia problem ten jest nieistotny i długo nie będzie istotny dopóki nie nauczysz się dobrze programować oraz nie będziesz miał potrzeby tworzenia bardziej złożonych projektów. Warto jednak abyś miał świadomość do czego można stosować zagnieżdżone pętle, więc wymienię teraz kilka praktycznych przykładów, które z pewnością prędzej czy później będą przedmiotem Twojego zainteresowania. Oto do czego przydają się zagnieżdżone pętle:
Przykładów zastosowania zagnieżdżonych pętli można mnożyć i nie sposób ich wszystkich wymienić. Myślę jednak, że podane powyżej przykłady będą dla Ciebie dobrym czynnikiem motywującym do tego, aby opanować dobrze zagadnienie zagnieżdżania pętli.
Zadanie domowe