Instrukcje iteracyjne, zwane pętlami (ang. iterative statement, loop) mają charakter cykliczny: wykonanie instrukcji jest powtarzane dopóki spełniony jest pewien warunek logiczny. Programista powinien zapewnić, że wykonanie pętli skończy się, a więc że warunek logiczny, od którego zależy dalsze powtarzanie instrukcji, będzie kiedyś niespełniony.
Inną możliwością przerwania pętli jest zastosowanie instrukcji powrotu (return), zaniechania (break) lub skoku (goto), co omówimy dalej.
Instrukcje iteracyjne występują w trzech odmianach, przy czym każda forma może być przekształcona do każdej z pozostałych; wybór jednej z tych form zależy tylko od wygody i upodobań programisty.
Przebieg każdej pętli może być modyfikowany lub przerwany instrukcjami break i continue.
while ( b ) instrgdzie wyrażenie b ma wartość logiczną a instr jest instrukcją. Składnia wymaga jednej instrukcji instr, więc jeśli ma ich być więcej, to należy wprowadzić instrukcję grupującą (patrz rozdział o instrukcjach złożonych ), tak jak to było w przypadku instrukcji warunkowych. Wykonanie instrukcji polega na cyklicznym powtarzaniu następujących czynności:
int liczba = 1; while ( liczba <= lim ) liczba *= 2;Natomiast fragment poniższy spowoduje, że program będzie wczytywał dane od użytkownika aż do chwili, gdy poda on liczbę w zakresie [5, 100]:
int wiek = 0; while ( wiek < 5 || wiek > 100) cin >> wiek;
do instr while ( b )gdzie wyrażenie b ma wartość logiczną, a instr jest pewną instrukcją. Składnia wymaga jednej instrukcji instr, więc jeśli ma ich być więcej, to należy wprowadzić instrukcję grupującą (tak jak to było w przypadku instrukcji warunkowych i pętli while). Wykonanie instrukcji polega na cyklicznym powtarzaniu następujących czynności:
Poniższy program symuluje serię rzutów dwiema kostkami do gry
i kończy się, gdy wyrzucone zostaną dwie szóstki. Użyto tu
standardowego generatora liczb losowych
(rand
inicjowana jednokrotnym wywołaniem
funkcji
srand z nagłówka
<cstdlib>)
do losowania wyrzucanej liczby oczek na każdej z obu kostek.
Ponieważ liczba prób (rzutów) nie jest z góry znana, a zawsze trzeba i tak
wykonać co najmniej jeden rzut, forma
do...while
stanowi tu
najbardziej naturalny wybór dla realizacji pętli:
1. #include <iostream> 2. #include <cstdlib> 3. using namespace std; 4. 5. int main() { 6. int x, y, roll = 0; 7. 8. srand(unsigned(time(0))); 9. 10. do { 11. x = (int)(rand()/(RAND_MAX + 1.)*6)+1; 12. y = (int)(rand()/(RAND_MAX + 1.)*6)+1; 13. cout << "Rzut nr " << ++roll << ": (" 14. << x << ", " << y << ")" << endl; 15. } while (x + y != 12); 16. }
Przykładowy wynik tego programu:
Rzut nr 1: (2, 1) Rzut nr 2: (5, 4) Rzut nr 3: (1, 5) Rzut nr 4: (2, 5) Rzut nr 5: (4, 5) Rzut nr 6: (4, 5) Rzut nr 7: (4, 4) Rzut nr 8: (5, 6) Rzut nr 9: (1, 6) Rzut nr 10: (4, 3) Rzut nr 11: (1, 2) Rzut nr 12: (2, 2) Rzut nr 13: (5, 2) Rzut nr 14: (3, 2) Rzut nr 15: (2, 4) Rzut nr 16: (1, 3) Rzut nr 17: (5, 6) Rzut nr 18: (6, 6)Oczywiście, ponieważ używamy tu generatora liczb losowych, wyniki będą się różnić przy każdym uruchomieniu programu.
for ( init ; b ; incr ) instrgdzie instr jest instrukcją (być może złożoną, być może pustą). Wyrażenie b ma wartość logiczną; jeśli jest pominięte, to przyjmuje się wartość true. Wyrażenie init jest albo jedną instrukcją deklaracyjną, albo jedną instrukcją wyrażeniową, albo listą oddzielonych przecinkami instrukcji wyrażeniowych, albo jest pominięte. Część „inkrementująca”, oznaczona tu przez incr, jest jedną instrukcją wyrażeniową albo listą oddzielonych przecinkami instrukcji wyrażeniowych, albo jest pominięta.
Nawet jeśli poszczególne elementy (init, b lub incr) są pominięte, nie wolno pominąć średników ani nawiasów okrągłych.
Przebieg wykonania instrukcji for jest następujący:
for (double x = 0, int k = size-1; x < k; x++, k--) { // ... ZLE !!! }Można natomiast zadeklarować więcej niż jedną zmienną tego samego typu; na przykład konstrukcja
for (int i = 0, k = size-1; i < k; i++, k--) { // OK }jest prawidłowa.
Pętla for jest najczęściej stosowaną formą instrukcji iteracyjnej. Jest szczególnie wygodna, jeśli z góry wiadomo, ile będzie powtórzeń instrukcji stanowiącej ciało pętli (instrukcja instr). Zwykle w części init nadajemy odpowiednie wartości zmiennym używanym w ciele pętli, a w części incr dokonujemy ich zmiany po każdym przebiegu pętli.
Jako przykład, z wykorzystaniem instrukcji deklaracyjnej
w części inicjującej
init
i sekwencji instrukcji
wyrażeniowych w części inkrementującej
incr, rozważmy poniższy program, w którym funkcja
reverse
odwraca kolejność elementów w tablicy liczb
całkowitych:
1. #include <iostream>
2. using namespace std;
3.
4. void reverse(int *tab, int size) {
5. if ( size < 2 ) return;
6.
7. for (int i = 0, k = size-1, aux; i < k; i++, k--) { ➊
8. aux = tab[i];
9. tab[i] = tab[k];
10. tab[k] = aux;
11. }
12. }
13.
14. void printTab(int *tab, int size) {
15. cout << "[ ";
16. for (int i = 0; i < size; i++)
17. cout << tab[i] << " ";
18. cout << "]" << endl;
19. }
20.
21. int main() {
22. int tab[] = { 1, 3, 5, 7, 2, 4, -9, 12 };
23. int size = sizeof(tab)/sizeof(tab[0]);
24.
25. printTab(tab,size);
26. reverse (tab,size);
27. printTab(tab,size);
28. }
Pętla w linii ➊ przebiega jednocześnie po elementach tablicy od początku (zmienną indeksującą jest tu i) i od końca (elementy indeksowane zmienną k); pętla kończy się, gdy te dwa indeksy spotkają się. W ten sposób zamieniane są miejscami element pierwszy z ostatnim, drugi z przedostatnim itd. W części inicjującej pętli zadeklarowaliśmy, prócz zmiennych indeksujących, również zmienną pomocniczą aux wykorzystywaną przy zamianie wartości każdej pary elementów tablicy. Wynik tego programu:
[ 1 3 5 7 2 4 -9 12 ] [ 12 -9 4 2 7 5 3 1 ]
Nowy standard (C++11) wprowadza jeszcze jedną formę
pętli, tzw. pętlę „foreach”.
Może ona być stosowana do przebiegania (iterowania po) kolekcji
z biblioteki standardowej, ale działa również dla tablic statycznych —
pod warunkiem, że w danym zakresie tablica jest widoczna jako posiadająca
typ tablicowy (a nie wskaźnikowy, jak to ma miejsce po przesłaniu
tablicy do funkcji). Składnię przedstawimy na przykładzie:
1. #include <iostream> 2. using namespace std; 3. 4. int main() { 5. int arr[] = {1,2,3,4,9,8,7,6}; 6. 7. for (int e : arr) cout << e << " "; ➊ 8. cout << endl; 9. for (int& e : arr) e -= 1; ➋ 10. for (auto e : arr) cout << e << " "; ➌ 11. cout << endl; 12. }
W linii ➊ zmienna e będzie w każdym przebiegu pętli zainicjowana niemodyfikowalną kopią kolejnych elementów tablicy arr. W linii ➋ natomiast, w każdym przebiegu pętli e będzie referencją do kolejnego elementu tablicy, a zatem elementy te można modyfikować (tu odejmujemy od wszystkich elementów jedynkę). Jak widzimy w linii ➌, nie musimy specyfikować jawnie typu elementów — kompilator może ten typ wydedukować z typu tablicy (bardziej ogólnie z typu kolekcji). To samo dotyczy referencji: w linii ➋ mogliśmy napisać auto& zamiast int&. Wydruk programu to
1 2 3 4 9 8 7 6 0 1 2 3 8 7 6 5
T.R. Werner, 21 lutego 2016; 20:17