arytmetyczne, relacyjne, logiczne
Transkrypt
arytmetyczne, relacyjne, logiczne
obiekty funkcyjne (funktory) Obiekty klas, w których przeciążono operator funkcyjny (wywołania funkcji) • Najczęstsze wykorzystanie jako predykaty (orzeczniki logiczne) używane w ogólnych algorytmach • Mogą zawierać dodatkowe dane potrzebne do wykonania operacji Obiekty funkcyjne biblioteki standardowej – wzorce klas Zdefiniowane w pliku nagłówkowym <functional> arytmetyczne, relacyjne, logiczne plus<T> minus<T> multiplies<T> divides<T> modulus<T> negate<T> equal_to<T> not_equal_to<T> greater<T> greater_equal<T> less<T> less_equal<T> logical_and<T> logical_or<T> logical_not<T> obiekty funkcyjne – przykłady multiplies<double> m; equal_to<char> e; logical_or<int> l; cout<< m(2.5, 3.1) <<” ”<< e(’a’, ’a’) <<” ”<< l(3, 1); // 7.75 1 1 int t[] = { 5, -7, 3, 1, -1, 2 }; list<int> l(t, t+6); // w C++11 list<int> l {5, -7, 3, 1, -1, 2}; l.sort( less<int>() ); // jest też algorytm sort( RandomIt, RandomIt, Pred ); // ale działa na iteratorach „dostępu swobodnego”, a takie // nie obsługują listy – dlatego lista ma własną metodę sort (i inne) ostream_iterator<int> out(cout, " "); copy(l.begin(), l.end(), out); // -7 -1 1 2 3 5 vector<int> v(t, t+6); sort( v.begin(), v.end(), greater<int>() ); copy( v.begin(), v.end(), out ); // 5 3 2 1 -1 -7 obiekty funkcyjne – przykłady struct intsort { bool operator() (const int& l, const int& r) { return l < r; } }; // można użyć własny funktor l.sort( intsort() ); // dzięki szablonowi można pokazać dowolny typ sekwencyjny template<typename T> struct prezentuj { prezentuj(ostream& o, string s = "") : strumien(o), separator(s) { } void operator () (const T& p) { strumien << p << separator; } private: ostream& strumien; string separator; }; template <typename T> void view(T con) { for_each(con.begin(), con.end(), prezentuj<typename T::value_type>(cout, " -=- ")); } // w programie... view(v); view(l); funktory – typy argumentów i zwracanych wartości Aby umożliwić adaptorom i innym komponentom operacje na obiektach funkcyjnych, przyjmujących jeden lub dwa argumenty, wymagane jest aby funktory te dostarczały przypisanie (typedef) odpowiednich typów nazwom argument_type i result_type (dla funktorów jednoargumentowych) oraz first_argument_type, second_argument_type i result_type (dla funktorów dwuargumentowych). Standardową procedurą ułatwiającą te definicje, jest dziedziczenie z następujących klas bazowych: template <class Arg, class Result> struct unary_function { typedef Arg argument_type; typedef Result result_type; }; template <class Arg1, class Arg2, class Result> struct binary_function { typedef Arg1 first_argument_type; typedef Arg2 second_argument_type; typedef Result result_type; }; adaptory obiektów funkcyjnych – wiązadła wzorce klas pozwalające na zmianę funkcjonalności obiektów funkcyjnych nie używa się ich bezpośrednio lecz za pomocą funkcji zwracających obiekty tych klas wiązadło (poniższe to „historyczne” wersje dla C++98) przekształca dwuargumentowy funktor w jednoargumentowy, związując jeden z argumentów z konkretną wartością bind1st – wiąże pierwszy argument – szablon funkcji tworzący funktor typu binder1st bind2nd – wiąże drugi argument – szablon funkcji tworzący funktor typu binder2nd – funktory te przechowują dwa argumenty, przekazane w wywołaniu bind1st() / bind2nd() – pierwszy argument musi być dwuargumentową funkcją (lub funktorem) – operator() obiektu binder1st / binder2nd wywołuje przekazaną funkcję dwuargumentową, podając jej otrzymany argument wywołania i zapamiętaną wartość zadaną, odpowiednio jako pierwszy / drugi argument // zawartość listy: -7 -1 1 2 3 5 l.remove_if(bind1st( less<int>(), 1 )); // 1 < prawy argument kolejne wyrazy z listy // z oryginalnej zawartości zostaną: -7 -1 1 l.remove_if(bind2nd( less<int>(), 1 )); // lewy argument kolejne wyrazy z listy < 1 // z oryginalnej zawartości zostaną: 1 2 3 5 A gdyby użyć np. equal_to<int> ? Wtedy wszystko jedno czy bind1st czy bind2nd – bo argumenty operatora == są symetryczne. std::bind ( C++11 ), bind1st, bind2nd – „przestarzałe” bind1st, bind2nd – są ograniczone, wiążą tylko pierwszy lub drugi argument – nie mogą wiązać funkcji z argumentem typu „referencja do” – nierzadko wymagają adaptacji obiektów funkcyjnych (za pomoca ptr_fun, mem_fun, mem_fun_ref) obiekt_funkcyjny std::bind( wielkość_wywoływalna, 1arg, 2arg, …, Narg ); Zastępcze obiekty pozwalają na mapowanie argumentów bind z argumentami wielkości wywoływalnej. Notacja: _n oznacza n-ty argument przekazany do obiektu funkcyjnego zwracanego przez bind. l.remove_if( bind ( less<int>(), 1, _1 )); // to samo co bind1st wcześniej l.remove_if( bind ( less<int>(), _1, 1 )); // to samo co bind2nd wcześniej Uwaga: zastępczy obiekt ma nazwę _1 (dla kolejnych parametrów _2 itd.) Te obiekty są formalnie umieszczone w przestrzeni nazw std::placeholders. Jeśli widzimy błąd kompilacji, trzeba dodać jedno z: using namespace std::placeholders; // cała przestrzeń nazw using std::placeholders::_1; // dyrektywa użycia konkretnego obiektu std::bind - przykłady #include <iostream> #include <functional> using namespace std; using namespace std::placeholders; int suma1( int n1, int n2, int& n3, int& n4 ) { n3 = 0; n4 = 0; return n1+n2+n3+n4; } int main() { cout << bind( suma1, 1, 2, 3, 4 )() << endl; // 3, argumenty przez wartość int var1 = 1, var2 = 2, var3 = 3, var4 = 4; cout << bind( suma1, var1, var2, var3, var4 )() << endl; // 3, jak wyżej cout << var1 << ", " << var2 << ", " << var3 << ", " << var4 << endl; // 1, 2, 3, 4 var1 = 1, var2 = 2, var3 = 3, var4 = 4; cout << bind( suma1, var1, var2, ref(var3), var4 )() << endl; // 3 cout << var1 << ", " << var2 << ", " << var3 << ", " << var4 << endl; // 1, 2, 0, 4 var1 = 1, var2 = 2, var3 = 3, var4 = 4; cout << bind( suma1, var1, _2, _1, var4 )(var2,var3) << endl; // 4 placeholders przez referencję cout << var1 << ", " << var2 << ", " << var3 << ", " << var4 << endl; // 1, 0, 3, 4 } cout << bind( suma1, _1, _2, _5, var4 )(77, var2, var3, var4, var1) << endl; std::bind – dalsze przykłady Wysłanie do strumienia: void wypisz( ostream& strumien, int n ) { strumien << n << endl; } bind( wypisz, ref(cout), 777)(); list<int> ll { 1,2,3,4,5 }; for_each( ll.begin(), ll.end(), bind(wypisz, ref(cout), _1) ); Zabawy z initializer_list: int suma(initializer_list<int> il) { int sum {0}; for ( auto n : il ) { cout<<n<<" "; sum+=n; } return sum; } // w programie: // to nie działa: bind( suma, { 1, 2, 3, 4, 5 } )(); int sumka = bind( suma, initializer_list<int>{ 1, 2, 3, 4, 5 } )(); // ok cout << bind( suma, _1 )( initializer_list<int>{ 1, 2, 3, 4, 5 } ); // ok negator adaptory obiektów funkcyjnych – negatory (C++98) to funkcja, która zmienia wartość logiczną funktora na wartość przeciwną not1 – odwraca wartość logiczną funktora jednoargumentowego not2 – odwraca wartość logiczną funktora dwuargumentowego // negacja unary_function template <class Predicate> unary_negate<Predicate> not1(const Predicate& pred); // negacja binary_function template <class Predicate> binary_negate<Predicate> not2(const Predicate& pred); // zawartość listy: -7 -1 1 2 3 5 l.remove_if( not1( bind1st( equal_to<int>(), 3 ) ) ); // bind nie działa… patrz dalej // z oryginalnej zawartości zostanie: 3 l.sort( not2( less<int>() ) ); // to samo jak użycie do sortowania greater<int> // czyli oryginalna zawartość: 5 3 2 1 -1 -7 adaptory wskaźników do funkcji – ptr_fun (C++98) Zamiast samodzielnie dziedziczyć z klas unary_function czy binary_function, można użyć specjalnego adaptora ptr_fun, przyjmującego wskaźnik do funkcji i konwertującego ją do postaci obiektu funkcyjnego. Funkcje te muszą być jedno- lub dwuargumentowe (nie mogą być bezargumentowe). template <class Arg, class Result> pointer_to_unary_function<Arg, Result> ptr_fun(Result (*f)(Arg)); // jednoargumentowa template <class Arg1, class Arg2, class Result> pointer_to_binary_function<Arg1,Arg2,Result> ptr_fun(Result (*f)(Arg1, Arg2)); // dwuargumentowa Typy zwracane przez funkcję ptr_fun rzeczywiście dziedziczą z odpowiednich klas: template <class Arg, class Result> class pointer_to_unary_function : public unary_function<Arg, Result>; template <class Arg1, class Arg2, class Result> class pointer_to_binary_function : public binary_function<Arg1,Arg2,Result>; ptr_fun – przykład (i alternatywy w C++11) #include <string> #include <iostream> #include <algorithm> #include <functional> bool isvowel(char c) { return std::string("aeoiuAEIOU").find(c) != std::string::npos; } int main() { std::string s = "Hello, world!"; std::copy_if(s.begin(), s.end(), std::ostreambuf_iterator<char>(std::cout), std::not1(std::ptr_fun(isvowel))); // C++11 alternatywy: // std::not1(std::cref(isvowel))); // std::not1(std::function<bool(char)>(isvowel))); // UWAGA: bind nie działa z not1 gdyż bind nie definiuje argument_type // jak tego oczekuje not1 } adaptory do metod składowych – mem_fun (C++98), mem_fn (C++11) Gdy chcemy zaadaptować metodę z klasy, przekazując ją przez wskaźnik do składowej klasy, używamy adaptor mem_fun, który generuje obiekt funkcyjny. Jego metoda, która ma być docelowo użyta, jest wywołana za pomocą wskaźnika do tego obiektu. Są wersje dla metod z przydomkiem const i dla metod modyfikujących (jak poniżej): template<class S, class T> mem_fun_t<S,T> mem_fun(S (T::*f)()); // bezargumentowa template<class S, class T, class A> mem_fun1_t<S,T,A> mem_fun(S (T::*f)(A)); // jednoargumentowa Tym razem zwracany typ to: template <class S, class T> class mem_fun_t : public unary_function<T*, S> { public: explicit mem_fun_t(S (T::*p)()); S operator()(T* p) const; }; template <class S, class T, class A> class mem_fun1_t : public binary_function<T*, A, S>; adaptory wskaźników do metod – mem_fun_ref (C++98) , mem_fn (C++11) Adaptor mem_fun_ref generuje obiekt funkcyjny, którego metoda jest wywoływany za pomocą referencji do obiektu (czyli bezpośrednio na rzecz danego obiektu). Są wersje dla metod z przydomkiem const i dla metod modyfikujących (poniżej bez const): template<class S, class T> mem_fun_ref_t<S,T> mem_fun_ref(S (T::*f)()); // bezargumentowa template<class S, class T, class A> mem_fun1_ref_t<S,T,A> mem_fun_ref(S (T::*f)(A)); // jednoargumentowa Zwracany typ to: template <class S, class T> class mem_fun_ref_t : public unary_function<T, S> { public: explicit mem_fun_ref_t(S (T::*p)()); S operator()(T& p) const; }; template <class S, class T, class A> class mem_fun1_ref_t : public binary_function<T, A, S>; ptr_fun, mem_fun, mem_fun_ref – przykłady (C++98) const char* tab[] = {"alice", "has", "a", "cat"}; replace_if( tab, tab+4, not1(bind2nd(ptr_fun(strcmp), ”alice”)),”jarek” ); // przykład ryzykowny, jeśli łańcuch ”jarek” byłby dłuższy niż łańcuch ”alice”... ostream_iterator<const char*> out (cout, " "); copy(tab, tab+4, out); // jarek has a cat struct A { void fun(int i) {cout<<"A::fun = "<< i <<endl;} }; struct B { int fun2(int i) const { return 2*i; } }; • bind może również int x[] = {-4, -2, 0, 1, 3}; zastępować adaptory! // za pomocą wskaźnika, czyli mem_fun • jeśli nie ma argumentów vector<A*> va; to mem_fn wygodniejsze va.push_back(new A); va.push_back(new A); // hej, a kto to potem usunie? ;-) for_each(va.begin(),va.end(),bind2nd(mem_fun(&A::fun), 5)); // za pomocą referencji do obiektu, czyli mem_fun_ref vector<B> v; v.push_back(B()); v.push_back(B()); // tu posprząta się samo... transform(v.begin(), v.end(), x, ostream_iterator<int>(cout, " "), mem_fun_ref(&B::fun2) ); // -8 -4 bind zastępuje też adaptory (C++11) class Klasa { public: Klasa(int n) : val( n ) { } void foo() { cout << val; } void bar() const { cout << 10+val; } private: int val; }; template<typename T> struct pointer { T* operator() (T& t) { return &t; } }; Klasa tab[] = {9, 1, 8, 2, 7, 3, 6, 4, 5, 0}; // for_each(tab, tab+10, mem_fun_ref(&Klasa::foo)); // 9182736450 for_each( tab, tab+10, bind(&Klasa::foo,_1) ); // 9182736450 list<const Klasa*> lista; transform(tab, tab+10, back_inserter(lista), pointer<Klasa>()); // for_each(lista.begin(), lista.end(), mem_fun(&Klasa::bar)); // 19111812171316141510 for_each(lista.begin(), lista.end(), bind(&Klasa::bar,_1)); // 19111812171316141510