Przejdź do głównej zawartości
Uwaga!

Ten artykuł nie jest skończony. Możesz pomóc w jego ukończeniu edytując tą stronę.

Pętle

W tej lekcji każemy programowi wykonywać wielokrotnie dany kod, czyli skorzystamy z pętli.

Motywacja​

Pętle mają wiele zastosowań, oto kilka z nich:

  • 👾 dodanie np. 10 nowych przeciwników do planszy w grze
  • 🖥 wyświetlenie każdego elementu z tablicy
  • ➗ wielokrotne wykonanie obliczeń (np. liczenie silni, ciąg fibonacciego)

W lekcji o wektorach pokazaliśmy już jedną pętlę, która wyświetlała wszystkie elementy tablicy:

Wyświetl każdą liczbę z tablicy
for (int n : numbers)
{
std::cout << n << ' ';
}

Jest to najprostsza wersja pętli w C++. W następnych sekcjach poznasz więcej ich rodzajów.

Rodzaje pętli​

W C++ mamy następujące pętle:

  • for
    • wersja dla zasięgów (range-based for)
    • wersja podstawowa
  • while
  • do ... while

Najczęściej używana jest for oraz while i o nich powiemy w tej lekcji. Jeśli chcesz poczytać o pętli do ... while to zapoznaj się z artykułem: Pętla do ... while.

Iteracja (definicja)

Iteracja - pojedynczy obieg pętli.

Wytłumaczenie​

Pętla for (range-based)​

Ten rodzaj pętli jest najczęściej stosowany do pracy z tablicami, choć może być użyty też w inny sposób.

Schemat pętli range-based 'for'
Schemat

W przykładzie pokazanym w sekcji Motywacja znajduje się właśnie pętla range-based for, czyli wersja pętli for dla tzw. zasięgów. Tablica w rozumieniu C++ również jest takim zasięgiem, więc możemy śmiało z niej skorzystać.

Najprostszy przykład:

Wyświetl każdą liczbę z tablicy
std::vector<int> numbers = { 13, 42, -1, 0, -3, -5 };

for (int n : numbers)
{
std::cout << n << ' ';
}

Ta pętla kolejno przechodzi przez każdy element tablicy numbers i zapisuje go do zmiennej n. Następnie wykonywany jest blok kodu zawarty w nawiasie klamrowym. W tym wypadku jest to wyświetlenie liczby.

Dwukropek

Zwróć uwagę, że po nazwie zmiennej n znajduje się dwukropek (:), nie średnik (;)! Nie używamy w tym zapisie żadnego znaku równości (=), bo wartość każdego elementu po kolei będzie automatycznie przypisywana do n.

Pętla while​

Schemat pętli 'while'
Schemat

Celowo przechodzimy teraz do pętli while, zamiast do zwykłego for, ponieważ ułatwi to wyjaśnienia. Pętla while wykonuje ciało pętli dopóki warunek jest spełniony:

Wyświetl liczby od 0 do 3
int number = 0;
while (number <= 3)
{
std::cout << number << ' ';
number++;
}
Wynik
0 1 2 3

Warunek zostanie sprawdzony przed każdym obiegiem pętli i tak długo jak jest on spełniony, czyli w tym wypadku tak długo jak number jest mniejsza lub równa 3, to będzie wykonywane ciało:

  • wyświetlenie number
  • zwiększenie number o 1

Po ostatnim obiegu pętli, wartość number będzie równa 4, więc warunek nie będzie spełniony, przez co pętla się zakończy i komputer przejdzie do wykonywania następnych instrukcji.

Pętla for​

Schemat pętli 'for'
Schemat

Ta pętla jest uproszczeniem pewnego bardzo często powtarzającego się schematu i jest ona zazwyczaj używana do krokowego przejścia przez pewien zakres (np. liczbowy).

Zacznijmy od przykładu:

Wyświetl liczby od 0 do 9
for (int i = 0; i < 10; i++)
{
std::cout << i << ' ';
}

Powyższa pętla wyświetla liczby od 0 do 9. Nawias okrągły przy for składa się z trzech części, oddzielonych średnikami:

FragmentOpis
int i = 0instrukcja początkowa (zazwyczaj utworzenie zmiennej)
i < 10warunek
i++wyrażenie iteracji

Gdy program zaczyna wykonywać pętlę for, jednorazowo wykonuje instrukcję początkową - w naszym wypadku tworzy zmienną i nadaje jej wartość 0. Program następnie:

  1. sprawdzi warunek
    • niespełniony: wyjdź z pętli
    • spełniony: idź do punktu 2
  2. wykona ciało pętli
  3. wykona wyrażenie iteracji i przejdzie do pkt. 1

Powyższa pętla for jest równoznaczna z:

int i = 0;
while (i < 10)
{
std::cout << i << ' ';
i++;
}

Iteracja po tablicach​

Pętli for bardzo często używamy do iterowania po tablicach, w sytuacji gdy albo potrzebujemy mieć dostęp do numeru iteracji lub gdy nie chcemy iterować po całym zakresie.

Iteracja po całej tablicy
std::vector<int> numbers = {10, 13, 15, 18, 60};
for (int i = 0; i < numbers.size(); i++)
{
std::cout << "numbers[" << i << "]: " << numbers[i] << '\n';
}
Iteracja po połowie tablicy
std::vector<int> numbers = {10, 13, 15, 18, 60};
for (int i = 0; i < numbers.size() / 2; i++)
{
std::cout << "numbers[" << i << "]: " << numbers[i] << '\n';
}

Pusty nawias​

Kod podawany w nawiasie pętli for jest opcjonalny. Średniki są wymagane.

Brak instrukcji w nawiasie
for ( ; ; )
{
// kod
}

Powyższy zapis sprawi, że pętla for będzie wykonywała się w nieskończoność (ze względu na pusty warunek), chyba że przerwiemy ją manualnie...

Przerwanie pętli​

Pętlę możemy przerwać w dowolnym momencie za pomocą instrukcji break:

for (int i = 0; i < 10; i++)
{
if (i == 5)
break;
std::cout << i << ' ';
}

Ta pętla wyświetli liczby od 0 do 4, ponieważ przy i równym 5 wykonanie pętli zostanie przerwane. W ten sam sposób możemy przerwać pętlę while.

Przerwanie obiegu pętli​

Aby pominąć wykonywanie aktualnego obiegu pętli używamy instrukcji continue:

for (int i = 0; i < 10; i++)
{
if (i == 5)
continue;
std::cout << i << ' ';
}

Pętla wyświetli liczby od 0 do 9 z pominięciem liczby 5, bo zanim wykona instrukcję wyświetlania (std::cout) to program przeskoczy do następnego obiegu.

Pętla for i continue

Zauważ, że użycie continue w pętli for nie pomija wyrażenia iteracji (zobacz schemat powyżej).

Przykłady​

Uwaga!

Ta sekcja wymaga rozbudowy. Możesz nam pomóc edytując tą stronę.

Potencjalne błędy​

Próba użycia zmiennej zadeklarowanej w for poza nią​

Instrukcja początkowa w pętli for najczęściej służy do zadeklarowania zmiennej iteracyjnej. Czasem się zdarza, że chcemy użyć tej zmiennej poza ciałem instrukcji for. Nie jest to możliwe, ponieważ zmienna ta jest dostępna tylko i wyłącznie wewnątrz ciała tej zmiennej.

#include <iostream>

int main()
{
for(int i = 0; i < 5; i++)
std::cout << i; // 🟢 Ok, zmienna użyta wewnątrz pętli
}

Niepoprawny warunek w for​

Jednym z najczęstszych błędow logicznych który zdarza się podczas używania pętli for to zapisanie złego warunku pętli.

Warunek pętli to wyrażenie, które decyduje o dalszym przebiegu pętli (lub w ogóle zaczęciu przebiegu pętli), więc jeśli zapiszemy niepoprawny warunek, nasza pętla może lecieć w nieskończoność, nigdy się nie zacząć, lub wykonać się niepoprawną liczbę razy.

Należy więc dokładnie analizować warunki w bardziej skomplikowanych pętlach.

Iteracja od tyłu​

Jeśli używamy auto lub std::size_t jako typ zmiennej iteracyjnej i chcemy iterować od N do 0, możemy się spotkać z przykrą niespodzianką.

Rozważmy przykładowy kod wypisujący liczby z tablicy od tyłu:

Niepoprawna pętla for
#include <iostream>
#include <vector>

int main()
{
std::vector<int> numbers = { 5, 4, 3, 2, 1 };

std::cout << "Liczby wypisane od tyłu: ";
for(auto i = numbers.size() - 1; i >= 0; i--)
{
std::cout << numbers[i] << ' ';
}
}

Kiedy uruchomimy ten program, wpadnie on w nieskończoną pętlę.

Jak na razie nie będziemy omawiać szczegółów tego problemu, jednak zapamiętajmy, że zamiast auto można w tym przypadku użyć int i to rozwiąże problem:

// ...
for(int i = numbers.size() - 1; i >= 0; i--)
// ...

Bardzo możliwe, że kompilator pokaże nam w tym przypadku ostrzeżenie, jednak nie należy się tym przejmować. Później w kursie omówimy szczegóły tego problemu i inne jego rozwiązania.

Dodatkowe informacje​

Nieskończona pętla​

Nieskończoną pętlę możemy utworzyć na dwa sposoby:

Nieskończona pętla for
for(;;) 
{
// ... kod
}
Nieskończona pętla while
while(true) 
{
// ... kod
}

Kod wykonywany w środku będzie się wykonywać w nieskończoność, dopóki nie będzie przerwany wewnątrz (np. za pomocą instrukcji break, return (którą poznamy w lekcji o funkcjach), czy wywołaniem funkcji, która nie powraca, np. std::exit)

Tego typu pętle są często wykorzystywane w programach w miejsach, gdzie ma działać kod obsługujący pewnego rodzaju zdarzenia. Np. nieskończona pętla obsługująca okienko graficzne, która obsługuje zdarzenia z systemu operacyjnego, pętla główna gry, etc.

Pętla w pętli​

Kod pętli jest takim samym kodem, jaki możemy napisać praktycznie gdziekolwiek indziej, to znaczy, że możemy również zapisać pętlę w pętli. Taki rodzaj pętli nazywamy pętlą zagnieżdżoną (tak samo instrukcję if w instrukcji if nazwiemy zagnieżdżoną instrukcją if).

Możemy to wykorzystać, żeby np. usunąć z listy zawodników drużyny e-sportowej każdego gracza, który przegrał:

Pętla zagnieżdżona
#include <iostream>
#include <string>
#include <vector>

int main()
{
std::vector<std::string> teamMembers = { "Marek", "Karolina", "Arek", "Filip", "Maja" };
std::vector<std::string> membersThatLost = { "Maja", "Marek", "Arek" };

for(int i = 0; i < teamMembers.size(); i++)
{
for(int j = 0; j < membersThatLost.size(); j++)
{
if(teamMembers[i] == membersThatLost[j])
teamMembers.erase(teamMembers.begin() + i);
}
}

std::cout << "Gracze, którzy jeszcze żyją: ";
for(auto member : teamMembers)
{
std::cout << member << ' ';
}
}
Wynik (konsola)
Gracze, którzy jeszcze żyją: Karolina Filip

Konwencja i, j​

Konwencją jest nazywanie zmiennej iteracyjnej w pętli i (skrót od iterator), oraz j w pętli zagnieżdżonej. Jesli czujemy potrzebę nazwania naszej zmiennej inaczej, bardziej opisowo, to powinniśmy to robić, jednak jeśli nasza pętla będzie zawierała stosunkowo mało kodu i będzie wiadomo czym jest i, to bez wahania można tę nazwę wykorzystać.

Uwaga!

Ten artykuł nie jest skończony. Możesz pomóc w jego ukończeniu edytując tą stronę.

Pętle

W tej lekcji każemy programowi wykonywać wielokrotnie dany kod, czyli skorzystamy z pętli.

Motywacja​

Pętle mają wiele zastosowań, oto kilka z nich:

  • 👾 dodanie np. 10 nowych przeciwników do planszy w grze
  • 🖥 wyświetlenie każdego elementu z tablicy
  • ➗ wielokrotne wykonanie obliczeń (np. liczenie silni, ciąg fibonacciego)

W lekcji o wektorach pokazaliśmy już jedną pętlę, która wyświetlała wszystkie elementy tablicy:

Wyświetl każdą liczbę z tablicy
for (int n : numbers)
{
std::cout << n << ' ';
}

Jest to najprostsza wersja pętli w C++. W następnych sekcjach poznasz więcej ich rodzajów.

Rodzaje pętli​

W C++ mamy następujące pętle:

  • for
    • wersja dla zasięgów (range-based for)
    • wersja podstawowa
  • while
  • do ... while

Najczęściej używana jest for oraz while i o nich powiemy w tej lekcji. Jeśli chcesz poczytać o pętli do ... while to zapoznaj się z artykułem: Pętla do ... while.

Iteracja (definicja)

Iteracja - pojedynczy obieg pętli.

Wytłumaczenie​

Pętla for (range-based)​

Ten rodzaj pętli jest najczęściej stosowany do pracy z tablicami, choć może być użyty też w inny sposób.

Schemat pętli range-based 'for'
Schemat

W przykładzie pokazanym w sekcji Motywacja znajduje się właśnie pętla range-based for, czyli wersja pętli for dla tzw. zasięgów. Tablica w rozumieniu C++ również jest takim zasięgiem, więc możemy śmiało z niej skorzystać.

Najprostszy przykład:

Wyświetl każdą liczbę z tablicy
std::vector<int> numbers = { 13, 42, -1, 0, -3, -5 };

for (int n : numbers)
{
std::cout << n << ' ';
}

Ta pętla kolejno przechodzi przez każdy element tablicy numbers i zapisuje go do zmiennej n. Następnie wykonywany jest blok kodu zawarty w nawiasie klamrowym. W tym wypadku jest to wyświetlenie liczby.

Dwukropek

Zwróć uwagę, że po nazwie zmiennej n znajduje się dwukropek (:), nie średnik (;)! Nie używamy w tym zapisie żadnego znaku równości (=), bo wartość każdego elementu po kolei będzie automatycznie przypisywana do n.

Pętla while​

Schemat pętli 'while'
Schemat

Celowo przechodzimy teraz do pętli while, zamiast do zwykłego for, ponieważ ułatwi to wyjaśnienia. Pętla while wykonuje ciało pętli dopóki warunek jest spełniony:

Wyświetl liczby od 0 do 3
int number = 0;
while (number <= 3)
{
std::cout << number << ' ';
number++;
}
Wynik
0 1 2 3

Warunek zostanie sprawdzony przed każdym obiegiem pętli i tak długo jak jest on spełniony, czyli w tym wypadku tak długo jak number jest mniejsza lub równa 3, to będzie wykonywane ciało:

  • wyświetlenie number
  • zwiększenie number o 1

Po ostatnim obiegu pętli, wartość number będzie równa 4, więc warunek nie będzie spełniony, przez co pętla się zakończy i komputer przejdzie do wykonywania następnych instrukcji.

Pętla for​

Schemat pętli 'for'
Schemat

Ta pętla jest uproszczeniem pewnego bardzo często powtarzającego się schematu i jest ona zazwyczaj używana do krokowego przejścia przez pewien zakres (np. liczbowy).

Zacznijmy od przykładu:

Wyświetl liczby od 0 do 9
for (int i = 0; i < 10; i++)
{
std::cout << i << ' ';
}

Powyższa pętla wyświetla liczby od 0 do 9. Nawias okrągły przy for składa się z trzech części, oddzielonych średnikami:

FragmentOpis
int i = 0instrukcja początkowa (zazwyczaj utworzenie zmiennej)
i < 10warunek
i++wyrażenie iteracji

Gdy program zaczyna wykonywać pętlę for, jednorazowo wykonuje instrukcję początkową - w naszym wypadku tworzy zmienną i nadaje jej wartość 0. Program następnie:

  1. sprawdzi warunek
    • niespełniony: wyjdź z pętli
    • spełniony: idź do punktu 2
  2. wykona ciało pętli
  3. wykona wyrażenie iteracji i przejdzie do pkt. 1

Powyższa pętla for jest równoznaczna z:

int i = 0;
while (i < 10)
{
std::cout << i << ' ';
i++;
}

Iteracja po tablicach​

Pętli for bardzo często używamy do iterowania po tablicach, w sytuacji gdy albo potrzebujemy mieć dostęp do numeru iteracji lub gdy nie chcemy iterować po całym zakresie.

Iteracja po całej tablicy
std::vector<int> numbers = {10, 13, 15, 18, 60};
for (int i = 0; i < numbers.size(); i++)
{
std::cout << "numbers[" << i << "]: " << numbers[i] << '\n';
}
Iteracja po połowie tablicy
std::vector<int> numbers = {10, 13, 15, 18, 60};
for (int i = 0; i < numbers.size() / 2; i++)
{
std::cout << "numbers[" << i << "]: " << numbers[i] << '\n';
}

Pusty nawias​

Kod podawany w nawiasie pętli for jest opcjonalny. Średniki są wymagane.

Brak instrukcji w nawiasie
for ( ; ; )
{
// kod
}

Powyższy zapis sprawi, że pętla for będzie wykonywała się w nieskończoność (ze względu na pusty warunek), chyba że przerwiemy ją manualnie...

Przerwanie pętli​

Pętlę możemy przerwać w dowolnym momencie za pomocą instrukcji break:

for (int i = 0; i < 10; i++)
{
if (i == 5)
break;
std::cout << i << ' ';
}

Ta pętla wyświetli liczby od 0 do 4, ponieważ przy i równym 5 wykonanie pętli zostanie przerwane. W ten sam sposób możemy przerwać pętlę while.

Przerwanie obiegu pętli​

Aby pominąć wykonywanie aktualnego obiegu pętli używamy instrukcji continue:

for (int i = 0; i < 10; i++)
{
if (i == 5)
continue;
std::cout << i << ' ';
}

Pętla wyświetli liczby od 0 do 9 z pominięciem liczby 5, bo zanim wykona instrukcję wyświetlania (std::cout) to program przeskoczy do następnego obiegu.

Pętla for i continue

Zauważ, że użycie continue w pętli for nie pomija wyrażenia iteracji (zobacz schemat powyżej).

Przykłady​

Uwaga!

Ta sekcja wymaga rozbudowy. Możesz nam pomóc edytując tą stronę.

Potencjalne błędy​

Próba użycia zmiennej zadeklarowanej w for poza nią​

Instrukcja początkowa w pętli for najczęściej służy do zadeklarowania zmiennej iteracyjnej. Czasem się zdarza, że chcemy użyć tej zmiennej poza ciałem instrukcji for. Nie jest to możliwe, ponieważ zmienna ta jest dostępna tylko i wyłącznie wewnątrz ciała tej zmiennej.

#include <iostream>

int main()
{
for(int i = 0; i < 5; i++)
std::cout << i; // 🟢 Ok, zmienna użyta wewnątrz pętli
}

Niepoprawny warunek w for​

Jednym z najczęstszych błędow logicznych który zdarza się podczas używania pętli for to zapisanie złego warunku pętli.

Warunek pętli to wyrażenie, które decyduje o dalszym przebiegu pętli (lub w ogóle zaczęciu przebiegu pętli), więc jeśli zapiszemy niepoprawny warunek, nasza pętla może lecieć w nieskończoność, nigdy się nie zacząć, lub wykonać się niepoprawną liczbę razy.

Należy więc dokładnie analizować warunki w bardziej skomplikowanych pętlach.

Iteracja od tyłu​

Jeśli używamy auto lub std::size_t jako typ zmiennej iteracyjnej i chcemy iterować od N do 0, możemy się spotkać z przykrą niespodzianką.

Rozważmy przykładowy kod wypisujący liczby z tablicy od tyłu:

Niepoprawna pętla for
#include <iostream>
#include <vector>

int main()
{
std::vector<int> numbers = { 5, 4, 3, 2, 1 };

std::cout << "Liczby wypisane od tyłu: ";
for(auto i = numbers.size() - 1; i >= 0; i--)
{
std::cout << numbers[i] << ' ';
}
}

Kiedy uruchomimy ten program, wpadnie on w nieskończoną pętlę.

Jak na razie nie będziemy omawiać szczegółów tego problemu, jednak zapamiętajmy, że zamiast auto można w tym przypadku użyć int i to rozwiąże problem:

// ...
for(int i = numbers.size() - 1; i >= 0; i--)
// ...

Bardzo możliwe, że kompilator pokaże nam w tym przypadku ostrzeżenie, jednak nie należy się tym przejmować. Później w kursie omówimy szczegóły tego problemu i inne jego rozwiązania.

Dodatkowe informacje​

Nieskończona pętla​

Nieskończoną pętlę możemy utworzyć na dwa sposoby:

Nieskończona pętla for
for(;;) 
{
// ... kod
}
Nieskończona pętla while
while(true) 
{
// ... kod
}

Kod wykonywany w środku będzie się wykonywać w nieskończoność, dopóki nie będzie przerwany wewnątrz (np. za pomocą instrukcji break, return (którą poznamy w lekcji o funkcjach), czy wywołaniem funkcji, która nie powraca, np. std::exit)

Tego typu pętle są często wykorzystywane w programach w miejsach, gdzie ma działać kod obsługujący pewnego rodzaju zdarzenia. Np. nieskończona pętla obsługująca okienko graficzne, która obsługuje zdarzenia z systemu operacyjnego, pętla główna gry, etc.

Pętla w pętli​

Kod pętli jest takim samym kodem, jaki możemy napisać praktycznie gdziekolwiek indziej, to znaczy, że możemy również zapisać pętlę w pętli. Taki rodzaj pętli nazywamy pętlą zagnieżdżoną (tak samo instrukcję if w instrukcji if nazwiemy zagnieżdżoną instrukcją if).

Możemy to wykorzystać, żeby np. usunąć z listy zawodników drużyny e-sportowej każdego gracza, który przegrał:

Pętla zagnieżdżona
#include <iostream>
#include <string>
#include <vector>

int main()
{
std::vector<std::string> teamMembers = { "Marek", "Karolina", "Arek", "Filip", "Maja" };
std::vector<std::string> membersThatLost = { "Maja", "Marek", "Arek" };

for(int i = 0; i < teamMembers.size(); i++)
{
for(int j = 0; j < membersThatLost.size(); j++)
{
if(teamMembers[i] == membersThatLost[j])
teamMembers.erase(teamMembers.begin() + i);
}
}

std::cout << "Gracze, którzy jeszcze żyją: ";
for(auto member : teamMembers)
{
std::cout << member << ' ';
}
}
Wynik (konsola)
Gracze, którzy jeszcze żyją: Karolina Filip

Konwencja i, j​

Konwencją jest nazywanie zmiennej iteracyjnej w pętli i (skrót od iterator), oraz j w pętli zagnieżdżonej. Jesli czujemy potrzebę nazwania naszej zmiennej inaczej, bardziej opisowo, to powinniśmy to robić, jednak jeśli nasza pętla będzie zawierała stosunkowo mało kodu i będzie wiadomo czym jest i, to bez wahania można tę nazwę wykorzystać.