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

Podobne dokumenty