Для примера функция void foo() имеет тип void(*)(), чтобы объявить и проинициализировать указатель такого типа, нужно написать void (*fPtr)() = nullptr, где fPtr и есть имя указателя. Далее можно отследить по коду. Погодите, мы углубились в язык, но совсем забыли про применение. Где же можно применить такие чудо-возможности? Например, для хранения коллекции разных действий (функций), или, например, для получения обратного вызова. Обратный вызов – это вызов функции, которая в свою очередь должна вызвать функцию, полученную в качестве параметра. Часто это может быть полезно, например, запуская метод click() для кнопки, и передавая ему действие, которое должно быть выполнено при нажатии на кнопку.
Еще одно полезное применение можно получить, для передачи в коллекцию действия, которое мы хотим выполнить над каждым элементом коллекции. В стандартной библиотеке есть множество вспомогательных алгоритмов, например, сортировка, которая уже реализует метод сравнения элементов для стандартных типов. Но что делать с пользовательскими типами? Мы сами можем создать метод сортировки и передать его в качестве функции. Но у такого решения есть один недостаток, функция имеет свое пространство имен, что требует передачу в нее других параметров, это часто не удобно, т.к. к примеру метод сортировки, ждет только определенные параметры, которые зафиксированы в типе (функции) параметра и если мы туда передадим еще что-то, то сломаем логику. Решить эту проблему призваны лямбда-выражения, они же лямбда-функции, они же замыкания. Это специальный синтаксис, позволяющий захватить внешние для самой лямбды переменные и использовать их внутри себя, оставляя привычным (для принимающей стороны) передачу параметров. С технической стороны, лямбды требуют отдельного рассмотрения, т.к. они имеют очень специфический синтаксис. Особенно в контексте работы со стандартной библиотекой. Как вариант, мы можем писать лямбду на месте самого параметра, принимающего функцию, что часто очень наглядно выражает логику программы. На текущий момент мы говорим о них, как о еще одной возможности передавать действие в качестве параметра (смотрите пример кода) и пока на этом остановимся.
#include <iostream>
#include <vector>
class Button {
public:
explicit Button(void (*_fn)()) : fn{_fn} { }
void click() {
fn();
}
void setFn(void (*_fn)()) {
fn = _fn;
}
private:
void (*fn)() = nullptr;
};
static void foo1() {
std::cout << "foo1 called!" << std::endl;
}
static void foo2() {
std::cout << "foo2 called!" << std::endl;
}
void foo3(void (*_l)()) {
_l();
}
int main() {
typedef void(*CB)();
//std::vector<void (*)()> calls = {foo1, foo2};
std::vector<CB> calls = {foo1, foo2};
for (auto i : calls) {
i();
}
//Output:
//>>foo1 called!
//>>foo2 called!
auto b1 = Button(foo1);
auto b2 = Button(foo2);
std::vector<Button> btns = {b1, b2};
for (auto b : btns) {
b.click();
}
//Output:
//>>foo1 called!
//>>foo2 called!
btns[0].setFn(foo2);
btns[1].setFn(foo1);
for (auto b : btns) {
b.click();
}
//Output:
//>>foo2 called!
//>>foo1 called!
foo3(foo2);
//Output:
//>>foo2 called!
foo3([]() -> void {
std::cout << "lambda called!";
});
//Output:
//>>lambda called!
return 0;
}
Перегуд В.
No comments:
Post a Comment