天天看點

C++ lambda表達式

文法:

[捕獲](形參)限制(可選)->傳回值類型(可選){函數體}
限制包括 說明符如

mutable

異常說明如

throw()

捕獲

捕獲形式

說明
[] 不捕獲任何外部變量
[變量名 , …] 預設以值的形式捕獲指定的多個外部變量并以逗号分隔
[this] 以值的形式捕獲this指針
[=] 以值的形式捕獲所有外部變量(需要拷貝)
[&] 以引用形式捕獲所有外部變量
[= , &x] 變量x以引用形式捕獲,其餘變量以傳值形式捕獲
[& , x] 變量x以值的形式不會,其餘變量以引用形式捕獲

捕獲示例

值捕獲

值捕獲和函數傳參中的值傳遞類似,被捕獲的變量的值在lambda表達式建立時通過值拷貝的方式傳入,故随後在外部對該變量的修改不會對lambda表達式中的值産生影響。

int a = 10, b = 100, c = 1000;
auto f = [a, b](){
	cout << a << " " << b << endl;
	cout << c << endl;//error 變量c沒有被捕獲 不能在lambda表達式中使用
};
f();//調用lambda表達式
           

但該方法在lambda表達式内部不能對拷貝的變量的值進行修改,即:

int a = 10, b = 100, c = 1000;
auto f = [a, b](){
	a = 20;//error
	cout << a << " " << b << endl;
};
f();
           

為了能在lambda表達式中修改捕獲的值的拷貝,需要加上關鍵字

mutable

,執行個體如下:

int a = 10, b = 100, c = 1000;
auto f = [a, b]()mutable{
	a = 20;
	cout << a << " " << b << endl;
};
f();
/**
輸出:
20 100
*/
           

引用捕獲

如果想在lambda表達式中改變外部變量的值,我們需要把變量的引用傳入,類似于函數傳參中的引用傳遞。

int a = 10;
auto f = [&a](){
	cout << "改變前: " << a << endl;
	a = 30;
	cout <<"改變後:" << a << endl;
};
f();
a = 40;
f();
/**
輸出:
改變前: 10
改變後:30
改變前: 40
改變後:30
*/
           

隐式捕獲

上面的值捕獲和引用捕獲都需要我們在捕獲清單中顯示列出Lambda表達式中使用的外部變量。除此之外,我們還可以讓編譯器根據函數體中的代碼來推斷需要捕獲哪些變量,這種方式稱之為隐式捕獲。隐式捕獲有兩種方式,分别是[=]和[&]。[=]表示以值捕獲的方式捕獲所有外部變量,[&]表示以引用捕獲的方式捕獲所有外部變量。

隐式值捕獲
int a = 10, b = 100, c = 1000;
auto f = [=]() {
	a = 20;//error
	cout << a << " " << b << " " << c << endl;
};
f();
/**
輸出:10 100 1000
*/
           

想要修改捕獲到的值,同樣需要添加關鍵字

mutable

.

int a = 10, b = 100, c = 1000;
auto f = [=]()mutable {
	a = 20;//correct
	cout << a << " " << b << " " << c << endl;
};
f();
/**
輸出:20 100 1000
*/
           
隐式引用捕獲
int a = 10, b = 100, c = 1000;
auto f = [&](){
	a = 20;//correct
	cout << a << " " << b << " " << c << endl;
};
f();
/**
輸出:20 100 1000
*/
           

混合捕獲

在上述的例子中隻使用了單一的形式,lambda表達式支援混合的方式捕獲變量。執行個體:

int a = 10, b = 100, c = 1000;
auto f = [& , a](){
	a = 20;//error
	cout << a << " " << b << " " << c << endl;
};
f();
           

上述示例中,變量a以值的形式捕獲其餘變量以引用的形式捕獲,由于沒加

mutable

關鍵字故對a的拷貝進行修改非法。

int a = 10, b = 100, c = 1000;
auto f = [& , a]()mutable{
	a = 20;//correct
	cout << a << " " << b << " " << c << endl;
};
f();
/**
輸出:20 100 1000
*/
           

參數

lambda表達式的參數與普通函數的參數幾乎相同,僅有如下限制:

  • 參數清單中不能有預設參數
  • 不支援可變參數
  • 所有參數必須有參數名

限制

說明符

mutable

,允許函數體修改各個捕獲對象的拷貝

constexpr

C++17

起),顯示指定函數調用符為

constexpr

,當函數體滿足

constexpr

函數要求時,即使未顯式指定,也會是

constexpr

異常說明

提供

throw

noexpect

字句

示例:

int a = 10, b = 100, c = 1000;
auto f = [&](int a, int b) mutable noexcept->int {
	return a + b;
};
cout << f(a, b) + f(b, c) << endl;
/**
輸出:1210
*/
           

傳回值類型

此處可以省略,讓

auto

自動推導。

泛型lambda

C++14

開始,可以使用

auto x

作為參數。則可以寫出如下代碼:

auto f = [](auto x) {
	cout << x << endl;
};
f(10);
f(10.11);
f("hello world!");
           

//輸出

10

10.11

hello world!

該特性的引入可以友善我們實作遞歸函數,相關示例将在後面提及。

生命周期

lambda表達式相關對象的生命周期如下:

  • 全局,更外層作用域的生命周期不受影響。
  • 使用值捕獲的情況,先于lambda表達式函數體構造對象,後于函數體執行完析構
  • 在lambda表達式函數體内的對象,在函數體執行時建立,在閉包析構函數内析構
  • lambda對象的生命周期為所在作用域結束,析構的順序為聲明的逆序析構

lambda表達式的實際使用

C++ 優先隊列

C++

優先隊列中的使用參考 priority_queue優先隊列

C++ 排序函數sort

#include<bits/stdc++.h>
using namespace std;

struct Point {
	int x, y;
	Point(int _x = 0 , int _y = 0):x(_x) , y(_y){}
};
int main() {
	std::vector<Point> points;
	for (int i = 0; i <= 10; ++i) {
		points.push_back({ rand() % 10 , rand() % 10 });
	}
	for (auto [x, y] : points) cout << x << " " << y << endl;
	cout << "After sort" << endl;
	//寫法一
	auto cmp = [&](const Point& a, const Point& b) {
		//按照x為第一優先級 y為第二優先級從小到大排序
		return a.x < b.x || (a.x == b.x && a.y < b.y);
	};
	std::sort(points.begin(), points.end(), cmp);
	//寫法二
	std::sort(points.begin(), points.end(), [&](const Point& a, const Point& b) {
		//按照x為第一優先級 y為第二優先級從小到大排序
		return a.x < b.x || (a.x == b.x && a.y < b.y);
		});
	//上述兩種寫法等價
	for (auto [x, y] : points) cout << x << " " << y << endl;
	return 0;
}
           

1 7

4 0

9 4

8 8

2 4

5 5

1 1

5 2

7 6

1 4

After sort

遞歸函數

#include<bits/stdc++.h>
#include<functional>
using namespace std;


int main() {
    //該部分的一些代碼來自cpp 參考手冊 lambda表達式部分 連結已經在文末給出
    std::cout << "模仿遞歸 lambda 調用:\n斐波那契數:";
    auto nth_fibonacci1 = [](int n) {
        std::function<int(int, int, int)> fib = [&](int a, int b, int n) {
            return n ? fib(a + b, a, n - 1) : b;
        };
        return fib(1, 0, n);
    };
    for (int i{ 1 }; i != 9; ++i) { std::cout << nth_fibonacci1(i) << ", "; }
    //傳遞 lambda 給泛型
    std::cout << "\n另一種 lambda 遞歸方案:\n斐波那契數:";
    auto nth_fibonacci2 = [](int n) {
        auto fib = [](auto self, int a, int b, int n) -> int {
            return n ? self(self, a + b, a, n - 1) : b;
        };
        return fib(fib, 1, 0, n);
    };
    for (int i{ 1 }; i != 9; ++i) { std::cout << nth_fibonacci2(i) << ", "; }
    //對于上述第二種遞歸方案也可以寫成如下形式
    std::cout << "\n第二種 lambda 遞歸方案另一種形式:\n斐波那契數:";
    auto nth_fibonacci = [](auto nth_fibonacci, int a, int b, int n)-> int {
        return n ? nth_fibonacci(nth_fibonacci, a + b, a, n - 1) : b;
    };
    for (int i{ 1 }; i != 9; ++i) { std::cout << nth_fibonacci(nth_fibonacci , 1 , 0 , i) << ", "; }
	return 0;
}
           

模仿遞歸 lambda 調用:

斐波那契數:1, 1, 2, 3, 5, 8, 13, 21,

另一種 lambda 遞歸方案:

第二種 lambda 遞歸方案另一種形式:

參考資料

C++ LAMBDA的演化

C++ lambda 分析

cpp 參考手冊 Lambda 表達式