wykład 10 Agata Półrola Wydział Matematyki i Informatyki UŁ semestr zimowy 2018/2019
Przesyłanie argumentów - cd Przesyłanie argumentów do funkcji - tablice wielowymiarowe Przekazywanie tablic wielowymiarowych jest podobne do przekazywania tablic jednowymiarowych, z tym że jedynie pierwszy wymiar może nie zostać określony (przykład: int fun2 (int a[][4], int ilosc wierszy);) użycie notacji tablicowej utrudnia obsługę funkcji zwracających (wygenerowane w funkcji) tablice Możliwe rozwiązania problemu: linearyzacja tablic użycie wskaźników
Przykład (Przekazywanie tablic wielowymiarowych) # include <iostream > using namespace std ; // -------------------------------------------------- void pobierztab (int tab [][4], int ilewierszy ); void wypisztab (int tab [][4], int ilewierszy ); int maxtab (int tab [][4], int ilewierszy ); // -------------------------------------------------- int main () int n; cout << " podaj dlugosc tablicy : "; cin >> n; int A[n ][4]; cout << " podaj zawartosc tablicy : " << endl ; pobierztab (A,n); cout << "\ ntwoja tablica : \n"; wypisztab (A,n); cout << endl << " wartosc maksymalna : " << maxtab (A,n) << endl ; return 0; // --------------------------------------------------
Przykład (Przekazywanie tablic wielowymiarowych - cd) void pobierztab (int tab [][4], int ilewierszy ) for ( int i =0; i< ilewierszy ; i ++) for ( int j =0; j <4; j ++) cout << " element " << i << "," << j << ": "; cin >> tab [i][j]; void wypisztab (int tab [][4], int ilewierszy ) for ( int i =0; i< ilewierszy ; i ++) for ( int j =0; j <4; j ++) cout << tab [i][j] << " "; cout << endl ; int maxtab (int tab [][4], int ilewierszy ) int m= tab [0][0]; for ( int i =0; i< ilewierszy ; i ++) for ( int j =0; j <4; j ++) if ( tab [i][j] > m) m = tab [i][j]; return m;
Argumenty (parametry) domyśłne czasami użytecznie jest przypisać pewnemu argumentowi wartość domyślną. Jeśli wywołując funkcję nie podamy wartości tego argumentu, to przyjmie on wówczas wartość domyślną wartość domyślną podaje się w deklaracji funkcji. Jeśli deklaracja i definicja są rozdzielone, to w definicji już wartości domyślnej nie powtarzamy
Przykład (Domyślne wartości parametrów) include <iostream > using namespace std ; // **************************************** double zamien_temperature ( double t, char skala = K ); // wartosc domyslna double CnaK ( double t); double CnaF ( double t); // **************************************** int main () double tempc ; char wybor ; cout << " podaj temperature w stopniach Celsjusza >"; cin >> tempc ; cout << " wybierz skale (F - Fahrenheit, K - Kelvin ) > "; cin >> wybor ; else if ( wybor == K wybor == F ) cout << " temperatura wynosi " << zamien_temperature (tempc, wybor ) << " " << wybor << " \n"; cout << " niepoprawna skala - zamieniam na skale Kelvina \n"; return 0; cout << " temperatura wynosi " << zamien_temperature ( tempc ) << " K \n";
Przykład (Domyślne wartości parametrów - cd) // **************************************** double zamien_temperature ( double t, char skala ) // wartosc domyslna nie jest powtorzona switch ( skala ) case K : return CnaK (t); case F : return CnaF (t); double CnaK ( double t) return t +273; double CnaF ( double t) return (9.0 * t) /5.0 + 32;
Funkcje inline Funkcje inline Wywołanie funkcji generuje koszt czasowy (na poziomie kodu maszynowego wywołanie funkcji wymaga pewnych dodatkowych operacji, jak przejście do inneg miejsca programu, utworzenie zmiennych lokalnych itp) jeśli program zawiera niewielkie funkcje które będą wykonywane wielokrotnie, to może być opłacalne zdefiniowanie ich jako funkcji inline
Funkcje inline - cd funkcje inline definiujemy poprzedzając standardową definicję funkcji słowem kluczowym inline. Deklaracja i definicja (ciało) takiej funkcji nie mogąbyć rozdzielone za każdym razem kiedy kompilator napotka wywołanie funkcji inline, umieszcza jej treść (dopasowaną do aktualnych danych) w linii w której to wywołanie nastąpiło (tak jakbyśmy je tam wpisali ręcznie) użycie funkcji inline skutkuje krótszym czasem wykonania, ale większym rozmiarem programu wykonywalnego (z tego powodu nie należy używać tego rozwiązania do dużych funkcji - może to prowadzić do uzyskania bardzo dużych programów) funkcja inline może zostać skompilowana jako zwykła funkcja, np. gdy kompilator nie obsługuje funkcji inline, lub z jakichś innych powodów wybierze zignorowanie żądanie traktowania funkcji jako inline
Przykład (Funkcje inline) # include <iostream > using namespace std ; // **************************************** double zamien_temperature ( double t, char skala = K ); // wartosc domyslna inline double CnaK ( double t) return t +273; inline double CnaF ( double t) return (9.0 * t) /5.0 + 32; // **************************************** int main () double tempc ; char wybor ; cout << " podaj temperature w stopniach Celsjusza >"; cin >> tempc ; cout << " wybierz skale (F - Fahrenheit, K - Kelvin ) > "; cin >> wybor ;
Przykład (Fukcje inline - cd) else if ( wybor == K wybor == F ) cout << " temperatura wynosi " << zamien_temperature (tempc, wybor ) << " " << wybor << " \n"; cout << " niepoprawna skala - zamieniam na skale Kelvina \n"; return 0; cout << " temperatura wynosi " << zamien_temperature ( tempc ) << " K \n"; // **************************************** double zamien_temperature ( double t, char skala ) // wartosc domyslna nie jest powtorzona switch ( skala ) case K : return CnaK (t); case F : return CnaF (t);
Przecążenie (przeładowanie) nazwy funkcji C++ umożliwia zdefiniowanie w jednym obszarze widzialności (np. pliku) kilku funkcji / operatorów o tej samej nazwie. Jest to nazywane przeciążaniem (przeładowaniem) nazwy funkcji (ang. function overloading) Przeciążona deklaracja funkcji deklaruje funkcję o tej samej nazwie jak inna deklaracja w tym samym zakresie widzialności, jednak obie deklaracje muszą mieć różne argumenty i oczywiście różne definicje (implementacje). Wywołanie przeciążonej funkcji/operatora wymaga tzw. rozwiązania przeładowania - kompilator decyduje którą funkcj uaktywnić, porównując liczbę i typy parametrów aktualnych i formalnych.
Przeciążanie funkcji - cd w C++ definicje przeciążanych funkcji muszą różnić się liczbą lub typem argumentów (inaczej kompilator zgłosi błąd) idopuzczalna jest też sytuacja gdy dwie funkcje mają argumenty tych samych typów, ale w różnej kolejności nie można przeciązyć funkcji za pomocą deklaracji różniącej się tylko zwracanym typem (przy rozróżnianiu funkcji typ zwracanej wartości nie jest brany pod uwagę)
Przykład (Przeciążanie funkcji) # include <iostream > using namespace std ; // ----------------------------------------------- void wypisz_ladnie ( char s []) ; // dwie funkcje o takiej samej nazwie void wypisz_ladnie ( int a); // ale roznych typach parametrow // ----------------------------------------------- int main () int li; char st [80]; cout << " podaj wyraz i liczbe > "; cin >> st ; cin >> li ; wypisz_ladnie (st); wypisz_ladnie (li); return 0; // ----------------------------------------------- void wypisz_ladnie ( char s []) cout << "\n\n* -* -* -* -* -* " << s << " * -* -* -* -* -* \n\n"; void wypisz_ladnie ( int a) cout << "\n\n -* -* -* -* -* >> " << a << " << -* -* -* -* -* \n\n";
Przeładowanie od środka dla kompilatora przeładowane funkcje mają różne nazwy - kompilator pamięta nazwy fukcji wraz z typami argumentów, np. void funct(void); jako funct Fv void funct(int,double); jako funct Fid void funct(double,int); jako funct Fdi etc. Podane nazwy są przykładowe; sposób zakodowania nazwy funkcji zależy od kompilatora
Statyczne zmienne lokalne Zmienne lokalne i globalne jeszcze raz zmienne globalne, zdefiniowane poza ciałem jakiejkolwiek funkcji, są dostępne dla wszystkich funkcji programu (poniżej linii w której zostały zdefiniowane) zmienne lokalne, zdefiniowane w ciele funkcji, są dostępne tylko dla tej funkcji (poniżej linii w której zostały zdefiniowane)
zmienne lokalne są zazwyczaj przechowywane na tzw. stosie (ang. stack). Obiekty takie nazywane są zmiennymi automatycznymi zmienne globalne przechowywane są w normalnym obszarze pamięci Konsekwencje: normalny obszar pamięci jest zerowany przed uruchomieniem programu, co powoduje że zmienne globalne są automatycznie inicjalizowane zerami (dla danego typu); niezainicjalizowane zmienne lokalne mają wartości przypadkowe zmienne globalne nie są niszczone automatycznie w czasie wykonywania programu; zmienne automatycze są automatycznie likwidowane po zakończeniu wykonywania bloku w którym zostały powołane do życia. Kolejne wykonanie tego samego bloku powoduje ponowne utworzenie tych zmiennych na stosie
Statyczne obiekty lokalne jeśli chcemy aby zmienna lokalna dla danej funkcji nie ginęła (istniała pomiędzy wywołaniami funkcji) i przy ponownym wykonaniu tej fyncji miała wartość taką jak przy zakończeniu poprzedniego wykonania, to musimy zdefiniować ją jako zmienną statyczną deklaracja zmiennej statycznej ma postać static typ zmiennej nazwa zmiennej; np. static int licznik; obiekty statyczne przechowywane są w normalnym obszarze pamięci
Przykład (Statyczne zmienne lokalne) # include <iostream > using namespace std ; int licznik ; // zmienna globalna // ----------------------------------- void pierwsza_funkcja (); void druga_funkcja (); // ----------------------------------- int main () pierwsza_funkcja (); pierwsza_funkcja (); pierwsza_funkcja (); druga_funkcja (); druga_funkcja (); pierwsza_funkcja (); cout << " licznik globalny : " << licznik << "\n"; return 0; // ----------------------------------- void pierwsza_funkcja () static int licznik ; // automatyczne zerowanie licznik = licznik + 1; cout << " pierwsza_funkcja wykonana " << licznik << " raz \n"; void druga_funkcja () static int licznik = 100; // aby rozroznic liczniki licznik = licznik + 1; cout << " druga_funkcja wykonana " << licznik << " raz \n"; Program wypisze wartości liczników: 1, 2, 3, 101, 102, 4, 0
Funkcje rekurencyjne funkcja może wywoływać samą siebie. Jest to tzw. rekurencja (rekursja) inaczej mówiąc - rekurencja to technika programistyczna pozwalająca programiście opisać operaję za pomocą niej samej rekurencja wymaga warunku stopu (zatrzymującego rekurencję), inaczej funkcja będzie się wywoływała w nieskończoność, tzn. do wyczerpania pamięci
Przykład (Funkcja rekurencyjna) # include <iostream > using namespace std ; int silnia_rek ( int N); // wersja rekurencyjna int silnia_it ( int N); // wersja iteracyjna // ---------------------------------- int main () int n; cout << " podaj n: "; cin >> n; cout << "n! = " << silnia_it (n) << endl ; cout << "n! = " << silnia_rek (n) << endl ; return 0; // ----------------------------------- int silnia_it ( int N) // n! = 1*2*3*...* n if (N ==0) return 1; int w = 1; for ( int i =2; i <=N; i ++) w = w*i; return w; int silnia_rek ( int N) // n! = n * (n -1)! if (N ==0) return 1; else return N * silnia_rek (N -1) ;
Funkcje rekurencyjne - zmienne Gdy funkcja rekurencyjna wywołuje samą siebie, na stosie są już zmienne automatyczne będące własnością pierwszego wywołania funkcji. Kolejne jej wywołanie umieszcza na stosie swoje własne zmienne automatyczne zmienne zadeklarowane jako statyczne nie tylko nie giną po zakończeniu danego wykonania funkcji, ale są również wspóne dla wszystkich rekurencyjnych wywołań tej fukcji