Rozdział 7.
Sterowanie przebiegiem działania programu
Większość działań programu powiązanych jest z warunkowymi rozgałęzieniami i pętlami. W rozdziale 4., „Wyrażenia i instrukcje”, poznałeś sposób, w jaki należy rozgałęzić działanie programu za pomocą instrukcji if.
W tym rozdziale:
dowiesz się, czym są pętle i jak się z nich korzysta,
nauczysz się tworzyć różnorodne pętle,
poznasz alternatywę dla głęboko zagnieżdżonych instrukcji if-else.
Pętle
Wiele problemów programistycznych rozwiązywanych jest przez powtarzanie operacji wykonywanych na tych samych danych. Dwie podstawowe techniki to: rekurencja (omawiana w rozdziale 5., „Funkcje”) oraz iteracja. Iteracja oznacza ciągłe powtarzanie tych samych czynności. Podstawową metodą wykorzystywaną przy iteracji jest pętla.
Początki pętli: instrukcja goto
W początkowym okresie rozwoju informatyki, programy były nieporadne, proste i krótkie. Pętle składały się z etykiety, zestawu wykonywanych instrukcji i skoku.
W C++ etykieta jest zakończoną dwukropkiem nazwą (:). Etykieta może być umieszczona po lewej stronie instrukcji języka C++, zaś skok odbywa się w wyniku wykonania instrukcji goto (idź do) z nazwą etykiety. Ilustruje to listing 7.1.
Listing 7.1. Pętla z użyciem słowa kluczowego goto
0: // Listing 7.1
1: // Pętla z instrukcją goto
2:
3: #include <iostream>
4:
5: int main()
6: {
7: int counter = 0; // inicjalizujemy licznik
8: loop: counter ++; // początek pętli
9: std::cout << "Licznik: " << counter << "\n";
10: if (counter < 5) // sprawdzamy wartość
11: goto loop; // skok do początku
12:
13: std::cout << "Gotowe. Licznik: " << counter << ".\n";
14: return 0;
15: }
Wynik
Licznik: 1
Licznik: 2
Licznik: 3
Licznik: 4
Licznik: 5
Gotowe. Licznik: 5.
Analiza
W linii 7., zmienna counter (licznik) jest inicjalizowana wartością 0. W linii 8 występuje etykieta loop (pętla), oznaczająca początek pętli. Zmienna counter jest inkrementowana, następnie wypisywana jest jej nowa wartość. W linii 10. sprawdzana jest wartość zmiennej. Gdy jest ona mniejsza od 5, wtedy instrukcja if jest prawdziwa i wykonywana jest instrukcja goto. W efekcie wykonanie programu wraca do linii 8. Program działa w pętli do chwili, gdy, wartość zmiennej counter osiągnie 5; to powoduje że program wychodzi z pętli i wypisuje końcowy komunikat.
Dlaczego nie jest zalecane stosowanie instrukcji goto?
Programiści unikają instrukcji goto, i mają ku temu znaczące powody. Instrukcja goto umożliwia wykonanie skoku do dowolnego miejsca w kodzie źródłowym, do przodu lub do tyłu. Nierozważne użycie tej instrukcji sprawia że kod źródłowy jest zagmatwany, nieestetyczny i trudny do przeanalizowania, kod taki nazywany „kodem spaghetti”.
Instrukcja goto
Aby użyć instrukcji goto, powinieneś napisać słowo kluczowe goto, a następnie nazwę etykiety. Spowoduje to wykonanie skoku bezwarunkowego.
Przykład
if (value > 10)
goto end;
if (value < 10)
goto end;
cout << "Wartosc jest rowna 10!";
end:
cout << "gotowe";
Aby uniknąć użycia instrukcji goto, opracowano bardziej skomplikowane, ściśle[Author ID1: at Mon Oct 22 16:10:00 2001
]wysoce[Author ID1: at Mon Oct 22 16:10:00 2001
] kontrolowalne instrukcje pętli: for, while oraz do...while.
Pętle while
Pętla while (dopóki) powoduje powtarzanie zawartej w niej sekwencji instrukcji tak długo, jak długo zaczynające pętlę wyrażenie warunkowe pozostaje prawdziwe. W przykładzie z listingu 7.1, licznik był inkrementowany aż do osiągnięcia wartości 5. Listing 7.2 przedstawia ten sam program przepisany tak, aby można było skorzystać z pętli while.
Listing 7.2. Pętla while
0: // Listing 7.2
1: // Pętla while
2:
3: #include <iostream>
4:
5: int main()
6: {
7: int counter = 0; // inicjalizacja warunku
8:
9: while(counter < 5) // sprawdzenie, czy warunek jest spełniony
10: {
11: counter++; // ciało pętli
12: std::cout << "Licznik: " << counter << "\n";
13: }
14:
15: std::cout << "Gotowe. Licznik: " << counter << ".\n";
16: return 0;
17: }
Wynik
Licznik: 1
Licznik: 2
Licznik: 3
Licznik: 4
Licznik: 5
Gotowe. Licznik: 5.
Analiza
Ten prosty program demonstruje podstawy działania pętli while. Gdy warunek jest spełniony, wykonywane jest ciało pętli. W tym przypadku w linii 9. sprawdzane jest, czy zmienna counter (licznik) ma wartość mniejszą od 5. Jeśli ten warunek jest spełniony (prawdziwy), wykonywane jest ciało pętli: w linii 11. następuje inkrementacja licznika, zaś jego wartość jest wypisywana w linii 12. Gdy warunek w linii 9. nie został spełniony (tzn. gdy zmienna counter ma wartość większą lub równą 5), wtedy całe ciało pętli while (linie od 10. do 13.) jest pomijane i program przechodzi do następnej instrukcji, czyli w tym przypadku do linii 14.
Instrukcja while
Składnia instrukcji while jest następująca:
while ( warunek )
instrukcja;
warunek jest wyrażeniem języka C++, zaś instrukcja jest dowolną instrukcją lub blokiem instrukcji C++. Gdy wartością wyrażenia warunek jest true (prawda), wykonywana jest instrukcja, po czym następuje powrót do początku pętli i ponowne sprawdzenie warunku. Czynność ta powtarza się, dopóki warunek zwraca wartość true. Gdy wyrażenie warunek ma wartość false, działanie pętli while kończy się i program przechodzi do instrukcji następujących po pętli.
Przykład
// zliczanie do 10
int x = 0;
while (x < 10)
cout << "X: " << x++;
Bardziej skomplikowane instrukcje while
Warunek sprawdzany w pętli while może być złożony, tak jak każde poprawne wyrażenie języka C++. Może zawierać wyrażenia tworzone za pomocą operatorów logicznych && (I), || (LUB) oraz ! (NIE). Taką nieco bardziej skomplikowaną instrukcję while przedstawia listing 7.3.
Listing 7.3. Warunek złożony w instrukcji while
0: // Listing 7.3
1: // Złożona instrukcja while
2:
3: #include <iostream>
4: using namespace std;
5:
6: int main()
7: {
8: unsigned short small;
9: unsigned long large;
10: const unsigned short MAXSMALL=65535;
11:
12: cout << "Wpisz mniejsza liczbe: ";
13: cin >> small;
14: cout << "Wpisz duza liczbe: ";
15: cin >> large;
16:
17: cout << "mala: " << small << "...";
18:
19: // w każdej iteracji sprawdzamy trzy warunki
20: while (small < large && large > 0 && small < MAXSMALL)
21: {
22: if (small % 5000 == 0) // wypisuje kropkę co każde 5000 linii
23: cout << ".";
24:
25: small++;
26:
27: large-=2;
28: }
29:
30: cout << "\nMala: " << small << " Duza: " << large << endl;
31: return 0;
32: }
Wynik
Wpisz mniejsza liczbe: 2
Wpisz duza liczbe: 100000
mala: 2.........
Mala: 33335 Duza: 33334
Analiza
Ten program to gra. Podaj dwie liczby, mniejszą i większą. Mniejsza liczba jest zwiększana o jeden, a większa liczba jest zmniejszana o dwa. Celem gry jest odgadnięcie, kiedy się „spotkają”.
Linie od 12. do 15. służą do wprowadzania liczb. W linii 20. rozpoczyna się pętla while, której działanie będzie kontynuowane, dopóki spełnione są wszystkie trzy poniższe warunki:
Mniejsza liczba nie jest większa od większej liczby.
Większa liczba nie jest ujemna ani równa zeru.
Mniejsza liczba nie przekracza maksymalnej wartości dla małych liczb całkowitych (MAXSMALL).
W linii 23. wartość zmiennej small (mała) jest obliczana modulo 5 000. Nie powoduje to zmiany wartości tej zmiennej; chodzi jedynie o to, że wartość 0 jest wynikiem działania modulo 5 000 tylko wtedy, gdy wartość zmiennej small jest wielokrotnością pięciu tysięcy. Za każdym razem, gdy otrzymujemy wartość zero, na ekranie wypisywana jest kropka, przedstawiająca postęp działań. W linii 25[Author ID1: at Mon Oct 22 16:11:00 2001
].6[Author ID1: at Mon Oct 22 16:11:00 2001
] następuje inkrementacja zmiennej small, zaś w linii 27[Author ID1: at Mon Oct 22 16:11:00 2001
].8[Author ID1: at Mon Oct 22 16:11:00 2001
] zmniejszenie zmiennej large (duża) o dwa.
Jeżeli w pętli while nie zostanie spełniony któryś z trzech warunków, pętla kończy działanie, a wykonanie programu przechodzi do linii 29., d[Author ID1: at Mon Oct 22 16:11:00 2001
]za zamykający[Author ID1: at Mon Oct 22 16:11:00 2001
]ego[Author ID1: at Mon Oct 22 16:11:00 2001
] nawiasu[Author ID1: at Mon Oct 22 16:11:00 2001
] klamrowy[Author ID1: at Mon Oct 22 16:12:00 2001
]ego[Author ID1: at Mon Oct 22 16:12:00 2001
] pętli while.
UWAGA Operator reszty z dzielenia (modulo) oraz warunki złożone zostały opisane w rozdziale 3, „Stałe i zmienne.”
continue oraz break
Może się zdarzyć, że przed wykonaniem całego zestawu instrukcji w pętli będziesz chcieć powrócić do jej początku. Służy do tego instrukcja continue (kontynuuj).
Może zdarzyć się także, że będziesz chcieć wyjść z pętli jeszcze przed spełnieniem warunku końca. Instrukcja break (przerwij) powoduje natychmiastowe wyjście z pętli i przejście wykonywania do następnych instrukcji programu.
Listing 7.4 demonstruje użycie tych instrukcji. Tym razem gra jest nieco bardziej skomplikowana. Użytkownik jest proszony o podanie liczby mniejszej i większej, liczby pomijanej oraz liczby docelowej. Mniejsza liczba jest zwiększana o jeden, a większa liczba jest zmniejszana o dwa. Za każdym razem, gdy mniejsza liczba jest wielokrotnością liczby pomijanej, nie jest wykonywane zmniejszanie. Gra kończy się, gdy mniejsza liczba staje się większa od większej liczby. Gdy większa liczba dokładnie zrówna się z liczbą docelową. wypisywany jest komunikat i gra zatrzymuje się.
Listing 7.4. Instrukcje break i continue
0: // Listing 7.4
1: // Demonstruje instrukcje break i continue
2:
3: #include <iostream>
4:
5: int main()
6: {
7: using namespace std;
8: unsigned short small;
9: unsigned long large;
10: unsigned long skip;
11: unsigned long target;
12: const unsigned short MAXSMALL=65535;
13:
14: cout << "Wpisz mniejsza liczbe: ";
15: cin >> small;
16: cout << "Wpisz wieksza liczbe: ";
17: cin >> large;
18: cout << "Wpisz liczbe pomijana: ";
19: cin >> skip;
20: cout << "Wpisz liczbe docelowa: ";
21: cin >> target;
22:
23: cout << "\n";
24:
25: // ustalamy dla pętli trzy warunki zatrzymania
26: while (small < large && large > 0 && small < MAXSMALL)
27:
28: {
29:
30: small++;
31:
32: if (small % skip == 0) // pomijamy zmniejszanie?
33: {
34: cout << "pominieto dla " << small << endl;
35: continue;
36: }
37:
38: if (large == target) // osiągnięto wartość docelową?
39: {
40: cout << "Osiagnieto wartosc docelowa!";
41: break;
42: }
43:
44: large-=2;
45: } // koniec pętli while
46:
47: cout << "\nMniejsza: " << small << " Wieksza: " << large << endl;
48: return 0;
49: }
Wynik
Wpisz mniejsza liczbe: 2
Wpisz wieksza liczbe: 20
Wpisz liczbe pomijana: 4
Wpisz liczbe docelowa: 6
pominieto dla 4
pominieto dla 8
Mniejsza: 10 Wieksza: 8
Analiza
W tej grze użytkownik przegrał; zmienna small (mała) stała się większa, zanim zmienna large (większa) zrównała się z liczbą docelową 6.
W linii 26. są sprawdzane warunki instrukcji while. Jeśli zmienna small jest mniejsza od zmiennej large, zmienna large jest większa od zera, a zmienna small nie przekroczyła maksymalnej wartości dla małych[Author ID1: at Mon Oct 22 16:12:00 2001
] krótkich [Author ID1: at Mon Oct 22 16:12:00 2001
]liczb całkowitych ([Author ID1: at Mon Oct 22 16:12:00 2001
]short[Author ID1: at Mon Oct 22 16:12:00 2001
])[Author ID1: at Mon Oct 22 16:12:00 2001
], program wchodzi do ciała pętli.
W linii 32. jest obliczana reszta z dzielenia (modulo) wartości zmiennej small przez wartość pomijaną. Jeśli zmienna small jest wielokrotnością zmiennej skip (pomiń), wtedy wykonywana jest instrukcja continue i program wraca do początku pętli, do linii 26. W efekcie pominięte zostają: sprawdzanie wartości docelowej i zmniejszanie zmiennej large.
W linii 38. następuje porównanie zmiennej target (docelowa) ze zmienną large. Jeśli są równe, wygrywa użytkownik. Wypisywany jest wtedy komunikat i wykonywana jest instrukcja break. Powoduje ona natychmiastowe wyjście z pętli i kontynuację wykonywania programu od linii 46.
UWAGA Instrukcje continue oraz break powinny być używane ostrożnie. Wraz z goto stanowią one dwie najbardziej niebezpieczne instrukcje języka (są one niebezpieczne z tych samych powodów co instrukcja goto). Programy zmieniające nagle kierunek działania są trudniejsze do zrozumienia, a używanie instrukcji continue i break według własnego uznania może uniemożliwić analizę nawet niewielkich pętli while.
Instrukcja continue
continue;
Powoduje pominięcie pozostałych instrukcji pętli while lub for i powrót do początku pętli. Przykład użycia tej instrukcji znajduje się na listingu 7.4.
Instrukcja break
break;
Powoduje natychmiastowe wyjście z pętli while lub for. Wykonanie programu przechodzi do zamykającego nawiasu klamrowego.
Przykład
while (warunek)
{
if (warunek2)
break;
// instrukcje
}
Pętla while(true)
Sprawdzanym w pętli while warunkiem może być każde poprawne wyrażenie języka C++. Dopóki ten warunek pozostanie spełniony, działanie pętli while nie zostanie przerwane. Używając wartości true jako wyrażenia w instrukcji while, możesz stworzyć pętlę, która będzie wykonywana bez końca. Listing 7.5 przedstawia liczenie do 10 za pomocą takiej konstrukcji języka.
Listing 7.5. Pętla while
0: // Listing 7.5
1: // Demonstruje pętlę (true)
2:
3: #include <iostream>
4:
5: int main()
6: {
7: int counter = 0;
8:
9: while (true)
10: {
11: counter ++;
12: if (counter > 10)
13: break;
14: }
15: std::cout << "Licznik: " << counter << "\n";
16: return 0;
17: }
Wynik
Licznik: 11
Analiza
W linii 9. rozpoczyna się pętla while z warunkiem, który zawsze jest spełniony. W linii 11. pętla inkrementuje wartość zmiennej licznikowej, po czym w linii 12. sprawdza, czy licznik przekroczył wartość 10. Jeśli nie, działanie pętli trwa. Jeśli licznik przekroczy wartość 10, wtedy instrukcja break w linii 13. powoduje wyjście z pętli, a działanie programu przechodzi do linii 15., w której wypisywany jest komunikat końcowy.
Program działa, lecz nie jest elegancki - stanowi dobry przykład użycia złego narzędzia. Ten sam efekt można osiągnąć, umieszczając funkcję sprawdzania wartości licznika tam, gdzie powinna się ona znaleźć — w warunku instrukcji while.
OSTRZEŻENIE Niekończące się pętle, takie jak while(true), mogą doprowadzić do zawieszenia się komputera gdy warunek wyjścia nie zostanie nigdy spełniony. Używaj ich ostrożnie i dokładnie testuj ich działanie.
C++ oferuje wiele sposobów wykonania danego zadania. Prawdziwa sztuka polega na wybraniu odpowiedniego narzędzia dla odpowiedniego zadania.
TAK |
NIE |
W celu wykonywania pętli, dopóki spełniony jest warunek, używaj pętli while. Bądź ostrożny używając instrukcji continue i break. Upewnij się, czy pętla while w pewnym momencie kończy działanie. |
Nie używaj instrukcji goto. |
Pętla do...while
Istnieje możliwość, że ciało pętli while nigdy nie zostanie wykonane. Instrukcja while sprawdza swój warunek przed wykonaniem którejkolwiek z zawartych w niej instrukcji, a gdy ten warunek nie jest spełniony, całe ciało pętli jest pomijane. Ilustruje to listing 7.6.
Listing 7.6. Pominięcie ciała pętli while
0: // Listing 7.6
1: // Demonstruje pominięcie ciała pętli while
2: // w momencie, gdy warunek nie jest spełniony.
3:
4: #include <iostream>
5:
6: int main()
7: {
8:
9: int counter;
10: std::cout << "Ile pozdrowien?: ";
11: std::cin >> counter;
12: while (counter > 0)
13: {
14: std::cout << "Hello!\n";
15: counter--;
16: }
17: std::cout << "Wartosc licznika: " << counter;
18: return 0;
19: }
Wynik
Ile pozdrowien?: 2
Hello!
Hello!
Wartosc licznika: 0
Ile pozdrowien?: 0
Wartosc licznika: 0
Analiza
W linii 10. użytkownik jest proszony o wpisanie wartości początkowej. Ta wartość jest umieszczana w zmiennej całkowitej counter (licznik). Wartość licznika jest sprawdzana w linii 12. i dekrementowana w ciele pętli while. Za pierwszym razem wartość licznika została ustawiona na 2, dlatego ciało pętli while zostało wykonane dwukrotnie. Jednak za drugim razem użytkownik wpisał 0. Wartość licznika została sprawdzona w linii 12. i tym razem warunek nie został spełniony; tj. zmienna counter nie była większa od zera. Zostało więc pominięte całe ciało pętli i komunikat „Hello” nie został wypisany ani razu.
Co zrobić komunikat „Hello” został wypisany co najmniej raz? Nie może tego zapewnić pętla while, gdyż jej warunek jest sprawdzany przed wypisywaniem komunikatu. Można to osiągnąć umieszczając instrukcję if przed pętlą while:
if (counter < 1) // wymuszamy minimalną wartość
counter = 1;
ale to rozwiązanie nie jest zbyt eleganckie.
do...while
Pętla do...while (wykonuj...dopóki) wykonuje ciało pętli przed sprawdzeniem warunku i sprawia że instrukcje w pętli zostaną wykonane co najmniej raz. Listing 7.7 stanowi zmodyfikowaną wersję listingu 7.6, w której została użyta pętla do...while.
Listing 7.7. Przykład pętli do...while.
0: // Listing 7.7
1: // Demonstruje pętlę do...while
2:
3: #include <iostream>
4:
5: int main()
6: {
7: using namespace std;
8: int counter;
9: cout << "Ile pozdrowien? ";
10: cin >> counter;
11: do
12: {
13: cout << "Hello\n";
14: counter--;
15: } while (counter >0 );
16: cout << "Licznik ma wartosc: " << counter << endl;
17: return 0;
18: }
Wynik
Ile pozdrowien? 2
Hello
Hello
Licznik ma wartosc: 0
Analiza
W linii 9. użytkownik jest proszony o wpisanie początkowej wartości, która jest umieszczana w zmiennej counter. W pętli do...while, ciało pętli jest wykonywane przed sprawdzeniem warunku, dlatego w każdym przypadku zostanie wykonane co najmniej raz. W linii 13. wypisywany jest komunikat, w linii 14. dekrementowany jest licznik, zaś dopiero w linii 15. następuje sprawdzenie warunku. Jeśli warunek jest spełniony, wykonanie programu wraca do początku pętli w linii 13.; w przeciwnym razie przechodzi do linii 16.
Instrukcje break i continue w pętlach do...while działają tak jak w pętli loop. Jedyna różnica pomiędzy pętlą while a pętlą do...while pojawia się w chwili sprawdzania warunku.
Instrukcja do...while
Składnia instrukcji do...while jest następująca:
do
instrukcja
while (warunek);
Wykonywana jest instrukcja, po czym sprawdzany jest warunek. Jeśli warunek jest spełniony, pętla jest powtarzana; w przeciwnym razie jej działanie się kończy. Pod innymi względami instrukcje i warunki są identyczne, jak w pętli while.
Przykład 1
// liczymy do 10
int x = 0;
do
cout << "X: " << x++;
while (x < 10);
Przykład 2
// wypisujemy małe litery alfabetu
char ch = 'a';
do
{
cout << ch << ' ';
ch++;
} while ( ch <= 'z' );
TAK |
Używaj pętli do...while, gdy chcesz mieć pewność że pętla zostanie wykonana co najmniej raz. Używaj pętli while, gdy chcesz pominąć pętlę (gdy warunek nie jest spełniony). Sprawdzaj wszystkie pętle, aby mieć pewność, że robią to, czego oczekujesz. |
Pętle for
Gdy korzystasz z pętli while, ustawiasz warunek początkowy, sprawdzasz, czy jest spełniony, po czym w każdym wykonaniu pętli inkrementujesz lub w inny sposób zmieniasz zmienną kontrolującą jej wykonanie. Demonstruje to listing 7.8.
Listing 7.8. Następna pętla while
0: // Listing 7.8
1: // Pętla while
2:
3: #include <iostream>
4:
5: int main()
6: {
7:
8: int counter = 0;
9:
10: while(counter < 5)
11: {
12: counter++;
13: std::cout << "Petla! ";
14: }
15:
16: std::cout << "\nLicznik: " << counter << ".\n";
17: return 0;
18: }
Wynik
Petla! Petla! Petla! Petla! Petla!
Licznik: 5.
Analiza
W linii 8. ustawiany jest warunek: zmienna counter (licznik) ustawiana jest na zero. W linii 10. następuje sprawdzenie, czy licznik jest mniejszy od 5. Inkrementacja licznika odbywa się w linii 12. W linii 16. wypisywany jest prosty komunikat, ale można przypuszczać, że przy każdej inkrementacji licznika można wykonać bardziej konkretną pracę.
Pętla for (dla) łączy powyższe trzy etapy w jedną instrukcję. Są to: inicjalizacja, test i inkrementacja. Pętla for składa się ze słowa kluczowego for, po którym następuje para nawiasów. Wewnątrz nawiasów znajdują się trzy, oddzielone średnikami, instrukcje.
Pierwsza instrukcja służy do inicjalizacji. Można w niej umieścić każdą poprawną instrukcję języka C++, ale zwykle po prostu tworzy się i inicjalizuje zmienną licznikową. Drugą instrukcją jest test, którym może być każde poprawne wyrażenie języka. Pełni ono taką samą funkcję, jak warunek w pętli while. Trzecia instrukcja jest działaniem. Zwykle w jego wyniku wartość zmiennej licznikowej jest zwiększana lub zmniejszana, ale oczywiście można tu zastosować każdą poprawną instrukcję języka C++. Zwróć uwagę, że instrukcje pierwsza i trzecia mogą być dowolnymi instrukcjami, lecz druga instrukcja musi być wyrażeniem — czyli instrukcją języka C++, zwracającą wartość. Pętlę for demonstruje listing 7.9.
Listing 7.9. Przykład pętli for
0: // Listing 7.9
1: // Pętla for
2:
3: #include <iostream>
4:
5: int main()
6: {
7:
8: int counter;
9: for (counter = 0; counter < 5; counter++)
10: std::cout << "Petla! ";
11:
12: std::cout << "\nLicznik: " << counter << ".\n";
13: return 0;
14: }
Wynik
Petla! Petla! Petla! Petla! Petla!
Licznik: 5.
Analiza
Instrukcja for w linii 9. łączy w sobie inicjalizację zmiennej counter, sprawdzenie, czy jej wartość jest mniejsza od 5, oraz inkrementację tej zmiennej. Ciało pętli for znajduje się w linii 10. Oczywiście, w tym miejscu mógłby zostać użyty blok instrukcji.
Składnia pętli for
Składnia instrukcji for jest następująca:
for (inicjalizacja; test; akcja )
instrukcja;
Instrukcja inicjalizacja jest używana w celu zainicjalizowania stanu licznika lub innego przygotowania do wykonania pętli. Instrukcja test jest dowolnym wyrażeniem języka C++, [Author ID1: at Mon Oct 22 16:15:00 2001
]które i[Author ID1: at Mon Oct 22 16:15:00 2001
] jest obliczane przed każdym wykonaniem zawartości pętli. Jeśli wyrażenie test ma wartość true, wykonywane jest ciało pętli, po czym wykonywana jest instrukcja akcja z nagłówka pętli (zwykle po prostu następuje inkrementacja zmiennej licznikowej).
Przykład 1
// dziesięć razy wpisuje napis "Hello"
for (int i = 0; i < 10; i++)
cout << "Hello! ";
Przykład 2
for (int i = 0; i < 10; i++)
{
cout << "Hello!" << endl;
cout << "wartoscia i jest: " << i << endl;
}
Zaawansowane pętle for
Instrukcje for są wydajne i działają w sposób elastyczny. Trzy niezależne instrukcje (inicjalizacja, test i akcja) umożliwiają stosowanie różnorodnych rozwiązań.
Pętla for działa w następującej kolejności:
Przeprowadza inicjalizację.
Oblicza wartość warunku [Author ID1: at Mon Oct 22 16:16:00 2001
]wyrażenie[Author ID1: at Mon Oct 22 16:16:00 2001
].
Jeśli warunek [Author ID1: at Mon Oct 22 16:16:00 2001
]wyrażenie[Author ID1: at Mon Oct 22 16:16:00 2001
] ma wartość true, wykonuje ciało pętli, a następnie wykonuje instrukcję akcji.
Przy każdym wykonaniu pętli powtarzane są kroki 2 i 3.
Wielokrotna inicjalizacja i inkrementacja
Inicjalizowanie więcej niż jednej zmiennej, testowanie złożonego wyrażenia logicznego czy wykonywanie więcej niż jednej instrukcji nie są niczym niezwykłym. Inicjalizacja i akcja mogą być zastąpione kilkoma instrukcjami C++, oddzielonymi od siebie przecinkami. Listing 7.10 przedstawia inicjalizację i inkrementację dwóch zmiennych.
Listing 7.10. Przykład instrukcji wielokrotnych w pętli for
0: //listing 7.10
1: // demonstruje wielokrotne instrukcje
2: // w pętli for
3:
4: #include <iostream>
5:
6: int main()
7: {
8:
9: for (int i=0, j=0; i<3; i++, j++)
10: std::cout << "i: " << i << " j: " << j << std::endl;
11: return 0;
12: }
Wynik
i: 0 j: 0
i: 1 j: 1
i: 2 j: 2
Analiza
W linii 9. dwie zmienne, i oraz j, są inicjalizowane wartością 0. Obliczany jest test (i < 3); ponieważ jest prawdziwy, wykonywane jest ciało pętli for, w którym wypisywane są wartości zmiennych. Na koniec wykonywana jest trzecia klauzula instrukcji for, w której są inkrementowane zmienne i oraz j.
Po wykonaniu linii 10., warunek jest sprawdzany ponownie, jeśli wciąż jest spełniony, działania się powtarzają (inkrementowane są zmienne i oraz j) i ponownie wykonywane jest ciało pętli. Dzieje się tak do momentu, w którym warunek nie będzie spełniony; wtedy nie jest wykonywana instrukcja akcji, a działanie programu wychodzi z pętli.
Puste instrukcje w pętli for
Każdą z instrukcji w nagłówku pętli for można pominąć. W tym celu należy oznaczyć jej położenie średnikiem (;). Aby stworzyć pętlę for, która działa dokładnie tak, jak pętla while, pomiń pierwszą i trzecią instrukcję. Przedstawia to listing 7.11.
Listing 7.11. Puste instrukcje w nagłówku pętli for
0: // Listing 7.11
1: // Pętla for z pustymi instrukcjami
2:
3: #include <iostream>
4:
5: int main()
6: {
7:
8: int counter = 0;
9:
10: for( ; counter < 5; )
11: {
12: counter++;
13: std::cout << "Petla! ";
14: }
15:
16: std::cout << "\nLicznik: " << counter << ".\n";
17: return 0;
19: }
Wynik
Petla! Petla! Petla! Petla! Petla!
Licznik: 5.
Analiza
Być może poznajesz, że ta pętla wygląda dokładnie tak, jak pętla while z listingu 7.8. W linii 8. inicjalizowana jest zmienna counter. Instrukcja for w linii 10. nie inicjalizuje żadnych wartości, lecz zawiera test warunku counter < 5. Nie występuje także instrukcja inkrementacji, więc ta pętla działa dokładnie tak samo, gdybyśmy napisali:
while (counter < 5)
Jak już wiesz, C++ oferuje kilka sposobów osiągnięcia tego samego celu. Żaden doświadczony programista C++ nie użyłby pętli for w ten sposób, przykład ten ilustruje jedynie elastyczność instrukcji for. W rzeczywistości, dzięki zastosowaniu instrukcji break i continue, istnieje możliwość stworzenia pętli for bez żadnej instrukcji w nagłówku. Pokazuje to listing 7.12.
Listing 7.12. Instrukcja for z pustym nagłówkiem
0: //Listing 7.12 ilustruje
1: //instrukcję for z pustym nagłówkiem
2:
3: #include <iostream>
4:
5: int main()
6: {
7:
8: int counter=0; // inicjalizacja
9: int max;
10: std::cout << "Ile pozdrowień?";
11: std::cin >> max;
12: for (;;) // pętla, która się nie kończy
13: {
14: if (counter < max) // test
15: {
16: std::cout << "Hello!\n";
17: counter++; // inkrementacja
18: }
19: else
20: break;
21: }
22: return 0;
23: }
Wynik
Ile pozdrowien?3
Hello!
Hello!
Hello!
Analiza
Z tej pętli usunęliśmy wszystko, co się dało. Inicjalizacja, test i akcja zostały przeniesione poza instrukcję for. Inicjalizacja odbywa się w linii 8., przed pętlą for. Test jest przeprowadzany w osobnej instrukcji if, w linii 14., i gdy się powiedzie, w linii 17. jest wykonywana akcja, czyli inkrementacja zmiennej counter. Jeśli warunek nie jest spełniony, w linii 20. następuje wyjście z pętli (spowodowane użyciem instrukcji break).
Choć program ten jest nieco absurdalny, jednak czasem pętle for(;;) lub while(true) są właśnie tym, czego nam potrzeba. Bardziej sensowny przykład wykorzystania takiej pętli zobaczysz w dalszej części rozdziału, przy okazji omawiania instrukcji switch.
Puste pętle for
Ponieważ w samym nagłówku pętli for można wykonać tak wiele pracy, więc czasem ciało pętli może już niczego nie robić. Dlatego pamiętaj o zastosowaniu instrukcji pustej (;) jako ciała funkcji. Średnik może zostać umieszczony w tej samej linii, co nagłówek pętli, ale wtedy łatwo go przeoczyć. Użycie pętli for z pustym ciałem przedstawia listing 7.13.
Listing 7.13. Instrukcja pusta w ciele pętli for.
0: //Listing 7.13
1: //Demonstruje instrukcję pustą
2: // w ciele pętli for
3:
4: #include <iostream>
5: int main()
6: {
7:
8: for (int i = 0; i<5; std::cout << "i: " << i++ << std::endl)
9: ;
10: return 0;
11: }
Wynik
i: 0
i: 1
i: 2
i: 3
i: 4
Analiza
Pętla for w linii 8. zawiera trzy instrukcje. Instrukcja inicjalizacji definiuje i inicjalizuje zmienną licznikową i wartością 0. Instrukcja warunku sprawdza, czy i < 5, zaś instrukcja akcji wypisuje wartość zmiennej i oraz inkrementuje ją.
Ponieważ ciało pętli nie wykonuje żadnych czynności, użyto w nim instrukcji pustej (;). Zwróć uwagę, że ta pętla for nie jest najlepiej zaprojektowana: instrukcja akcji wykonuje zbyt wiele pracy. Lepiej więc byłoby zmienić tę pętlę w następujący sposób:
8: for (int i = 0; i<5; i++)
9: std::cout << "i: " << i << std::endl;
Choć obie wersje działają tak samo, druga z nich jest łatwiejsza do zrozumienia.
Pętle zagnieżdżone
Pętle mogą być zagnieżdżone, tj. pętla może znajdować się w ciele innej pętli. Pętla wewnętrzna jest wykonywana wielokrotnie, przy każdym wykonaniu pętli zewnętrznej. Listing 7.14 przedstawia zapisywanie znaczników do macierzy, za pomocą zagnieżdżonych pętli for.
Listing 7.14. Zagnieżdżone pętle for
0: //Listing 7.14
1: //Ilustruje zagnieżdżone pętle for
2:
3: #include <iostream>
4:
5: int main()
6: {
7: using namespace std;
8: int rows, columns;
9: char theChar;
10: cout << "Ile wierszy? ";
11: cin >> rows;
12: cout << "Ile kolumn? ";
13: cin >> columns;
14: cout << "Jaki znak? ";
15: cin >> theChar;
16: for (int i = 0; i<rows; i++)
17: {
18: for (int j = 0; j<columns; j++)
19: cout << theChar;
20: cout << "\n";
21: }
22: return 0;
23: }
Wynik
Ile wierszy? 4
Ile kolumn? 12
Jaki znak? x
xxxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxxx
Analiza
Użytkownik jest proszony o podanie ilości wierszy i kolumn oraz znaku, jaki ma zostać użyty do wypisania[Author ID1: at Mon Oct 22 16:17:00 2001
] wydruk[Author ID1: at Mon Oct 22 16:17:00 2001
]owania zarysu [Author ID1: at Mon Oct 22 16:17:00 2001
]macierzy. Pierwsza pętla for, w linii 16., inicjalizuje[Author ID1: at Mon Oct 22 16:17:00 2001
] ustawia wartość początkową [Author ID1: at Mon Oct 22 16:17:00 2001
]licznika[Author ID1: at Mon Oct 22 16:18:00 2001
] (i) na 0, po czym przechodzi do wykonania ciała zewnętrznej pętli.
W linii 18., pierwszej linii ciała zewnętrznej pętli for, tworzona jest kolejna pętla for. Jest w niej inicjalizowany drugi licznik (j), także wartością 0, po czym program przechodzi do wykonania ciała pętli wewnętrznej. W linii 19. wypisywany jest wybrany znak, a program wraca do nagłówka wewnętrznej pętli. Zwróć uwagę, że wewnętrzna pętla for posiada tylko jedną instrukcję (wypisującą znak). Gdy sprawdzany warunek jest spełniony (j < columns), zmienna j jest inkrementowana i wypisywany jest następny znak. Czynność powtarzana jest tak długo, aż j zrówna się z ilością kolumn.
Gdy warunek w wewnętrznej pętli nie zostanie spełniony (w tym przypadku po wypisaniu dwunastu znaków), wykonanie programu przechodzi do linii 20., w której wypisywany jest znak nowej linii. Następuje powrót do nagłówka pętli zewnętrznej, w którym odbywa się sprawdzenie warunku (i < rows). Jeśli ten warunek zostaje spełniony, zmienna i jest inkrementowana i ponownie wykonywane jest ciało pętli.
W drugiej iteracji zewnętrznej pętli for ponownie rozpoczyna się wykonanie pętli wewnętrznej. Zmiennej j ponownie przypisywana jest wartość 0 i cała pętla wykonywana jest jeszcze raz.
Powinieneś zwrócić uwagę, że wewnętrzna pętla jest wykonywana w całości przy każdym wykonaniu pętli zewnętrznej. Dlatego wypisywanie znaku powtarza się (columns · rows) razy.
UWAGA Wielu programistów oznacza [Author ID1: at Mon Oct 22 16:18:00 2001
]nadaje zmiennym licznikowym nazwy i oraz j. Ta tradycja sięga czasów języka FORTRAN, w którym jedynymi zmiennymi licznikowymi były zmienne i, j, k, l, m oraz n.
Inni programiści wolą używać dla zmiennych licznikowych bardziej opisowych nazw, takich jak licznik1 czy licznik2. Jednak zmienne i oraz j są tak popularne, że nie powodują żadnych nieporozumień, gdy zostaną użyte w nagłówkach pętli for.
Zakres zmiennych w pętlach for
W przeszłości zakres zmiennych zadeklarowanych w pętlach for rozciągał się także na blok zewnętrzny. Standard ANSI ograniczył ten zakres do bloku pętli for (nie gwarantują tego jednak wszystkie kompilatory). Możesz sprawdzić swój kompilator za pomocą poniższego kodu:
#inlude <iostream>
int main()
{
// czy i ogranicza się tylko do pętli for?
for (int i = 0; i < 5; i++)
{
std::cout << "i: " << i << std::endl;
}
i = 7; // nie powinno być w tym zakresie!
return 0;
}
Jeśli kod skompiluje się bez kłopotów, oznacza to, że twój kompilator nie obsługuje tego aspektu standardu ANSI.
Jeśli kompilator zaprotestuje, że i nie jest zdefiniowane (w linii i = 7), oznacza to, że obsługuje nowy standard. Aby stworzyć kod, który skompiluje się za każdym razem, możesz zmienić go następująco:
#inlude <iostream>
int main()
{
int i; // zadeklarowane poza pętlą
for (i = 0; i < 5; i++)
{
std::cout << "i: " << i << std::endl;
}
i = 7; // teraz jest w zakresie w każdym kompilatorze
return 0;
}
Podsumowanie pętli
W rozdziale 5., „Funkcja,” nauczyłeś się, jak rozwiązywać problem ciągu Fibonacciego za pomocą rekurencji. Dla przypomnienia: ciąg Fibonacciego rozpoczyna się od wyrazów 1, 1, 2, 3..., zaś każde kolejne wyrazy stanowią sumę dwóch poprzednich:
1, 1, 2, 3, 5, 8, 13, 21, 34...
N-ty wyraz ciągu jest sumą wyrazów n-1 i n-2. Problemem rozwiązywanym w rozdziale piątym było obliczenie wartości n-tego wyrazu ciągu. W tym celu używaliśmy rekurencji. Tym razem, jak pokazuje listing 7.15, użyjemy iteracji.
Listing 7.15. Obliczanie wyrazów ciągu Fibonacciego za pomocą iteracji.
0: // Listing 7.15
1: // Demonstruje obliczanie wartości n-tego
2: // wyrazu ciągu Fibonacciego za pomocą iteracji
3:
4: #include <iostream>
5:
6: int fib(int position);
7: int main()
8: {
9: using namespace std;
10: int answer, position;
11: cout << "Ktory wyraz ciagu? ";
12: cin >> position;
13: cout << "\n";
14:
15: answer = fib(position);
16: cout << position << " wyraz ciagu Fibonacciego ";
17: cout << "ma wartosc " << answer << ".\n";
18: return 0;
19: }
20:
21: int fib(int n)
22: {
23: int minusTwo=1, minusOne=1, answer=2;
24:
25: if (n < 3)
26: return 1;
27:
28: for (n -= 3; n; n--)
29: {
30: minusTwo = minusOne;
31: minusOne = answer;
32: answer = minusOne + minusTwo;
33: }
34:
35: return answer;
36: }
Wynik
Ktory wyraz ciagu? 4
4 wyraz ciagu Fibonacciego ma wartosc 3.
Ktory wyraz ciagu? 5
5 wyraz ciagu Fibonacciego ma wartosc 5.
Ktory wyraz ciagu? 20
20 wyraz ciagu Fibonacciego ma wartosc 6765.
Ktory wyraz ciagu? 30
30 wyraz ciagu Fibonacciego ma wartosc 832040.
Analiza
W listingu 7.15 obliczyliśmy wartości wyrazów ciągu Fibonacciego, stosując iterację zamiast rekurencji. Ta metoda jest szybsza i zajmuje mniej pamięci niż rekurencja.
W linii 11. użytkownik jest proszony o podanie numeru wyrazu ciągu. Następuje wywołanie funkcji fib(), która oblicza wartość tego wyrazu. Jeśli numer wyrazu jest mniejszy od 3, funkcja zwraca wartość 1. Począwszy od trzeciego wyrazu, funkcja iteruje (działa w pętli), używając następującego algorytmu:
Ustawia pozycję wyjściową: przypisuje zmiennej answer (wynik) wartość 2, zaś zmiennym minusTwo (minus dwa) i minusOne (minus jeden) wartość 1. Zmniejsza numer wyrazu o trzy, gdyż pierwsze dwa wyrazy zostały już obsłużone przez pozycję wyjściową.
Dla każdego wyrazu,[Author ID1: at Mon Oct 22 16:18:00 2001 ] aż do wyrazu poszukiwanego,[Author ID1: at Mon Oct 22 16:18:00 2001 ] obliczana jest wartość ciągu Fibonacciego. Odbywa się to poprzez:
Przypisanie bieżącej wartości zmiennej minusOne do zmiennej minusTwo.
Przypisanie bieżącej wartości zmiennej answer do zmiennej minusOne.
Zsumowanie wartości zmiennych minusOne oraz minusTwo i przypisanie tej sumy zmiennej answer.
Dekrementację zmiennej licznikowej n.
Gdy zmienna n osiągnie zero, funkcja zwraca wartość zmiennej answer.
Dokładnie tak samo rozwiązywalibyśmy ten problem na papierze. Gdybyś został poproszony o podanie wartości piątego wyrazu ciągu Fibonacciego, napisałbyś:
1, 1, 2,
i pomyślałbyś: „jeszcze dwa wyrazy.” Następnie dodałbyś 2+1 i dopisałbyś 3, myśląc: „Jeszcze jeden.” Na koniec dodałbyś 3+2 i otrzymałbyś w wyniku 5. Rozwiązanie tego zadania polega na każdorazowym przesuwaniu operacji sumowania w prawo i zmniejszaniu ilości pozostałych do obliczenia wyrazów ciągu.
Zwróć uwagę na warunek sprawdzany w linii 28. (n). Jest to idiom języka C++, który stanowi odpowiednik n != 0. Ta pętla for jest wykonywana, dopóki wartość n nie osiągnie zera (które odpowiada wartości logicznej false). Zatem nagłówek tej pętli for mógłby zostać przepisany następująco:
for (n -=3; n != 0; n--)
dzięki temu pętla byłaby bardziej czytelna. Jednak ten idiom jest tak popularny, że nie ma sensu z nim walczyć.
Skompiluj, zbuduj i uruchom ten program, po czym porównaj jego działanie z korzystającym z rekurencji programem z rozdziału piątego. Spróbuj obliczyć wartość 25. wyrazu ciągu i porównaj czas działania obu programów. Rekurencja to elegancka metoda, ale ponieważ z wywołaniem funkcji wiąże się pewien narzut, i ponieważ jest ona wywoływana tak wiele razy, metoda ta jest wolniejsza od iteracji. Wykonywanie operacji arytmetycznych na mikrokomputerach zostało zoptymalizowane, dlatego rozwiązania iteracyjne powinny działać bardzo szybko.
Uważaj, by nie wpisać zbyt wysokiego numeru wyrazu ciągu. Ciąg Fibonacciego rośnie bardzo szybko i nawet przy niewielkich wartościach zmienne całkowite typu long zostają przepełnione.
Instrukcja Switch
Z rozdziału 4. dowiedziałeś się jak korzystać z instrukcji if i else. Gdy zostaną one zbyt głęboko zagnieżdżone, stają się całkowicie niezrozumiałe. Na szczęście C++ oferuje pewną alternatywę. W odróżnieniu od instrukcji if, ,która sprawdza jedną wartość, instrukcja switch (przełącznik) umożliwia podjęcie działań na podstawie jednej z wielu różnych wartości. Ogólna postać instrukcji switch wygląda następująco:
switch (wyrażenie)
{
case wartośćJeden: instrukcja;
break;
case wartośćDwa: instrukcja;
break;
....
case wartośćN: instrukcja;
break;
default: instrukcja;
}
wyrażenie jest dowolnym wyrażeniem języka C++, zaś jego [Author ID1: at Mon Oct 22 16:19:00 2001
]instrukcje są dowolnymi instrukcjami lub blokami instrukcji, pod warunkiem jednak, że ich wynikiem jest liczba typu [Author ID1: at Mon Oct 22 16:19:00 2001
]integer[Author ID1: at Mon Oct 22 16:19:00 2001
] (lub [Author ID1: at Mon Oct 22 16:19:00 2001
]jej wynik jest jednoznacznie ko[Author ID1: at Mon Oct 22 16:19:00 2001
]n[Author ID1: at Mon Oct 22 16:20:00 2001
]wertowalny [Author ID1: at Mon Oct 22 16:19:00 2001
]do takiej liczby)[Author ID1: at Mon Oct 22 16:20:00 2001
]. Wartości muszą być stałymi (literałami lub wyrażeniami o stałej wartości)[Author ID1: at Mon Oct 22 16:21:00 2001
]. Należy jednak[Author ID1: at Mon Oct 22 16:21:00 2001
] również [Author ID1: at Mon Oct 22 16:21:00 2001
]pamiętać, że instrukcja switch sprawdza jedynie czy wartość[Author ID1: at Mon Oct 22 16:21:00 2001
]a[Author ID1: at Mon Oct 22 16:22:00 2001
]jedynie równoś[Author ID1: at Mon Oct 22 16:22:00 2001
]ć wyrażeniaodpowiada którejś z wartości[Author ID1: at Mon Oct 22 16:22:00 2001
]; nie można stosować operatorów relacji ani operacji logicznych.
Jeśli któraś z wartości case jest równa wartości wyrażenia, program przechodzi do instrukcji tuż po tej wartości [Author ID1: at Mon Oct 22 16:23:00 2001
]w związanej z nią klauzuli[Author ID1: at Mon Oct 22 16:23:00 2001
]case i jego wykonanie jest kontynuowane aż do napotkania instrukcji break (przerwij). Jeśli wartość wyrażenia nie pasuje do żadnej z wartości klauzul[Author ID1: at Mon Oct 22 16:24:00 2001
] case, wykonywana jest klauzula[Author ID1: at Mon Oct 22 16:24:00 2001
]instrukcja [Author ID1: at Mon Oct 22 16:24:00 2001
] default (domyślna[Author ID1: at Mon Oct 22 16:24:00 2001
]y[Author ID1: at Mon Oct 22 16:24:00 2001
]). Jeśli klauzula[Author ID1: at Mon Oct 22 16:24:00 2001
] nie występuje default i wartość wyrażenia nie pasuje do żadnej z wartości klauzul[Author ID1: at Mon Oct 22 16:25:00 2001
] case, instrukcja[Author ID1: at Mon Oct 22 16:26:00 2001
]a[Author ID1: at Mon Oct 22 16:25:00 2001
] switch jest pomijana[Author ID1: at Mon Oct 22 16:26:00 2001
] nie spowoduje żadnej akcji [Author ID1: at Mon Oct 22 16:26:00 2001
]i program przechodzi do następnych instrukcji w kodzie.
UWAGA Stosowanie w instrukcji switch klauzuli[Author ID1: at Mon Oct 22 16:27:00 2001
]przypadku[Author ID1: at Mon Oct 22 16:27:00 2001
] default jest dobrym pomysłem. Jeśli nie znajdziesz dla niego[Author ID1: at Mon Oct 22 16:27:00 2001
]j[Author ID1: at Mon Oct 22 16:27:00 2001
] innego zastosowania, użyj go[Author ID1: at Mon Oct 22 16:27:00 2001
]jej[Author ID1: at Mon Oct 22 16:27:00 2001
] do wykrycia sytuacji, której wystąpienie nie było przewidziane; wypisz wtedy odpowiedni komunikat błędu. Może to być bardzo pomocne podczas debuggowania programu.
Należy pamiętać, że w przypadku braku instrukcji break na końcu bloku instrukcji w[Author ID1: at Mon Oct 22 16:28:00 2001
] (klauzuli[Author ID1: at Mon Oct 22 16:28:00 2001
]po[Author ID1: at Mon Oct 22 16:28:00 2001
] case), wykonanie przechodzi także do następnego[Author ID1: at Mon Oct 22 16:28:00 2001
]j[Author ID1: at Mon Oct 22 16:28:00 2001
] przypadku [Author ID1: at Mon Oct 22 16:28:00 2001
]klauzuli[Author ID1: at Mon Oct 22 16:28:00 2001
] case. Czasem takie działanie jest zamierzone, ale zwykle jest po prostu błędem. Jeśli zdecydujesz się na wykonanie instrukcji w kilku kolejnych klauzulach[Author ID1: at Mon Oct 22 16:29:00 2001
]przypadkach[Author ID1: at Mon Oct 22 16:29:00 2001
] case, pamiętaj o umieszczeniu obok komentarza, który wyjaśni, że nie pominąłeś instrukcji break przypadkowo.
Listing 7.16 przedstawia użycie instrukcji switch.
Listing 7.16. Przykład instrukcji switch
0: //Listing 7.16
1: // Demonstruje instrukcję switch
2:
3: #include <iostream>
4:
5: int main()
6: {
7: using namespace std;
8: unsigned short int number;
9: cout << "Wpisz liczbe pomiedzy 1 i 5: ";
10: cin >> number;
11: switch (number)
12: {
13: case 0: cout << "Za mala, przykro mi!";
14: break;
15: case 5: cout << "Dobra robota!\n"; // przejście dalej
16: case 4: cout << "Niezle!\n"; // przejście dalej
17: case 3: cout << "Wysmienicie!\n"; // przejście dalej
18: case 2: cout << "Cudownie!\n"; // przejście dalej
19: case 1: cout << "Niesamowicie!\n";
20: break;
21: default: cout << "Zbyt duza!\n";
22: break;
23: }
24: cout << "\n\n";
25: return 0;
26: }
Wynik
Wpisz liczbe pomiedzy 1 i 5: 3
Wysmienicie!
Cudownie!
Niesamowicie!
Wpisz liczbe pomiedzy 1 i 5: 8
Zbyt duza!
Analiza
Użytkownik jest proszony o podanie liczby. Ta liczba jest przekazywana do instrukcji switch. Jeśli ma wartość 0, odpowiada klauzuli case w linii 13., dlatego jest wypisywany komunikat: „Za mala, przykro mi!”, po czym instrukcja break kończy działanie instrukcji switch. Jeśli liczba ma wartość 5, wykonanie przechodzi do linii 15., w której wypisywany jest odpowiedni komunikat, po czym przechodzi do linii 16., w której wypisywany jest kolejny komunikat, i tak dalej, aż do napotkania instrukcji break w linii 20.
Efektem działania tej instrukcji switch dla liczb pomiędzy 1 a 5 jest wypisanie odpowiadającej ilości komunikatów. Jeśli wartością liczby nie jest ani 0 ani 5, zakłada się, że jest ona zbyt duża i w takim przypadku w linii 21. wykonywana jest instrukcja klauzuli default.
Instrukcja switch
Składnia instrukcji switch jest następująca:
switch (wyrażenie)
{
case wartośćJeden: instrukcja;
case wartośćDwa: instrukcja;
....
case wartośćN: instrukcja;
default: instrukcja;
}
Instrukcja switch umożliwia rozgałęzienie programu (w zależności od wartości wyrażenia). Na początku wykonywania instrukcji następuje obliczenie wartości wyrażenia, gdy odpowiada ona którejś z wartości klauzul[Author ID1: at Mon Oct 22 16:29:00 2001
]przypadku[Author ID1: at Mon Oct 22 16:29:00 2001
] case, wykonanie programu przechodzi do tego [Author ID1: at Mon Oct 22 16:29:00 2001
]właśnie [Author ID1: at Mon Oct 22 16:42:00 2001
]przypadku[Author ID1: at Mon Oct 22 16:29:00 2001
]danej klauzul[Author ID1: at Mon Oct 22 16:29:00 2001
]i[Author ID1: at Mon Oct 22 16:29:00 2001
]. Wykonywanie instrukcji jest kontynuowane aż do końca ciała [Author ID1: at Mon Oct 22 16:30:00 2001
]i[Author ID1: at Mon Oct 22 16:30:00 2001
]i[Author ID1: at Mon Oct 22 16:30:00 2001
]nstrukcji switch lub do czasu napotkania instrukcji break.
Jeśli wartość wyrażenia nie odpowiada żadnej z wartości klauzul[Author ID1: at Mon Oct 22 16:31:00 2001
]przypadków[Author ID1: at Mon Oct 22 16:31:00 2001
] case i występuje przypadek [Author ID1: at Mon Oct 22 16:32:00 2001
]klauzula[Author ID1: at Mon Oct 22 16:32:00 2001
]default, wykonanie przechodzi do klauzuli[Author ID1: at Mon Oct 22 16:32:00 2001
]przypadku[Author ID1: at Mon Oct 22 16:32:00 2001
] default. W przeciwnym razie wykonywanie instrukcji switch się kończy.
Przykład 1
switch (wybor)
{
case 0:
cout << "Zero!" << endl;
break;
case 1:
cout << "Jeden!" << endl;
break;
case 2:
cout << "Dwa!" << endl;
break;
default:
cout << "Domyślna!" << endl;
}
Przykład 2
switch (wybor)
{
case 0:
case 1:
case 2:
cout << "Mniejsza niż 3!";
break;
case 3:
cout << "Równa 3!";
break;
default:
cout << "Większa niż 3!";
}
Użycie instrukcji switch w menu
Listing 7.17 wykorzystuje omawianą wcześniej pętli for(;;). Takie pętle są nazywane pętlami nieskończonymi, gdyż są wykonywane bez końca, aż do natrafienia na kończącą ich działanie instrukcję. Pętla nieskończona jest używana do tworzenia menu, pobrania polecenia od użytkownika, wykonania odpowiednich działań i powrót do menu. Jej działanie powtarza się dopóty, dopóki użytkownik nie zdecyduje się na wyjście z menu.
UWAGA Niektórzy programiści wolą pisać:
#define EVER ;;
for (EVER)
{
// instrukcje...
}
Pętla nieskończona nie posiada warunku wyjścia. Aby opuścić taką pętlę, należy użyć instrukcji break. Pętle nieskończone są także zwane pętlami wiecznymi.
Listing 7.17. Przykład pętli nieskończonej
0: //Listing 7.17
1: //Używa nieskończonej pętli do
2: //interakcji z użytkownikiem
3: #include <iostream>
4:
5: // prototypy
6: int menu();
7: void DoTaskOne();
8: void DoTaskMany(int);
9:
10: using namespace std;
11:
12: int main()
13: {
14: bool exit = false;
15: for (;;)
16: {
17: int choice = menu();
18: switch(choice)
19: {
20: case (1):
21: DoTaskOne();
22: break;
23: case (2):
24: DoTaskMany(2);
25: break;
26: case (3):
27: DoTaskMany(3);
28: break;
29: case (4):
30: continue; // nadmiarowa!
31: break;
32: case (5):
33: exit=true;
34: break;
35: default:
36: cout << "Prosze wybrac ponownie!\n";
37: break;
38: } // koniec instrukcji switch
39:
40: if (exit)
41: break;
42: } // koniec pętli for(;;)
43: return 0;
44: } // koniec main()
45:
46: int menu()
47: {
48: int choice;
49:
50: cout << " **** Menu ****\n\n";
51: cout << "(1) Pierwsza opcja.\n";
52: cout << "(2) Druga opcja.\n";
53: cout << "(3) Trzecia opcja.\n";
54: cout << "(4) Ponownie wyswietl menu.\n";
55: cout << "(5) Wyjscie.\n\n";
56: cout << ": ";
57: cin >> choice;
58: return choice;
59: }
60:
61: void DoTaskOne()
62: {
63: cout << "Opcja pierwsza!\n";
64: }
65:
66: void DoTaskMany(int which)
67: {
68: if (which == 2)
69: cout << "Opcja druga!\n";
70: else
71: cout << "Opcja trzecia!\n";
72: }
Wynik
**** Menu ****
(1) Pierwsza opcja.
(2) Druga opcja.
(3) Trzecia opcja.
(4) Ponownie wyswietl menu.
(5) Wyjscie.
: 1
Opcja pierwsza!
**** Menu ****
(1) Pierwsza opcja.
(2) Druga opcja.
(3) Trzecia opcja.
(4) Ponownie wyswietl menu.
(5) Wyjscie.
: 3
Opcja trzecia!
**** Menu ****
(1) Pierwsza opcja.
(2) Druga opcja.
(3) Trzecia opcja.
(4) Ponownie wyswietl menu.
(5) Wyjscie.
: 5
Analiza
Ten program łączy w sobie kilka zagadnień omawianych w tym i poprzednich rozdziałach. Oprócz tego przedstawia popularne zastosowanie instrukcji switch.
W linii 15. zaczyna się pętla nieskończona. Wywoływana jest w niej funkcja menu(), wypisująca na ekranie menu i zwracająca numer polecenia wybranego przez użytkownika. Na podstawie tego numeru polecenia, instrukcja switch (zajmująca linie od 18. do 38.) wywołuje odpowiednią funkcję obsługi polecenia.
Gdy użytkownik wybierze polecenie 1., następuje „skok” do instrukcji case 1: w linii 20. W linii 21. wykonanie przechodzi do funkcji DoTaskOne() (wykonaj zadanie 1.), wypisującej komunikat i zwracającej sterowanie. Po powrocie z tej funkcji program wznawia działanie od linii 22., w której instrukcja break kończy działanie instrukcji switch, co powoduje przejście do linii 39. W linii 40. sprawdzana jest wartość zmiennej exit (wyjście). Jeśli wynosi true, w linii 41. wykonywana jest instrukcja break, powodująca wyjście z pętli for(;;); jeśli zmienna ma wartość false, program wraca do początku pętli w linii 15.
Zwróć uwagę, że instrukcja continue w linii 30. jest nadmiarowa. Gdybyśmy ją pominęli i napotkali instrukcję break, instrukcja switch zakończyłaby działanie, zmienna exit miałaby wartość false, pętla zostałaby wykonana ponownie, a menu zostałoby wypisane ponownie. Jednak dzięki tej instrukcji continue może pominąć[Author ID1: at Mon Oct 22 16:32:00 2001
]można pominąć sprawdzanie zmiennej exit.
TAK |
NIE |
Aby uniknąć głęboko zagnieżdżonych instrukcji if, używaj instrukcji switch.
Pieczołowicie dokumentuj wszystkie zamierzone przejścia pomiędzy
W instrukcjach switch stosuj |
Nie zapominaj o instrukcji break na końcu każdego[Author ID1: at Mon Oct 22 16:33:00 2001
] |
Program podsumowujący wiadomości
{uwaga skład: jest to zawartość rozdziału „Week 1 In Review” }
Listing 7.18. Program podsumowujący wiadomości
0: #include <iostream>
1: using namespace std;
2: enum CHOICE { DrawRect = 1, GetArea, GetPerim,
3: ChangeDimensions, Quit};
4:
5: // Deklaracja klasy Rectangle
6: class Rectangle
7: {
8: public:
9: // konstruktory
10: Rectangle(int width, int height);
11: ~Rectangle();
12:
13: // akcesory
14: int GetHeight() const { return itsHeight; }
15: int GetWidth() const { return itsWidth; }
16: int GetArea() const { return itsHeight * itsWidth; }
17: int GetPerim() const { return 2*itsHeight + 2*itsWidth; }
18: void SetSize(int newWidth, int newHeight);
19:
20: // Inne metody
21:
22:
23: private:
24: int itsWidth;
25: int itsHeight;
26: };
27:
28: // Implementacja metod klasy
29: void Rectangle::SetSize(int newWidth, int newHeight)
30: {
31: itsWidth = newWidth;
32: itsHeight = newHeight;
33: }
34:
35:
36: Rectangle::Rectangle(int width, int height)
37: {
38: itsWidth = width;
39: itsHeight = height;
40: }
41:
42: Rectangle::~Rectangle() {}
43:
44: int DoMenu();
45: void DoDrawRect(Rectangle);
46: void DoGetArea(Rectangle);
47: void DoGetPerim(Rectangle);
48:
49: int main ()
50: {
51: // inicjalizujemy prostokąt jako 30,5
52: Rectangle theRect(30,5);
53:
54: int choice = DrawRect;
55: int fQuit = false;
56:
57: while (!fQuit)
58: {
59: choice = DoMenu();
60: if (choice < DrawRect || choice > Quit)
61: {
62: cout << "\nBledny wybor, prosze sprobowac ponownie.\n\n";
63: continue;
64: }
65: switch (choice)
66: {
67: case DrawRect:
68: DoDrawRect(theRect);
69: break;
70: case GetArea:
71: DoGetArea(theRect);
72: break;
73: case GetPerim:
74: DoGetPerim(theRect);
75: break;
76: case ChangeDimensions:
77: int newLength, newWidth;
78: cout << "\nNowa szerokosc: ";
79: cin >> newWidth;
80: cout << "Nowa wysokosc: ";
81: cin >> newLength;
82: theRect.SetSize(newWidth, newLength);
83: DoDrawRect(theRect);
84: break;
85: case Quit:
86: fQuit = true;
87: cout << "\nWyjscie...\n\n";
88: break;
89: default:
90: cout << "Blad wyboru!\n";
91: fQuit = true;
92: break;
93: } // koniec instrukcji switch
94: } // koniec petli while
95: return 0;
96: } // koniec funkcji main
97:
98: int DoMenu()
99: {
100: int choice;
101: cout << "\n\n *** Menu *** \n";
102: cout << "(1) Rysuj prostokat\n";
103: cout << "(2) Obszar\n";
104: cout << "(3) Obwod\n";
105: cout << "(4) Zmien rozmiar\n";
106: cout << "(5) Wyjscie\n";
107:
108: cin >> choice;
109: return choice;
110: }
111:
112: void DoDrawRect(Rectangle theRect)
113: {
114: int height = theRect.GetHeight();
115: int width = theRect.GetWidth();
116:
117: for (int i = 0; i<height; i++)
118: {
119: for (int j = 0; j< width; j++)
120: cout << "*";
121: cout << "\n";
122: }
123: }
124:
125:
126: void DoGetArea(Rectangle theRect)
127: {
128: cout << "Obszar: " << theRect.GetArea() << endl;
129: }
130:
131: void DoGetPerim(Rectangle theRect)
132: {
133: cout << "Obwod: " << theRect.GetPerim() << endl;
134: }
Wynik
*** Menu ***
(1) Rysuj prostokat
(2) Obszar
(3) Obwod
(4) Zmien rozmiar
(5) Wyjscie
1
******************************
******************************
******************************
******************************
******************************
*** Menu ***
(1) Rysuj prostokat
(2) Obszar
(3) Obwod
(4) Zmien rozmiar
(5) Wyjscie
2
Obszar: 150
*** Menu ***
(1) Rysuj prostokat
(2) Obszar
(3) Obwod
(4) Zmien rozmiar
(5) Wyjscie
3
Obwod: 70
*** Menu ***
(1) Rysuj prostokat
(2) Obszar
(3) Obwod
(4) Zmien rozmiar
(5) Wyjscie
4
Nowa szerokosc: 10
Nowa wysokosc: 8
**********
**********
**********
**********
**********
**********
**********
**********
*** Menu ***
(1) Rysuj prostokat
(2) Obszar
(3) Obwod
(4) Zmien rozmiar
(5) Wyjscie
2
Obszar: 80
*** Menu ***
(1) Rysuj prostokat
(2) Obszar
(3) Obwod
(4) Zmien rozmiar
(5) Wyjscie
3
Obwod: 36
*** Menu ***
(1) Rysuj prostokat
(2) Obszar
(3) Obwod
(4) Zmien rozmiar
(5) Wyjscie
5
Wyjscie...
Analiza
Ten program wykorzystuje większość wiadomości, jakie zdobyłeś czytając poprzednie rozdziały. Powinieneś umieć wpisać, skompilować, połączyć i uruchomić program, a ponadto zrozumieć w jaki sposób działa (pod warunkiem że uważnie czytałeś dotychczasowe rozdziały).
Sześć pierwszych linii przygotowuje nowe typy i definicje, które będą używane w programie.
W liniach od 6. do 26. jest zadeklarowana klasa Rectangle (prostokąt). Zawiera ona publiczne akcesory przeznaczone do odczytywania i ustawiania wysokości i szerokości prostokąta, a także metody obliczania jego obszaru i obwodu. Linie od 29. do 40. zawierają definicje tych funkcji klasy, które nie zostały zdefiniowane inline.
Prototypy funkcji dla funkcji globalnych znajdują się w liniach od 44. do 47., zaś sam program zaczyna się w linii 49. Działanie programu polega na wygenerowaniu prostokąta, a następnie wypisaniu menu, zawierającego pięć opcji: rysowanie prostokąta, obliczanie jego obszaru, obliczanie jego obwodu, zmiana rozmiarów prostokąta oraz wyjście.
W linii 55. ustawiany jest znacznik (flaga)[Author ID1: at Mon Oct 22 16:34:00 2001 ]; jeśli wartością tego znacznika jest false, działanie pętli jest kontynuowane. Wartość true jest przypisywana do tego znacznika tylko wtedy, gdy użytkownik wybierze z menu polecenie Wyjście.
Inne opcje, z wyjątkiem Zmień rozmiar, wywołują odpowiednie funkcje. Dzięki temu działanie instrukcji switch jest bardziej przejrzyste. Opcja Zmień rozmiar nie może wywoływać funkcji, gdyż zmieniłoby to rozmiary prostokąta. Jeśli prostokąt zostałby przekazany (przez wartość) do funkcji takiej, jak na przykład DoChangeDimensions() (zmień rozmiary), wtedy rozmiary zostałyby zmienione jedynie w lokalnej kopii prostokąta w tej funkcji i nie zostałyby odzwierciedlone w prostokącie w [Author ID1: at Mon Oct 22 16:35:00 2001 ]funkcji main(). Z rozdziału 8., „Wskaźniki,” oraz rozdziału 10., „Funkcje zaawansowane,” dowiesz się, w jaki sposób ominąć to ograniczenie. Na razie jednak zmiana rozmiarów odbywa się bezpośrednio w funkcji main().
Zwróć uwagę, że użycie wyliczenia[Author ID1: at Mon Oct 22 16:36:00 2001
]typu wyliczeniowego[Author ID1: at Mon Oct 22 16:36:00 2001
] sprawiło, że instrukcja switch jest bardziej przejrzysta i łatwiejsza do zrozumienia. Gdyby przełączanie zależało od liczb (1 - 5) wybranych przez użytkownika, musiałbyś stale zaglądać do opisu menu, aby dowiedzieć się, do czego służy dana opcja.
W linii 60. następuje sprawdzenie, czy opcja wybrana przez użytkownika mieści się w dozwolonym zakresie. Jeśli nie, [Author ID1: at Mon Oct 22 16:37:00 2001
]jest wypisywany komunikat błędu i następuje odświeżenie (czyli ponowne wypisanie) menu. Zauważ, że instrukcja switch posiada „niemożliwy[Author ID1: at Mon Oct 22 16:37:00 2001
]ą[Author ID1: at Mon Oct 22 16:37:00 2001
]” klauzulę[Author ID1: at Mon Oct 22 16:37:00 2001
]przypadek[Author ID1: at Mon Oct 22 16:37:00 2001
] default. Stanowi ona[Author ID1: at Mon Oct 22 16:37:00 2001
] pomoc przy debuggowaniu. Gdy program działa, instrukcja ta nigdy nie powinna zostać wykonana.
2 Część I ♦ Podstawy obsługi systemu WhizBang (Nagłówek strony)
2 F:\korekta\r07-06.doc