Przeciążanie funkcji, szablony funkcji
Transkrypt
Przeciążanie funkcji, szablony funkcji
SYSTEMY INFORMATYCZNE M.A. Jankowska, G. Sypniewska-Kamińska LABORATORIUM NR 03 TEMAT : WYBRANE ZAAWANSOWANE ZAGADNIENIA ZWIĄZANE Z FUNKCJAMI W C++ I. Przeciążanie funkcji Terminami przeciążanie, przeładowanie albo polimorfizm funkcji określamy rozwiązanie pozwalające na stosowanie w jednym projekcie tej samej nazwy dla wielu funkcji. Listę parametrów formalnych funkcji nazywamy sygnaturą funkcji. Jeżeli dwie funkcje mają identyczne listy parametrów formalnych, co do liczby parametrów, ich typów i uporządkowania na liście, to mówimy, że funkcje mają takie same sygnatury. Nazwy parametrów są nieistotne, mogą się różnić. W języku C++ można nadawać identyczne nazwy funkcjom, które różnią się sygnaturami, na przykład liczbą parametrów lub ich typami. Poniżej wypisano prototypy kilku przeciążonych funkcji, które w pewnym projekcie służą do obliczania sumy dwóch albo trzech liczb. double double double double add(double a, double b); add(double a, double b, double c); add(double* a, double b); add(const double* a, double b); Sygnatury tych funkcji są różne, więc można stosować przeciążanie. Kompilator na podstawie liczby i typów argumentów aktualnych pojawiających się przy wywołaniu funkcji add wybierze odpowiednią jej wersję. Zwróćmy uwagę, że tylko różne sygnatury są wymagane do zastosowania mechanizmu przeciążania funkcji, można więc przy spełnieniu tego warunku nadawać jednakową nazwę wielu funkcjom o rozmaitym przeznaczeniu. Zazwyczaj jednak z przeciążania korzysta się w celu objęcia wspólną nazwą kilku funkcji służących do tego samego celu, ale mających różne co do liczby albo najczęściej tylko co typu parametry. Poniżej kod programu, w którym zastosowano polimorfizm funkcji dla kilku funkcji służących do dodawania dwóch bądź trzech argumentów różnych typów. #include "stdafx.h" #include <iostream> using std::cout; double double double double using std::cin; using std::endl; add(double a, double b); add(double a, double b, double c); add(double* a, double b); add(const double* a, double b); int _tmain(int argc, _TCHAR* argv[]) { double x, y, z; cout <<"x = ";cin>>x; cout <<"y = ";cin>>y; cout <<"x + y = "<< add(x,y)<<endl; cout <<"z = ";cin>>z; cout <<"x + y + z = "<< add(x,y,z)<<endl; cout <<"x + z = "<< add(&x, z)<<endl; cout <<"y + z = "<< add(&y, z)<<endl; return 0; } double add(double a, double b) { return a+b; } double add(double a, double b, double c) { return a+b+c; } double add(double* a, double b) { return *a+b; } double add(const double* a, double b) { return *a+b; } Należy pamiętać, że w trakcie kompilacji typ i referencja do tego typu są nierozróżnialne. Zatem pomimo formalnej różnicy w obu postaciach list parametrów funkcji double add(double a, double b); double add(double& a, double& b); Laboratorium 3 1 SYSTEMY INFORMATYCZNE M.A. Jankowska, G. Sypniewska-Kamińska kompilator nie jest w stanie wybrać właściwej funkcji. W tym przypadku przeciążanie nie jest możliwe, co kompilator sygnalizuje komunikatem : error C2668: 'add' : ambiguous call to overloaded function Zazanczmy jeszcze, że do zastosowania mechanizmu przeciążania nie wystarczy sama różnica w typach funkcji. Przy próbie nadania jednakowych nazw funkcjom o podanych niżej prototypach int add(float a, float b); float add(float a, float b); po komplilacji pojawi sie komunikat o błędzie : error C2556: 'int add(double,double)' : overloaded function differs only by return type from 'double add(double,double)' Konieczne jest, aby funkcje różniły się nie tylko typem ale sygnaturami, na przykład int add(int a, float b); float add(float a, float b); II. Szablony funkcji Szablon funkcji jest narzędziem służącym do zdefiniowania wielu funkcji, które wykonują takie same operacje na argumentach różnych typów I ewentualnie mogą generować wynik różniący się typem. Szablon można więc traktować jako definicję funkcji przetwarzającej dane hipotetycznego typu (albo kilku hipotetycznych typów). Typ w definicji szablonu jest parametrem reprezentowanym przez dowolnie wybrany identyfikator. Poniżej zamieszczono definicję szablonu funkcji przeznaczonej do dodawania dwóch n-elementowych wektorów o elementach dowolnego typu. template <typename typ> void add_vectors( int n, typ* a, typ* b, typ* c) { for (int i=0; i<n; i++) { c[i] = a[i] + b[i]; } } W powyższej definicji typ jest identyfikatorem wybranym przez programistę. Jeżeli w programie z poprzedniego zadania zastosujemy szablon funkcji, to kod programu będzie następujący: #include "stdafx.h" #include <iostream> #include <ctime> using std::cout; using std::cin; using std::endl; template <typename typ> void add_vectors( int n, typ* a, typ* b, typ* c); int _tmain(int argc, _TCHAR* argv[]) { int p[20], q[20], wynik[20]; float fp[20], fq[20], fwynik[20]; double d1[20], d2[20], d3[20]; srand(static_cast<unsigned>(time(0))); int n; cout <<" n = "; cin>>n; for (int i=0; i<n; i++) { p[i] = rand(); q[i] = rand(); fp[i] = 2.0f/static_cast<float>(rand()); fq[i] = 4.0f/static_cast<float>(rand()); d1[i] = static_cast<double>(rand())/100.0; d2[i] = static_cast<double>(rand())/100.0; } add_vectors(n, p, q, wynik); for (int i=0; i<n; i++) { cout <<p[i]<<" + "<<q[i]<<" = "<<wynik[i]<<endl; } Laboratorium 3 2 SYSTEMY INFORMATYCZNE M.A. Jankowska, G. Sypniewska-Kamińska cout << endl; add_vectors(n, fp, fq, fwynik); for (int i=0; i<n; i++) { cout <<fp[i]<<" + "<<fq[i]<<" = "<<fwynik[i]<<endl; } cout << endl; add_vectors(n, d1, d2, d3); for (int i=0; i<n; i++) { cout <<d1[i]<<" + "<<d2[i]<<" = "<<d3[i]<<endl; } return 0; } template <typename typ> void add_vectors( int n, typ* a, typ* b, typ* c) { } for (int i=0; i<n; i++) { c[i] = a[i] + b[i]; } Definicja szablonu zapisana za funkcją main albo w odrębnym pliku wymaga umieszczenia w kodzie prototypu szablonu, czyli instukcji template <typename typ> void add_vectors( int n, typ* a, typ* b, typ* c); Programiści stosują dwie konwencje zapisywania nagłówka szablonu: – w jednej linii, jak w powyższym przykładzie, – w dwóch liniach; w pierwszej zapisują template <typename typ>, w drugiej nagłówek funkcji, na przykład template <typename typ> void add_vectors( int n, typ* a, typ* b, typ* c) Jeżeli kompitator napotka wywołanie funkcji o nazwie zdefiniowanej w szablonie, to na podstawie typów argumentów aktualnych wygeneruje potrzebną wersję tej funkcji dla konkretnego typu/typów. Zastosowanie szablonu zwalnia więc programistę z konieczności żmudnego tworzenia wielu wersji analogicznych funkcji realizujących taki sam algorytm dla danych różnych typów. W przypadku szablonu funkcji, która generuje watość, nagłówek szblonu musi jeszcze zawierać identyfikator typu wartości funkcji. Może to być ustalony typ, ale niekoniecznie - można zdefiniować typ wartości funkcji w postaci parametru. Na przykład szablon funcji przeznaczonej do obliczania iloczynu skalarnego dwóch n-elementowych wektorów o elementach dowolnego typu i zwracającej wynik jako wartość zgodną z typem elementów wektorów może mieć postać template <typename typ> typ scalar_product( int n, typ* a, typ* b) { typ s = 0; for (int i = 0; i<n; i++) { s = s + a[i]*b[i]; } return s; } Jeżeli natomiast funkcja ma zwracać jako wynik wartość typu double, niezależnie od typu argumentów, to szablon można napisać następująco: template <typename typ> double scalar_product( int n, typ* a, typ* b) { typ s = 0; for (int i = 0; i<n; i++) { s = s + a[i]*b[i]; } return s; } W pewnych przypadkach komplilator na podstawie wywołania funkcji nie jest w stanie jednoznacznie ustalić typu zwracanego Laboratorium 3 3 SYSTEMY INFORMATYCZNE M.A. Jankowska, G. Sypniewska-Kamińska przez funkcję. Pojawia się wówczas błąd kompilacji z opisem : could not deduce template argument for 'typ' Wówczas programista musi wskazać w jawny sposób typ wyniku funkcji przy jej wywołaniu. Nazwę typu funkcji należy umieścić w nawiasach kątowych, pomiędzy nazwą funkcji a listą argumentów, na przykład funkcja_1<double>(n); Laboratorium 3 4