Wstęp do Programowania 2 dr Bożena Woźna-Szcześniak bwozna@gmail.com Akademia im. Jana Długosza Wykład 4
Funkcje przeciażone - Idea Przeciażanie funkcji (polimorfizm funkcji), to kolejna nowość w języku 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ć rodzinę 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 sygnaturę. C++ pozwala zdefiniować dwie funkcjie o takiej samej nazwie pod warunkiem, że różnia się sygnaturami. Funkcje moga się różnić liczba argumentów, ich typem, albo pod oba względami.
Funkcje przeciażone - Przykład void pisz(const char*,int);//#1 void pisz(const char*); //#2 void pisz(double,int); //#3 void pisz(long,int); //#4 void pisz(int,int); //#5
Funkcje przeciażone - Przykład void pisz("bozena",1);//#1 void pisz("szkoła"); //#2 void pisz(2.3,4); //#3 void pisz(1999l,5); //#4 void pisz(3,5); //#5
Funkcje przeciażone - uwagi Niektóre sygnatury, które wydaja się 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 s a 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) if (digits > ct) { ct = digits - ct; digits++; 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 między funkcjami wplatanymi, a zwykłymi nie polega na sposobie ich kodowania przez programistę, 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 następujacej 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 zastępuje wywołanie funkcji jej kodem. Funkcje wplatane działaja nieco szybciej niż zwykłe, ale wymagaja większej ilości pamięci kod zostaje wzbogacony o wymagana liczbe kopii funkcji wplecionej.
Funkcje wplatane (ang. inline) - definicja Poprzedzić deklarację funkcji słowem kluczowym inline Poprzedzić definicję funkcji słowem kluczowym inline
Funkcje wplatane (ang. inline) - definicja Poprzedzić deklarację funkcji słowem kluczowym inline Poprzedzić definicję funkcji słowem kluczowym inline Czesto pomija się prototyp i umieszcza cała definicję tam, gdzie powinna się 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ędzi języka 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ć funkcję w kategoriach uniwersalnego typu. Definicję wzorca rozpoczyna się zawsze od sekwencji: template < parametry-wzorca > definicja-wzorca
Funkcje wzorcowe Wzorzec funkcji zamiana template < class typ > /* Instrukacja informującą że definowany jest wzorzec. Slowa template i class są obowiązkowe. 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 będzie generował odpowiednia funkcję zwyczajna. Co więcej 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 się przeciaża definicje zwykłych funkcji.
Przeciażanie wzorców - przykład #include <iostream> template <class T> void Swap(T &a, T &b); // szablon oryginalny 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ć funkcję niewzorcowa, funkcje wzorcowa oraz funkcje wzorcowa z jawna specjalizacja. Prototyp i definicję 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