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高级编程