Function Object & Lambda 函数对象与匿名函数

#函数对象

一个重载了括号运算符()的类就是一个函数类。当该类的对象调用()运算符的时候,表现形式就像是函数一样,因此取名函数对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Function {
  public:
    void operator()(int a) {
        ++callCount;
        cout << "a: " << a << endl;
        cout << "Call Count: " << callCount << endl;
    }

  private:
    int callCount = 0;
};

#对比普通函数的优势

  1. 函数对象有自己的状态,可以保存在类中,比如调用次数CallCount

    1
    2
    3
    4
    5
    6
    
     int main() {
         Function f;
         for (int i = 0; i < 5; ++i)
             f(i);
         return 0;
     }
    

    输出结果: 函数对象调用

  2. 函数对象有自己的类型,类型就是对应的类,而普通函数是没有的。使用这个类型可以用来传参,挂钩子,为STL库的函数的钩子提供自己的实现:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    class Compare {
     public:
         bool operator()(const pair<int, int> &a, const pair<int, int> &b) {
             return a.second < b.second; /* 根据第二个元素构建大根堆 */
         }
     };
    
     int main() {
         /* 传入函数类型,挂钩子 */
         priority_queue<pair<int, int>, vector<pair<int, int>>, Compare> heap;
         heap.emplace(5, 18);
         heap.emplace(514, 123);
         heap.emplace(40, 11);
         heap.emplace(51, 21);
         heap.emplace(53, 91);
         heap.emplace(4, 146);
         while (!heap.empty()) {
             auto &ele = heap.top();
             cout << "1: " << ele.first << " 2: " << ele.second << endl;
             heap.pop();
         }
         return 0;
     }
    

    输出结果: alt text

  3. operator()直接定义在类内部,默认是内联函数,编译器会决定是否内联。

#Lambda

就是一个匿名函数对象,会有对应的匿名类。

1
[捕获列表](参数列表) -> 返回类型 { 函数体 }

返回类型编译器可以自己推导,可以不加。

捕获操作就像是在匿名类中添加成员变量,可以用来保存状态:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
int main() {
    int times = 0;
    auto f = [times](int a) mutable {
        ++times;
        cout << "a: " << a << endl;
        cout << "Call Count: " << times << endl;
    };

    for (int i = 0; i < 5; ++i)
        f(i);
    /* 
        lambda表达式调用也可以使用括号运算符的方式调用:
        f.operator()(i);
        等价于f(i)
    */

    return 0;
}

Lambda可调用括号运算符,有变量保存状态,因此就是等价一个匿名的函数类,编译的时候编译器为其实现一个类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class AnonClass {
  public:
    /* 重载括号运算符 */
    void operator()(int a) {
        ++times;
        cout << "a: " << a << endl;
        cout << "Call Count: " << times << endl;
    }

  private:
    int times = 0; /* 捕获的变量会被实现成类的成员变量 */
};

#值捕获

值捕获是默认状态,也可以用=显式表示。值捕获时如果要对捕获的变量进行修改,必须加上mutable关键字。

mutable关键字的作用是把值捕获进来的变量变成左值: alt text

#引用捕获

修改捕获的变量同时会对外部变量进行修改。

1
2
3
4
int result = 0;
auto f = [&result](int a, int b) { result = a + b; };
f(4, 5);
cout << result << endl; /* 输出9 */

#混合捕获

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
int x = 10;
int y = 20;

auto lambda = [x, &y]() {
    // x 是值捕获,y 是引用捕获
    cout << "x: " << x << endl; // x: 10
    cout << "y: " << y << endl; // y: 20
    y = 30;                     // 修改外部变量 y
};

lambda();

cout << "y: " << y << endl; // y: 30,因为 y 是通过引用捕获的
updatedupdated2025-01-012025-01-01