Wyrażenia lambda w C++

Lambda Expressions C



Dlaczego wyrażenie lambda?

Rozważ następujące stwierdzenie:

intmyInt= 52;

Tutaj myInt jest identyfikatorem, l-wartością. 52 to literał, prvalue. Obecnie można specjalnie zakodować funkcję i umieścić ją w pozycji 52. Taka funkcja nazywa się wyrażeniem lambda. Rozważ także następujący krótki program:







#włączać

za pomocą przestrzeń nazwgodziny;

intfn(intPoprzez)

{

intodpowiedź=Poprzez+ 3;

powrótodpowiedź;

}


intGłówny()

{

fn(5);



powrót 0;

}

Obecnie możliwe jest specjalne zakodowanie funkcji i umieszczenie jej w pozycji argumentu 5 wywołania funkcji fn(5). Taka funkcja nazywana jest wyrażeniem lambda. Wyrażenie lambda (funkcja) w tej pozycji jest wartością pr.



Dowolny literał z wyjątkiem literału łańcuchowego jest wartością pr. Wyrażenie lambda to specjalny projekt funkcji, który pasowałby jako literał w kodzie. Jest to funkcja anonimowa (nienazwana). W tym artykule wyjaśniono nowe wyrażenie podstawowe języka C++, nazywane wyrażeniem lambda. Do zrozumienia tego artykułu wymagana jest podstawowa znajomość C++.



Treść artykułu

Ilustracja wyrażenia lambda

W poniższym programie funkcja będąca wyrażeniem lambda jest przypisywana do zmiennej:





#włączać

za pomocą przestrzeń nazwgodziny;

automatycznyfn= [](intzatrzymać)

{

intodpowiedź=zatrzymać+ 3;

powrótodpowiedź;

};


intGłówny()

{

automatycznyzmienna=fn(2);

koszt <<zmienna<< ' ';


powrót 0;

}

Dane wyjściowe to:

5

Poza funkcją main() znajduje się zmienna fn. Jego typ to auto. Auto w tej sytuacji oznacza, że ​​rzeczywisty typ, taki jak int lub float, jest określany przez prawy operand operatora przypisania (=). Po prawej stronie operatora przypisania znajduje się wyrażenie lambda. Wyrażenie lambda to funkcja bez poprzedzającego typu zwracanego. Zwróć uwagę na użycie i położenie nawiasów kwadratowych, []. Funkcja zwraca 5, czyli int, który określi typ fn.



W funkcji main() znajduje się instrukcja:

automatycznyzmienna=fn(2);

Oznacza to, że fn poza main() kończy się jako identyfikator funkcji. Jego niejawnymi parametrami są parametry wyrażenia lambda. Typ zmiennej variab to auto.

Zauważ, że wyrażenie lambda kończy się średnikiem, podobnie jak definicja klasy lub struktury, kończy się średnikiem.

W poniższym programie funkcja będąca wyrażeniem lambda zwracającym wartość 5 jest argumentem innej funkcji:

#włączać

za pomocą przestrzeń nazwgodziny;

próżniainnefn(intnie1,int (*ptr)(int))

{

intnie2= (*ptr)(2);

koszt <<nie1<< '' <<nie2<< ' ';

}


intGłówny()

{

innefn(4,[](intzatrzymać)

{

intodpowiedź=zatrzymać+ 3;

powrótodpowiedź;

});


powrót 0;
}

Dane wyjściowe to:

Cztery pięć

Są tu dwie funkcje, wyrażenie lambda i funkcja otherfn(). Wyrażenie lambda jest drugim argumentem funkcji otherfn(), wywoływanej w funkcji main(). Zauważ, że funkcja lambda (wyrażenie) nie kończy się średnikiem w tym wywołaniu, ponieważ tutaj jest to argument (nie samodzielna funkcja).

Parametr funkcji lambda w definicji funkcji otherfn() jest wskaźnikiem do funkcji. Wskaźnik ma nazwę, ptr. Nazwa ptr jest używana w definicji otherfn() do wywołania funkcji lambda.

Wyrok,

intnie2= (*ptr)(2);

W definicji otherfn() wywołuje funkcję lambda z argumentem 2. Zwracana wartość wywołania '(*ptr)(2)' z funkcji lambda jest przypisana do no2.

Powyższy program pokazuje również, jak funkcja lambda może być użyta w schemacie funkcji zwrotnej C++.

Części wyrażenia lambda

Części typowej funkcji lambda są następujące:

[] () {}
  • [] to klauzula przechwytywania. Może zawierać przedmioty.
  • () dotyczy listy parametrów.
  • {} dotyczy treści funkcji. Jeśli funkcja jest samodzielna, powinna kończyć się średnikiem.

Przechwytuje

Definicja funkcji lambda może być przypisana do zmiennej lub użyta jako argument do innego wywołania funkcji. Definicja takiego wywołania funkcji powinna mieć jako parametr, wskaźnik do funkcji, odpowiadający definicji funkcji lambda.

Definicja funkcji lambda różni się od definicji funkcji normalnej. Może być przypisany do zmiennej w zasięgu globalnym; ta funkcja przypisana do zmiennej może być również zakodowana wewnątrz innej funkcji. Po przypisaniu do zmiennej o zasięgu globalnym, jej treść może widzieć inne zmienne w zasięgu globalnym. Po przypisaniu do zmiennej wewnątrz normalnej definicji funkcji, jej ciało może zobaczyć inne zmienne w zakresie funkcji tylko z pomocą klauzuli przechwytywania, [].

Klauzula przechwytywania [], znana również jako introducer lambda, umożliwia wysyłanie zmiennych z otaczającego zakresu (funkcji) do treści funkcji wyrażenia lambda. Mówi się, że treść funkcji wyrażenia lambda przechwytuje zmienną, gdy otrzymuje obiekt. Bez klauzuli przechwytywania [] zmienna nie może zostać wysłana z otaczającego zakresu do treści funkcji wyrażenia lambda. Poniższy program ilustruje to, z zakresem funkcji main() jako otaczającym zakresem:

#włączać

za pomocą przestrzeń nazwgodziny;

intGłówny()

{

intNS= 5;


automatycznyfn= [NS]()

{

koszt <<NS<< ' ';

};

fn();


powrót 0;

}

Wyjście to 5 . Bez nazwy, id, wewnątrz [], wyrażenie lambda nie widziałoby zmiennej id z zakresu funkcji main().

Przechwytywanie przez odniesienie

Powyższy przykład użycia klauzuli przechwytywania polega na przechwytywaniu przez wartość (szczegóły poniżej). Podczas przechwytywania przez referencję, lokalizacja (przechowywanie) zmiennej, np. id powyżej, otaczającego zakresu jest udostępniana wewnątrz ciała funkcji lambda. Tak więc zmiana wartości zmiennej w treści funkcji lambda zmieni wartość tej samej zmiennej w otaczającym zakresie. Każda zmienna powtórzona w klauzuli przechwytywania jest poprzedzona znakiem ampersand (&), aby to osiągnąć. Poniższy program ilustruje to:

#włączać

za pomocą przestrzeń nazwgodziny;

intGłówny()

{

intNS= 5; pływakft= 2,3; zwęglaćch= 'DO';

automatycznyfn= [&NS,&ft,&ch]()

{

NS= 6;ft= 3.4;ch= 'B';

};

fn();

koszt <<NS<< ',' <<ft<< ',' <<ch<< ' ';

powrót 0;

}

Dane wyjściowe to:

6, 3.4, B

Potwierdzenie, że nazwy zmiennych w treści funkcji wyrażenia lambda dotyczą tych samych zmiennych poza wyrażeniem lambda.

Przechwytywanie według wartości

Podczas przechwytywania przez wartość, kopia lokalizacji zmiennej, otaczającego zakresu, jest udostępniana wewnątrz ciała funkcji lambda. Chociaż zmienna w treści funkcji lambda jest kopią, na razie nie można zmienić jej wartości w treści. Aby osiągnąć przechwytywanie według wartości, każda zmienna powtórzona w klauzuli przechwytywania nie jest niczego poprzedzona. Poniższy program ilustruje to:

#włączać

za pomocą przestrzeń nazwgodziny;

intGłówny()

{

intNS= 5; pływakft= 2,3; zwęglaćch= 'DO';

automatycznyfn= [id, ft, ch]()

{

//id = 6; stopa = 3,4; ch = „B”;

koszt <<NS<< ',' <<ft<< ',' <<ch<< ' ';

};

fn();

NS= 6;ft= 3.4;ch= 'B';

koszt <<NS<< ',' <<ft<< ',' <<ch<< ' ';

powrót 0;

}

Dane wyjściowe to:

5, 2.3, A

6, 3.4, B

Jeśli wskaźnik komentarza zostanie usunięty, program nie skompiluje się. Kompilator wyświetli komunikat o błędzie, że nie można zmienić zmiennych wewnątrz definicji wyrażenia lambda w treści funkcji. Chociaż zmienne nie mogą być zmieniane wewnątrz funkcji lambda, mogą być zmieniane poza funkcją lambda, jak pokazuje powyższe wyjście programu.

Miksowanie przechwytów

Przechwytywanie przez odniesienie i przechwytywanie przez wartość można mieszać, jak pokazuje poniższy program:

#włączać

za pomocą przestrzeń nazwgodziny;

intGłówny()

{

intNS= 5; pływakft= 2,3; zwęglaćch= 'DO'; głupotabl= prawda;


automatycznyfn= [identyfikator, stopy,&ch,&bl]()

{

ch= 'B';bl= fałszywe;

koszt <<NS<< ',' <<ft<< ',' <<ch<< ',' <<bl<< ' ';

};

fn();


powrót 0;

}

Dane wyjściowe to:

5, 2,3, B, 0

Kiedy wszystkie schwytane, są przez odniesienie:

Jeśli wszystkie zmienne, które mają być przechwycone, są przechwycone przez odwołanie, wystarczy jeden & w klauzuli przechwytywania. Poniższy program ilustruje to:

#włączać

za pomocą przestrzeń nazwgodziny;

intGłówny()

{

intNS= 5; pływakft= 2,3; zwęglaćch= 'DO'; głupotabl= prawda;


automatycznyfn= [&]()

{

NS= 6;ft= 3.4;ch= 'B';bl= fałszywe;

};

fn();

koszt <<NS<< ',' <<ft<< ',' <<ch<< ',' <<bl<< ' ';


powrót 0;

}

Dane wyjściowe to:

6, 3,4, B, 0

Jeśli niektóre zmienne mają być przechwycone przez referencję, a inne przez wartość, to jedna & będzie reprezentować wszystkie referencje, a reszta nie będzie poprzedzona niczym, jak pokazuje poniższy program:

za pomocą przestrzeń nazwgodziny;

intGłówny()

{

intNS= 5; pływakft= 2,3; zwęglaćch= 'DO'; głupotabl= prawda;


automatycznyfn= [&, identyfikator, ft]()

{

ch= 'B';bl= fałszywe;

koszt <<NS<< ',' <<ft<< ',' <<ch<< ',' <<bl<< ' ';

};

fn();


powrót 0;

}

Dane wyjściowe to:

5, 2,3, B, 0

Zauważ, że sam & (tj. & bez identyfikatora) musi być pierwszym znakiem w klauzuli przechwytywania.

Gdy wszystkie są schwytane, są według wartości:

Jeśli wszystkie zmienne, które mają być przechwycone, mają być przechwycone według wartości, wystarczy tylko jedna = w klauzuli przechwytywania. Poniższy program ilustruje to:

#włączać

za pomocą przestrzeń nazwgodziny;

intGłówny()
{

intNS= 5; pływakft= 2,3; zwęglaćch= 'DO'; głupotabl= prawda;


automatycznyfn= [=]()

{

koszt <<NS<< ',' <<ft<< ',' <<ch<< ',' <<bl<< ' ';

};

fn();


powrót 0;


}

Dane wyjściowe to:

5, 2.3, A, 1

Notatka : = jest obecnie tylko do odczytu.

Jeśli niektóre zmienne mają być przechwycone przez wartość, a inne przez odniesienie, wtedy jedna = będzie reprezentować wszystkie skopiowane zmienne tylko do odczytu, a reszta będzie miała &, jak pokazuje poniższy program:

#włączać

za pomocą przestrzeń nazwgodziny;

intGłówny()

{

intNS= 5; pływakft= 2,3; zwęglaćch= 'DO'; głupotabl= prawda;


automatycznyfn= [=,&ch,&bl]()

{

ch= 'B';bl= fałszywe;

koszt <<NS<< ',' <<ft<< ',' <<ch<< ',' <<bl<< ' ';

};

fn();


powrót 0;

}

Dane wyjściowe to:

5, 2,3, B, 0

Zauważ, że = sam musi być pierwszym znakiem w klauzuli przechwytywania.

Klasyczny schemat funkcji wywołania zwrotnego z wyrażeniem lambda

Poniższy program pokazuje, jak można wykonać klasyczny schemat funkcji zwrotnej za pomocą wyrażenia lambda:

#włączać

za pomocą przestrzeń nazwgodziny;

zwęglać *wyjście;


automatycznycba= [](zwęglaćna zewnątrz[])

{

wyjście=na zewnątrz;

};



próżniamainFunc(zwęglaćWejście[],próżnia (*dla)(zwęglać[]))

{

(*dla)(Wejście);

koszt<<„dla głównej funkcji”<<' ';

}


próżniafn()

{

koszt<<'Ale już'<<' ';

}


intGłówny()

{

zwęglaćWejście[] = 'dla funkcji zwrotnej';

mainFunc(wejście, cba);

fn();

koszt<<wyjście<<' ';



powrót 0;

}

Dane wyjściowe to:

dla głównej funkcji

Ale już

dla funkcji zwrotnej

Przypomnijmy, że gdy definicja wyrażenia lambda jest przypisana do zmiennej w zakresie globalnym, jej treść funkcji może zobaczyć zmienne globalne bez stosowania klauzuli przechwytywania.

Typ spływu zwrotnego

Zwracany typ wyrażenia lambda to auto, co oznacza, że ​​kompilator określa typ zwracany z wyrażenia zwracanego (jeśli jest obecne). Jeśli programista naprawdę chce wskazać typ zwracany, to zrobi to jak w poniższym programie:

#włączać

za pomocą przestrzeń nazwgodziny;

automatycznyfn= [](intzatrzymać) -> int

{

intodpowiedź=zatrzymać+ 3;

powrótodpowiedź;

};


intGłówny()

{

automatycznyzmienna=fn(2);

koszt <<zmienna<< ' ';


powrót 0;

}

Dane wyjściowe to 5. Po liście parametrów wpisywany jest operator strzałki. Po nim następuje typ zwracany (w tym przypadku int).

Zamknięcie

Rozważ następujący segment kodu:

strukturaCla

{

intNS= 5;

zwęglaćch= 'do';

}obj1, obj2;

Tutaj Cla jest nazwą klasy struct. Obj1 i obj2 to dwa obiekty, które zostaną utworzone z klasy struct. Wyrażenie lambda jest podobne w implementacji. Definicja funkcji lambda jest rodzajem klasy. Gdy funkcja lambda jest wywoływana (wywoływana), obiekt jest tworzony ze swojej definicji. Ten obiekt nazywa się zamknięciem. Jest to zamknięcie, które wykonuje pracę, jakiej oczekuje się od lambdy.

Jednak kodowanie wyrażenia lambda, takie jak powyższa struktura, spowoduje, że obj1 i obj2 zostaną zastąpione odpowiednimi argumentami parametrów. Poniższy program ilustruje to:

#włączać

za pomocą przestrzeń nazwgodziny;

automatycznyfn= [](intparam1,intparam2)

{

intodpowiedź=param1+param2;

powrótodpowiedź;

} (2,3);


intGłówny()

{

automatycznygdzie=fn;

koszt <<gdzie<< ' ';


powrót 0;

}

Wynik to 5. Argumenty to 2 i 3 w nawiasach. Zauważ, że wywołanie funkcji wyrażenia lambda, fn, nie przyjmuje żadnego argumentu, ponieważ argumenty zostały już zakodowane na końcu definicji funkcji lambda.

Wniosek

Wyrażenie lambda jest funkcją anonimową. Składa się z dwóch części: klasy i przedmiotu. Jego definicja to rodzaj klasy. Gdy wyrażenie jest wywoływane, z definicji tworzony jest obiekt. Ten obiekt nazywa się zamknięciem. Jest to zamknięcie, które wykonuje pracę, jakiej oczekuje się od lambdy.

Aby wyrażenie lambda odebrało zmienną z zewnętrznego zakresu funkcji, potrzebuje niepustej klauzuli przechwytywania w treści funkcji.