Funkcje. Spotkanie 5 Dr inż. Dariusz JĘDRZEJCZYK Tworzenie i używanie funkcji Przekazywanie argumentów do funkcji Domyślne wartości argumentów Przeładowanie nazw funkcji Dzielenie programu na kilka plików Przykłady 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 2 Funkcje są podstawowymi jednostkami wykonawczymi i są jednym z elementów pozwalających na projektowanie. Realizują coś, co określało się zazwyczaj jako podprogram (ang. subroutine), aczkolwiek w C++ mają one nieco większe możliwości. Te funkcje możemy tylko wywoływać, umożliwiają one zatem programowanie proceduralne, jednak jeszcze nie funkcjonalne. <mod> <typzwr> <nazwa>( <typ> <spi> <arg>, <typ> <spi> <arg> <...>); 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 3 1
Definicja funkcji - zawiera instrukcje umożliwiające realizację odpowiedniego fragmentu programu. W odróżnieniu od deklaracji przydzielany jest obszar pamięci, w którym znajdzie się kod wynikowy funkcji. Deklaracja funkcji - informacja dla kompilatora, że funkcja o zadanej nazwie, zwracanym typie oraz liczbie i typie argumentów może zostać użyta w danym module programu. Deklaracja funkcji musi kończyć się średnikiem. Nazywana jest również prototypem funkcji. Wywołanie funkcji - ma postać nazwy tej funkcji z podaną w nawiasach okrągłych listą oddzielonych przecinkami argumentów. Oczywiście liczba argumentów wywołania musi być dokładnie taka jak liczba parametrów w deklaracji i definicji funkcji. 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 4 Deklaracja <mod><typzwr><nazwa>( Definicja <mod><typzwr><nazwa>( { /* ciało funkcji */ <typ><spi><arg>, <typ><spi><arg>, <...>); <typ><spi><arg>, <typ><spi><arg>, <...>) 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 5 <mod> - opcjonalny modyfikator funkcji. <typzwr> - Jest to typ danej, jaka będzie zwrócona przez funkcję, jeśli jej wywołanie zostało umieszczone w wyrażeniu. Wynik zwracamy instrukcją return, a funkcja nie zwraca wyniku jeśli wynik ma być void. <nazwa> - Nazwa funkcji, przez którą będzie ona wywoływana. Wbrew pozorom, w C++ nie jest to jednoznaczna identyfikacja funkcji; o jednoznacznej identyfikacji stanowi poza nazwą również lista typów argumentów. <typ> <spi> <arg> - Deklaracja zmiennej lokalnej, przez którą będzie przekazywany argument. Element <arg> jest opcjonalny w deklaracji i nie musi się on zgadzać z tym podanym w definicji. Element <spi> - sposób przekazywania informacji do funkcji. 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 6 2
static - funkcja ma zasięg wewnętrzny i nie jest dostępna w innych jednostkach kompilacji, extern - nie oznacza nic, bowiem sygnowaną przez niego właściwość funkcja posiada domyślnie. inline - funkcja powinna zostać rozwinięta w miejscu wywołania. Należy pamiętać, że: można jej używać, jeśli funkcja jest niewielkich rozmiarów, dają większe pole do popisu kompilatorowi w dziedzinie optymalizacji, funkcja taka powinna być ZDEFINIOWANA w każdej jednostce kompilacji osobno (deklarować TYLKO w plikach nagłówkowych albo tylko na terenie jednej jednostki kompilacji). 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 7 wartość referencja wskaźnik typ nieokreślony int mojafunkcja(void); int& mojafunkcja(void); int* mojafunkcja(void); void mojafunkcja(void); 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 8 Słowo kluczowe return może występować wewnątrz ciała funkcji i potrafi ono zwrócić wartość, którą da się odczytać w miejscu, gdzie funkcja została wywołana. Wykorzystanie return pozwala na zwrócenie tylko jednego obiektu. Ponadto: może występować wewnątrz funkcji więcej niż raz, może występować w funkcjach, które nie zwracają wartości, może zwracać obiekt (liczbę lub zmienną), może zwracać wyrażenie (które przed zwróceniem zostanie obliczone). 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 9 3
Podczas nadawania nazw funkcji należy wykorzystać konwencję nazw taką jak w przypadku nazywania zmiennych. Ponadto nazwa funkcji: powinna odpowiadać temu co realizuje funkcja, powinna być odczasownikowa np.: obliczanie, wypisanie itp.. może składać się z dwóch lub więcej wyrazów. Wyrazy nie powinny być oddzielone spacją 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 10 <typ> - typ argumentu przekazywanego do funkcji <spi> - sposób przekazywania informacji do funkcji może być realizowany przez (przykład deklaracji dla jednego argumentu): wartość void mojafunkcja( int pierwszyargument ); referencję void mojafunkcja( int& pierwszyargument ); wskaźnik void mojafunkcja( int* pierwszyargument ); <arg> - nazwa przekazywanego argumentu w przykładzie powyżej to: pierwszyargument 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 11 Do funkcji można przekazać dowolną liczbę argumentów o dowolnym typie i dowolnym sposobie przekazywania informacji. void mojafunkcja( int a, float b, char c); void mojafunkcja( int& a, float& b, char& c); void mojafunkcja( int& a, float* b, char c); void mojafunkcja( int* a, float* b, char* c); void mojafunkcja( int* a, float b, char& c); void mojafunkcja( int a, float& b, char c); 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 12 4
Domyślne wartości argumentów funkcji powinny znaleźć się: w definicji jeśli występuje tylko definicja w deklaracji jeśli występuje deklaracja i definicja funkcji Funkcje takie mają następujące cechy: wywołanie funkcji może nastąpić z inną liczbą argumentów niż ma to miejsce w definicji domniemywać należy argumenty licząc od prawej strony podczas opuszczania argumentów - eliminacja poszczególnych argumentów również liczona jest od prawej strony 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 13 void przeliczodleglosc( float _odl, float _skala = 0 ); void przeliczodleglosc( float _odl, float _skala ){ switch( _skala ){ case 0: cout<< _odl << " [mm]\n"; break; case 1: cout<< _odl*0.1 << " [cm]\n"; break; case 2: cout<< _odl*0.01 << " [m]\n"; break; case 3: cout<< _odl*0.001<< " [km]\n"; break; default: cout<< _odl << " [mm]\n"; break; 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 14 Możliwe wywołania funkcji: void przeliczodleglosc( float _odl, float _skala = 0 ); przeliczodleglosc( 1.0 ); przeliczodleglosc( 1.0, 0.0 ); przeliczodleglosc( 1.0, 1.0 ); przeliczodleglosc( 1.0, 2.0 ); przeliczodleglosc( 1.0, 3.0 ); przeliczodleglosc( 1.0, 3.0, 2.0 ); 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 15 5
1. float suma(float _a, float _b){ 2. return ( _a + _b ); 3. 4. int main( void ){ 5. float a, b, c; 6. cout<<"podaj liczbe a: "; cin>>a; 7. cout<<"podaj liczbe b: "; cin>>b; 8. c = suma(a, b); 9. cout<<"suma liczb a i b wynosi: "<< c<< endl; 10. return 0; 11. 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 16 1. float suma(float _a, float _b); 2. int main( void ){ 3. float a, b, c; 4. cout<<"podaj liczbe a: "; cin>>a; 5. cout<<"podaj liczbe b: "; cin>>b; 6. c = suma(a, b); 7. cout<<"suma liczb a i b wynosi: "<< c<< endl; 8. return 0; 9. 10. float suma(float _a, float _b){ 11. return ( _a + _b ); 12. 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 17 1. float suma(float& _a, float* _d); 2. int main( void ){ 3. float a, b, c, *d; 4. cout<<"podaj liczbe a: "; cin>>a; 5. cout<<"podaj liczbe b: "; cin>>b; 6. d = &b; 7. c = suma(a, d); 8. cout<<"suma liczb a i b wynosi: "<< c<< endl; 9. return 0; 10. 11. float suma(float& _a, float* _b){ 12. return ( _a + *_d ); 13. 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 18 6
Przeładowanie/przeciążanie nazw funkcji polega na wielokrotnym wykorzystaniu takiej samej jej nazwy, różniącej się tylko typem i ilością argumentów (przeciążanie funkcji można zaliczyć do elementów polimorfizmu). W trakcie kompilacji program znajduje właściwą funkcję po liczbie oraz typach argumentów. Możliwe jest więc współistnienie kilku funkcji o tej samej nazwie, lecz różniących się typami argumentów. int dodaj(int a, int b) { return a+b; double dodaj(double a, double b) { return a+b; int main(){ cout<< dodaj(5,4); cout<< dodaj(5.0,4.0); return 0; // dodaj(int,int) // dodaj(double,double) 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 19 Niejednoznaczność i błędy Nie zawsze kompilator jest w stanie odpowiednio odróżnić funkcje o tej samej nazwie, muszą się one od siebie wyraźnie różnić. Czasem zdarzają się też sytuacje, w której dwie funkcje o tej samej nazwie mogą współistnieć, ale dane wywołanie byłoby niejednoznaczne: Jeśli funkcje (jedna lub obie) przyjmują argumenty domyślne, to mogą współistnieć jeśli te niedomyślne argumenty mają te same typy, tyle że wywołanie z podaniem wyłącznie wymaganych argumentów będzie wtedy niejednoznaczne (i odrzucone). Najlepiej nie mieszać ze sobą przeciążania i argumentów domyślnych. Podczas wybierania funkcji do wywołania uwzględnia się też domyślne konwersje i trzeba znać ich priorytety; np. jeśli funkcję przeciążymy na typy short i long, to wywołanie z typem float jest niejednoznaczne. 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 20 Aby wydzielić funkcje do osobnego pliku, należy utworzyć dwa pliki: Plik źródłowy o rozszerzeniu.cpp. W tym pliku powinien znajdować się kod źródłowy funkcji. Plik nagłówkowy o rozszerzeniu.h. W tym pliku powinien znajdować się tylko opis funkcji zwany prototypem funkcji. Dzięki temu do różnych projektów można dołączać tylko wybrane pliki zawierające funkcje, które są w nich akurat potrzebne. Innymi słowy pliki te są cegiełkami, z których można budować różne projekty. 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 21 7
W pliku nagłówkowym powinny znaleźć się instrukcje uniemożliwiające kompilatorowi wczytanie tego samego pliku wielokrotnie. Można to realizować na dwa sposoby: wykorzystanie definiowania stałych 1. #ifndef NAZWA_PLIKU_ZA_ZWYCZAJ 2. #define NAZWA_PLIKU_ZA_ZWYCZAJ 3. // definicja funkcji 4. #endif // NAZWA_PLIKU_ZA_ZWYCZAJ dyrektywy pragma 1. #pragma once 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 22 Rekurencja polega na odwoływaniu się funkcji do samej siebie. Należy przy tym uważać na to, aby taka rekurencja miała koniec. Dobrym przykładem ilustrowania rekurencji jest obliczanie silni: unsigned long silnia(unsigned a){ if(a <= 1) return 1; return a*silnia(a-1); Rekurencja jest mało wydajna i gdy tylko można zastąpić ją pętlą (czasem jest to bardzo trudne) należy to robić: unsigned long silnia(unsigned a){ unsigned long x=1; while(a > 1) x *= a--; return x; 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 23 Programy należy dzielić na funkcje. W niektórych firmach programistycznych obowiązuje zasada, że cały kod funkcji musi się mieścić na jednym ekranie monitora! Jest to bardzo ostry wymóg, ale zmusza programistów do myślenia i dzielenia kodu na małe części. 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 24 8
Dziękuję za uwagę i zapraszam na 15 minut przerwy. W dalszej części ćwiczenia do samodzielnego wykonania. 11/3/2016 AGH, Katedra Informatyki Stosowanej i Modelowania 25 9