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ć.