前幾天被朋友問到 c++ 的 lambda 到底要怎麼用
老實說,10多年來都在寫 web 的東西,c++ 也有10多年沒真的在用
網路到處爬了好一陣子,大概了解 c++ lambda 是怎麼一回事了
簡單說就是隱函數或匿名函數,也就是沒有函數名的函數
如果寫過 javascript 應該就很清楚(PHP v5.3 也開始支援匿名函數)
<script>
setTimeout(function() {
alert("Hi!")},
2000
); // show Hi!
</script>
第一個參數原本應該給一個函數名,而這裡就是用了所謂的匿名函數
這種用法,感覺有點像是 c/c++ 的 inline XD
在 c++ 11 也正式開始提供這種語法(特性),網路上找到很多說明大多是
用在處理集合物件,例如..
const int size = 5;
int array[size] = {1, 3, 5, 7, 9};
for_each(array, array + size, [](int i) { cout << i * i << endl; });
[](int i) { cout << i * i << endl; }
這東西就是 c++ 11 lambda 的新語法
[](){};
這是基本樣式
[] => 函數開頭,設定帶入可視範圍變數的模式,大概有這幾種用法
1. []: 省略,不帶入任何變數
2. [=]: 全部以傳值方式帶入變數
3. [&]: 全部以參考方式帶入變數
4. [var1]: (或是 &var1)指定帶入的變數,忽略其他變數
5. [=, &var1]: 指定 var1 為參考方式,其他則傳值方式
6. [&, var1]: 指定 var1 為傳值方式,其他則參考方式
7. [this]: 傳物件的 this 指標
(下面會有使用範例)
() => 函數的參數,可以省略
{} => 函數的主體,可以省略
三種括號內都可以省略,所以
[](){};
是一個合法的匿名函數,只是什麼事也不做
上面那個 for_each 範例在執行時,會自動將 array 內容依序丟給後面匿名函數當做參數
也就是 i, 然後在函數裡印出 i * i
-----
其實我比較想要知道的是一般用法,例如像是 javascript 裡這樣的用法
<script>
var fVar = function(arg) {
return arg % 5;
};
alert(fVar(9)); //show 4
</script>
或是上面 setTimeout 的裡用法,下面整理了幾種方式,
其實都差不多,只是用的場合不同
(1)
$ cat lambda01.cc
#include <iostream>
using namespace std;
int main() {
int a = 5, b = 6;
auto fn = [&](int c) { a++; b--; return a + b + c; };
cout << "fn(3): " << fn(3) << endl;
cout << "a: " << a << endl;
cout << "b: " << b << endl;
return 0;
}
環境是 ubuntu 12.04 g++ 4.6.3,編譯時要加 -std=c++0x
g++ 4.7.x 才正式支援 c++11 標準,所以 g++ 4.7 以後編譯時就可以直接用 -std=c++11
$ g++ -std=c++0x -o lambda01 lambda01.cc
$ ./lambda01
fn(3): 14
a: 6
b: 5
因為使用 [&], a, b 變數會以參考的方式被帶入函數,因此在函數裡可以修改 a, b 的值
(2)
$ cat lambda02.cc
#include <iostream>
#include <string>
using namespace std;
int main() {
string str1, str2;
str1 = "Hello ";
str2 = "World!";
auto sn = [=](string str3){ cout << str1 << str2 << str3 << endl; };
str1 = "『Hello ";
str2 = "World!』";
sn(" Yo~~");
return 0;
}
$ g++ -std=c++0x -o lambda02 lambda02.cc
$ ./lambda02
Hello World! Yo~~
因為是 [=],函數內不能修改 str1, str2 內容,而且 str1, str2 值會被鎖定在函數之前的值
所以,雖然在函數之後 str1, str2 內容被改了,sn() 仍然會印出原來的值
如果改用 [&], 結果會怎樣呢? ^_^
(3)
$ cat lambda03.cc
#include <iostream>
using namespace std;
int main() {
[](){ cout << "我是合法的 lambda 函數!" << endl; };
cout << [](){ int x = 9; return x * x; }() << endl;
cout << [](int x){ return x * x; }(12) << endl;
int a = 20;
cout << [](int &x) { x++; return x * x; }(a) << endl;
cout << "a: " << a << endl;
return 0;
}
$ g++ -std=c++0x -o lambda03 lambda03.cc
$ ./lambda03
81
144
441
a: 21
(1)(2)兩種用法是先將匿名函數指定給一個變數,然後透過這個變數來呼叫函數
(3)則是連指定給變數的動作都省了,就類似 for_each 裡的用法
第 2,3 行大括號後面的小括號是必要的,如果沒有那對小括號,則函數只是被定義在那裡而已
實際上這個函數在執行時並沒有被呼叫,就像第 1 行,因為後面沒接 (), 因此沒有印出那串字
要在定義函數後立即呼叫執行的話,最後面要加(), 傳入的參數也是要放在這個 () 裡
將第 1 行成
[](){ cout << "我是合法的 lambda 函數!" << endl; }();
$ g++ -std=c++0x -o lambda03 lambda03.cc
$ ./lambda03
我是合法的 lambda 函數!
81
144
441
a: 21
(4)
$ cat lambda04.cc
#include <iostream>
using namespace std;
int main() {
int x = 31;
int b = 5;
if (x > [b](int x){ cout << "(x:" << x << ")" << endl; return x * b; }(6)) {
cout << "Yes [ x:" << x << ", b:" << b << " ]" << endl;
} else {
cout << "No [ x:" << x << ", b:" << b << " ]" << endl;
}
b = 6;
cout << ((x > [b](int x){ return x * b; }(6)) ? "yes" : "no") << endl;
return 0;
}
$ g++ -std=c++0x -o lambda04 lambda04.cc
$ ./lambda04
(x:6)
Yes [ x:31, b:5 ]
no
這個例子是將匿名函數放在判斷式裡,所以 x 實際上是跟匿名函數的傳回值做比較
而且在匿名函數還可以用 cout 印出資料來 XD
既然可以放在判斷式裡,那也就是說,應該也可以用在迴圈的判斷式吧?
(5)
$ cat lambda05.cc
#include <iostream>
using namespace std;
int main() {
int b = 10;
while ([&b](){ return b--; }()) {
cout << b << ' ';
}
cout << endl << "b: " << b << endl;
b = 10;
while ([](int &x){ return x--; }(b)) {
cout << b << ' ';
}
cout << endl << "b: " << b << endl;
return 0;
}
$ g++ -std=c++0x -o lambda05 lambda05.cc
$ ./lambda05
9 8 7 6 5 4 3 2 1 0
b: -1
9 8 7 6 5 4 3 2 1 0
b: -1
兩個 while 迴圈執行結果一樣,第 1 個直接以參考方式抓外面變數 b 來用
第 2 個則是以傳統方式將 b 以參考方式傳入
不過匿名函數不建議用在這種情形,一不小心搞錯變數就形成無窮迴圈了
(6)
$ cat lambda06.cc
#include <iostream>
using namespace std;
int main() {
int l = 0, m = 1, n = 2;
[=, &l](){ printf("(i)l: %d, m: %d, n: %d\n", l++, m, n); }();
printf("(o)l: %d, m: %d, n: %d\n", l, m, n);
l = 0;
m = 1;
n = 2;
[&, l](){ printf("(i)l: %d, m: %d, n: %d\n", l, m++, n++); }();
printf("(o)l: %d, m: %d, n: %d\n", l, m, n);
return 0;
}
(還是 printf 好用 XD)
$ g++ -std=c++0x -o lambda06 lambda06.cc
$ ./lambda06
(i)l: 0, m: 1, n: 2
(o)l: 1, m: 1, n: 2
(i)l: 0, m: 1, n: 2
(o)l: 0, m: 2, n: 3
第 1 個匿名函數除了 l 外,其他傳值帶入,
所以 l 可以做 ++, 其他變數如果也做 ++
編譯時會丟出 read-only object 的錯誤訊息。
第 2 個函數是反過來,除了 l 外,其他以參考方式帶入
同樣的,如果在第 2 個函數內對 l 動手動腳,編譯時會丟出錯誤訊息
(7)
$ cat lambda07.cc
#include <iostream>
using namespace std;
class MyClass {
private:
int _num1;
public:
MyClass(int num) : _num1(num) {}
void show() {
[this]() { cout << "_num1: " << _num1 << endl; } ();
}
};
int main() {
MyClass mClass(123);
mClass.show();
return 0;
}
$ ./lambda07
_num1: 123
[this] 只知道如何使用,還沒想到具體的應用場合
在 show() 裡的匿名函數裡用到了 _num1,如果沒設定 [this] 的話,會有錯誤
error: ‘this’ was not captured for this lambda function
-----
以上,是花了整個晚上爬網歸納結果所得,如有錯誤,請用力指正 ^_^
ps:
如果想知道自己系統環境裡的 c++ 編譯器有沒有支援 c++11 標準
將
[](){};
這行丟給他編譯看看就知道了 XD
記得要加 -std=c++0x 或 -std=c++11 喔