Funkcje w akcji
Transkrypt
Funkcje w akcji
Funkcje i ich wykorzystanie 1 Funkcje i ich wykorzystanie Ćwiczenie to poświęcone jest tworzeniu programu wykorzystującego podprogramy — funkcje — jako narzędzia do strukturalizacji i hierarchizacji kodu programu. Program ten pozwala na trening w stosowaniu funkcji. Mimo iż wykonywane w ramach tych ćwiczeń podprogramy są proste, pozwalają one na wyrobienie umiejętności systematycznego budowania programów zawierających większą liczbę linii kodu. Materiał teoretyczny zawarty jest w materiałach wykładowych. 1.1 Figury płaskie Należy program pozwalający na obliczanie pól i obwodów wybranych figur płaskich: kwadratu, prostokąta, koła, trójkąta, trapezu. Scenariusz działania programu: 1. Program wyświetla informację o jego przeznaczeniu. 2. Program wyświetla menu główne pozwalające na wybór figury, dla której mają być wykonane obliczenia. 3. Po wybraniu figury, program wczytuje wymagane dane oraz wyświetla obliczone pole i obwód. 4. Program wraca do menu głównego, pozwalając na powtórzenie obliczeń dla dowolnej figury. 5. Program kończy swoje działanie po naciśnięciu przez użytkownika wybranego klawisza zakończenia. Przykładowa organizacja komunikacji z użytkownikiem Przykładowy przebieg wykonania programu prezentuje, umieszczony dalej Rysunek 1 oraz Rysunek 2. Prototypowa wersja kodu programu przedstawiona jest dalej. Program zawiera przykładową implementację funkcji wczytującej liczbę rzeczywistą (funkcja wczytaj_liczbe_f). Wymagania Program ma być podzielony na procedury i funkcje realizujące spójne funkcjonalnie czynności. Ilustruje to przedstawiony dalej program przykładowy. Po realizacji pierwszej wersji programu, opartej na poniższym szablonie, należy koniecznie rozbudować program o elementy opisane w sekcji Rozszerzenia. 1 Funkcje i ich wykorzystanie Rysunek 1. Przebieg dialogu z użytkownikiem — menu główne Rysunek 2. Obliczenia dla kwadratu i powrót do menu Rozszerzenia Proszę rozbudować program tak, by dla każdej figury wyświetlał podmenu, pozwalające użytkownikowi wybrać, czy obliczone ma być pole czy też obwód. Należy zastosować schemat analogiczny do zastosowanego w programie głównym. Ponieważ takie podmenu będzie się powtarzać dla każdej z figur, należy obsługę tego podmenu zrealizować np. w postaci funkcji, tak, aby w programie nie powtarzać podobnych sekwencji kodu. Proszę rozszerzyć program o kontrolę poprawności prowadzanych parametrów na etapie ich wpisywania, tak, aby program nie pozwolił na wprowadzenie zerowej lub ujemnej liczby określającej np. bok, czy promień. Kontrolowane prowadzanie danych należy zawrzeć w odpowiednim podprogramie. Program przykładowy #include <stdio.h> #include <stdlib.h> #include <ctype.h> /* Prototypy wykorzystywanych w programie funkcji */ void wyswietl_menu( void ); void obliczenia_dla_kwadratu( void ); void obliczenia_dla_prostokata( void ); void obliczenia_dla_kola( void ); void obliczenia_dla_trojkata( void ); void obliczenia_dla_trapezu( void ); float wczytaj_liczbe_f( char komunikat[] ); void czyszczenie_bufora( void ); float pole_kwadratu( float bok ); float obwod_kwadratu( float bok ); int main() 2 Funkcje i ich wykorzystanie { int klawisz; puts( "\nObliczam parametry figur plaskich" ); do { wyswietl_menu(); klawisz = tolower( getchar() ); fflush( stdin ); switch( klawisz ) { case '1' : obliczenia_dla_kwadratu(); break; case '2' : obliczenia_dla_prostokata(); break; case '3' : obliczenia_dla_kola(); break; case '4' : obliczenia_dla_trojkata(); break; case '5' : obliczenia_dla_trapezu(); break; default : if( klawisz != 'z' ) putchar( '\a' ); break; } } while( klawisz != 'z' ); return EXIT_SUCCESS; } void wyswietl_menu( void ) { puts( "\n1.Kwadrat\n2.Prostokat\n3.Kolo\n4.Trojkat\n5.Trapez" ); printf( "\nWybierz numer figury lub Z by zakonczyc: " ); } void obliczenia_dla_kwadratu( void ) { float bok; printf( "\nObliczenia dla kwadratu" ); bok = wczytaj_liczbe_f( "\nPodaj dlugosc boku: " ); printf( "Pole : %g\nObwod: %g", pole_kwadratu( bok ), obwod_kwadratu( bok ) ); printf( "\nNacisnij Enter by powrocic do menu..." ); ( void )getchar(); } void obliczenia_dla_prostokata( void ) { } void obliczenia_dla_kola( void ) { } void obliczenia_dla_trojkata( void ) { } 3 Funkcje i ich wykorzystanie void obliczenia_dla_trapezu( void ) { } float pole_kwadratu( float bok ) { return bok * bok; } float obwod_kwadratu( float bok ) { return 4 * bok; } float wczytaj_liczbe_f( char komunikat[] ) { char bufor_tekstowy[ 80 ]; printf( komunikat ); fgets( bufor_tekstowy, 80, stdin ) ; return atof( bufor_tekstowy ); } Proszę przeanalizować kod i sposób analogiczny zaprogramować brakujące funkcje. Wiem, że niektóre podprogramy wyglądają na „naciągane”. Są krótkie, gdyby je wyeliminować, program stałby się krótszy. Nie to jest jednak naszym celem. Celem jest systematyczne i konsekwentne stosowanie podprogramów do podziału programu na funkcjonalnie spójne fragmenty oraz do wyeliminowania powtarzających się fragmentów kodu. 1.2 Funkcje identyfikujące rodzaj znaku Należy napisać funkcje, pozwalające na identyfikowanie typu znaku przekazanego parametrem. Rozważmy funkcję: int is_lower( char c ); Rezultatem funkcji ma być wartość 0, jeżeli znak c nie jest małą literą, lub wartość różną od zera (np. 1), gdy znak c jest małą literą. Wykorzystanie funkcji może być następujące: int main() { char znak; printf( "Wprowadz mala litere; " ); znak = getchar(); if( is_lower( znak ) ) printf( "Wprowadziales mala litere %c", znak ); else printf( "Nie wprowadziles malej litery!"); . . . } Pozostałe funkcje mogą być wykorzystane w analogiczny sposób. int is_upper( char c ); 4 Funkcje i ich wykorzystanie Rezultatem funkcji ma być wartość 0, jeżeli znak c nie jest dużą literą, lub wartość różną od zera (np. 1), gdy znak c jest dużą literą. int is_digit( char c ); Rezultatem funkcji ma być wartość 0, jeżeli znak c nie jest cyfrą dziesiętną, lub wartość różną od zera (np. 1), gdy znak c jest cyfrą dziesiętną. int is_oct_digit( char c ); Rezultatem funkcji ma być wartość 0, jeżeli znak c nie jest cyfrą ósemkową, lub wartość różną od zera (np. 1), gdy znak c jest cyfrą ósemkową. int is_hex_digit( char c ); Rezultatem funkcji ma być wartość 0, jeżeli znak c nie jest cyfrą szesnastkową, lub wartość różną od zera (np. 1), gdy znak c jest cyfrą szesnastkową. 1.3 Funkcje zamiany wielkości liter Należy napisać funkcje, pozwalające na zamianę wielkości znaku, będącego literą, odpowiednio z dużej na małą, oraz z małej na dużą (zakładamy wykorzystanie kodu ASCII): char to_lower( char c ); char to_upper( char c ); Jeżeli funkcja to_lower otrzyma w parametrze c znak będący dużą literą, rezultatem funkcji ma być odpowiednia mała litera. Jeżeli znak c nie jest dużą literą, rezultatem funkcji ma być znak c. Analogicznie, jeżeli funkcja to_upper otrzyma w parametrze c znak będący małą literą, rezultatem funkcji ma być odpowiednia duża litera. Jeżeli znak c nie jest małą literą, rezultatem funkcji ma być znak c. Należy wykorzystać fakt, że przesunięcie pomiędzy literami dużymi a małymi w kodzie ASCII wynosi 32 (zobacz materiały wykładowe). Wykorzystanie funkcji to_lower może być następujące: int main() { char znak; printf( "Wprowadz mala litere; " ); znak = getchar(); if( ! is_lower( znak ) ) printf( "Nie wprowadziles malej litery!"); else { znak = to_upper( znak ); printf( "Duzy ddpowiednik wprowadzonej litery to %c", znak ); } . . . } 5 Funkcje i ich wykorzystanie 1.4 Wczytywanie liczby z zakresu Należy napisać funkcję: float czytaj_liczbe_z_zakresu( float dol, float gora ); Funkcja odczytuje z klawiatury liczbę rzeczywistą, przy czym pozwala tylko na wprowadzenie liczby należącej do przedziału domkniętego określonego parametrami dol i gora. Odpytywanie użytkownika trwa tak długo, aż zostanie wprowadzona liczba z odpowiedniego zakresu. Należy kontrolować wartości parametrów dol i gora oraz zaproponować metodę sygnalizacji błędu. 1.5 Równanie kwadratowe Dane jest równanie kwadratowe: Ax2 + Bx + Cx = 0 Należy napisać funkcje wyznaczające deltę, pojedynczy pierwiastek, pierwiastki podwójne. Funkcja obliczająca deltę: float delta( float a, float b, float c ); Funkcja obliczająca pierwiastek podwójny (delta równa zero): float x0( float a, float b, float c ); Funkcja obliczająca dwa pierwiastki (delta większa od zera): float x1( float a, float b, float c, float delta ); float x2( float a, float b, float c, float delta ); Proszę napisać program rozwiązujący równanie kwadratowe w oparciu o te funkcje. 1.6 Układ równań liniowych Dany jest układ równań liniowych A1x + B1y = C1 A2x + B2y = C2 Rozwiązanie układu równań może polegać na wyliczeniu odpowiednich wyznaczników W, Wx, Wy a następnie ich ilorazów — zgodnie z informacjami poznanym na zajęciach z matematyki. Proszę napisać funkcje wyznaczające wartości wyznaczników: float w( float a1, float b1, float a2, float b2 ); float wx( float c1, float b1, float c2, float b2 ); float wy( float a1, float c1, float a2, float c2 ); Należy zaprojektować i zaimplementować program pozwalający na rozwiązywanie dowolnego układu takich równań. Program powinien umożliwiać wczytanie współczynników A1, B1, C1, B2, C2, następnie powinien wyznaczyć rozwiązania równań metodą wyznacznikową. Należy 6 Funkcje i ich wykorzystanie identyfikować i prawidłowo zareagować na sytuację, gdy układ jest nieokreślony. 1.7 Suplement — problem buforowanego wejścia Funkcja getchar() wczytuje kolejny znak ze strumienia wejściowego stdin. Na poziomie biblioteki funkcji identyfikowanej przez nagłówek stdio.h następuje buforowanie informacji. Można podejrzewać, że buforowanie to jest przyczyną braku natychmiastowej reakcji na naciśnięcie klawisza, konieczne jest potwierdzenie wprowadzanego znaku naciśnięciem klawisza Enter. Niestety nawet zakaz buforowania strumienia (zobacz funkcja setbuf) nie przynosi zwykle rezultatu. Przyczyną jest nałożenie się dwóch mechanizmów buforowania — wewnętrznego, właściwego dla biblioteki standardowego wejścia/wyjścia oraz buforowania na poziomie systemu operacyjnego. I to właśnie ten ostatni rodzaj buforowania sprawia problem przy wczytywaniu danych. Istnieją dwa sposoby czyszczenia bufora wypełnionego niechcianymi znakami. Jeden wykorzystuje funkcję fflush, przeznaczoną właśnie do zapisywania buforów systemowych. Wydaje się, że skutecznym sposobem na wyeliminowanie nieodczytanych jeszcze znaków, oczekujących w buforze, będzie: c = getchar(); fflush( stdin ); Rozwiązanie to jest skuteczne i zwykle działa. Dlaczego zwykle? Otóż zastosowanie funkcji fflush powinno się ograniczać do strumieni otwartych dla zapisu i aktualizacji. Dosłownie: „The function fflush forces a write of all buffered data for the given output or update stream via the stream's underlying write function. … .” Zatem funkcja ta nie powinna działać dla strumienia wejściowego stdin, otwartego dla odczytu. Dlaczego zatem działa? Implementacje funkcji bibliotecznych powinny być zgodne z ustalonymi standardami zdefiniowanymi odpowiednio w normach ANSI/ISO. Jednak zdarza się, że norma nie precyzuje pewnych szczególnych przypadków. Powoduje to, że niektóre implementacje różnie zachowują się w owych, niedoprecyzowanych sytuacjach. Zobaczmy inny opis funkcji fflush: „If the given stream has been opened for writing operations the output buffer is phisically written to the file. If the stream was open for reading operations the content of the input buffer is cleared. ... .” Według tego opisu „wymiecienie” bufora strumienia otwartego do odczytu powoduje skasowanie zawartości bufora. I o to nam właśnie chodzi. Niestety, pisząc oprogramowanie o maksymalizowanym stopniu przenośności nie możemy oczekiwać, że pewna funkcja zadziała tak jak chcemy w zakresie swych cech niezdefiniowanych. Zatem dobrze jest zamiast funkcją fflush posłużyć się własną funkcją o implementacji pokazanej niżej. 7 Funkcje i ich wykorzystanie /*---------------------------------------------------------------Funkcja: void clr_input_buffer() Przeznaczenie i opis dzialania Funkcja powoduje wyczyszczenie bufora strumienia stdin, poprzez Odczytanie wszystkich znakow zapisanych w buforze, lacznie ze znakiem \n. Funkcja jest bezparametrowa i nie udostepnia rezultatu ----------------------------------------------------------------*/ void clr_input_buffer( void ) { while( getchar() != '\n' ) ; } 8