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
(idz 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
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 zródłowym, do przodu lub do tyłu.
Nierozważne użycie tej instrukcji sprawia że kod zró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 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
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
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:
1. Mniejsza liczba nie jest większa od większej liczby.
2. Większa liczba nie jest ujemna ani równa zeru.
3. 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. następuje inkrementacja zmiennej small, zaś w linii 27. 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., za zamykający nawias klamrowy 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
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 krótkich liczb całkowitych (short), 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
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 znalezć 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 Nie używaj instrukcji goto.
jest warunek, używaj pętli while.
Bądz ostrożny używając instrukcji continue i
break.
Upewnij się, czy pętla while w pewnym
momencie kończy działanie.
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
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
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
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
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++,
które 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:
1. Przeprowadza inicjalizacjÄ™.
2. Oblicza wartość warunku .
3. Jeśli warunek 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
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
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
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
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
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 17: {
18: for (int j = 0; 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
wydrukowania zarysu macierzy. Pierwsza pętla for, w linii 16., ustawia wartość początkową
licznika (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 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
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
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
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:
1. 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ą.
2. Dla każdego wyrazu, aż do wyrazu poszukiwanego, obliczana jest wartość ciągu
Fibonacciego. Odbywa siÄ™ to poprzez:
a. Przypisanie bieżącej wartości zmiennej minusOne do zmiennej minusTwo.
b. Przypisanie bieżącej wartości zmiennej answer do zmiennej minusOne.
c. Zsumowanie wartości zmiennych minusOne oraz minusTwo i przypisanie tej sumy
zmiennej answer.
d. DekrementacjÄ™ zmiennej licznikowej n.
3. 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 instrukcje są dowolnymi
instrukcjami lub blokami instrukcji, pod warunkiem jednak, że ich wynikiem jest liczba typu
integer (lub jej wynik jest jednoznacznie konwertowalny do takiej liczby).. Należy również
pamiętać, że instrukcja switch sprawdza jedynie równość wyrażenia; 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 case i jego wykonanie jest kontynuowane aż do napotkania
instrukcji break (przerwij). Jeśli wartość wyrażenia nie pasuje do żadnej z wartości case,
wykonywana jest instrukcja default (domyślna). Jeśli nie występuje default i wartość
wyrażenia nie pasuje do żadnej z wartości case, instrukcja switch nie spowoduje żadnej akcji
i program przechodzi do następnych instrukcji w kodzie.
UWAGA Stosowanie w instrukcji switch przypadku default jest dobrym pomysłem. Jeśli nie
znajdziesz dla niego innego zastosowania, użyj go 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 (po case),
wykonanie przechodzi także do następnego przypadku case. Czasem takie działanie jest
zamierzone, ale zwykle jest po prostu błędem. Jeśli zdecydujesz się na wykonanie instrukcji w
kilku kolejnych przypadkach 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
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 przypadku case, wykonanie programu przechodzi do tego właśnie
przypadku. Wykonywanie instrukcji jest kontynuowane aż do końca ciała instrukcji switch lub
do czasu napotkania instrukcji break.
Jeśli wartość wyrażenia nie odpowiada żadnej z wartości przypadków case i występuje
przypadek default, wykonanie przechodzi do przypadku 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
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żna pominąć sprawdzanie zmiennej exit.
TAK NIE
Aby uniknąć głęboko zagnieżdżonych instrukcji Nie zapominaj o instrukcji break na końcu
if, używaj instrukcji switch.
każdego przypadku case, chyba że celowo
chcesz by program przeszedł bezpośrednio
Pieczołowicie dokumentuj wszystkie
dalej.
zamierzone przejścia pomiędzy przypadkami
case.
W instrukcjach switch stosuj przypadek
default, choćby do wykrycia sytuacji
pozornie niemożliwej.
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
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; i118: {
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); 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 funkcji main(). Z rozdziału 8., Wskazniki, 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 typu wyliczeniowego 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, jest wypisywany komunikat błędu i następuje odświeżenie (czyli
ponowne wypisanie) menu. Zauważ, że instrukcja switch posiada niemożliwy przypadek
default. Stanowi on pomoc przy debuggowaniu. Gdy program działa, instrukcja ta nigdy nie
powinna zostać wykonana.
Wyszukiwarka
Podobne podstrony:
r07 04 ojqz7ezhsgylnmtmxg4rpafsz7zr6cfrij52jhi
R07 (8)
r07 0001
r07 01
r07 03
r07
R07 (5)
r07 02
R07
R07 (19)
więcej podobnych podstron