Każdy programista wie, co to jest funkcja - blok kodu wywoływany wielokrotnie w programie. W języku C++ istnieją także inne konstrukcje, które są podobne do funkcji. Podlegają one tzw. kaczemu typowaniu - "jeśli porusza się jak kaczka i kwacze jak kaczka, jest kaczką".
Inaczej mówiąc, takie konstrukcje zachowują się jak funkcje - mogą być używane w takich samych miejscach i kontekstach, jak zwykłe funkcje. Co to są za konstrukcje? Wyliczmy je po kolei:
Wskaźnik do funkcji jest zmienną, która przechowuje adres funkcji. Oczywiście oprócz adresu musi też przechowywać informacje o przekazanych paramatrach i typie zwracanej wartości.
Załóżmy, że mamy następującą definicję funkcji:
int fun(char c, int i)
{
return i + c;
}
Dla tej funkcji zdefiniujmy odpowiedni wskaźnik:
int (*funPtr)(char, int)
Składnia jest trochę dziwna, ponieważ nazwę wskaźnika otaczają nawiasy okrągłe. Gdybyśmy się jednak ich pozbyli, wyrażenie przekszatałciłoby się w deklarację zwykłej funkcji zwracającej wskaźnik do int
.
Ponieważ wyrażenia określające wskaźniki do funkcji są zazwyczaj dość skomplikowane, używa się odpowiedniego aliasu. W starszych wersjach C++ wykorzystywało się w tym celu specyfikator typedef
, ale od C++ można użyć słowa kluczowego using
:
typedef int (*FUN_PTR_OLD)(char, int); // starszy C++
using FunPtrNew = int (*)(char, int); // C++11 i nowsze
Zmienną wskaźnikową można teraz zainicjalizować adresem funkcji fun
. Zróbmy to używając obu rodzajów aliasów:
FUN_PTR_OLD funPtr1 { fun };
FunPtrNew funPtr2 { fun };
Zmiennymi wskaźnikowymi są funPtr1
i funPtr2
. Obie wskazują tę samą funkcję fun
.
Można też użyć słowa kluczowego auto
i dzięki niemu uprościć deklarację zmiennej wskaźnikowej:
auto funPtr3 { fun };
Wskaźnika do funkcji można używać tak samo, jak zwykłej funkcji:
std::cout << funPtr3(60, 10) << std::endl;
Dodatkowo może on być parametrem innych funkcji (zwanych funkcjami wyższego rzędu), na przykład:
void funCaller(FunPtrNew funPtr)
{
std::cout << funPtr(60, 10) << std::endl;
}
Wskaźniki do funkcji można wykorzystywać w konstruowaniu procedur obsługi (callback-ów). Ma to miejsce w programowaniu asynchronicznym, gdy należy powiadamiać aplikację o zakończeniu pewnej operacji, która przebiega w tle.
Obiekty funkcyjne są podobne do wskaźników do funkcji w tym sensie, że zachowują się jak funkcje. Różnica polega na tym, że są one pełnoprawnymi obiektami, które mogą zawierać składowe, a dzięki temu przechowywać stan.
Są one tworzone poprzez przeciążenie operatora wywołania funkcji (nawiasów okrągłych). Utwórzmy jeden z najprostszych obiektów funkcyjnych:
class IsZero
{
public:
bool operator()(const int val) const
{
return val == 0;
}
};
Wykorzystanie tak zdefiniowanego obiektu funkcyjnego jest łatwe. Wystarczy utworzyć zmienną, a następnie ją użyć:
IsZero isZero;
std::cout << std::boolalpha << isZero(5) << std::endl;
std::cout << isZero(-10) << std::endl;
std::cout << isZero(0) << std::endl;
Oto uzyskane wyniki:
false
false
true
Obiekt funkcyjny można również użyć "w miejscu" (bez definiowania zmiennej). Należy zastosować składnię z nawiasami okrągłymi, po których następują kolejne nawiasy okrągłe, wewnątrz których podaje się argumenty:
std::cout << IsZero()(7) << std::endl;
Zauważmy, że w powyższym wywołaniu użyto nazwy klasy.
Jak we wcześniejszym przypadku wskaźników do funkcji, użyjmy obiektu funkcyjnego jako parametru funkcji:
class Add2Vals
{
public:
int operator()(const char c, const int i) const
{
return c + i;
}
};
void funCaller2(Add2Vals funObj)
{
std::cout << funObj(1, 2) << std::endl;
}
int main()
{
funCaller2(Add2Vals());
}
Specjalnym przypadkiem obiektów funkcyjnych są wyrażenia lambda. Zostały one dokładnie przeanalizowane w oddzielnym artykule, więc zapraszam do jego przeczytania.
Do utworzenia rysunku użyto następujących zdjęć:
"Duck" by dew drop157 is licensed under CC BY 2.0
"Mandarin duck" by Bernard Spragg is licensed under CC0 1.0
"Lesser Whistling Duck" by Koshyk is licensed under CC BY 2.0
"Duck" by Ersu is licensed under CC BY 2.0
https://creativecommons.org/licenses/by/2.0/