Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Piotr Szawdyński
Kurs C++

Zagnieżdżanie pętli

[lekcja] Rozdział 30. Rozdział omawia zagadnienie zagnieżdżania pętli.

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:
  • rozumiałem czym są zmienne i jak z nich korzystać (choć tu większą rolę odgrywała intuicja, a nie wiedza);
  • rozumiałem co zrobi pętla for, ale nie rozumiałem dlaczego;
  • we wszystkich kursach w pętlach for występowało magiczne 'i', które się bezmyślnie przepisywało bez świadomości czym było to owe 'i';
  • jak już się trafiała druga pętla to zazwyczaj była to pętla z literką 'j', ale tego już się nie zauważało, więc się przyjmowało, że to również powinno być 'i' (w końcu w książkach literówki są na porządku dziennym).

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:
C/C++
for( int i = 1; i <= 10; i++ )
{
    //Powtarzany blok instrukcji
}
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:
C/C++
#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 );
           
        } //for
    } //for
    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:
  • uruchomiła się pierwsza pętla;
  • została wywołana funkcja
    wypiszJeden
     wypisująca określony komunikat;
  • uruchomiła się druga pętla;
  • została wywołana funkcja
    wypiszDwa
     wypisująca określony komunikat;
  • uruchomiła się trzecia pętla;
  • wykonały się wszystkie instrukcje w pętli trzeciej;
  • nastąpił powrót do pętli drugiej, która jest kontynuowana od miejsca ostatniego zakończenia (w końcu warunek kończący drugiej pętli jest nadal prawdziwy);
  • (...) i tak dalej można ten przykład rozpisywać aż dojdzie się do samego końca, tj. sytuacji w której pierwsza pętla zostanie zakończona (bo wynik warunku kończącego pierwszą pętlę zwróci fałsz), po czym nastąpi zakończenie programu.

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:
C/C++
#include <iostream>

int main()
{
    for( int pierwszaZmienna = 1; pierwszaZmienna <= 10; ++pierwszaZmienna )
    {
        for( int x = 1; x <= pierwszaZmienna; ++x )
             std::cout << "*";
       
        std::cout << std::endl;
    } //for
    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:
  • możliwość wykonywania operacji wymagających przeszukiwania wszystkich kombinacji określonych danych i w określony sposób;
  • można obliczać wszelkiego rodzaju permutacje;
  • można napisać w prosty sposób sortowanie danych w porządku rosnącym bądź malejącym;
  • można łatwo rysować mapy kafelkowe (stosowane w grach 2D);
  • można łatwo rysować mapy wysokościowe (ang. heightmaps) (stosowane w grach 3D);
  • umożliwia przetwarzanie obrazów (np. połączenie dwóch obrazków w jeden).
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

  • Wyobraź sobie, że masz pięć pudełek. W każdym pudełku możesz umieścić jedną liczbę całkowitą z przedziału od 1 do 3 włącznie. Napisz program, który wypisze na ekranie wszystkie możliwe kombinacje w jaki sposób można zapełnić pudełka. Fragment danych wypisywanych na ekranie:
    1 1 1 1 1
    1 1 1 1 2
    1 1 1 1 3
    1 1 1 2 1
    1 1 1 2 2
    1 1 1 2 3
    1 1 1 3 1
    ...
    3 3 3 3 1
    3 3 3 3 2
    3 3 3 3 3
  • Wyobraź sobie, że masz pięć piłek i chcesz wylosować trzy z nich. Napisz program, który wypisze na ekranie wszystkie możliwe kombinacje piłek jakie można wylosować. Dane jakie powinny zostać wypisywane na ekranie:
    1 2 3
    1 2 4
    1 2 5
    1 3 4
    1 3 5
    1 4 5
    2 3 4
    2 3 5
    2 4 5
    3 4 5
  • Poeksperymentuj z dwoma powyższymi zadaniami zwiększając np. liczbę pudełek. Zobacz czy wyniki są zgodne z Twoimi oczekiwaniami.
  • Napisz program, który zliczy liczbę możliwych kombinacji wylosowania 6 liczb ze zbioru 49 liczb (reasumując: rozpatrujesz problem popularnej gry liczbowej Lotto). Zadanie należy rozwiązać przy pomocy zagnieżdżonych pętli (nie można używać wzoru).

    Weryfikacja poprawności zadania
    Jeżeli uzyskana wartość mieści się w przedziale od 12 do 15 milionów to znaczy, że poprawnie rozwiązałeś zadanie.
Poprzedni dokument Następny dokument
Słowa kluczowe continue, break Pętla while