08 - 可调用对象
可调用对象
如果一个对象可以使用调用运算符()
,()
里可以放参数,这个对象就是可调用对象。可调用对象主要有以下三类:
函数
函数自然可以调用()
运算符,是最典型的可调用对象。
可调用对象的主要用法,给其他函数当参数:
void test(int i)
{
std::cout << i << std::endl;
}
// 函数指针类型定义,C++应该用 using 而不是 typedef
using pf_type = void(*)(int);
void myFunc(pf_type pf, int i)
{
pf(i);
}
int main()
{
myFunc(test, 200);
}
仿函数
具有operator()
函数的类对象,此时类对象可以当作函数使用,因此成为仿函数。
class Test
{
public:
void operator()(int i)
{
std::cout << i << std::endl;
}
}
int main()
{
Test t;
t(200);
}
lambda表达式
就是匿名函数,普通的函数在使用前需要找个地方定义它,而lambda表达式在需要函数时,直接就地写一个就好,省去了定义函数的过程,增加开发效率。
以下是一个最简单的lambda表达式,及其使用:
// 输出"hello"
auto basicLambda{ [] {std::cout << "hello" << std::endl; } };
basicLambda();
lambda表达式的构成
完整的lambda表达式格式为[] () ->ret {}
,接下来说说各个组件:
捕获块[ ]
[ ]
代表捕获块/捕获列表,表示lambda表达式可以访问前文的哪些变量。常用的捕获块示例如下表:
捕获块 | 含义 |
---|---|
[] | 不捕获任何变量 |
[=] | 按值捕获所有变量 |
[&] | 按引用捕获所有变量 |
[i] | 按值捕获i |
[&i] | 按引用捕获i |
[&, x, y] | 默认按引用捕获所有变量,但变量x,y通过值捕获 |
[=, &x, &y] | 默认按值捕获所有变量,但变量x,y通过引用捕获 |
参数( )
()
代表lambda表达式的参数列表。
返回值与函数体 ->ret{}
->ret {}
代表lambda函数的返回值与函数体,其中->ret
可以省略,让类型自动推断:
int i{ 10 };
auto ret{ [i](int elem)->int {
std::cout << i << std::endl;
std::cout << elem << std::endl;
return 1;
}};
std::cout << ret(110) << std::endl;
lambda表达式作为参数
lambda表达式作为函数参数可谓十分方便,例如上边函数部分中,test()
还要单独拉出来定义,而学了lambda表达式后就能这样写:
using pf_type = void(*)(int);
void myFunc(pf_type pf, int i)
{
pf(i);
}
int main()
{
//myFunc(test, 200)
myFunc([](int i) {
std::cout << i << std::endl;
}, 200);
}
需要注意的是,当lambda表达式作为函数指针时,捕获列表必须为空,而如果我们想捕获点参数,就很麻烦。
C++11提供了<functional>
库,我们弃用函数指针,开始使用它提供的std::function
类型的函数签名:
#include <functional>
using func_type = std::function<void(int)>;
void myFunc(func_type func, int i)
{
func(i);
}
int main()
{
// 利用std::function,顺便捕获i2
int i2{ 233 };
myFunc([i2](int i) {
std::cout << i + i2 << std::endl;
}, 200);
}
可见我们能捕获参数了,应该用std::function
而不是函数指针。
参考资料
- 飘零的落花 - 现代C++详解
- C++20高级编程