www.zhblog.net

高阶函数


在〈一级函数与 algorithm〉,看了几个algorithm的函数可以接受函数的例子,既然如此,函数也可以返回函数,这边的指的函数传递,包括了函数指针、lambda 表达式。

从函数中返回函数指针,基本上没什么问题,因为函数指针不会消失,然而,从函数中返回 lambda 表达式,就得留意一下了,因为函数中的 lambda 表达式,生命周期就是局限于函数之中,如果如下返回函数:

#include <iostream>
using namespace std;

auto foo() {
    auto f = [] { cout << "foo" << endl; };
    return f;
}

int main() {
    auto fn = foo();
    fn();

    return 0;
}

那么没什么问题,f会复制给fn,然而如果是返回参考:

auto& foo() {
    auto f = [] { cout << "foo" << endl; };
    return f; 
}

因为foo函数执行过后,调用者参考的f变量已经无效,编译时就会产生警讯:

warning: reference to local variable 'f'

另一个问题是,若以参考方式捕捉了局部变量:

auto foo() {
    string text = "foo";
    auto f = [&] { cout << text << endl; };
    return f;
}

编译虽然会过,然而实际上捕捉的变量在foo函数执行过后已经无效,最后调用返回的 lambda 表达式时,就会发生不可预期的结果,如果你是从其他具有一级函数特性的语言来到 C++,要记得的就是,C++ 的 lambda 表达式,并不会扩展被捕捉变量的生命周期。

这并不是指返回 lambda 表达式时,就不能用&来捕捉变量,主要还是要看捕捉的变量,其地址是否有效,例如以下就没有问题,因为实际上 lambda 表达式捕捉的变量,参考的地址是main中的text变量地址:

#include <iostream>
#include <string>
using namespace std;

auto foo(string &text) {
    auto f = [&] { cout << text << endl; };
    return f;
}

int main() {
    string text = "foo";
    auto f = foo(text);
    f();
    return 0;
}

来看看返回 lambda 表达式的一个例子,感觉像是函数产生了新函数,记忆了指定的实参:

#include <iostream>
using namespace std;

auto add(int m) {
    return [m] (int n) { return m + n ; };
}

int main() {
    auto plus10 = add(10);
    cout << plus10(20) << endl;  // 30
    cout << plus10(40) << endl;  // 50

    return 0;
}

那么可不可以接受函数、返回函数呢?

#include <iostream>
using namespace std;

int binary_fun(int, int);

int add(int m, int n) {
    return m + n;
}

int mul(int m, int n) {
    return m * n;
}

auto bind(decltype(binary_fun) bf, int fst) {
    return [bf, fst] (int snd) { return bf(fst, snd); };
}

int main() {
    auto add10 = bind(add, 10);
    auto mul5 = bind(mul, 5); 

    cout << add10(30) << endl; // 40
    cout << mul5(20) << endl;  // 100

    return 0;
}

范例的bind方法,可以接受函数并返回函数,返回的函数绑定了第一个实参,像bind这类可以接受函数、返回函数的函数,称为高阶函数(high-order function)。

实际上,functional标头文件就提供了个bind可以使用,而且更有弹性,可以指定要绑定哪个参数:

#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;

int add(int m, int n) {
    return m + n;
}

int mul(int m, int n) {
    return m * n;
}

int main() {
    auto add10 = bind(add, _1, 10);
    auto mul5 = bind(mul, _1, 5); 

    cout << add10(30) << endl;  // 40
    cout << mul5(20) << endl;   // 100

    return 0;
}

占位符_1是位于std::placeholders命名空间之中,代表返回的函数可接受的第一个参数,以上例来说,bind(add, _1, 10)表示adda会是占位符_1b会是 10,因此返回的函数第一个参数接受到的实参,相当于指定了a的值。

因此若有多个参数要绑定,会是如下:

#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;

void foo(int a, int b, int c, int d) {
    cout << "a: " << a << endl 
         << "b: " << b << endl
         << "c: " << c << endl
         << "d: " << d << endl;
}

int main() {
    auto wat = bind(foo, _1, 20, _2, 40);
    wat(10, 30);

    return 0;
}

在上例中,b被绑定为 30,d被绑定为 40,返回的函数第一个实参值会是a的值,第二个实参值会是c的值,因此结果显示如下:

a: 10
b: 20
c: 30
d: 40

因此,如果想交换参数顺序,可以如下:

#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;

void foo(int a, int b) {
    cout << "a: " << a << endl 
         << "b: " << b << endl;
}

int main() {
    auto wat = bind(foo, _2, _1);
    wat(10, 20);

    return 0;
}

执行结果如下:

a: 20
b: 10

实际上,functional中包含了对应于运算符的函子(Functor),像是plusminusmultiplies等,之后的文件会谈到函子,在这边只要先知道,它就是个类,重载了调用运算符(),构造其实例之后,可以看成是个函数。

因此,上例可以进一步修改如下:

#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;

int main() {
    auto add10 = bind(plus<int>{}, _1, 10);
    auto mul5 = bind(multiplies<int>{}, _1, 5); 

    cout << add10(30) << endl;  // 40
    cout << mul5(20) << endl;   // 100

    return 0;
}

bind默认不处理参考,因此若是以下的范例:

#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;

void foo(int &a, const int &b) {
    a++;
    cout << &b << endl;
}

int main() {
    int a = 10;
    int b = 20;
    auto wat = bind(foo, a, b);
    wat();
    cout << "a: " << a << endl 
         << "b: " << &b << endl;
    return 0;
}

执行之后,main中的a值依旧是 10,而foob地址与mainb不同:

0x61feb0
a: 10
b: 0x61feb8

若要符合参数的参考指定,可以使用refcref,后者的 c 代表了const,例如:

#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;

void foo(int &a, const int &b) {
    a++;
    cout << &b << endl;
}

int main() {
    int a = 10;
    int b = 20;
    auto wat = bind(foo, ref(a), cref(b));
    wat();
    cout << "a: " << a << endl 
         << "b: " << &b << endl;
    return 0;
}

执行之后,main中的a值是 11,而foob地址与mainb相同:

0x61feb0
a: 11
b: 0x61feb0




展开阅读全文

评论

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 心情