C++之Lambda表達式(匿名函數)

原文地址:https://www.cnblogs.com/jimodetiantang/p/9016826.html

1、概述

C++ 11 中的 Lambda 表達式用於定義並創建匿名的函數對象,以簡化編程工作。

Lambda 的語法形式如下:

[函數對象參數] (操作符重載函數參數) mutable 或 exception 聲明 -> 返回值類型 {函數體}

可以看到,Lambda 主要分爲五個部分:[函數對象參數]、(操作符重載函數參數)、mutable 或 exception 聲明、-> 返回值類型、{函數體}.

2、Lambda 語法分析

2.1、[函數對象參數]

標識一個 Lambda 表達式的開始,這部分必須存在,不能省略。函數對象參數是傳遞給編譯器自動生成的函數對象類的構造函數的。函數對象參數只能使用那些到定義 Lambda 爲止時 Lambda 所在作用範圍內可見的局部變量(包括 Lambda 所在類的 this)。函數對象參數有以下形式:

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

2.2、(操作符重載函數參數)

標識重載的 () 操作符的參數,沒有參數時,這部分可以省略。參數可以通過按值(如: (a, b))和按引用 (如: (&a, &b)) 兩種方式進行傳遞。

2.3、mutable 或 exception 聲明

這部分可以省略。按值傳遞函數對象參數時,加上 mutable 修飾符後,可以修改傳遞進來的拷貝(注意是能修改拷貝,而不是值本身)。exception 聲明用於指定函數拋出的異常,如拋出整數類型的異常,可以使用 throw(int)。

2.4、-> 返回值類型

標識函數返回值的類型,當返回值爲 void,或者函數體中只有一處 return 的地方(此時編譯器可以自動推斷出返回值類型)時,這部分可以省略。

2.5、{函數體}

標識函數的實現,這部分不能省略,但函數體可以爲空。

3、示例

[] (int x, int y) { return x + y; } // 隱式返回類型
[] (int& x) { ++x;  } // 沒有 return 語句 -> Lambda 函數的返回類型是 'void'
[] () { ++global_x;  } // 沒有參數,僅訪問某個全局變量
[] { ++global_x; } // 與上一個相同,省略了 (操作符重載函數參數)


可以像下面這樣顯示指定返回類型:

[] (int x, int y) -> int { int z = x + y; return z; }

在這個例子中創建了一個臨時變量 z 來存儲中間值。和普通函數一樣,這個中間值不會保存到下次調用。什麼也不返回的Lambda 函數可以省略返回類型,而不需要使用 -> void 形式。

Lambda 函數可以引用在它之外聲明的變量. 這些變量的集合叫做一個閉包. 閉包被定義在 Lambda 表達式聲明中的方括號 [] 內。這個機制允許這些變量被按值或按引用捕獲。如下圖的例子:

[]          //未定義變量,試圖在Lambda內使用任何外部變量都是錯誤的.
[x, &y]	    //x按值捕獲, y按引用捕獲.
[&]         //用到的任何外部變量都隱式按引用捕獲.
[=]	        //用到的任何外部變量都隱式按值捕獲.
[&, x]      //x顯式地按值捕獲,其它變量按引用捕獲.
[=, &z]     //z按引用捕獲,其它變量按值捕獲.

3.1、示例 1

std::vector<int> some_list;
int total = 0;
for (int i = 0; i < 5; ++i) some_list.push_back(i);
std::for_each(begin(some_list), end(some_list), [&total](int x)
{
    total += x;
});

此例計算 list 中所有元素的總和。變量 total 被存爲 Lambda 函數閉包的一部分。因爲它是棧變量(局部變量)total 引用,所以可以改變它的值。

3.2、示例 2

std::vector<int> some_list;
int total = 0;
int value = 5;
std::for_each(begin(some_list), end(some_list), [&, value, this](int x)
{
    total += x * value * this->some_func();
});

此例中 total 會存爲引用, value 則會存一份值拷貝。對 this 的捕獲比較特殊,它只能按值捕獲。this 只有當包含它的最靠近它的函數不是靜態成員函數時才能被捕獲。對 protect 和 private 成員來說,這個 Lambda 函數與創建它的成員函數有相同的訪問控制。如果 this 被捕獲了,不管是顯式還是隱式的,那麼它的類的作用域對 Lambda 函數就是可見的。訪問 this 的成員不必使用 this-> 語法,可以直接訪問。

4、總結

不同編譯器的具體實現可以有所不同,但期望的結果是: 按引用捕獲的任何變量,Lambda 函數實際存儲的應該是這些變量在創建這個 Lambda 函數的函數的棧指針,而不是 Lambda 函數本身棧變量的引用。不管怎樣,因爲大多數 Lambda 函數都很小且在局部作用中,與候選的內聯函數很類似,所以按引用捕獲的那些變量不需要額外的存儲空間。

如果一個閉包含有局部變量的引用,在超出創建它的作用域之外的地方被使用的話,這種行爲是未定義的!

Lambda 函數是一個依賴於實現的函數對象類型。這個類型的名字只有編譯器知道,如果用戶想把 lambda 函數做爲一個參數來傳遞,那麼形參的類型必須是模板類型或者必須能創建一個 std::function 類似的對象去捕獲 lambda 函數。使用 auto 關鍵字可以幫助存儲 lambda 函數。

auto my_lambda_func = [&](int x) { /* ... */ };
auto my_onheap_lambda_func = new auto([=](int x) { /* ... */ });


這裏有一個例子,把匿名函數存儲在變量、數組或 vector 中,並把它們當做命名參數來傳遞。

#include <functional>
#include <vector>
#include <iostream>
double eval(std::function<double(doubie)> f, double x = 2.0) {return f(x);}
int main ()
{
	std::function<double (double)> 	f0 		= [](double x){return 1;};
	auto							f1 		= [](double x){return x;};
	decltype(f0)					fa[3] 	= {f0, f1, [](double x){return x*x;}};
	std::vector<decltype(f0)> 		fv		= (f0, f1};
	fv.push_back([](double x){return x*x;});
	for(int i=0; i<fv.size(); i++) 	std::cout << fv[i](2.0) << "\n";
	for(int i=0; i<3; i++) 			std::cout << fa[i](2.0) << "\n";
	for (auto &f: fv) 				std::cout << f(2.0) << "\n";
	for (auto &f: fa)				std::cout << f(2.0) << "\n";
	std::cout << eval (f0) << "\n";
	std::cout << eval (f1) << "\n";
	return 0;
}


一個沒有指定任何捕獲的 lambda 函數,可以顯式轉換成一個具有相同聲明形式函數指針。所以,像下面這樣做是合法的:

auto a_lambda_func = [](int x) { /* ... */ };
void (*func_ptr)(int) = a_lambda_func;
func_ptr(4); // calls the lambda
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章