Wstep do Programowania 2
Transkrypt
Wstep do Programowania 2
Wstep ˛ do Programowania 2 dr Bożena Woźna-Szcześniak [email protected] Akademia im. Jana Długosza Wykład 4 Funkcje przeciażone ˛ - Idea Przeciażanie ˛ funkcji (polimorfizm funkcji), to kolejna nowość w jezyku ˛ C++. “Polimorficzny” znaczy majacy ˛ wiele postaci. Polimorfizm (przeciażanie) ˛ funkcji pozwala na używanie wielu funkcji o tej samej nazwie. Mechanizm przeciażania ˛ funkcji pozwala zaprojektować rodzine˛ funkcji, które wykonuja˛ podobne operacje, ale maja˛ różne listy argumentów. Funkcje przeciażone ˛ - Idea Kluczem do przeciażania ˛ funkcji jest lista argumentów, nazywana sygnatura˛ funkcji. Jeżeli dwie funkcje używaja˛ takiej samej liczby argumentów tego samego typu i w takiej samej kolejności, to maja˛ taka˛ sama˛ sygnature. ˛ C++ pozwala zdefiniować dwie funkcjie o takiej samej nazwie pod warunkiem, że różnia˛ sie˛ sygnaturami. Funkcje moga˛ sie˛ różnić liczba˛ argumentów, ich typem, albo pod oba wzgledami. ˛ Funkcje przeciażone ˛ - Przykład void void void void void pisz(const char*,int);//#1 pisz(const char*); //#2 pisz(double,int); //#3 pisz(long,int); //#4 pisz(int,int); //#5 Funkcje przeciażone ˛ - Przykład void void void void void pisz("Bozena",1);//#1 pisz("Szkoła"); //#2 pisz(2.3,4); //#3 pisz(1999L,5); //#4 pisz(3,5); //#5 Funkcje przeciażone ˛ - uwagi Niektóre sygnatury, które wydaja˛ sie˛ odmienne, nie moga˛ współistnieć w programie, np. double kwadrat(double); oraz double kwadrat(double&); Dlaczego? Kompilator nie może ustalić po sposbie wywołania, której funkcji powinien użyć. W obu przypadkach wywołanie wyglada ˛ tak samo: double x = 2.3 ; kwadrat(x); double x = 2.3 ; double &y = x; kwadrat(y); Podczas dopasowywania funkcji rozróżniane sa˛ natomiast zmienne const i nie-const. Funkcje przeciażone ˛ - Przykład include <iostream> unsigned long left(unsigned long num, unsigned ct); char * left(const char * str, int n ); int main() { using namespace std; char trip[] = "Hawaii!!"; unsigned long n = 12345678; int i; char * temp; for (i = 1; i < 10; i++) { cout << left(n, i) << endl; temp = left(trip,i); cout << temp << endl; delete [] temp; } return 0; } Funkcje przeciażone ˛ - Przykład // Funkcja ta zwraca pierwszych ct cyfr liczby num. unsigned long left(unsigned long num, unsigned ct) { unsigned digits = 1; unsigned long n = num; if (ct == 0 || num == 0) return 0; // jezeli brak cyfr, zwraca 0 while (n /= 10) digits++; if (digits > ct) { ct = digits - ct; while (ct--) num /= 10; return num; // zwraca ct skrajnych lewych cyfr } else // jesli ct >= liczby cyfr return num; //zwraca cala liczbe } Funkcje przeciażone ˛ - Przykład // Funkcja zwraca wskaznik nowego lancucha zawierajacego // pierwszych n znakow lancucha str. char * left(const char * str, int n) { if(n < 0) n = 0; char * p = new char[n+1]; int i; // kopiowanie znaków for (i = 0; i < n && str[i]; i++) p[i] = str[i]; // ustawienie reszty znaków na zera while (i <= n) p[i++] = ’\0’; return p; } Funkcje wplatane (ang. inline) -idea Funkcje wplatane (inline), to nowy mechnizm w C++ służacy ˛ do przyspieszenia programów. Podstawowa różnica miedzy ˛ funkcjami wplatanymi, a zwykłymi nie polega na sposobie ich kodowania przez programiste, ˛ ale sposobie ich właczania ˛ przez kompilator do programu. Zwykłe wywołanie funkcji wymaga przeskoczenia pod pewien adres (adres funkcji) i powrotu do punktu wyjścia z funkcji. Dokładniej, gdy program dochodzi do wywołania funkcji, zapisuje adres instrukcji nastepujacej ˛ bezpośrednio po wywołaniu, kopiuje argumenty funkcji na stosie i skacze z powrotem do instrukcji, której adres właśnie zapisał. Skakanie to powoduje wydłużenie czasu wykonania programu. Funkcje wplatane (ang. inline) -idea Funkcje wplatane pozwalaja˛ unikać wspomnianych wcześniej skoków. Sa˛ to funkcje, których skompilowany kod zostaje “wpleciony” w kod programu - kompilator zastepuje ˛ wywołanie funkcji jej kodem. Funkcje wplatane działaja˛ nieco szybciej niż zwykłe, ale wymagaja˛ wiekszej ˛ ilości pamieci ˛ – kod zostaje wzbogacony o wymagana˛ liczbe kopii funkcji wplecionej. Funkcje wplatane (ang. inline) - definicja Poprzedzić deklaracje˛ funkcji słowem kluczowym inline Poprzedzić definicje˛ funkcji słowem kluczowym inline Funkcje wplatane (ang. inline) - definicja Poprzedzić deklaracje˛ funkcji słowem kluczowym inline Poprzedzić definicje˛ funkcji słowem kluczowym inline Czesto pomija sie˛ prototyp i umieszcza cała˛ definicje˛ tam, gdzie powinna sie˛ znaleźć. Kompilator nie jest zobowiazany ˛ do spełnienia naszego żadania. Może zdecydować, że funkcja jest zbyt duża, albo, że wywołuje sama˛ siebie ! Wniosek ! Funkcje rekurencyjne nie moga˛ być inline. Funkcje wplatane (ang. inline) - przykład #include <iostream> // definicja funkcji inline inline double square(double x) { return x * x; } int main(){ using namespace std; double a, b; double c = 13.0; a = square(5.0); b = square(4.5 + 7.5); cout << "a = " << a << ", b = " << b << "\n"; cout << "c = " << c; cout << ", c kwadrat = " << square(c++) << "\n"; cout << "Teraz c = " << c << "\n"; return 0; } Funkcje wzorcowe Wzorce to jedno z najsilniejszych statycznych narz˛edzi jezyka ˛ C++, pozwalajace ˛ na uogólniony (uniwersalny) zapis funckji, struktur i klas. Wzorce pozwalaja˛ na skrócenie konieczność pisania i powtarzania tych samych sekwencji. Mamy do dyspozycji trzy podstawowe rodzaje wzorców: wzorzec funkcji, wzorzec struktury oraz wzorzec klasy. Wzorce funckji pozwalaja˛ zdefiniować funkcje˛ w kategoriach uniwersalnego typu. Definicje˛ wzorca rozpoczyna sie˛ zawsze od sekwencji: template < parametry-wzorca > definicja-wzorca Funkcje wzorcowe Wzorzec funkcji zamiana template < class typ > /* Instrukacja informujac ˛ a˛ że definowany jest wzorzec. Slowa template i class sa˛ obowiazkowe. ˛ Zamiast słowa class można użyć słowo typename */ void zamiana (typ &a, typ&b) { typ c =a; a=b; b=c; } Wzorzec funkcji zamiana template < typename typ > void zamiana (typ &a, typ&b) { typ c =a; a=b; b=c; Funkcje wzorcowe Uwaga 1 Wzorców należy używać wtedy, gdy funkcja ma stosować ten sam algorytm do operowania na różnorodnych typach danych ! Funkcje wzorcowe Uwaga 1 Wzorców należy używać wtedy, gdy funkcja ma stosować ten sam algorytm do operowania na różnorodnych typach danych ! Uwaga 2 Wzorce funkcji nie skracaja˛ programów wykonywalnych. Dla każdego użytego wzorca funkcji program bedzie ˛ generował odpowiednia˛ funkcje˛ “zwyczajna”. ˛ Co wiecej ˛ ostateczny kod nie zawiera żadnych wzorców, tylko rzeczywiste funkcje wygenerowane na użytek programu. Funkcje wzorcowe - przykład #include <iostream> // prototyp szablonu funkcji template <class Typ> // lub typename Typ void Swap(Typ &a, Typ &b); int main(){ using namespace std; int i = 10, j = 20; cout << "i, j = " << i << ", " << j << ".\n"; cout << "Uzycie funkcji obslugujacej typ int, " "wygenerowanej przez kompilator:\n"; Swap(i,j); // generuje void Swap(int &, int &) cout << "Teraz i, j = " << i << ", " << j << ".\n"; double x = 24.5, y = 81.7; cout << "x, y = " << x << ", " << y << ".\n"; cout << "Uzycie funkcji obslugujacej typ double, " "wygenerowanej przez kompilator:\n"; Swap(x,y); // generuje void Swap(double &, double &) cout << "Teraz x, y = " << x << ", " << y << ".\n"; return 0; } template <class Typ> // definicja szablonu funkcji void Swap(Typ &a, Typ &b){ Typ c = a; a = b; b = c; } Przykład - wykonanie i, j = 10, 20. Uzycie funkcji obslugujacej typ int, wygenerowanej przez kompilator: Teraz i, j = 20, 10. x, y = 24.5, 81.7. Uzycie funkcji obslugujacej typ double, wygenerowanej przez kompilator: Teraz x, y = 81.7, 24.5. Wzorzec funkcji Minimum #include <iostream> // prototyp szablonu funkcji template <class T> inline const T& Min(const T& t1, const T& t2) { if ( t1 < t2 ) return t1; return t2; } int main() { using namespace std; int i = 10, j = 20; cout << "i, j = " << i <<", "<<j<< ".\n"; cout << "Uzycie funkcji obslugujacej typ int, " "wygenerowanej przez kompilator:\n"; // generuje void Min(const int &, const int &) cout << "Min z "<<i<<" i "<<j<<" = "<< Min(i,j)<<endl; double x = 24.5, y = 81.7; cout << "x, y = "<< x << ", " << y << ".\n"; cout << "Uzycie funkcji obslugujacej typ double, " "wygenerowanej przez kompilator:\n"; // generuje void Min(const double &, const double &) cout << "Teraz Min z "<<x<<" i "<<y<<" = "<<Min(x,y)<<endl; return 0; } Wzorzec funkcji Minimum - wykonanie i, j = 10, 20. Uzycie funkcji obslugujacej typ int, wygenerowanej przez kompilator: Min z 10 i 20 = 10 x, y = 24.5, 81.7. Uzycie funkcji obslugujacej typ double, wygenerowanej przez kompilator: Teraz Min z 24.5 i 81.7 = 24.5 Wzorzec funkcji a struktura #include <iostream> // prototyp szablonu funkcji template <class Typ> // lub typename Any void Swap(Typ &a, Typ &b); struct pracownik{ char nazwisko[40]; int zarobki; }; int main() { .... } template <class Typ> void Swap(Typ &a, Typ &b) { Typ c = a; a = b; b = c; } Wzorzec funkcji a struktura int main(){ using namespace std; int i = 10, j = 20; cout << "i, j = " << i << ", " << j << ".\n"; cout << "Uzycie funkcji obslugujacej typ int, " "wygenerowanej przez kompilator:\n"; Swap(i,j); // generuje void Swap(int &, int &) cout << "Teraz i, j = " << i << ", " << j << ".\n"; pracownik x = {"Kowalski", 1000}, y = {"Nowak", 1500}; cout << "x = " << x.nazwisko << ", " << x.zarobki << ".\n"; cout << "y = " << y.nazwisko << ", " << y.zarobki << ".\n"; cout << "Uzycie funkcji obslugujacej typ pracownik, " "wygenerowanej przez kompilator:\n"; Swap(x,y); // generuje void Swap(pracownik &, pracownik &) cout << "Teraz: \n"; cout << "x = " << x.nazwisko << ", " << x.zarobki << ".\n"; cout << "y = " << y.nazwisko << ", " << y.zarobki << ".\n"; return 0; } Wzorzec funkcji a struktura i, j = 10, 20. Uzycie funkcji obslugujacej typ int, wygenerowanej przez kompilator: Teraz i, j = 20, 10. x = Kowalski, 1000. y = Nowak, 1500. Uzycie funkcji obslugujacej typ pracownik, wygenerowanej przez kompilator: Teraz: x = Nowak, 1500. y = Kowalski, 1000. Przeciażanie ˛ wzorców Nie wszystkie argumenty wzorca musza˛ być typami generycznymi. Definicje wzorców można przeciażać, ˛ tak jak sie˛ przeciaża ˛ definicje zwykłych funkcji. Przeciażanie ˛ wzorców - przykład #include <iostream> template <class T> // szablon oryginalny void Swap(T &a, T &b); template <class T> // nowy szablon void Swap(T *a, T *b, int n); void Show(int a[],int); Przeciażanie ˛ wzorców - przykład int main() { using namespace std; int i = 10, j = 20; cout << "i, j = " << i << ", " << j << ".\n"; cout << "Uzycie funkcji obslugujacej typ int, " "wygenerowanej przez kompilator:\n"; Swap(i,j); // pasuje do szablonu oryginalnego cout << "Teraz i, j = " << i << ", " << j << ".\n"; const int Lim = 8; int d1[Lim] = {0,7,0,4,1,7,7,6}; int d2[Lim] = {0,6,2,0,1,9,6,9}; cout << "Tablice poczatkowo:\n"; Show(d1,Lim); Show(d2,Lim); Swap(d1,d2,Lim); // pasuje do nowego szablonu cout << "Tablice po zamianie:\n"; Show(d1,Lim); Show(d2,Lim); return 0; } Przeciażanie ˛ wzorców - przykład template <class T> void Swap(T &a, T &b){ T c = a; a = b; b = c; } template <class T> void Swap(T a[], T b[], int n){ T temp; for (int i = 0; i < n; i++) { temp = a[i]; a[i] = b[i]; b[i] = temp; } } void Show(int a[], int n) { using namespace std; cout << a[0] << a[1] << "/"; cout << a[2] << a[3] << "/"; for (int i = 4; i < n; i++) cout << a[i]; cout << endl; } Przeciażanie ˛ wzorców - przykład i, j = 10, 20. Uzycie funkcji obslugujacej typ int, wygenerowanej przez kompilator: Teraz i, j = 20, 10. Tablice poczatkowo: 07/04/1776 06/20/1969 Tablice po zamianie: 06/20/1969 07/04/1776 Jawna specjalizacja Danej nazwie funkcji można przypisać funkcje˛ niewzorcowa, ˛ funkcje wzorcowa˛ oraz funkcje wzorcowa˛ z jawna˛ specjalizacja. ˛ Prototyp i definicje˛ jawnej specjalizacji należy poprzedzić słowem kluczowym template jednocześnie wymieniajac ˛ nazwe wyspecjalizowanego typu. Specjalizacja ma pierwszeństwo przed zwykłym wzorcem, a funkcja niewzorcowa ma pierwszeństwo przed oboma. Jawna specjalizacja struktura pracownk struct pracownik char nazwisko[40]; double zarobki; ; Prototypy funkcji do przestawiania struktur pracownik: niewzorcowy prototyp funkcji: void Swap(pracownik &a, pracownik &b); wzorcowy prototyp funkcji: template <class Typ> void Swap(Typ &a, Typ &b); jawna specjalizacja: template <> void Swap<pracownik>(pracownik &a, pracownik &b); Jawna specjalizacja - przykład #include <iostream> template <class Typ> void Swap(Typ &a, Typ &b); struct pracownik { char name[40]; double zarobki; }; // jawna specjalizacja template <> void Swap<pracownik>(pracownik &j1, pracownik &j2); void Show(pracownik &j); Jawna specjalizacja - przykład int main(){ using namespace std; cout.precision(2); cout.setf(ios::fixed, ios::floatfield); int i = 10, j = 20; cout << "i, j = " << i << ", " << j << ".\n"; cout << "Uzycie generowanej przez kompilator funkcji " "zamieniajacej wartosci int:\n"; Swap(i,j); // generuje void Swap(int &, int &) cout << "Teraz i, j = " << i << ", " << j << ".\n"; pracownik K = {"Kowalski Jan", 73000.60}; pracownik N = {"Nowak Tadeusz", 78060.72}; cout << "Przed zamiana struktur pracownik:\n"; Show(K); Show(N); Swap(K, N); // uzywa void Swap(pracownik &, pracownik &) cout << "Po zamianie struktur pracownik:\n"; Show(K); Show(N); return 0; } Jawna specjalizacja - przykład template <class Typ> // wersja ogólna void Swap(Typ &a, Typ &b){ Typ c = a; a = b; b = c; } // zamienia tylko pola zarobki struktury pracownik - specjalizacja template <> void Swap<pracownik>(pracownik &j1, pracownik &j2) { double t1 = j1.zarobki; j1.zarobki = j2.zarobki; j2.zarobki = t1; } void Show(pracownik &j) { using namespace std; cout << j.name << ": " << j.zarobki << "PLN" << endl; } Jawna specjalizacja - przykład, wykonanie i, j = 10, 20. Uzycie generowanej przez kompilator funkcji zamieniajacej wartosci int Teraz i, j = 20, 10. Przed zamiana struktur pracownik: Kowalski Jan: 73000.60PLN Nowak Tadeusz: 78060.72PLN Po zamianie struktur pracownik: Kowalski Jan: 78060.72PLN Nowak Tadeusz: 73000.60PLN