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