【C++】C++之Lambda表示式

李春港發表於2020-12-05

作者:李春港
出處:https://www.cnblogs.com/lcgbk/p/14088462.html

一、前言

由於前段時間在閱讀一些C++原始碼的時候發現了Lambda表示式,所以在此也記錄下Lambda表示式的使用。

很早之前Lambda在很多高階語言中,就已經被廣泛地使用了,在一個程式中Lambda表示式可以理解為是匿名函式。在C++中,到了C++11標準才引入了這個Lambda表示式,這是C++11最重要而且也是最常用的特性之一。

使用Lambda表示式,不需要額外地定義函式名,可以更直接編寫程式,有比較好的可讀性和可維護性;不需要另外宣告和定義函式體,避免了程式程式碼的膨脹。

二、Lambda表示式格式說明

2.1 完整的Lambda表示式格式

[capture list] (params list) mutable exception-> return type { function body }

說明:

名稱 解析
[capture list] 捕獲列表:lambda 表示式可以通過捕獲列表捕獲一定範圍內的變數。
(params list) 形參列表,用於傳參(可以省略)。
mutable 用來說明是否可以修改按值捕獲的變數(可以省略),如果需要修改按值捕獲的變數,則需要新增。
exception 異常設定(可以省略)。
return type 返回型別 (可省略,如果省略則自動從函式體中判斷返回型別,return後的值。如果沒有則返回void)。
function body 函式體,即邏輯程式碼。

2.2 常見的Lambda表示式格式

編號 格式 特性
格式1 [capture list] (params list) -> return type {function body} 1、無法修改捕獲列表中的變數值。
格式2 [capture list] (params list) {function body} 1、無法修改捕獲列表中的變數值;2、返回型別由return返回的值型別確定,如果沒有return語句,則返回型別為void。
格式3 [capture list] {function body} 1、無法修改捕獲列表中的變數值;2、返回型別由return返回的值型別確定,如果沒有return語句,則返回型別為void;3、不能傳入引數,類似普通的無參函式。

2.3 lambda 表示式捕獲列表

捕獲形式 解析
[ ] 不捕獲任何變數。
[&] 捕獲外部作用域中所有變數,並作為引用在函式體中使用(按引用捕獲)。
[=] 捕獲外部作用域中所有變數,並作為副本在函式體中使用(按值捕獲)。
[=,&x] 按值捕獲外部作用域中所有變數,並按引用捕獲 x 變數。
[x] 按值捕獲 x 變數,同時不捕獲其他變數。
[this] 捕獲當前類中的 this 指標,讓 lambda 表示式擁有和當前類成員函式同樣的訪問許可權。如果已經使用了 & 或者 =,就預設新增此選項。

注意:

  • 如果是按值捕獲,那麼是否可以改變捕獲的變數值,取決於mutable關鍵字。

三、示例

3.1 STL的sort函式引數使用Lambda

/*****************************************************************************
** Copyright © 2020 lcg. All rights reserved.
** File name: Lambda.cpp
** Description: 在STL的sort函式引數使用Lambda表示式
** Author: lcg
** Version: 1.0
** Date: 2020.12.04
*****************************************************************************/

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

bool cmp(int a, int b)
{
    return  a < b;
}

int main()
{
    vector<int> vec{ 3, 2, 5, 7, 3, 2 };
    vector<int> lbvec(vec);

    /**1、不使用Lambda表示式的寫法**/
    sort(vec.begin(), vec.end(), cmp); 
    cout << "predicate function:" << endl;
    for (int it : vec) // 此for迴圈寫法也是在C++11才出現
        cout << it << ' ';
    cout << endl;

    /**2、使用Lambda表示式的寫法**/
    sort(lbvec.begin(), lbvec.end(), [](int a, int b) -> bool { return a < b; });
    cout << "lambda expression:" << endl;
    for (int it : lbvec)
        cout << it << ' ';
}

可以看到這種情況使用Lambda表示式可以使程式碼更加直觀、簡介,無需再定義 cmp(int a, int b) 函式。

3.2 有返回值的Lambda表示式

/** 1、標明返回型別**/
auto f = [](int a) -> int { return a + 1; };
std::cout << f(1) << std::endl;  /**輸出: 2**/

/** 2、無標明返回型別**/
auto f = [](int a) { return a + 1; };
std::cout << f(1) << std::endl;  /**輸出: 2**/

當沒有標明返回型別的時候,系統會根據return回來的值來判斷返回值的型別,auto會自動檢索返回值的型別。

3.3 無引數Lambda表示式

auto f = []() { return 1; };
std::cout << f() << std::endl;  /**輸出: 1**/

3.4 捕獲外部變數的Lambda表示式

/*****************************************************************************
** Copyright © 2020 lcg. All rights reserved.
** File name: Lambda.cpp
** Description: 捕獲外部變數的Lambda表示式
** Author: lcg
** Version: 1.0
** Date: 2020.12.04
*****************************************************************************/

#include <iostream>

class A
{
public:
    int i_ = 0;
    void func(int x, int y)
    {
        /* error,沒有捕獲外部變數*/
        auto x1 = []{ return i_; };

        /*OK,按值捕獲所有外部變數,包括了this指標*/
        auto x2 = [=]{ return i_ + x + y; };

         /*OK,按引用捕獲所有外部變數,包括了this指標*/
        auto x3 = [&]{ return i_ + x + y; };

        /*OK,捕獲this指標,Lambda擁有和此類中普通函式一樣的許可權*/
        auto x4 = [this]{ return i_; };

        /*error,沒有捕獲x、y,因為x、y變數不屬於this*/
        auto x5 = [this]{ return i_ + x + y; };

        /* OK,捕獲this指標、x、y*/
        auto x6 = [this, x, y]{ return i_ + x + y; };

        /*OK,捕獲this指標,並修改成員的值*/
        auto x7 = [this]{ return i_=7; };
        x7();
        std::cout<<i_<<std::endl;//輸出7

        /*OK,捕獲所有外部變數,預設捕獲this指標,並修改成員的值*/
        auto x8 = [=]{ return i_=8; };
        x8();
        std::cout<<i_<<std::endl;//輸出8

        /*error,因為i_不屬於捕獲範圍的變數,所以無法按值捕獲i_變數,可以通過捕獲this指標來獲取i_使用權*/
        auto x9 = [i_]{ return i_++; };

        /*error,原因同上*/
        auto x10 = [&i_]{ return i_++; };

        /*ok,按引用捕獲所有變數,預設捕獲this指標,並修改成員的值*/
        auto x11 = [&]{ return i_=11; };
        x11();
        std::cout<<i_<<std::endl;//輸出11

        /*error,按值捕獲x變數,並修改值*/
        auto x12 = [x]{ return x++; };

        /*ok,按值捕獲x變數,並修改值*/
        auto x13 = [x]()mutable{ return x=13; };
        x13();
        std::cout<<x<<std::endl;//輸出1

        /*ok,按引用捕獲x變數,並修改值*/
        auto x14 = [&x]{ return x=14; };
        x14();
        std::cout<<x<<std::endl;//輸出14
    }
};


int main(int argc, char *argv[])
{
    A l;
    l.func(1,2);

    int a = 0, b = 1;
    /*error,沒有捕獲外部變數*/
    auto f1 = []{ return a; };

    /*OK,捕獲所有外部變數,改變a值*/
    auto f2 = [&]{ return a=2; };
    f2();
    std::cout<<a<<std::endl;//輸出2

    /*OK,捕獲所有外部變數,並返回a*/
    auto f3 = [=]{ return a; };

    /*error,a是以複製方式捕獲的,無法修改*/
    auto f4 = [=]{ return a=4; };

    /*ok,a是以複製方式捕獲的,修改a值*/
    auto f4 = [=]()mutable{ return a=4; };
    f4();
    std::cout<<a<<std::endl;//輸出2

    /*error,沒有捕獲變數b*/
    auto f5 = [a]{ return a + b; };

    /*OK,捕獲a和b的引用,並對b做自加運算*/
    auto f6 = [a, &b]{ return a + (b++); };

    /*OK,捕獲所有外部變數和b的引用,並對b做自加運算*/
    auto f7 = [=, &b]{ return a + (b++); };

    return 0;
}

總結:

  • 當按值的方式獲取外部變數時,是無法更改獲取過來的值的,除非使用mutable關鍵字宣告,就可以更改(更改的不是外部變數原本地址裡的值,而是lambda函式體內的副本);
  • 當按引用的方式捕獲外部變數時,lambda函式體內可以更改此值,更改的是外部變數原本地址裡的值;
  • 當在類中,lambda表示式捕獲this指標時,lambda函式體內可以直接改變該類中的變數,和類中的普通函式擁有一樣的許可權。

相關文章