Paradygmaty programowania Programowanie generyczne w C++

Transkrypt

Paradygmaty programowania Programowanie generyczne w C++
Paradygmaty programowania
Programowanie generyczne w C++
Dr inż. Andrzej Grosser
Czestochowa,
2016
,
2
Spis treści
1. Zadanie 3
5
1.1. Wprowadzenie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.2. Obiekty funkcyjne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.2.1. Wyrażenia lambda . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.2.2. Klasy funktorów . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
1.3. Algorytmy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
1.4. Wskazówki do zadania . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
3
4
Spis treści
1. Zadanie 3
1.1. Wprowadzenie
Biblioteka algorytmów pozwala na parametryzowanie algorytmów za pomoca, funktorów. Role, funktora może pelnić wskaźnik do funkcji, wyrażenie lambda oraz klasa z
przeladowanym operatorem nawiasów okrag
, lych.
1.2. Obiekty funkcyjne
1.2.1. Wyrażenia lambda
Wyrażenia lambda daja, możliwość utworzenia domkniecia:
obiektu anonimowej funk,
cji, która ma możliwość przechwytywania zmiennych z zasiegu
ja, otaczajacego.
,
,
Podstawowa skladnia wyrażeń lambda jest nieskomplikowana:
[ l i s t a −przechwytywanych−zmiennych ] ( parametry )
{ c i a l o−wyra ż e ni a −lambda}
W nawiasach kwadratowych sa, przekazywane zmienne z zasiegu
otaczajacego
wyrażenie
,
,
lambda, dzieki
tej konstrukcji jest możliwe użycie zmiennych wewnatrz
ciala wyrażenia
,
,
lambda. Zmienne moga, być przechwytywane przez wartość (sama nazwa zmiennej) lub
przez referencje, (nazwa zmienne poprzedzona &). Oprócz wybranych zmiennych można
wciagn
, ać
, do wyrażenia lambda wszystkie zmienne przez referencje, lub przez kopiowanie.
Na przyklad:
– [x,y](){} - do wyrażenia lambda przekazywane sa, kopie zmiennych x i y, nie można
ich modyfikować,
– [&x,&y](){} - do wyrażenia lambda przekazywane sa, zmienne x i y za pomoca,
referencji, można te zmienne modyfikować,
– [x,&y](){} - do wyrażenia lambda przekazywane sa, zmienne x i y, x jest kopiowane,
zaś y przekazywane za pomoca, referencji,
– [&](){} - przechwytuje wszystkie zmienne z zasiegu
otaczajacego
przez referencje,
,
,
5
6
1. Zadanie 3
– [=](){} - kopiuje wszystkie zmienne z zasiegu
otaczajacego,
,
,
– [](){} - nic nie jest przekazywane do wyrażenia lambda.
Parametry oznaczaja, parametry wywolania funkcji. W tej formie wyrażenie lambda
może zawierać jedynie prosta, instrukcje, powrotu return(C++11). Na podstawie wyrażenia używanego z return kompilator jest w stanie wywnioskować typ wartości zwracanej z
funkcji.
Na podstawie tego krótkiego wprowadzenia można przedstawić kilka wyrażeń lambda:
auto fun = [ ] ( double x , double y ) { return x > y ; } ;
s t d : : c ou t << s t d : : b o o l a l p h a << fun ( 4 , 6 ) << s t d : : e n d l ;
W ten sposób utworzono wyrażenie lambda, które nie przechwytuje zmiennych z zewnatrz
,
(pusta lista zmiennych), oczekuje na wejściu dwóch parametrów x i y i zwraca wynik ich
porównania (bool).
double a = 5 ;
auto fun2 = [ a ] ( double x ) { return x > a ; } ;
s t d : : c ou t << s t d : : b o o l a l p h a << fun2 ( 6 ) << s t d : : e n d l ;
Z kolei w tym przypadku do zasiegu
wyrażenia lambda przekazywana jest zmienna a,
,
która jest porównywana z przekazywanym do funktora parametrem.
Typ wartości zwracanej W niektórych sytuacjach kompilator nie jest w stanie wywnioskować typu wartości zwracanej, dlatego trzeba go wtedy jawnie poinformować, jaki
bedzie
typ wartości zwracanej. Zapis jest nastepuj
acy:
,
,
,
[ l i s t a −przechwytywanych−zmiennych ] ( parametry ) −> typ
{ c i a l o−wyra ż e ni a −lambda}
Na przyklad:
double tab [ ] = { 1 , 2 , 3 } ;
auto fun2 = [ tab ] ( ) −> double {
double sum = 0 . 0 ;
for ( auto elem : tab )
sum += elem ;
return sum ;
};
s t d : : c ou t << fun2 ( ) << s t d : : e n d l ;
1.2. Obiekty funkcyjne
7
1.2.2. Klasy funktorów
Oprócz wyrażeń lambda jezyk
C++ pozwala na zdefiniowanie funktorów, klas, które
,
zachowuja, sie, jak funkcja. Skladnia jest nastepuj
aca:
,
,
c l a s s Pred {
s t d : : s t r i n g nazwiskoPoszukiwane ;
public :
Pred ( s t d : : s t r i n g naz ) : nazwiskoPoszukiwane ( naz ) {}
bool operator ( ) ( const Osoba& o )
{
return o . nazwisko == nazwiskoPoszukiwane ;
}
};
// . . .
Osoba osoba ;
// K o n s t r u k t o r f u n k t o r a
Pred pred ( ”Nocul ”) ;
// Operator f u n k c y j n y
s t d : : c out << pred ( osoba ) ;
To, że definiujemy funktor pokazuje slowo kluczowe operator z nastepuj
acymi
po nim
,
,
nawiasami okrag
nich nie wpisuje sie, nazwy argumentów, parametry
, lymi () wewnatrz
,
wpisuje sie, w drugiej parze nawiasów okrag
, lych. Klasy funktorów moga, zawierać dane,
funkcje skladowe (szczególnie używane sa, konstruktory).
Zwróćcie uwage, na dwa aspekty wykorzystania funktora - jego utworzenie z zainicjowaniem za pomoca, konstruktora i jego wykorzystanie (wywolanie operatora funkcyjnego).
Ma to szczególne znaczenie, przy ich wykorzystywaniu, gdy wywolanie funktora bedzie
za,
szyte wewnatrz
szablony, jedyne co jest widoczne to utworzenie funktora. Bedzie
to lepiej
,
,
widoczne na nastepuj
acym
przykladzie:
,
,
c l a s s Generator {
public :
Generator ( int l i c z ) : l i c z b a ( l i c z )
{}
int operator ( ) ( ) {
return l i c z b a ;
}
private :
int l i c z b a ;
};
8
1. Zadanie 3
//Moż na w y o b r a z i ć s o b i e n a s t e, p u j a, cy s z a b l o n
template<typename I t e r , typename Gen>
void g e n e r a t e ( I t e r b , I t e r e , Gen g ) {
for ( ; b!= e;++b )
//Uż y c i e o p e r a t o r a f u n k c y j n e g o
∗b = g ( ) ;
}
// . . .
int [ 1 0 ] tab ;
//Tu j e s t wo l any k o n s t r u k t o r
Gen g ( 1 2 ) ;
g e n e r a t e ( tab , tab + 1 0 , g ) ;
//To samo , a l e z u ż yciem anonimowego o b i e k t u
g e n e r a t e ( tab , tab +10 , Gen ( 1 2 ) ) ;
Obiekt klasy Generator jest przekazywany do szablonu funkcji generate - jest zainicjowany konstruktorem. Operator funkcyjny jest wykorzystywany wewnatrz
szablonu (co
,
robi ten funktor?). Ten aspekt wykorzystania obiektu funkcyjnego jest niewidoczny w
przypadku korzystania z funkcji bibliotecznych.
1.3. Algorytmy
Jak wspomniano funktory sa, wykorzystywane prze algorytmy ogólne z biblioteki STL.
Algorytmy ogólne to szablony funkcji przygotowane dla wielu zadań takich jak sortowanie, wyszukiwanie wartości itd. Oczywiście różne algorytmy maja, różne wymagania
odnośnie funktorów - co maja, zwracać jakie wartości pobierać, na przyklad (przyklady z
wyrażeniami lambda):
– Jeśli coś generujemy to funktor nie powinien pobierać żadnych argumentów i zwracać
generowany obiekt.
– Funktory używane do wyszukiwania elementów powinny zwracać wartość logiczna,
(bool) oraz pobierać jeden argument.
#include <i o s t r e a m >
#include <a l g o r i t h m >
#include <v e c t o r >
using namespace s t d ;
int main ( ) {
1.4. Wskazówki do zadania
9
v e c t o r <double> vec = { 1 , 2 , 3 , 4 , 5 } ;
// Z n a j d u j e i t e r a t o r do p i e r w s z e g o elementu s p e l n i a j a, cego
predykat
auto i t e r = f i n d i f ( vec . b e g i n ( ) , vec . end ( ) ,
[ ] ( double x ) { return x > 4 ; } ) ;
cout << ∗ i t e r ;
}
– Funktory używane do nadawania porzadku
elementom (np. w przypadku sortowania)
,
powinny zwracać wartość logiczna, i pobierać dwa argumenty, które chcemy porównywać.
#include <i o s t r e a m >
#include <a l g o r i t h m >
#include <v e c t o r >
using namespace s t d ;
int main ( ) {
v e c t o r <double> vec = { 1 , 2 , 3 , 4 , 5 } ;
// S o r t u j e e l e m e n t y w e k t o r a w p o r z a, dku m a l e j acycm
,
s o r t ( vec . b e g i n ( ) , vec . end ( ) ,
[ ] ( double x , double y ) { return x > y ; } ) ;
}
1.4. Wskazówki do zadania
Wektor vector<T> to odpowiednik tablicy, lista list<T> to lista dwukierunkowa.
Zbiór set<T> to struktura uporzadkowana
przechowujaca
klucze. Poczatki
sekwencji jak
,
,
,
widać na podstawie zalaczonych
przykladów sa, pokazywane za pomoca, iteratorów (dla
,
zwyklej tablicy wystarczaja, wskaźniki na poczatek
i pierwszy element tablicy - tak jak we
,
wprowadzeniu) - sa, one uzyskiwane za pomoca, metod begin() i end().
Klasa ostream_iterator wymaga przeciażenia
operatora wysylania do strumienia ,
inaczej dostaniecie kilka ekranów bledów
(kompilator stara sie, być pomocny i próbuje
,
ustalić co móglby programista użyć w danym momencie). Oczywiście przeciażenia
opera,
tora jest wymagane dla klas i struktur zdefiniowanych przez użytkownika.
W celu użycia klas i szablonów należy dolaczyć
naglówki, odpowiednio:
,
– vector<T> - <vector>,
10
1. Zadanie 3
– list<T> - <list>,
– set<T> - <set>,
– sort,remove_if,generate - <algorithm>,
– ostream_iterator<T> - <iterator>,
Zwrócić uwage, na różnice pomiedzy
przekazywaniem funktora jako parametru szablonu
,
oraz jako parametru dla funkcji. Opisy algorytmów http:
– http://www.sgi.com/tech/stl/,
– http://www.cplusplus.com/reference/.