Skip to content

ch19 first-class function

这篇文章记录一小点头等函数的概念,头等函数的概念类似于将函数认为是一等公民,能和正常的类型一样作为参数、返回值等等。

等同于 c 风格的函数指针

c 风格的函数指针在 cpp 中仍然有相同的用法,为了避免声明时候的复杂,可以采用 auto* 这样的方式来声明函数指针。

函数指针别名

可以使用 using 定义函数指针的别名。简单的使用可以是 using func = bool (*)(const string &, const string &),复杂的甚至可以带上模板,比如 template <typename T> using func = bool (*)(const T &,const T &)

函数对象

函数对象实际上就是一个普通的类对象,只不过这个类重载了括号运算符,使函数对象可以调用了而已。cpp <functional> 库中已经定义了很多这种函数对象的模板,直接拿来使用就行,在使用的时候最好采用 std::less<>() 的方式,不需要输入模板类型,这是自动推断的一种特例,编译器会对其优化。

lambda 表达式

lambda 表达式实际上是函数对象的一种语法糖,使用 lambda 表达式的时候编译器实际上把 lambda 表达式转换成函数对象进行使用。

lambda 表达式的基本形态是 [](int x, int y) -> void {return x < y},lambda 表达式总是可以传递给对应的函数指针,编译器内部实现 lambda 表达式到函数指针的转换。

(int x, int y) 称为传入参数,在没有传入参数的时候这个括号可以省略。-> void 称返回值,也可以省略,在省略的时候实际上启用类型的自动推断。

[] 称捕获列表,能够捕获当前作用域内的可见的变量,当不捕获变量的时候不能省略:

  • [=]:以值传递的形式捕获变量,对于变量的改动不会影响函数外部的变量,可以指定具体的变量名进行捕获,如 [=x],但是在函数内部只能访问 x,不能有x = 1,这样的操作,因为这个语法糖在实际编译的过程中捕获的变量成为了类内部的一个常规非const变量,但是 lambda 表达式 转换成了类内部带 const 标签的函数,所以不能对成员变量的值进行更改。
  • [&]:以引用传递的方式捕获变量,对于变量的改动会影响到外部的变量,同样可以指定变量名进行捕获,如 [&x],不指定变量名默认捕获全部。在函数内部能够有改变 x 值的行为,因为捕获的变量在内部是一个引用变量,因此即使加上了 const 标签也不影响对值的改变。
  • [this]:在类方法或者类成员中定义的 lambda 表达式通过这种方法能够捕获类中的所有变量。

std::functional<> 模板

当想给一个函数传递一个回调的时候,可行的选择是函数指针、函数对象、lambda 表达式,如果是这样,就分别要为这三种可能性编写三个不同的接口。为了解决这个问题,引入了 std::functional<> 模板。可以通过 std::functional<bool(int,int)> 的方式来实例化这个模板,将函数接受的参数类型指定为这个模板,就能够同时接受上面描述的这三种函数。