Co to jest wyrażenie lambda w C ++ 11? Kiedy miałbym go użyć? Jakiego rodzaju problemy rozwiązują, które nie były możliwe przed ich wprowadzeniem?
Przydałoby się kilka przykładów i przypadków użycia.
Co to jest wyrażenie lambda w C ++ 11? Kiedy miałbym go użyć? Jakiego rodzaju problemy rozwiązują, które nie były możliwe przed ich wprowadzeniem?
Przydałoby się kilka przykładów i przypadków użycia.
Odpowiedzi:
C ++ zawiera przydatne funkcje ogólne, takie jak std::for_each
i std::transform
, które mogą być bardzo przydatne. Niestety mogą być również uciążliwe w użyciu, szczególnie jeśli funktor , który chcesz zastosować, jest unikalny dla konkretnej funkcji.
#include <algorithm>
#include <vector>
namespace {
struct f {
void operator()(int) {
// do something
}
};
}
void func(std::vector<int>& v) {
f f;
std::for_each(v.begin(), v.end(), f);
}
Jeśli użyjesz tylko f
raz i w tym konkretnym miejscu, wydaje się przesadą pisać całą klasę, aby zrobić coś trywialnego i jednorazowego.
W C ++ 03 możesz mieć ochotę napisać coś takiego, aby utrzymać funktor lokalny:
void func2(std::vector<int>& v) {
struct {
void operator()(int) {
// do something
}
} f;
std::for_each(v.begin(), v.end(), f);
}
jednak nie jest to dozwolone, f
nie można go przekazać do funkcji szablonu w C ++ 03.
C ++ 11 wprowadza lambdas, które pozwalają napisać wbudowany, anonimowy funktor, który zastąpi struct f
. W przypadku małych prostych przykładów może to być łatwiejsze do odczytania (utrzymuje wszystko w jednym miejscu) i potencjalnie prostsze w utrzymaniu, na przykład w najprostszej formie:
void func3(std::vector<int>& v) {
std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}
Funkcje lambda to po prostu cukier syntaktyczny dla anonimowych funktorów.
W prostych przypadkach obliczany jest typ zwrotu lambda, np .:
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) { return d < 0.00001 ? 0 : d; }
);
}
jednak kiedy zaczniesz pisać bardziej złożone lambdy, szybko napotkasz przypadki, w których kompilator nie może wydedukować typu zwrotu, np .:
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) {
if (d < 0.0001) {
return 0;
} else {
return d;
}
});
}
Aby rozwiązać ten problem, możesz jawnie określić typ zwracany dla funkcji lambda, używając -> T
:
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) -> double {
if (d < 0.0001) {
return 0;
} else {
return d;
}
});
}
Do tej pory nie używaliśmy niczego innego niż to, co zostało przekazane do niej w lambdzie, ale możemy również używać innych zmiennych w obrębie lambda. Jeśli chcesz uzyskać dostęp do innych zmiennych, możesz użyć klauzuli przechwytywania ( []
wyrażenia), która do tej pory nie była używana w tych przykładach, np .:
void func5(std::vector<double>& v, const double& epsilon) {
std::transform(v.begin(), v.end(), v.begin(),
[epsilon](double d) -> double {
if (d < epsilon) {
return 0;
} else {
return d;
}
});
}
Możesz przechwytywać zarówno według odwołania, jak i wartości, które możesz określić za pomocą &
i =
odpowiednio:
[&epsilon]
przechwytywanie przez odniesienie[&]
przechwytuje wszystkie zmienne używane w lambda przez odniesienie[=]
przechwytuje wszystkie zmienne używane w lambda według wartości[&, epsilon]
przechwytuje zmienne takie jak z [&], ale epsilon według wartości[=, &epsilon]
przechwytuje zmienne jak z [=], ale epsilon przez odniesienieGenerowane operator()
jest const
domyślnie, z implikacją, że przechwytywanie nastąpi const
po domyślnym dostępie do nich. Powoduje to, że każde wywołanie z tym samym wejściem daje ten sam wynik, jednak można oznaczyć lambda jakomutable
żądanie, operator()
aby nie zostało wygenerowane const
.
const
zawsze ...
()
- jest przekazywany jako lambda z zerowym argumentem, ale ponieważ () const
nie pasuje do lambda, szuka konwersji typu, która na to pozwala, w tym niejawnego rzutowania -do-funkcji-wskaźnik, a następnie wywołuje to! Podstępny!
std::function<double(int, bool)> f = [](int a, bool b) -> double { ... };
Ale zwykle pozwalamy kompilatorowi wydedukować typ: auto f = [](int a, bool b) -> double { ... };
(i nie zapomnij #include <functional>
)
return d < 0.00001 ? 0 : d;
gwarantowany jest zwrot podwójny, gdy jeden z operandów jest stałą całkowitą (wynika to z domyślnej reguły promocji operatora?: Operator, w której drugi i trzeci operand są równoważone względem siebie za pomocą zwykłej arytmetyki konwersje bez względu na to, który zostanie wybrany). Zmiana na 0.0 : d
być może ułatwiłaby zrozumienie przykładu.
Koncepcja C ++ funkcji lambda wywodzi się z rachunku lambda i programowania funkcjonalnego. Lambda jest nienazwaną funkcją przydatną (w rzeczywistym programowaniu, nie w teorii) dla krótkich fragmentów kodu, których nie można ponownie użyć i których nie warto nazywać.
W C ++ funkcja lambda jest zdefiniowana w ten sposób
[]() { } // barebone lambda
lub w całej okazałości
[]() mutable -> T { } // T is the return type, still lacking throw()
[]
to lista przechwytywania, lista ()
argumentów i {}
treść funkcji.
Lista przechwytywania określa, co z zewnątrz lambda powinno być dostępne wewnątrz ciała funkcji i jak. Może to być:
Możesz mieszać dowolne z powyższych elementów na liście oddzielonej przecinkami [x, &y]
.
Lista argumentów jest taka sama jak w każdej innej funkcji C ++.
Kod, który zostanie wykonany po wywołaniu lambda.
Jeśli lambda ma tylko jedną instrukcję return, typ zwracany można pominąć i ma domyślny typ decltype(return_statement)
.
Jeśli lambda jest oznaczona jako zmienna (np. []() mutable { }
), Można mutować wartości, które zostały przechwycone przez wartość.
Biblioteka zdefiniowana w standardzie ISO znacznie korzysta z lambdas i podnosi użyteczność kilku pasków, ponieważ teraz użytkownicy nie muszą zaśmiecać kodu małymi funktorami w dostępnym zakresie.
W C ++ 14 lambdów zostało poszerzonych o różne propozycje.
Teraz można zainicjować element listy przechwytywania =
. Pozwala to na zmianę nazw zmiennych i przechwytywanie przez przenoszenie. Przykład wzięty ze standardu:
int x = 4;
auto y = [&r = x, x = x+1]()->int {
r += 2;
return x+2;
}(); // Updates ::x to 6, and initializes y to 7.
i jeden wzięty z Wikipedii pokazujący, jak robić zdjęcia std::move
:
auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};
Lambdas mogą być teraz ogólne ( auto
byłoby to równoważne z T
tym, gdyby
T
gdzieś w otaczającym zakresie był argument szablonu typu):
auto lambda = [](auto x, auto y) {return x + y;};
C ++ 14 pozwala przewidywać typy zwrotów dla każdej funkcji i nie ogranicza jej do funkcji formularza return expression;
. Dotyczy to również lambd.
r = &x; r += 2;
ale dzieje się tak z pierwotną wartością 4.
Wyrażenia lambda są zwykle używane do enkapsulacji algorytmów, aby można je było przekazać do innej funkcji. Jednak możliwe jest natychmiastowe wykonanie lambda od definicji :
[&](){ ...your code... }(); // immediately executed lambda expression
jest funkcjonalnie równoważny z
{ ...your code... } // simple code block
To sprawia, że wyrażenia lambda są potężnym narzędziem do refaktoryzacji złożonych funkcji . Zaczynasz od zawijania sekcji kodu w funkcji lambda, jak pokazano powyżej. Proces jawnej parametryzacji można następnie przeprowadzać stopniowo za pomocą testów pośrednich po każdym kroku. Po pełnym sparametryzowaniu bloku kodu (jak pokazano po usunięciu &
), możesz przenieść kod do zewnętrznego miejsca i ustawić go jako normalną funkcję.
Podobnie możesz użyć wyrażeń lambda do zainicjowania zmiennych na podstawie wyniku algorytmu ...
int a = []( int b ){ int r=1; while (b>0) r*=b--; return r; }(5); // 5!
Jako sposób na podzielenie logiki programu może okazać się przydatne przekazanie wyrażenia lambda jako argumentu do innego wyrażenia lambda ...
[&]( std::function<void()> algorithm ) // wrapper section
{
...your wrapper code...
algorithm();
...your wrapper code...
}
([&]() // algorithm section
{
...your algorithm code...
});
Wyrażenia lambda pozwalają również tworzyć nazwane funkcje zagnieżdżone , co może być wygodnym sposobem uniknięcia podwójnej logiki. Używanie nazwanych lambdas jest również nieco łatwiejsze dla oczu (w porównaniu do anonimowych lambdów wbudowanych), gdy przekazuje się nietrywialną funkcję jako parametr do innej funkcji. Uwaga: nie zapomnij o średniku po zamykającym nawiasie klamrowym.
auto algorithm = [&]( double x, double m, double b ) -> double
{
return m*x+b;
};
int a=algorithm(1,2,3), b=algorithm(4,5,6);
Jeśli kolejne profilowanie ujawni znaczny narzut związany z inicjalizacją dla obiektu funkcji, możesz przepisać to jako normalną funkcję.
if
oświadczeń: if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespace
zakładając, że i
jeststd::string
[](){}();
.
(lambda: None)()
Składnia Pythona jest o wiele bardziej czytelna.
main() {{{{((([](){{}}())));}}}}
Odpowiedzi
P: Co to jest wyrażenie lambda w C ++ 11?
Odp .: Pod maską jest to obiekt klasy generowanej automatycznie z przeciążeniem operatora () const . Taki obiekt nazywa się zamknięciem i jest tworzony przez kompilator. Ta koncepcja „zamknięcia” jest zbliżona do koncepcji wiązania z C ++ 11. Ale lambda zazwyczaj generują lepszy kod. Połączenia przez zamknięcia umożliwiają pełne wstawianie.
P: Kiedy miałbym go użyć?
Odp .: Aby zdefiniować „prostą i małą logikę” i poprosić kompilatora o wykonanie generacji z poprzedniego pytania. Dajesz kompilatorowi kilka wyrażeń, które mają znajdować się wewnątrz operatora (). Wszystkie inne kompilatory rzeczy wygenerują dla Ciebie.
P: Jakiego rodzaju problemy rozwiązują, które nie były możliwe przed ich wprowadzeniem?
Odp .: Jest to rodzaj cukru składniowego, takiego jak przeciążanie operatorów zamiast funkcji do niestandardowych operacji dodawania, subrtact ... Zapisuje jednak więcej wierszy niepotrzebnego kodu, aby zawrzeć 1-3 linie prawdziwej logiki w niektórych klasach itp.! Niektórzy inżynierowie uważają, że jeśli liczba wierszy jest mniejsza, wówczas istnieje mniejsze prawdopodobieństwo popełnienia błędów (tak też uważam)
Przykład użycia
auto x = [=](int arg1){printf("%i", arg1); };
void(*f)(int) = x;
f(1);
x(1);
Dodatki dotyczące lambdas, nie objęte pytaniem. Zignoruj tę sekcję, jeśli nie jesteś zainteresowany
1. Przechwycone wartości. Co możesz uchwycić
1.1 Możesz odwoływać się do zmiennej o statycznym czasie przechowywania w lambdas. Wszyscy zostali schwytani.
1.2 Możesz użyć lambda do przechwytywania wartości „według wartości”. W takim przypadku przechwycone zmienne zostaną skopiowane do obiektu funkcji (zamknięcie).
[captureVar1,captureVar2](int arg1){}
1.3 Możesz przechwycić jako odniesienie. & - w tym kontekście oznacza odniesienie, a nie wskaźniki.
[&captureVar1,&captureVar2](int arg1){}
1.4 Istnieje notacja do przechwytywania wszystkich zmiennych niestatycznych według wartości lub referencji
[=](int arg1){} // capture all not-static vars by value
[&](int arg1){} // capture all not-static vars by reference
1.5 Istnieje notacja do przechwytywania wszystkich zmiennych niestatycznych według wartości lub referencji i określania smth. więcej. Przykłady: Przechwyć wszystkie zmienne statyczne według wartości, ale poprzez odniesienie przechwyć Param2
[=,&Param2](int arg1){}
Przechwyć wszystkie zmienne statyczne przez odniesienie, ale przez przechwycenie wartości Param2
[&,Param2](int arg1){}
2. Odliczenie typu zwrotu
2.1 Typ powrotu lambda można wywnioskować, jeśli lambda jest jednym wyrażeniem. Lub możesz to wyraźnie określić.
[=](int arg1)->trailing_return_type{return trailing_return_type();}
Jeśli lambda ma więcej niż jedno wyrażenie, to typ zwracany musi być określony za pomocą końcowego typu zwracanego. Podobną składnię można również zastosować do funkcji automatycznych i funkcji składowych
3. Przechwycone wartości. Czego nie można uchwycić
3.1 Można przechwytywać tylko zmienne lokalne, a nie zmienną składową obiektu.
4. Konwersje
4.1 !! Lambda nie jest wskaźnikiem funkcji i nie jest funkcją anonimową, ale lambdy bez przechwytywania można pośrednio przekonwertować na wskaźnik funkcji.
ps
Więcej informacji na temat gramatyki lambda można znaleźć w Roboczym szkicu dla języka programowania C ++ # 337, 16.01.2012, 5.1.2. Wyrażenia lambda, s. 88
W C ++ 14 dodano dodatkową funkcję o nazwie „przechwytywanie init”. Pozwala na arbitrażowe zgłaszanie członków danych zamknięcia:
auto toFloat = [](int value) { return float(value);};
auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};
[&,=Param2](int arg1){}
nie wydaje się poprawna składnia. Prawidłowa forma to[&,Param2](int arg1){}
Funkcja lambda to anonimowa funkcja tworzona bezpośrednio. Może przechwytywać zmienne, jak niektórzy wyjaśnili (np. Http://www.stroustrup.com/C++11FAQ.html#lambda ), ale istnieją pewne ograniczenia. Na przykład jeśli istnieje taki interfejs zwrotny,
void apply(void (*f)(int)) {
f(10);
f(20);
f(30);
}
możesz napisać funkcję na miejscu, aby użyć jej tak jak tej, którą zastosowano poniżej:
int col=0;
void output() {
apply([](int data) {
cout << data << ((++col % 10) ? ' ' : '\n');
});
}
Ale nie możesz tego zrobić:
void output(int n) {
int col=0;
apply([&col,n](int data) {
cout << data << ((++col % 10) ? ' ' : '\n');
});
}
z powodu ograniczeń w standardzie C ++ 11. Jeśli chcesz używać przechwytywania, musisz polegać na bibliotece i
#include <functional>
(lub inny algorytm podobny do biblioteki STL, aby uzyskać go pośrednio), a następnie pracować ze std :: function zamiast przekazywać normalne funkcje jako parametry takie jak to:
#include <functional>
void apply(std::function<void(int)> f) {
f(10);
f(20);
f(30);
}
void output(int width) {
int col;
apply([width,&col](int data) {
cout << data << ((++col % width) ? ' ' : '\n');
});
}
apply
był szablonem, który akceptuje funktor, działałby
Jednym z najlepszych wyjaśnień lambda expression
jest autor C ++ Bjarne Stroustrup w swoim ***The C++ Programming Language***
rozdziale książkowym 11 ( ISBN-13: 978-0321563842 ):
What is a lambda expression?
Ekspresja N , czasami określane mianem lambda funkcji lub (niesłusznie ściśle mówiąc, ale potocznie) w lambda , to uproszczony notacja dla definiowania i stosując anonimowego obiektu funkcji . Zamiast definiować nazwaną klasę za pomocą operatora (), później tworząc obiekt tej klasy i wreszcie wywołując ją, możemy użyć skrótu.
When would I use one?
Jest to szczególnie przydatne, gdy chcemy przekazać operację jako argument do algorytmu. W kontekście graficznych interfejsów użytkownika (i innych miejsc) takie operacje są często nazywane wywołaniami zwrotnymi .
What class of problem do they solve that wasn't possible prior to their introduction?
Tutaj wydaje mi się, że każdą akcję wykonaną za pomocą wyrażenia lambda można rozwiązać bez nich, ale z dużo większym kodem i znacznie większą złożonością. Wyrażenie lambda jest to sposób optymalizacji kodu i sposób uczynienia go bardziej atrakcyjnym. Jak smutne przez Stroustup:
skuteczne sposoby optymalizacji
Some examples
poprzez wyrażenie lambda
void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0
{
for_each(begin(v),end(v),
[&os,m](int x) {
if (x%m==0) os << x << '\n';
});
}
lub poprzez funkcję
class Modulo_print {
ostream& os; // members to hold the capture list int m;
public:
Modulo_print(ostream& s, int mm) :os(s), m(mm) {}
void operator()(int x) const
{
if (x%m==0) os << x << '\n';
}
};
lub nawet
void print_modulo(const vector<int>& v, ostream& os, int m)
// output v[i] to os if v[i]%m==0
{
class Modulo_print {
ostream& os; // members to hold the capture list
int m;
public:
Modulo_print (ostream& s, int mm) :os(s), m(mm) {}
void operator()(int x) const
{
if (x%m==0) os << x << '\n';
}
};
for_each(begin(v),end(v),Modulo_print{os,m});
}
jeśli potrzebujesz, możesz wymienić lambda expression
jak poniżej:
void print_modulo(const vector<int>& v, ostream& os, int m)
// output v[i] to os if v[i]%m==0
{
auto Modulo_print = [&os,m] (int x) { if (x%m==0) os << x << '\n'; };
for_each(begin(v),end(v),Modulo_print);
}
Lub załóż inną prostą próbkę
void TestFunctions::simpleLambda() {
bool sensitive = true;
std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});
sort(v.begin(),v.end(),
[sensitive](int x, int y) {
printf("\n%i\n", x < y);
return sensitive ? x < y : abs(x) < abs(y);
});
printf("sorted");
for_each(v.begin(), v.end(),
[](int x) {
printf("x - %i;", x);
}
);
}
wygeneruje następny
0
1
0
1
0
1
0
1
0
1
0 sortedx - 1; x - 3; x - 4; x - 5; x - 6; x - 7; x - 33;
[]
- jest to lista przechwytywania lub lambda introducer
: jeśli lambdas
nie wymagają dostępu do lokalnego środowiska, możemy z niego skorzystać.
Cytat z książki:
Pierwszym znakiem wyrażenia lambda jest zawsze [ . Wprowadzający lambda może przybierać różne formy:
• [] : pusta lista przechwytywania. Oznacza to, że w ciele lambda nie można używać żadnych lokalnych nazw z otaczającego kontekstu. W przypadku takich wyrażeń lambda dane są uzyskiwane z argumentów lub zmiennych nielokalnych.
• [&] : domyślnie przechwytywanie przez odniesienie. Można używać wszystkich lokalnych nazw. Dostęp do wszystkich zmiennych lokalnych można uzyskać przez odniesienie.
• [=] : domyślnie przechwytywanie według wartości. Można używać wszystkich lokalnych nazw. Wszystkie nazwy odnoszą się do kopii zmiennych lokalnych pobranych w punkcie wywołania wyrażenia lambda.
• [lista przechwytywania]: jawne przechwytywanie; lista przechwytywania to lista nazw zmiennych lokalnych, które mają zostać przechwycone (tj. zapisane w obiekcie) przez odniesienie lub wartość. Zmienne o nazwach poprzedzonych znakiem & są przechwytywane przez odniesienie. Inne zmienne są przechwytywane według wartości. Lista przechwytywania może również zawierać to i nazwy, po których następuje ... jako elementy.
• [&, capture-list] : domyślnie przechwytuj przez odniesienie wszystkie zmienne lokalne o nazwach niewymienionych na liście. Lista przechwytywania może to zawierać. Nazw wymienionych na liście nie można poprzedzać &. Zmienne nazwane na liście przechwytywania są przechwytywane według wartości.
• [= lista przechwytywania] : domyślnie przechwytuj według wartości wszystkie zmienne lokalne o nazwach niewymienionych na liście. Lista przechwytywania nie może tego zawierać. Wymienione nazwy muszą być poprzedzone &. Zmienne wymienione na liście przechwytywania są przechwytywane przez odniesienie.
Zauważ, że nazwa lokalna poprzedzona znakiem & jest zawsze przechwytywana przez odniesienie, a nazwa lokalna nieprzedstawiona przez & jest zawsze przechwytywana przez wartość. Tylko przechwytywanie przez odniesienie pozwala modyfikować zmienne w środowisku wywołującym.
Additional
Lambda expression
format
Dodatkowe referencje:
for (int x : v) { if (x % m == 0) os << x << '\n';}
Odkryłem, że jednym praktycznym zastosowaniem jest ograniczenie kodu płyty kotła. Na przykład:
void process_z_vec(vector<int>& vec)
{
auto print_2d = [](const vector<int>& board, int bsize)
{
for(int i = 0; i<bsize; i++)
{
for(int j=0; j<bsize; j++)
{
cout << board[bsize*i+j] << " ";
}
cout << "\n";
}
};
// Do sth with the vec.
print_2d(vec,x_size);
// Do sth else with the vec.
print_2d(vec,y_size);
//...
}
Bez lambda może być konieczne zrobienie czegoś w różnych bsize
przypadkach. Oczywiście możesz stworzyć funkcję, ale co jeśli chcesz ograniczyć użycie w ramach funkcji użytkownika duszy? natura lambda spełnia ten wymóg i używam go w tym przypadku.
Lambda w c ++ są traktowane jako „dostępna funkcja on the go”. tak, dosłownie w podróży, definiujesz to; Użyj tego; a po zakończeniu zakresu funkcji rodzica funkcja lambda zniknęła.
c ++ wprowadził go w c ++ 11 i wszyscy zaczęli go używać jak w każdym możliwym miejscu. przykład i czym jest lambda można znaleźć tutaj https://en.cppreference.com/w/cpp/language/lambda
Opiszę, czego nie ma, ale trzeba wiedzieć dla każdego programisty c ++
Lambda nie jest przeznaczona do stosowania wszędzie i każdej funkcji nie można zastąpić lambda. Nie jest to również najszybszy w porównaniu do normalnej funkcji. ponieważ ma pewne koszty ogólne, którymi musi zająć się lambda.
z pewnością pomoże w niektórych przypadkach zmniejszyć liczbę linii. można go zasadniczo użyć do sekcji kodu, która jest wywoływana w tej samej funkcji raz lub więcej razy i ten fragment kodu nie jest potrzebny nigdzie indziej, aby można było utworzyć dla niego samodzielną funkcję.
Poniżej znajduje się podstawowy przykład lambda i tego, co dzieje się w tle.
Kod użytkownika:
int main()
{
// Lambda & auto
int member=10;
auto endGame = [=](int a, int b){ return a+b+member;};
endGame(4,5);
return 0;
}
Jak kompiluje to:
int main()
{
int member = 10;
class __lambda_6_18
{
int member;
public:
inline /*constexpr */ int operator()(int a, int b) const
{
return a + b + member;
}
public: __lambda_6_18(int _member)
: member{_member}
{}
};
__lambda_6_18 endGame = __lambda_6_18{member};
endGame.operator()(4, 5);
return 0;
}
tak jak widać, jaki rodzaj narzutu dodaje, gdy go używasz. więc nie warto używać ich wszędzie. można go stosować w miejscach, w których mają one zastosowanie.
Jeden problem, który rozwiązuje: kod prostszy niż lambda dla wywołania w konstruktorze, który używa funkcji parametru wyjściowego do inicjowania elementu const
Możesz zainicjować stałego członka swojej klasy, wywołując funkcję, która ustawia jej wartość, zwracając jej wynik jako parametr wyjściowy.