天天看點

C++中的Lambda表達式1. Lambda 表達式的組成2. 捕獲語句的使用 & 可變規範 mutable 3. 參數清單4. 特殊用法5. 總結

1.

Lambda

表達式的組成

先對

lambda

表達式有一個直覺的認識,參考下面程式,該程式完成的是将輸入的數組

nums

按照絕對值大小進行升序排列。

int main() {
    std::vector<int> nums = {1, 5, 3, 4, 2, -1, 10};
    std::sort(nums.begin(), nums.end(), [](int a, int b) mutable throw() -> bool {
        // lambda 表達式函數體,在這裡做到了将輸入數組升序排列
        return (std::abs(a) < std::abs(b));
    });
    for (int i : nums) std::cout << i << " ";
    // >: 1 -1 2 3 4 5 10
}           

抛開邊邊角角,單獨拿出最重要的一部分來學習,

[](int a, int b) mutable throw() -> bool{ // statement }

就是

lambda

表達式最原始的内容。在該表達式中,每一部分的含義如下叙述:

  1. []

    捕獲子句:用來捕獲周圍範圍中出現的變量,也被稱為引導子句,可以在其中聲明擷取的變量是按值通路還是引用來通路,預設值為

    &

    ,上文中的例子和

    [&]

    是一樣的效果,具體例子見下文。
  2. ()

    參數清單:用來擷取參數,對于一個一般的

    lambda

    函數,使用起來和一般的指針函數沒有差別,也是需要有參數清單的,具體例子見下文。
  3. mutable

    可變類型(可選):一般來說,在

    lambda

    體中調用運算符的變量,都是以

    const value

    來使用的,加上這個

    mutable

    之後,人家變成了變量來使用,具體栗子見下文。
  4. throw()

    異常類型(可選):和普通函數一樣樣,

    lambda

    函數也可能引發異常,如果不會引發異常的話,直接聲明

    noexcept

    就可以啦~
  5. -> bool

    傳回類型(可選):繼續和普通函數一樣
  6. {// statement }

    lambda

    體:和一般的函數體一樣。

不難發現,

lambda

函數和一般的函數沒有太大差別,基本上隻有在頭部位置有特殊文法。

2. 捕獲語句的使用 & 可變規範

mutable

拿出栗子:

int main() {
    int num = 1; // 在上文中聲明好變量 num
    auto f = [n = num]() { // 在下文中通過 捕獲[] 來擷取 num,并在 lambda 函數體中進行使用
        std::cout << n << std::endl;
        // std::cout << ++num << std::endl; // 錯誤的使用,因為 num 是不可變的常量
    };
    f(); // >: 1
    auto m = [num]() mutable {
        std::cout << ++num << std::endl; // 将内部變量聲明成 mutable 可變類型,此時可以修改内部變量
    };
    m(); // >: 2
    std::cout << num << std::endl; // >: 2
}           

C++14

及以後的版本中,可以通過

capture

語句從周圍(Surrounding Scope)捕獲變量,在

[]

子句中指定要捕獲哪些變量,以及按照何種方式使用它們。和普通文法一樣,帶有

字首的變量可以通過引用進行通路,而沒有字首

的變量可以通過值進行通路。而空的捕獲子句[]表示

lambda

表達式的主體在閉包範圍内不通路外部任何變量。 當然~,也可以使用預設的捕獲模式來訓示如何捕獲

lambda

中引用的任何外部變量:

[&]

表示周圍所有變量都是通過引用捕獲的,而

[=]

意味着它們按值所捕獲。

一般情況下,

lambda

的函數調用運算符是常量值,但是使用

mutable

關鍵字可以修改預設值,

mutable

使

lambda

表達式的函數體可以修改按值捕獲的變量。

3. 參數清單

再拿出一個栗子:

int main() {
    auto y = [](int a, int b) {
        return a + b;
    };
    std::cout << y(3, 2); // >: 5
}           

從這裡開始,也就是參數清單開始,後面的内容都是可選項,也就是如果為空,那麼就直接省略不寫即可。例如:

int main() {
    auto empty = [] {
        std::cout << "Wow!空的~" << std::endl;
        // 啥也沒有隻有個函數體};
    };
    empty(); // >: Wow!空的~
}           

4. 特殊用法

4.1 花裡胡哨的

lambda

嵌套

int main() {
    // 兩層 lambda 嵌套,看起來挺花裡胡哨
    auto embed_embed_lambda = [](int a) {
        std::cout << a << " - - ";
        return [](int c) { return c / 2; };
    };
    std::cout << embed_embed_lambda(2)(2) << std::endl; // >: 2 - - 1
}           

4.2 高階

lambda

函數

高階函數是指,采用另一個

lambda

表達式作為其參數或傳回

lambda

表達式的

lambda

表達式(不知不覺想起了俄羅斯套娃🤔)

int main() {
    // 傳回 function 對象的 lambda 表達式
    auto get_function = [](int x) -> std::function<int(int)> {
        return [=](int y) { return x + y; };
    };
    // 使用 function 為對象作為其參數的 lambda 表達式
    auto param_function = [](const std::function<int(int)> &f, int n) {
        return f(n) * 2;
    };
    auto ans = param_function(get_function(2), 3); // x = 2, n = 3
    std::cout << ans << std::endl;
}           

5. 總結

寫到此處,關于

C++

lambda

文法規範和用法已經學習了一小部分,它作為一種友善靈活的方法随用随學也是闊以的。

因為參數類型和函數模闆參數一樣可以被推導而無需和具體參數類型耦合,有利于重構代碼;和使用auto聲明變量的作用類似,它也允許避免書寫過于複雜的參數類型。特别地,不需要顯式指出參數類型使得使用高階函數變得更加容易。

以下程式源碼:

/**
 * Created by Xiaozhong on 2020/8/30.
 * Copyright (c) 2020/8/30 Xiaozhong. All rights reserved.
 */
#include <functional>
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> nums = {1, 5, 3, 4, 2, -1, 10};
    std::sort(nums.begin(), nums.end(), [&](int a, int b) mutable throw() -> bool {
        // lambda 表達式函數體,在這裡做到了将輸入數組升序排列
        return (std::abs(a) < std::abs(b));
    });
    for (int i : nums) std::cout << i << " ";
    // >: 1 -1 2 3 4 5 10

    int num = 1; // 在上文中聲明好變量 num
    auto f = [n = num]() { // 在下文中通過 捕獲[] 來擷取 num,并在 lambda 函數體中進行使用
        std::cout << n << std::endl;
        // std::cout << ++num << std::endl; // 錯誤的使用,因為 num 是不可變的常量
    };
    f(); // >: 1
    auto m = [num]() mutable {
        std::cout << ++num << std::endl; // 将内部變量聲明成 mutable 可變類型,此時可以修改内部變量
    };
    m(); // >: 2
    std::cout << num << std::endl; // >: 2

    auto y = [](int a, int b) {
        return a + b;
    };
    std::cout << y(3, 2); // >: 5

    auto empty = [] {
        std::cout << "Wow!空的~" << std::endl;
        // 啥也沒有隻有個函數體};
    };
    empty(); // >: Wow!空的~

    // 聲明一個函數,然後直接使用 (5, 3)
    int n = [](int a, int b) { return a + b; }(5, 3);
    std::cout << n << std::endl; // >: 8

    // 兩層 lambda 嵌套,看起來挺花裡胡哨
    auto embed_embed_lambda = [](int a) {
        std::cout << a << " - - ";
        return [](int c) { return c / 2; };
    };
    std::cout << embed_embed_lambda(2)(2) << std::endl; // >: 2 - - 1

    // 傳回 function 對象的 lambda 表達式
    auto get_function = [](int x) -> std::function<int(int)> {
        return [=](int y) { return x + y; };
    };

    // 使用 function 為對象作為其參數的 lambda 表達式
    auto param_function = [](const std::function<int(int)> &f, int n) {
        return f(n) * 2;
    };

    auto ans = param_function(get_function(2), 3);
    std::cout << ans << std::endl;
}           

繼續閱讀