C++ Lambda表達式的基本使用

0.前言

Lambda表達式(也叫lambda函數,或簡稱lambda),是從C++ 11開始引入並不斷完善的,是能夠捕獲作用域中變量的匿名函數對象。因爲C++是不能嵌套定義函數的,所以lambda就成了我們構造閉包的主要手段,不過在對象的生命週期上還是有點不同。本文主要展示lambda的基本使用(一些我不常用或者新標準如C++20就先不做筆記了,以在線文檔爲準)。

目錄

1.認識 Lambda

1.1.捕獲列表 [ ]

1.2.形參列表 ( ) 

1.3.說明符 

1.4.返回類型 ->

1.5.函數體 { }

2.基本使用

3.參考


1.認識 Lambda

lambda的基本語法如下:

當定義一個lambda時,編譯器生成一個與lambda對應的新的(未命名的)類類型。下面對重要的組成部分進行說明:

1.1.捕獲列表 [ ]

捕獲列表是零或多個捕獲符的逗號分隔符列表,可選地以默認捕獲符開始(僅有的默認捕獲符是 & 和 = )。默認情況下,從lambda生成的類都包含一個對應該lambda所捕獲變量的數據成員。類似任何普通類地數據成員,lambda的數據成員也在lambda對象創建時被初始化。類似參數傳遞,變量的捕獲方式也可以是值或引用。

值捕獲:

void func()
{
	int i = 100;//局部變量
	//將i拷貝到明位f的可調用對象
	auto f = [i] { return i; };
	i = 0;
	int j = f(); //j=100,因爲i是創建時拷貝的
}

引用捕獲:

void func()
{
	int i = 100;//局部變量
	//對象f包含i的引用
	auto f = [&i] { return i; };
	i = 0;
	int j = f(); //j=0,傳遞的是引用
}

除了自己列出捕獲列表的變量,還可以讓編譯器根據lambda中代碼來推斷我們要使用哪些變量(隱式捕獲),用過使用&或=指示編譯器推斷捕獲列表。&則採用引用捕獲的方式,=則採用值捕獲的方式。混合使用隱式捕獲和顯示捕獲,則兩者須使用不同的方式,一個爲引用捕獲,一個爲值捕獲。

lambda捕獲列表:

  • [ ]。空捕獲列表,lambda不能使用所在函數中的變量。
  • [=]。函數體內可以使用Lambda所在作用範圍內所有可見的局部變量(包括Lambda所在類的this),並且是值傳遞方式(相當於編譯器自動爲我們按值傳遞了所有局部變量)。
  • [&]。函數體內可以使用Lambda所在作用範圍內所有可見的局部變量(包括Lambda所在類的this),並且是引用傳遞方式(相當於編譯器自動爲我們按引用傳遞了所有局部變量)。
  • [this]。函數體內可以使用Lambda所在類中的成員變量。
  • [a]。將a按值進行傳遞。按值進行傳遞時,函數體內不能修改傳遞進來的a的拷貝,因爲默認情況下函數是const的。要修改傳遞進來的a的拷貝,可以添加mutable修飾符。
  • [&a]。將a按引用進行傳遞。
  • [=,&a, &b]。除a和b按引用進行傳遞外,其他參數都按值進行傳遞。
  • [&, a, b]。除a和b按值進行傳遞外,其他參數都按引用進行傳遞。

懸垂引用:

若以引用隱式或顯式捕獲非引用實體,而在該實體的生存期結束之後調用lambda對象的函數調用運算符,則發生未定義行爲。C++ 的閉包並不延長被捕獲的引用的生存期。這同樣適用於被捕獲的this指針所指向的對象的生存期。

1.2.形參列表 ( ) 

lambda形參列表和一般的函數形參列表類似,但不允許默認實參(C++14 前)。當以 auto 爲形參類型時,該 lambda 爲泛型 lambda(C++14 起)。與一個普通函數調用類似,調用一個lambda時給定的實參被用來初始化lambda的形參。

//代碼在VS2019中測試
void func()
{
	int i = 1, j = 2;
	auto f = [](int a,int &b) {
		a = 10;
		b = 20;
		//輸出:10  20
		std::cout << a << " " << b << std::endl;
	};
	f(i,j);
	//輸出:1 20
	std::cout << i << " " << j << std::endl;
}
//代碼在VS2019中測試
void func()
{
	auto f = [](auto a,int b=10) {
		std::cout << a << " " << b << std::endl;
	};
	f(1.5, 2);
	f(true);
}

1.3.說明符 

允許以下說明符:

  • mutable:允許 函數體 修改各個複製捕獲的對象,以及調用其非 const 成員函數;
  • constexpr:顯式指定函數調用運算符爲 constexpr 函數。此說明符不存在時,若函數調用運算符恰好滿足針對 constexpr 函數的所有要求,則它也會是 constexpr;  (C++17 起)
  • consteval:指定函數調用運算符爲立即函數。不能同時使用 consteval 和 constexpr。(C++20 起)

默認情況下,對於一個值被拷貝的變量,lambda不會改變其值。假如我們希望能改變一個被捕獲的變量的值,就必須在參數列表後面加上關鍵字mutable。而一個引用捕獲的變量則不受此限制。 

//代碼在VS2019中測試
void func()
{
	int i = 10, j = 10;
	//加上mutable纔可以在lambda函數中改變捕獲的變量值
	auto f = [i, &j]() mutable {
		i = 100, j = 100;
	};
	i = 0, j = 0;
	f();
	//輸出:0 100
	std::cout << i << " " << j << std::endl;
}

1.4.返回類型 ->

當我們需要爲一個lambda定義返回類型時,需要使用尾置返回類型。返回類型若缺省,則根據函數體中的 return 語句進行推斷(如果有多條return語句,需要保證類型一直,否則編譯器無法自動推斷)。默認情況下,如果一個lambda函數體不包含return語句,則編譯器假定返回void。

void func()
{
	auto f = []() ->double {
		if (1 > 2)
			return 1;
		else 
			return 2.0;
	};
	std::cout << f() << std::endl;
}

如果不顯示指定返回類型,則int和double兩種返回類型會導致推斷衝突。

1.5.函數體 { }

略,同普通函數的函數體。

2.基本使用

參照在線文檔中的示例,此示例演示如何傳遞 lambda 給泛型算法,以及 lambda 表達式所產生的對象能如何存儲於 std::function 對象。(已在VS2019中測試)

#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>

int main()
{
    std::vector<int> c = { 1, 2, 3, 4, 5, 6, 7 };
    int x = 5;
    c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; }), c.end());

    std::cout << "c: ";
    std::for_each(c.begin(), c.end(), [](int i) { std::cout << i << ' '; });
    std::cout << '\n';

    // 閉包的類型不能被指名,但可用 auto 提及
    // C++14 起,lambda 能擁有自身的默認實參
    auto func1 = [](int i = 6) { return i + 4; };
    std::cout << "func1: " << func1() << '\n';

    // 與所有可調用對象相同,閉包能可以被捕獲到 std::function 之中
    // (這可能帶來不必要的開銷)
    std::function<int(int)> func2 = [](int i) { return i + 4; };
    std::cout << "func2: " << func2(6) << '\n';

    system("pause");
    return 0;
}

輸出:

 

3.參考

參考書籍:《C++ Primer》中文第五版

在線文檔:https://zh.cppreference.com/w/cpp/language/lambda

參考博客排版:https://www.cnblogs.com/jimodetiantang/p/9016826.html

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章