函数也是对象
函数可以作为另一个函数的参数:
#include <cstdio>
template <class Func>
void call_wait(Func func) {
func(0);
func(1);
}
int main() {
auto myFunc = [](int n) {
printf("Number %d\n", n);
};
call_wait(myFunc);
return 0;
}
并且,这个作为参数的函数也可以有参数:
#include <cstdio>
void print_number(int n) {
printf("Number %d\n", n);
}
void call_twice(void func(int)) {
func(0);
func(1);
}
int main() {
call_twice(print_number);
return 0;
}
函数作为模板类型
甚至可以直接将 func 的类型作为一个模板参数,从而不需要写 void(int)。
这样还会允许函数的参数类型为其他类型,比如 void(float)。
这样 call_twice 会自动对每个不同的 func 类型编译一遍,从而允许编译器更好地进行自动适配与优化。
#include <cstdio>
void print_float(float f) {
printf("Float %f\n", f);
}
void print_int(int n) {
printf("Int %d\n", n);
}
template <class Func>
void call_twice(Func func) {
func(0);
func(1);
}
int main() {
call_twice(print_float);
call_twice(print_int);
return 0;
}
lambda表达式
C++11 引入的 lambda 表达式允许我们在函数体内创建一个函数,大大地方便了函数式编程。
语法就是先一个空的 [],然后是参数列表,然后是 {} 包裹的函数体。
再也不用被迫添加一个全局函数了!
int main() {
auto myFunc = [](int n) {
printf("Number %d\n", n);
};
call_wait(myFunc);
return 0;
}
lambda表达式:返回类型、自动推导返回类型
lambda 表达式的返回类型写在参数列表后面,用一个箭头 -> 表示。
int main() {
auto twice = [](int n) -> int {
return n * 2;
};
call_twice(twice);
return 0;
}
如果 lambda 表达式不通过 -> 指定类型,则和 -> auto 等价,自动根据函数体内的 return 语句决定返回类型,如果没有 return 语句则相当于 -> void。
lambda表达式:捕获main中的变量、修改main中的变量
lambda 函数体中,还可以使用定义它的 main 函数中的变量,只需要把方括号 [] 改成 [&] 即可。
函数可以引用定义位置所有的变量,这个特性在函数式编程中称为闭包(closure)。
[&] 不仅可以读取 main 中的变量,还可以写入 main 中的变量,比如可以通过 counter++ 记录该函数被调用了多少次:
int main() {
int fac = 2;
int count = 0;
auto twice = [&](int n) -> int {
count++;
return n * fac;
};
call_twice(twice);
return 0;
}
lambda表达式:传常引用避免拷贝开销
此外,最好把模板参数的 Func 声明为 Func const & 以避免不必要的拷贝:
#include <iostream>
template <class Func>
void call_twice(Func const& func) {
std::cout << func(0) << std::endl;
std::cout << func(1) << std::endl;
std::cout << "Func 的大小为:" << sizeof(func) << std::endl;
}
int main() {
int fac = 2;
int count = 0;
auto twice = [&](int n) {
count++;
return n * fac;
};
call_twice(twice);
std::cout << "调用了 " << count << " 次" << std::endl;
return 0;
}
lambda表达式:作为返回值
既然函数可以作为参数,当然也可以作为返回值!
由于 lambda 表达式永远是个匿名类型,我们需要将 make_twice 的返回类型声明为 auto 让他自动推导。
auto make_twice() {
return [](int n) {
return n * 2;
};
}
int main() {
auto twice = make_twice();
call_twice(twice);
return 0;
}
作为返回值:[&]出问题了
然而当我们试图用 [&] 捕获参数 fac 时,却出了问题:
fac 似乎变成 32764 了?
这是因为 [&] 捕获的是引用,是 fac 的地址,而 make_twice 已经返回了,导致 fac 的引用变成了内存中一块已经失效的地址。
总之,如果用 [&],请保证 lambda 对象的生命周期不超过他捕获的所有引用的寿命。
auto make_twice(int fac) {
return [&](int n) {
return n * fac;
};
}
int main() {
auto twice = make_twice(2);
call_twice(twice);
return 0;
}
作为返回值:[=]解决问题
这时,我们可以用 [=] 来捕获,他会捕获 fac 的值而不是引用。
[=] 会给每一个引用了的变量做一份拷贝,放在 Func 类型中。
不过他会造成对引用变量的拷贝,性能可能会不如 [&]。
auto make_twice(int fac) {
return [=](int n) {
return n * fac;
};
}
lambda表达式:如何避免用模板参数
虽然
但是有时候我们希望通过头文件的方式分离声明和实现,或者想加快编译,这时如果再用 template class 作为参数就不行了。
为了灵活性,可以用 std::function 容器。
只需在后面尖括号里写函数的返回类型和参数列表即可,比如:std::function<int(float, char *)>;
void call_twice(std::function<int(int)> const& func) {
std::cout << func(0) << std::endl;
std::cout << func(1) << std::endl;
std::cout << "Func 的大小为:" << sizeof(func) << std::endl;
}
std::function<int(int)> make_twice(int fac) {
return [=](int n) {
return n * fac;
};
}
int main() {
auto twice = make_twice(2);
call_twice(twice);
return 0;
}
如何避免用模板参数2:无捕获的 lambda 可以传为函数指针
另外,如果你的 lambda 没有捕获任何局部变量,也就是 [],那么不需要用 std::function<int(int)>,直接用函数指针的类型 int(int) 或者 int(*)(int) 即可。
函数指针效率更高一些,但是 [] 就没办法捕获局部变量了(全局变量还是可以的)。
最大的好处是可以伺候一些只接受函数指针的 C 语言的 API 比如 pthread 和 atexit。
void call_twice(int func(int)) {
std::cout << func(0) << std::endl;
std::cout << func(1) << std::endl;
std::cout << "Func 的大小为:" << sizeof(func) << std::endl;
}
int main() {
call_twice([](int n) {
return n * 2;
});
return 0;
}
lambda + 模板:双倍快乐
可以将 lambda 表达式的参数声明为 auto,声明为 auto 的参数会自动根据调用者给的参数推导类型,基本上和 template
auto const & 也是同理,等价于模板函数的 T const &。
带 auto 参数的 lambda 表达式,和模板函数一样,同样会有惰性、多次编译的特性。
template <class Func>
void call_twice(Func const& func) {
std::cout << func(1) << std::endl;
std::cout << func(3.14f) << std::endl;
}
int main() {
call_twice([](auto n) {
return n * 2;
});
return 0;
}
/** 等价于:
template <class T>
auto twice(T n) {
return n*2;
}
*/
C++20前瞻:函数也可以 auto,lambda 也可以
void call_twice(auto const& func) {
std::cout << func(1) << std::endl;
std::cout << func(3.14f) << std::endl;
}
int main() {
call_twice([] <class T> (auto n) {
return n * 2;
});
return 0;
}
lambda 用途举例:yield模式
这里用了 type_traits 来获取 x 的类型。
decay_t<int const &> = int
is_same_v<int, int> = true
is_same_v<float, int> = false
更多这类模板请搜索 c++ type traits。
template <class Func>
void fetch_data(Func const& func) {
for (int i = 0; i < 32; ++i) {
func(i);
func(i + 0.5f);
}
}
int main() {
std::vector<int> res_i;
std::vector<float> res_f;
fetch_data([&](auto const& x) {
using T = std::decay_t<decltype(x)>;
if constexpr (std::is_same_v<T, int>) {
res_i.push_back(x);
} else if constexpr (std::is_same_v<T, float>) {
res_f.push_back(x);
}
});
return 0;
}
lambda 用途举例:立即求值
再也不需要烦人的 flag 变量
int main() {
std::vector<int> arr = {1, 2, 3, 4, 5};
int tofind = 3;
int index = [&]() {
for (int i = 0; i < arr.size(); ++i) {
if (tofind == arr[i])
return i;
}
}();
std::cout << index << std::endl;
return 0;
}
lambda 用途举例:局部实现递归(匿名递归)
int main() {
std::vector<int> arr = {1, 4, 2, 8, 5, 7, 1, 4};
std::set<int> visited;
auto dfs = [&] (auto const& dfs, int index) -> void {
if (visited.find(index) == visited.end()) {
visited.insert(index);
std::cout << index << std::endl;
int next = arr[index];
dfs(dfs, next);
}
};
dfs(dfs, 0);
return 0;
}
Reference:
标签:std,06,int,auto,编程,C++,twice,return,lambda From: https://www.cnblogs.com/cloudflow/p/16714122.html