Wprowadzenie do programowania w języku C — struktura programu
Transkrypt
Wprowadzenie do programowania w języku C — struktura programu
Wprowadzenie do programowania w języku C Część trzecia Struktura programu Autor Roman Simiński Kontakt [email protected] www.us.edu.pl/~siminski Niniejsze opracowanie zawiera skrót treści wykładu, lektura tych materiałów nie zastąpi uważnego w nim uczestnictwa. Opracowanie to jest chronione prawem autorskim. Wykorzystywanie jakiegokolwiek fragmentu w celach innych niż nauka własna jest nielegalne. Dystrybuowanie tego opracowania lub jakiejkolwiek jego części oraz wykorzystywanie zarobkowe bez zgody autora jest zabronione. Podstawy i języki programowania Język C Struktura programu Struktura programu jednomodułowego Problem Napisać program obliczający obwód i pole koła o promieniu podanym przez użytkownika. Spróbujmy to napisać z wykorzystaniem podprogramów W języku C nie występuje podział podprogramów na procedury i funkcje. Wszystkie podprogramy są funkcjami, istnieje jednak możliwość wykorzystywania funkcji jak procedur, bądź deklarowania funkcji tak, by przypominały procedury. #include <stdio.h> #include <stdlib.h> int main() { przywitanie(); Wywołanie funkcji return EXIT_SUCCESS; } int przywitanie( void ) { puts( "\nCzesc, tu funkcja przywitanie" ); return 0; } Copyright © Roman Simiński Definicja funkcji Strona : 2 Podstawy i języki programowania Język C Struktura programu Struktura programu jednomodułowego — funkcje Czy testowy program się skompiluje bez problemów? Tak, bez błędów i ostrzeżeń — w starszych wersjach kompilatorów. Tak, bez błędów lecz z ostrzeżeniem — w nowszych wersjach kompilatorów, oraz tych pracujących w trybie zgodności z normą ANSI. Nie, wystąpi błąd — w kompilatorach pracujących w trybie C++. Dlaczego występują te rozbieżności? Definicja funkcji przywitanie występuje po jej wywołaniu. Kompilator na etapie wywołania jej jeszcze nie zna. Czyni w stosunku do niej założenia — że to funkcja, której rezultatem jest wartość int. To założenie może być słusznie albo nie. Aby uniknąć niejednoznaczności, wprowadza się prototypy funkcji. Aby kompilator mógł kontrolować poprawność wywołania funkcji, należy to wywołanie poprzedzić definicją lub deklaracją wywoływanej funkcji. Deklaracja przyjmuje postać prototypu funkcji. Definicja funkcji int przywitanie( void ) { puts( "\nCzesc, tu funkcja przywitanie" ); return 0; } Copyright © Roman Simiński Prototyp funkcji int przywitanie( void ); Strona : 3 Podstawy i języki programowania Język C Struktura programu Struktura programu jednomodułowego — funkcje Ogólna postać definicji funkcji: <typ_rezultatu> nazwa_funkcji ( lista_parametrów_formalnych ) { ciało_funkcji } Ogólna postać prototypu funkcji: <typ_rezultatu> nazwa_funkcji ( lista_parametrów_formalnych ); Dwie wersje poprawnego programu przykładowego Z prototypem: Po „paskalowemu”: . . . int przywitanie( void ); int main() { przywitanie(); return EXIT_SUCCESS; } int przywitanie( void ) { . . . } Copyright © Roman Simiński . . . int przywitanie( void ) { . . . } int main() { przywitanie(); return EXIT_SUCCESS; } Strona : 4 Podstawy i języki programowania Język C Struktura programu Struktura programu jednomodułowego — funkcje Dlaczego przywitanie to funkcja, skoro nie udostępnia sensownego rezultatu? int przywitanie( void ) { puts( "\nCzesc, tu funkcja przywitanie" ); return 0; } Może lepiej tak, czyli jak zrobić z funkcji procedurę: void przywitanie( void ) { puts( "\nCzesc, tu funkcja przywitanie" ); } Ale może być kłopot int main() { przywitanie(); return EXIT_SUCCESS; } void przywitanie( void ) { puts( "\nCzesc, tu funkcja przywitanie" ); } Copyright © Roman Simiński Kompilator zakłada, że funkcja będzie miała rezultat int. Strona : 5 Podstawy i języki programowania Język C Struktura programu Struktura programu jednomodułowego — funkcje, podsumowanie Podsumowanie informacji o prototypach Starsze implementacje C dopuszczały wywoływanie funkcji wcześniej kompilatorowi nieznanych. W trakcie kompilowania wywołania nieznanej funkcji przez domniemanie przyjmowano, że zwraca ona wartość int i nic nie wiadomo na temat jej parametrów. Nie pozwalało to kompilatorowi kontrolować poprawności wywołania funkcji. Aby kompilator mógł kontrolować poprawność wywołania funkcji, należy to wywołanie poprzedzić definicją lub deklaracją wywoływanej funkcji. Deklaracja przyjmuje postać prototypu funkcji. Deklaracja i definicja funkcji powinna być zgodna. Jeżeli w obrębie jednego pliku wystąpi niezgodność, kompilator zgłosi błąd kompilacji. Copyright © Roman Simiński Strona : 6 Podstawy i języki programowania Język C Struktura programu Struktura programu jednomodułowego — funkcje, podsumowanie Uwagi na temat definicji funkcji: Słowo kluczowe void, będące nazwą typu, oznacza brak, nieobecność jakiejkolwiek wartości. Jeżeli typem rezultatu będzie typ określany słowem kluczowym void, to oznacza, iż funkcja nie udostępnia rezultatu – staje się wtedy czymś podobnym do procedury z języka Pascal. Jeżeli w miejscu listy parametrów formalnych występuje słowo kluczowe void, to oznacza, że funkcja nie posiada parametrów. Jeżeli funkcja „zwraca wartość” w miejscu wywołania, w ciele funkcji powinna wystąpić instrukcja return, a po niej, wyrażenie o typie zgodnym z typem rezultatu funkcji. Na liście parametrów formalnych, dla każdego parametru określamy jego typ. Copyright © Roman Simiński Strona : 7 Podstawy i języki programowania Język C Struktura programu Struktura programu jednomodułowego — obliczanie obwodu i pola koła A może by tak w końcu wrócić do programu, który mieliśmy napisać... #include <stdio.h> #include <stdlib.h> #define PI 3.14 #define MAKS_DL 80 void oblicz( void ) { float r; char s[ MAKS_DL ]; printf("\nPodaj promien R = " ); fgets( s, MAKS_DL, stdin ); r = atof( s ); void komunikat_wstepny( void ); void oblicz( void ); float obwod_kola( float r ); float pole_kola( float r ); int main() { komunikat_wstepny(); oblicz(); return EXIT_SUCCESS; } void komunikat_wstepny( void ) { puts("\nObliczam obwod ..."); } Copyright © Roman Simiński printf( "Obwod : %g\n", obwod_kola(r) ); printf( "Pole : %g\n", pole_kola(r) ); printf( "\nNacisnij Enter by ..." ); getchar(); } float obwod_kola( float r ) { return 2 * PI * r; } float pole_kola( float r ) { return PI * r * r; } Strona : 8 Podstawy i języki programowania Język C Struktura programu Struktura programu jednomodułowego — przykłady funkcji Kilka prostych funkcji void print_message( void ) { puts( "\nObliczam pole kola" ); } int zawsze_5( void ) /* Wiem, że to bez sensu :-\ */ { return 5; } float do_kwadratu( float a ) { return a * a; } float oblicz_spalanie( float paliwo, float dystans ) { return ( paliwo * 100 ) / dystans; } Copyright © Roman Simiński Strona : 9 Podstawy i języki programowania Język C Struktura programu Struktura programu jednomodułowego — przykłady funkcji Zamiast . . . printf("\nPodaj promien R = " ); fgets( s, MAKS_DL, stdin ); r = atof( s ); printf( "Obwod : %g\n", obwod_kola(r) ); printf( "Pole : %g\n", pole_kola(r) ); . . . A może tak: . . . printf("\nPodaj promien R = " ); r = wczytaj_liczbe_f(); printf( "Obwod : %g\n", obwod_kola( r ) ); printf( "Pole : %g\n", pole_kola( r ) ); . . . Przykład użytecznej funkcji float wczytaj_liczbe_f( void ) { char bufor_tekstowy[ 80 ]; fgets( bufor_tekstowy, 80, stdin ); return atof( bufor_tekstowy ); } Copyright © Roman Simiński Strona : 10 Podstawy i języki programowania Język C Struktura programu Struktura programu jednomodułowego — przykłady funkcji Zamiast . . . printf("\nPodaj promien R = " ); r = wczytaj_liczbe_f(); printf( "Obwod : %g\n", obwod_kola( r ) ); printf( "Pole : %g\n", pole_kola( r ) ); . . . A może tak: . . . r = wczytaj_liczbe_f( "\nPodaj promien R = " ); printf( "Obwod : %g\n", obwod_kola( r ) ); printf( "Pole : %g\n", pole_kola( r ) ); . . . Przykład użytecznej funkcji — wersja kolejna float wczytaj_liczbe_f( char komunikat[] ) { char bufor_tekstowy[ 80 ]; printf( komunikat ); fgets( bufor_tekstowy, 80, stdin ); return atof( bufor_tekstowy ); } Copyright © Roman Simiński Strona : 11 Podstawy i języki programowania Język C Struktura programu Struktura programu jednomodułowego — przykłady funkcji Przykład wykorzystania rezultatu do sygnalizacji błędów float oblicz_spalanie( float paliwo, float dystans ) { if( dystans == 0 ) return –1; else return fabs( ( paliwo * 100 ) / dystans ); } . . . spalanie = oblicz_spalanie( 40.5, 0 ); if( spalanie == -1 ) printf( "Nie dokonam obliczeń dla błędnych danych" ); else printf( "Spalanie %4.2f l na 100 km", spalanie ); . . . Uwaga na instrukcję return float oblicz_spalanie( float paliwo, float dystans ) { if( dystans == 0 ) printf( "Nie dokonam obliczeń dla zerowego dystansu" ); else return ( paliwo * 100 ) / dystans; } Copyright © Roman Simiński // return ??? Strona : 12 Podstawy i języki programowania Język C Struktura programu Struktura programu jednomodułowego — uwagi Parę pułapek związanych z definicjami funkcji W języku C wolno czasem opuścić słowo kluczowe int, wtedy zostanie ono przyjęte domyślnie. To, że tak robić można nie oznacza, że tak robić trzeba. Zapisy równoważne int fun() { . . . } fun() { . . . } Nawiasy po nazwie funkcji są konieczne, nawet gdy funkcja nie ma parametrów. Nawiasy występują zarówno przy definicji, deklaracji jak i przy wywołaniu funkcji. Jednak puste nawiasy wbrew pozorom nie oznaczają braku parametrów a ich nieokreśloną liczbę! Trzy następujące po sobie kropki ... oznaczają zmienną liczbę parametrów, jakie można przekazać funkcji. Zapisy równoważne int fun() { . . . } int fun( ... ) { . . . } Dozwolone wywołania funkcji fun: fun(); fun( 5 ); fun( "Ala" ); fun( 2 * a ); fun( "Ala" , "As" ); Copyright © Roman Simiński Strona : 13 Podstawy i języki programowania Język C Struktura programu Struktura programu jednomodułowego — uwagi Funkcja printf jest przykładem funkcji o zmiennej liczbie parametrów: printf( "Witaj w programie" ); printf( "Pole kola %g, jego obwod %g", pole_kola( r ), obwod_kola( r ) ); printf( "Spalanie %4.2f l na 100 km", ( paliwo * 100 ) / dystans ); W języku C każdy programista może pisać własne funkcje ze zmienną liczbą parametrów, wymaga to zastosowania odpowiednich makr – zostanie to omówione później. Słowo kluczowe void oznacza brak, nieobecność jakiejkolwiek wartości. void fun( void ) { . . . } Copyright © Roman Simiński Zapis oznacza, że funkcja fun nie udostępnia żadnej wartości, oraz nie otrzymuje żadnych parametrów. Strona : 14 Podstawy i języki programowania Język C Struktura programu Struktura programu jednomodułowego — uwagi W języku C wszystkie funkcje są zewnętrzne void oblicz_pole_kola( void ) { float r; char s[ MAKS_DL ]; printf("\nPodaj promien R = " ); gets( s ); r = atof( s ); printf( "Pole kola wynosi %g", pole_kola( r ) ); } float pole_kola( float r ) { return PI * r * r; } Procedure ObliczPoleKola; Var R : Real; Function PoleKola : Real; { Funkcja nielokalna } Const Pi = 3.14; Begin PoleKola := Pi * R * R; End; Begin WriteLn; WriteLn( ”Podaj promień R: ” ); ReadLn( R ); WriteLn( ”Pole wynosi: ”, PoleKola ); End; Copyright © Roman Simiński Strona : 15 Podstawy i języki programowania Język C Struktura programu Deklaracje i definicje zmiennych ― klasy pamięci Zmienne klasy auto — zmienne automatyczne void fun( float a ) { int i = 0; char c = 'A'; float f; if( i == 0 ) { float i = 100.0; int k; . . . } Przesłanianie identyfikatorów ― w obrębie tego bloku instrukcji if nazwa i oznacza, lokalną w tym bloki, zmienną typu float. } Zmienne klasy auto mogą być definiowane na początku każdego bloku (standard C89 języka C, w standardzie C99 i w języku C++ jest inaczej). Zmienne klasy auto pojawiają się i znikają wraz z wejściem i wyjściem sterowania do bloku w którym są zadeklarowane. Zmienne deklarowane wewnątrz bloku są automatycznymi, jeżeli nie podano klasy pamięci albo jawnie użyto specyfikatora auto. Copyright © Roman Simiński Strona : 16 Podstawy i języki programowania Język C Struktura programu Deklaracje i definicje zmiennych ― klasy pamięci Zmienne klasy auto — zmienne automatyczne,cd... #include <stdio.h> #include <stdlib.h> void fun( float a ) { int i = 0; int j; printf( "\na = %.2f | i = %d | j = %d", a, i, j ); } int main() { fun( 1.0 ); fun( 2.0 ); fun( 3.0 ); return EXIT_SUCCESS; } Zmienne klasy auto: Nie zachowują swoich wartości pomiędzy swoimi kolejnymi kreacjami. O ile nie zostaną zainicjalizowane, maja wartości przypadkowe. Parametry formalne funkcji też są klasy auto. Copyright © Roman Simiński Strona : 17 Podstawy i języki programowania Język C Struktura programu Deklaracje i definicje zmiennych ― klasy pamięci Gdzie w pamięci operacyjnej lokowane są zmienne automatyczne? Zmienna klasy auto tworzone są automatycznie, lokowane są na stosie. Stos to element procesu, służący do przechowywania danych chwilowych. Na stosie lokowane są zmienne auto, w tym argumenty funkcji, oraz adresy powrotu dla wywoływanych podprogramów. Stos ma ustalony i ograniczony rozmiar ― należy sprawdzić ustalenia rozmiaru stosu w opcjach kompilatora (konsolidatora). Może się zdarzyć, że stos ma rozmiar rzędu kilku kilobajtów. Wobec powyższego, niebezpieczna może być poniższa definicja dużej tablicy: void fun( void ) { float tab[ 10000 ]; . . . } Uwaga: użycie słowa kluczowego auto dla deklaracji zmiennej, która jest domyślnie właśnie klasy auto, jest bezcelowe: { { auto int i; } Copyright © Roman Simiński int i; } Strona : 18 Podstawy i języki programowania Język C Struktura programu Deklaracje i definicje zmiennych ― klasy pamięci Zmienne klasy static — zmienne statyczne #include <stdio.h> void fun_auto( void ) { int i = 1; printf( "\nAuto %d", i++ ); } void fun_static( void ) { static int i = 1; printf( "\nStatic %d\n", i++ ); } Operator ++ zwiększa o 1 wartość zmiennej przy której występuje. Zatem, zapis: i++ równoważny jest: i = i + 1 int main() { fun_auto(); fun_static(); fun_auto(); fun_static(); fun_auto(); fun_static(); . . . } Copyright © Roman Simiński Strona : 19 Podstawy i języki programowania Język C Struktura programu Deklaracje i definicje zmiennych ― klasy pamięci Zmienne klasy static — zmienne statyczne, cd... Zmienne statyczne mogą być lokalne w bloku lub zewnętrzne dla wszystkich bloków. Jeżeli zmienna wewnątrz bloku zostanie zadeklarowana ze specyfikatorem static, to: ✔ jest raz inicjowana wartością inicjalizatora lub nadawana jest jej wartość zerowa odpowiednio do typu. ✔ przechowuje wartość po opuszczeniu i ponownym wejściu do bloku. Statyczne zmienne lokalne stanowią prywatną, nieulotną pamięć danej funkcji czy bloku. Szkic przykładowego zastosowania zmiennej statycznej ― funkcja z limitem wywołań void fun_z_limitem_wywolan( void ) { static int licznik_wywolan = 0; if( licznik_wywolan < 10 ) { licznik_wywolan++; /* Tutaj odpowiednie instrukcje */ } else puts( "Wersja demo -- limit wywolan wyczerpany" ); } Copyright © Roman Simiński Strona : 20 Podstawy i języki programowania Język C Struktura programu Deklaracje i definicje zmiennych ― klasy pamięci Zmienne klasy register — zmienne rejestrowe Deklaracja zmiennej jako register jest równoważna z deklaracją auto, ale wskazuje że deklarowany obiekt będzie intensywnie wykorzystywany, i w miarę możliwości będzie umieszczony w rejestrze procesora. Jeżeli nie jest możliwe umieszczenie zmiennej w rejestrze, pozostaje ona w pamięci. Zmienne rejestrowe pozwalają zredukować zajętość pamięci i poprawić szybkość wykonania operacji takie zmienne wykorzystujących. Jednak większość współczesnych kompilatorów wykorzystuje optymalizację rejestrową, zatem wiele zmiennych i tak przechowywanych jest w rejestrach, mimo braku jawnej specyfikacji jako register. register i; for( i = 0; i < 10; i++ ) { . . . } int very_time_critical_fun( register int i, register char c ) { . . . } Copyright © Roman Simiński Strona : 21 Podstawy i języki programowania Język C Struktura programu Deklaracje i definicje zmiennych ― klasy pamięci Zmienne klasy extern — zmienne zewnętrzne #include <stdio.h> #include <stdlib.h> float dystans, paliwo; void komunikat_wstepny( void ) { . . . } int main() { komunikat_wstepny(); czytaj_dane(); pisz_wyniki(); return EXIT_SUCCESS; } void czytaj_dane( void ) { . . . dystans = fabs( atof( linia ) ); . . . paliwo = fabs( atof( linia ) ); } void pisz_wyniki( void ) { if( dystans == 0 ) printf( "Nie policze spalania dla zerowego dystansu" ); else printf( "Spalanie %4.2f l na 100 km", ( paliwo * 100 ) / dystans ); } Copyright © Roman Simiński Strona : 22 Podstawy i języki programowania Język C Struktura programu Deklaracje i definicje zmiennych ― klasy pamięci Zmienne klasy extern — zmienne zewnętrzne, cd... Zmienne zewnętrzne deklarowane są na zewnątrz wszystkich funkcji. Zasięg zmiennej zewnętrznej rozciąga się od miejsca deklaracji do końca tego pliku. Zmienne zewnętrzne istnieją stale, nie pojawiają się i nie znikają, zachowują swoje wartości i są dostępne dla wszystkich funkcji programy występujących w zakresie danej zmiennej. Zmienna zewnętrzna jest raz inicjowana wartością inicjalizatora lub nadawana jest jej wartość zerowa odpowiednio do typu. Jeżeli dla zmiennej zewnętrznej użyjemy specyfikacji static, to oznacza to uprywatnienie (ograniczenie dostępu) w obrębie danego pliku źródłowego. Zmienne zewnętrzne oraz ich właściwe definiowanie i deklarowanie mają istotne znaczenie przy organizacji programów wielomodułowych. Delikatne kwestie ― definicja a deklaracja extern int i; Deklaracja zmiennej jako extern nie powoduje przydziału pamięci i może występować w programie wiele razy. Copyright © Roman Simiński int i; Definicja zmiennej, powodująca przydzielenie pamięci. Strona : 23 Podstawy i języki programowania Język C Struktura programu Deklaracje i definicje zmiennych ― przekazywanie parametrów W języku C parametry przekazywane są przez wartość Każda funkcja może posiadać parametry — na etapie definicji funkcji określa się ich liczbę, typy i nazwy. Parametry te nazywane są parametrami formalnymi funkcji. float pole_trojkata( float a, float h ) /* Definicja funkcji */ { return 0.5 * a * h; a i h to nazwy parametrów formalnych } funkcji Każdą funkcje można wywołać. Na etapie wywołania funkcji określa się konkretne wartości jakie maja przyjąć parametry formalne. Wartości, z którymi jest wywoływana funkcja nazywane są parametrami aktualnymi wywołania funkcji. float podstawa = 10, wysokosc = 5, pole; pole = pole_trojkata( podstawa, wysokosc ); pole = pole_trojkata( 20, 10 ); Copyright © Roman Simiński podstawa i wysokosc oraz 20 i 10 to parametry aktualne Strona : 24 Podstawy i języki programowania Język C Struktura programu Deklaracje i definicje zmiennych ― przekazywanie parametrów W języku C parametry przekazywane są przez wartość W języku C obowiązuje zasada przekazywania parametrów przez wartość. Zakład ona, iż na etapie wywołania funkcji wartość parametru aktualnego kopiowana jest do odpowiedniego parametru formalnego. Parametr formalny i aktualny są od siebie niezależne i, po przekazaniu wartości, nie występuje pomiędzy nimi żadne powiązanie. W języku C nie występuje jawnie przekazywanie parametrów przez zmienną. Copyright © Roman Simiński Strona : 25 Język C Podstawy i języki programowania Struktura programu Ilustracja przekazywania parametrów przez wartość a -- to parametr formalny funkcji inc i -- to parametr aktualny wywołania funkcji inc Stan pamięci operacyjnej i 5 a 5 Kopiowanie wartości parametru aktualnego i do parametru formalnego a Wywołanie funkcji inc Wykonanie funkcji inc Stan pamięci operacyjnej i a 5 6 5 a++ Inkrementacja parametru formalnego a Copyright © Roman Simiński Po wykonaniu funkcji inc Stan pamięci operacyjnej i 5 a 6 Parametr a będący klasy auto jest usuwany Strona : 26