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:
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 - 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.
SchematW 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:
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.
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
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:
int number = 0;
while (number <= 3)
{
std::cout << number << ' ';
number++;
}
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
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:
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:
Fragment | Opis |
---|---|
int i = 0 | instrukcja początkowa (zazwyczaj utworzenie zmiennej) |
i < 10 | warunek |
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:
- sprawdzi warunek
- niespełniony: wyjdź z pętli
- spełniony: idź do punktu 2
- wykona ciało pętli
- 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.
std::vector<int> numbers = {10, 13, 15, 18, 60};
for (int i = 0; i < numbers.size(); i++)
{
std::cout << "numbers[" << i << "]: " << numbers[i] << '\n';
}
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.
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.
Zauważ, że użycie continue
w pętli for
nie pomija wyrażenia iteracji (zobacz schemat powyżej).
Przykłady
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.
- ✔ OK
- ✔ OK
- ❌ Źle
#include <iostream>
int main()
{
for(int i = 0; i < 5; i++)
std::cout << i; // 🟢 Ok, zmienna użyta wewnątrz pętli
}
#include <iostream>
int main()
{
int sum = 0;
for(int i = 0; i < 5; i++)
{
sum += i; // 🟢 Ok, zmienna użyta wewnątrz pętli
}
std::cout << sum << '\n';
}
#include <iostream>
int main()
{
for(int i = 0; i < 5; i++)
std::cout << i;
std::cout << i; // 🔴 Nie ok, zmienna użyta poza pętlą
}
W przypadku niepoprawnego kodu wyżej, możemy spotkać się z nastepującymi błędami:
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:
#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:
for(;;)
{
// ... kod
}
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ł:
#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 << ' ';
}
}
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ć.