LEKCJA 13. Jak tworzyć w programie pętle i rozgałęzienia. _______________________________________________________________ W trakcie tej lekcji: 1. Dowiesz się znacznie więcej o pętlach. 2. Przeanalizujemy instrukcje warunkowe i formułowanie warunków. _______________________________________________________________ Zaczniemy tę lekcję nietypowo - od słownika, ponieważ dobrze jest rozumieć dokładnie co się pisze. Tym razem słownik jest trochę obszerniejszy. Pozwalam sobie przytoczyć niektóre słowa powtórnie - dla przypomnienia i Twojej wygody. Do organizacji pętli będą nam potrzebne następujące słowa: [S!] conditional expressions - wyrażenia warunkowe structural loops - pętle strukturalne ________________________________________________________________ if - jeżeli (poprzedza warunek do sprawdzenia); else - a jeśli nie, to (w przeciwnym wypadku...); for - dla; while - dopóki (dopóki nie spełnimy warunku); do - wykonaj, wykonuj; break - przerwij (wykonanie pętli); switch - przełącz; case - przypadek, wariant (jedna z możliwości); goto - idź do... default - domyślny, (automatyczny, pozostały); continue - kontynuuj (pętlę); ________________________________________________________________ UWAGA: W C/C++ nie stosuje się słowa THEN. PĘTLA TYPU for. Ogólna postać pętli for jest następująca: for (W_inicjujące; W_logiczne; W_kroku) Instrukcja; gdzie skrót W_ oznacza wyrażenie. Każde z tych wyrażeń może zostać pominięte (patrz --> for(;;)). Wykonanie pętli for przebiega następująco: 1. Wykonanie JEDEN raz WYRAŻENIA INICJUJĄCEGO. 2. Obliczenie wartości LOGICZNEJ wyrażenia logicznego. 3. Jeśli W_logiczne ma wartość PRAWDA (TRUE) nastąpi wykonanie Instrukcji. 4. Obliczenie wyrażenia kroku. 5. Powtórne sprawdzenie warunku - czy wyrażenie logiczne ma wartość różną od zera. Jeśli wyrażenie logiczne ma wartość zero, nastąpi zakończenie pętli. Warunek jest testowany PRZED wykonaniem instrukcji. Jeśli zatem nie zostanie spełniony warunek, instrukcja może nie wykonać się ANI RAZ. Instrukcja może być INSTRUKCJĄ GRUPUJĄCĄ, składającą się z instrukcji prostych, deklaracji i definicji zmiennych lokalnych: { ciąg deklaracji lub definicji; ciąg instrukcji; } Ogromnie ważny jest fakt, że C++ ocenia wartość logiczną wyrażenia według zasady: 0 - FALSE, FAŁSZ, inaczej ZERO LOGICZNE jeśli WYRAŻENIE == 0 lub jest fałszywe w znaczeniu logicznym; 1 - TRUE, PRAWDA, JEDYNKA LOGICZNA, jeśli wyrażenie ma DOWOLNĄ WARTOŚĆ NUMERYCZNĄ RÓŻNĄ OD ZERA (!) lub jest prawdziwe w sensie logicznym. Przykład: "Klasycznie" zastosowana pętla for oblicza pierwiastki kwadratowe kolejnych liczb całkowitych. #include #include void main() { int n; for (n=0; n<=100; n++) printf("%f\t", sqrt(n)); getch(); } Wyrażenie inicjujące może zostać pominięte. Innymi słowy zmienna może zostać zainicjowana na zewnątrz pętli, a pętla przejmie ją taką jaka jest w danym momencie. Przykładowo: ..... { float n; n=(2*3)/(3*n*n - 1.234); ...... for (; n<=100; n++) printf("%f4.4\t", sqrt(n)); Przykład: Warunek przerwania pętli może mieć także inny charakter. W przykładzie pętla zostanie przerwana, jeśli różnica pomiędzy kolejnymi pierwiastkami przekroczy 3.0. void main() { float y=0, n=0; for (; (sqrt(n)-y)<=3.0; n++) €€€€€{ y=sqrt(n); €€€€€ printf("%f2.3\t", y); €€€€€} getch(); } UWAGA: Sprawdź, czy nawias (sqrt(n)-y)<=3 można pominąć? Jaki jest priorytet operatorów w wyrażeniach: (sqrt(n)-y)<=3.0 i sqrt(n)-y<=3.0 Jaki będzie wynik? Dlaczego? Przykład: Instrukcja stanowiąca ciało pętli może być instrukcją pustą a wszystkie istotne czynności mogą zostać wykonane w ramach samego "szkieletu" for. Program przykładowy sprawdza ile kolejnych liczb całkowitych trzeba zsumować by uzyskać sumę nie mniejszą niż tysiąc. void main() { float SUMA=0, n=0; for (; SUMA < 1000; SUMA+=(++n)); printf("%f", n); getch(); } [???] CZY NIE MOŻNA JAŚNIEJ ??? ________________________________________________________________ Można, ale po nabraniu wprawy takie skróty pozwolą Ci przyspieszyć tworzenie programów. Zmniejszenie wielkości pliku tekstowego jest w dzisiejszych czasach mniej istotne. Rozszyfrujmy zapis SUMA+=(++n). Preinkrementacja następuje PRZED użyciem zmiennej n, więc: 1. Najpierw ++n, czyli n=n+1. 2. Potem SUMA=SUMA+ (n+1). Dla wyjaśnienia przedstawiam dwie wersje (obie z pętlą for): void main() { void main() float SUMA=0; { float SUMA=0, n=0; int n; for (; SUMA < 1000; SUMA+=(++n)); } clrscr(); for (n=0; SUMA<=1000; n++) { SUMA=SUMA+n; } } ________________________________________________________________ To jeszcze nie koniec pokazu elastyczności C/C++. W pętli for wolno nam umieścić więcej niż jedno wyrażenie inicjujące i więcej niż jedno wyrażenie kroku oddzielając je przecinkami. flat a, b, c; const float d=1.2345; void main() { for (a=5,b=3.14,c=10; c; ++a,b*=d,c--) printf("\n%f\t%f\t%f", a,b,c); getch(); } Zwróć uwagę, że zapisy warunku: if (c)...; i if (c != 0)...; są w C++ równoważne. Przykład: Program będzie pisał kropki aż do naciśnięcia dowolnego klawisza, co wykryje funkcja kbhit(), będąca odpowiednikem KeyPressed w Pascalu. Zapis !kbhit() oznacza "NIE NACIŚNIĘTO KLAWISZA", czyli w buforze klawiatury nie oczekuje znak. Zwróć uwagę, że funkcja getch() może oczekiwać na klawisz w nieskończoność. Aby uniknąć kłopotliwych sytuacji, czasem znacznie wygodniej jest zastosować kbhit(), szczególnie, jeśli czekamy na DOWOLNY klawisz. void main() { for (; !kbhit(); printf(".")); } Przykład: Wskaźnik w charakterze zmiennej roboczej w pętli typu for. Pętla powoduje wypisanie napisu. char *Ptr = "Jakis napis"; void main() { for (; (*Ptr) ;) printf("%c",*Pt++); getch(); } AUTOMATYCZNE GENEROWANIE TABLIC W PĘTLI for Na dyskietce znajdziesz jeszcze kilka przykładów FORxx.CPP użycia pętli. A teraz, zanim będziemy kontynuować naukę - przykładowy program do zabawy. Pętla for służy do wykrywania zgodności klawisza z elementami tablicy TABL[]. W tablicy D[] umieszczone zostały częstotliwości kolejnych dźwięków, które program oblicza sam, wykorzystując przybliżony współczynnik. [P030.CPP] # include "conio.h" # include "dos.h" # include "math.h" # include "stdio.h" char TABL[27]={"zsxdcvgbhnjm,ZSXDCVGBHNJM<"}; char k; float D[26]; int i; void main() { clrscr(); printf("[A]- KONIEC, dostepne klawisze: \n"); printf(" ZSXDCVGBHNJM,i [Shift]"); D[0]=200.0; for(i=1; i<26; i++) D[i]=D[i-1]*1.0577; for (;;) //patrz przyklad {*} { k = getch(); for(i=0; i<27; i++) { if (k==TABL[i]) { sound(D[i]); delay(100); nosound(); } }; if (k=='a'|| k=='A') break; //Wyjście z pętli. k = '0'; }; } Po uruchomieniu programu klawisze działają w sposób przypominający prosty klawiszowy instrument muzyczny. Automatyczne zainicjowanie tablicy wielowymiarowej możemy pozostawić C++. Wielkość tablicy może być znana na etapie kompilacji programu, lub określona w ruchu programu. C++ traktuje stałą (const) jako szczególny przypadek wyrażenia stałowartościowego (ang. true constant expression). Jeśli zadeklarowaliśmy zmienną wymiar jako stałą, możemy zastosować ją np. do zwymiarowania tablicy TAB[]. Przykład poniżej przedstawia takie właśnie zastosowanie stałych w C++. [P031.CPP] /* Inicjowanie tablicy przy pomocy stałej */ # include main() { const int wymiar = 7; //Deklaracja stałej char TAB[wymiar]; //Deklaracja tablicy cout << "\n Wielkosc tablicy TAB[] wynosi: " << sizeof TAB; cout << " bajtow."; return 0; } Umożliwia to dynamiczne inicjowanie tablic pod warunkiem rygorystycznego przestrzegania zasady, że do zainicjowana stałej możemy zastosować wyłącznie wyrażenie stałowartościowe. . [S] sizeof - wielkość w bajtach. DANE PREDEFINIOWANE. Dla ułatwienia życia programiście producenci kompilatorów C++ stosują stałe predefiniowane w plikach nagłówkowych, np.: _stklen - wielkość stosu, O_RDONLY - tryb otwarcia pliku "tylko do odczytu", GREEN - numer koloru w palecie, itp., itp. Predefiniowanych stałych możemy używać do deklarowania indeksów/rozmiarów tablic. PĘTLA TYPU while. Pętlę typu while stosuje się na ogół "do skutku", tj. do momentu spełnienia warunku, zwykle wtedy, gdy nie jesteśmy w stanie przewidzieć potrzebnej ilości cykli. Konstrukcja pętli while wygląda następująco: while (Wyrażenie_logiczne) Instrukcja; Jeśli Wyrażenie_logiczne ma wartość różną od zera, to zostanie wykonana Instrukcja. Sprawdzenie następuje PRZED wykonaniem Instrukcji, toteż Instrukcja może nie zostać wykonana ANI RAZU. Instrukcja może być INSTRUKCJĄ GRUPUJĄCĄ. Przykład Stosujemy pętlę while do programu piszącego kropki (patrz wyżej). void main() { while (!kbhit()) printf("."); } Przykład Stosujemy pętlę while w programie obliczającym sumę. void main(){ float SUMA=0, n=0; clrscr(); while (SUMA<1000) SUMA+=(++n); printf("SUMA: %4.0f ostatnia liczba: %3.0f", SUMA, n); getch(); } [P032.CPP] char *Pointer1="Koniec napisu to \0, *Pointer==0 "; char *Pointer2="Koniec napisu to \0, *Pointer==0 "; void main(){ clrscr(); while (*Pointer1) printf("%c", *Pointer1++); printf("\nZobacz ten NUL na koncu lancucha znakow\n"); while (*Pointer2) printf("%c", *Pointer2++); printf("%d", *Pointer2); getch(); } PĘTLA do...while. Konstrukcja dwuczłonowa do...while tworzy pętlę, która: * jest wykonywana zawsze CO NAJMNIEJ JEDEN RAZ, ponieważ warunek jest sprawdzany nie na wejściu do pętli, a na wyjściu z pętli; * przerwanie pętli powodowane jest przez NIESPEŁNIENIE WARUNKU. Schemat pętli do...while jest następujący: do Instrukcja while (Wyrażenie_logiczne); Instrukcja może być instrukcją grupującą. Przykład: void main() { do {printf(".");} while (!kbhit()); printf("Koniec petli...."); } INSTRUKCJA WARUNKOWA if, if...else i if...else...if.. Instrukcja warunkowa ma postać: if (Wyrażenie) Instrukcja; if (Wyrażenie) Instrukcja1 else Instrukcja2; Jeśli Wyrażenie ma wartość różną od zera (LOGICZNĄ bądź NUMERYCZNĄ !) to zostanie wykonana Instrukcja1, w przeciwnym razie wykonana zostanie Instrukcja2. Instrukcje mogą być instrukcjami grupującymi. Słowa kluczowe if i else mogą być stosowane wielokrotnie. Pozwala to tworzyć np. tzw. drzewa binarne. Przykład: void main() { float a; scanf("%f", &a); if (a<0) printf("Ujemna!"); €€€€€else if (a==0) printf("Zero!"); €€€€€€€€€€else printf("Dodatnia!"); } Przykład: if (a>0) if (a<100) printf("Dwucyfrowa"); else printf("100+"); inaczej: if(a>0) {if(a<100) printf("Dwucyfrowa"); else printf("100+");} Wyrażenie może zawierać operatory logiczne: if (a>0 && a<100) printf("Dwucyfrowa"); else printf("100+"); Zapis 100+ oznacza "sto i więcej". Przykład: C++ pozwala na krótszy zapis instrukcji warunkowej: (a>b)? MAX=a : MAX=b; inaczej: if (a>b) MAX=a; else MAX=b; INSTRUKCJE break i continue. Instrukcja break powoduje natychmiastowe bezwarunkowe opuszczenie pętli dowolnego typu i przejście do najbliższej instrukcji po zakończeniu pętli. Jeśli w pętli for opuścimy wyrażenie logiczne, to zostanie automatycznie przyjęte 1. Pętla będzie zatem wykonywana bezwarunkowo w nieskończoność. W przykładzie poniżej nieskończoną pętlę przerywa po podaniu z kalwiatury zera instrukcja break. Przykład: float a, sigma=0; void main(){ for (;;) { printf("\n Podaj liczbe do sumowania\n"); scanf("%f", &a); if (a==0) break; sigma+=a; printf("\n SUMA: %f",sigma); } printf("Nastapil BREAK"); getch(); } Instrukcja continue. Instrukcja continue powoduje przedwczesne, bezwarunkowe zakończenie wykonania wewnętrznej instrukcji pętli i podjęcie próby realizacji następnego cyklu pętli. Próby, ponieważ najpierw zostanie sprawdzony warunek kontynuacji pętli. Program z przykładu poprzedniego zmodyfikujemy w taki sposób, by * jeśli liczba jest dodatnia - dodawał ją do sumy sigma; * jeśli liczba jest ujemna - nie robił nic, pomijał bieżącą pętlę przy pomocy rozkazu continue; (Ponieważ warunek wejściowy pętli jest zawsze spełniony, to pętlę zawsze uda się kontynuować.) * jeśli liczba równa się zero - przerywał pętlę instrukcją break Przykład: float a, sigma=0; void main() { for (;;) { printf("\n Sumuje tylko liczby dodatnie\n"); scanf("%f", &a); if (a<0) continue; if (a==0) break; sigma+=a; printf("\n SUMA: %f",sigma); } printf("Nastapil BREAK"); getch(); } INSTRUKCJE switch i case. Instrukcja switch dokonuje WYBORU w zależności od stanu wyrażenia przełączającego (selector) jednego z możliwych przypadków - wariantów (case). Każdy wariant jest oznaczony przy pomocy stałej - tzw. ETYKIETY WYBORU. Wyrażenie przełączające może przyjmować wartości typu int. Ogólna postać istrukcji jest następująca: switch (selector) { case STAŁA1: Ciąg_instrukcji-wariant 1; case STAŁA2: Ciąg_instrukcji-wariant 2; ............................... case STAŁAn: Ciąg_instrukcji-wariant n; default : Ostatni_ciąg_instrukcji; } Należy podkreślić, że po dokonaniu wyboru i skoku do etykiety wykonane zostaną również WSZYSTKIE INSTRUKCJE PONIŻEJ DANEJ ETYKIETY. Jeśli chcemy tego uniknąć, musimy dodać rozkaz break. [P033.CPP] # define pisz printf //dla przypomnienia # include void main() { int Numer_Dnia; pisz("\nPodaj numer dnia tygodnia\n"); scanf("%d", &Numer_Dnia); switch(Numer_Dnia) { case 1: pisz("PONIEDZIALEK."); case 2: pisz("WTOREK"); case 3: pisz("SRODA."); case 4: pisz("CZWARTEK."); case 5: pisz("PIATEK."); case 6: pisz("SOBOTA."); case 7: pisz("NIEDZIELA."); default: pisz("\n *********************"); } } Zwróć uwagę, że w przykładzie wariant default zostanie wykonany ZAWSZE, nawet jeśli podasz liczbę większą niż 7. [P034.CPP] # define pisz printf # include void main() { int Numer_Dnia; pisz("\nPodaj numer dnia tygodnia\n"); scanf("%d", &Numer_Dnia); switch(Numer_Dnia) { case 1: pisz("PON."); break; case 2: pisz("WTOR"); break; case 3: pisz("SRO."); break; case 4: pisz("CZW."); break; case 5: pisz("PIO."); break; case 6: pisz("SOB."); break; case 7: pisz("NIEDZ."); break; default: pisz("\n ?????"); } } Instrukcja break przerywa wykonanie. Wariant default zostanie wykonany TYLKO w przypadku podania liczby większej niż 7. INSTRUKCJA POWROTU return. Służy do zakończenia wykonania zawierającej ją funkcji i może mieć postać: return; return stała; return Wyrażenie; return (wyrażenie); Przykład: Definiujemy funkcję _dodaj() zwracającą, poprzez instrukcję return wartość przekazanego jej w momencie wywołania argumentu powiększoną o 5. float _dodaj(float x) { x+=5; return x; } Funkcja _dodaj() zwraca wartość i nadaje tę wartość zmiennej wynik zadeklarowanej nazewnątrz funkcji i znanej w programie głównym. A oto program w całości. [P035.CPP] float funkcja_dodaj(float x) { x += 5; return x; } float dana = 1, wynik = 0; void main() { clrscr(); wynik = funkcja_dodaj(dana); printf("%f", wynik); } INSTRUKCJA SKOKU BEZWARUNKOWEGO goto I ETYKIETY. Składnia instrukcji skoku goto jest następująca: goto Identyfikator_etykiety; UWAGA: Po każdej etykiecie musi wystąpić CO NAJMNIEJ JEDNA INSTRUKCJA. Jeśli etykieta oznacza koniec programu, to musi po niej wystąpić instrukcja pusta. Instrukcja goto nie cieszy się powodzeniem ani dobrą sławą (niesłusznie!). Ostrożne i umiejętne jej stosowanie jeszcze nikomu nie zaszkodziło. Należy tu zaznaczyć, że etykieta nie wymaga deklaracji. Przykład: Program poniżej generuje dźwięki i "odlicza". [P036.CPP] #include #include void main() { int czestotliwosc=5000, n=10, milisekundy=990; printf("\n"); start: { sound(czestotliwosc); delay(milisekundy); nosound(); czestotliwosc/=1.2; printf("%d\b", --n); if (n) goto start; //petle strukturalne zrob sam(a) } koniec: ; } // Tu jest instrukcja pusta. [S!] DOS API function names - nazwy funkcji z interfejsu DOS ________________________________________________________________ sound - dźwięk; delay - opóźnienie, zwłoka; nosound - bez dźwięku (wyłącz dźwięk); ________________________________________________________________ [Z] ________________________________________________________________ 1. Biorąc pod uwagę, że iloraz częstotliwości kolejnych dźwięków jest stały tzn. Fcis/Fc=Ffis/Ff=....=const oraz, że oktawa to podwojenie częstotliwości, opracuj program i oblicz częstotliwości poszczególnych dźwięków. 2. Spróbuj zastosować w programie przykładowym kolejno pętle for, while, do...while. 3. Zastosuj we własnym programie doświadczalnym instrukcję switch.