快用一用 lambda 表达式吧,让你的代码更简洁、更漂亮!
目錄
lambda 表達(dá)式
定義 lambda 表達(dá)式
捕獲子句
按值捕獲
按引用捕獲
捕獲特定的變量
捕獲this指針
結(jié)合 lambda 使用 STL 算法
lambda 表達(dá)式
lambda 表達(dá)式提供了一種便捷、簡(jiǎn)潔的語(yǔ)法來(lái)快速定義回調(diào)函數(shù)或函數(shù)對(duì)象。而且,不只語(yǔ)法簡(jiǎn)潔,lambda 表達(dá)式還允許在使用回調(diào)的地方定義回調(diào)的邏輯。這通常比在某個(gè)類(lèi)定義的函數(shù)調(diào)用運(yùn)算符的某個(gè)地方定義這種邏輯要好得多。因此,使用 lambda 表達(dá)式一般能夠得到極具表達(dá)力且可讀性仍然很好的代碼。看下面這個(gè)例子:
bool compare(int a,int b){return a>b; } 使用函數(shù) sort(asd.begin(), asd.end(), compare);使用lambda表達(dá)式 sort(asd.begin(), asd.end(), [](int &a, int &b)->bool {return a > b;});lambda 表達(dá)式與函數(shù)定義有許多相似的地方。最基本的 lambda 表達(dá)式提供了一種定義沒(méi)有名稱(chēng)的函數(shù)(即匿名函數(shù))的方法。在應(yīng)用中會(huì)發(fā)現(xiàn) lambda 表達(dá)式與普通函數(shù)不同,因?yàn)?lambda 表達(dá)式可以訪問(wèn)自己定義的作用域內(nèi)存儲(chǔ)的變量。lambda 表達(dá)式的計(jì)算結(jié)果是一個(gè)函數(shù)對(duì)象。該函數(shù)對(duì)象的正式名稱(chēng)是 lambda 閉包,但是有很多人也稱(chēng)之為 lambda 函數(shù)。(?閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù) )
?
?
定義 lambda 表達(dá)式
一個(gè)基本的 lambda 表達(dá)式:
[] (int x, int y) { return x < y; }可以看出,lambda 表達(dá)式的定義看上去很像函數(shù)的定義。主要區(qū)別在于,lambda 表達(dá)式不指定返回類(lèi)型和函數(shù)名稱(chēng),并且始終以方括號(hào)開(kāi)頭。表達(dá)式開(kāi)始的?" [ ] " 稱(chēng)為 lambda 引導(dǎo),它們標(biāo)記了 lambda 表達(dá)式的開(kāi)頭。lambda 引導(dǎo)的內(nèi)容并不總是為空的。lambda 引導(dǎo)后跟的 " ( ) " 是 lambda 參數(shù)列表。對(duì)于沒(méi)有參數(shù)的?lambda 表達(dá)式,可以省略空的參數(shù)列表 ()。即可以將形式為 [](){......},的 lambda 表達(dá)式進(jìn)一步縮減為 []{......}。空的 lambda 引導(dǎo)不能省略,因?yàn)樗鼧?biāo)記 lambda 表達(dá)式的開(kāi)始。
有的人可能會(huì)覺(jué)得 lambda 表達(dá)式?jīng)]有函數(shù)名稱(chēng)還可以理解,但是沒(méi)有返回類(lèi)型是不是就有點(diǎn)過(guò)分了(玩呢?)。lambda 表達(dá)式可以包含任意數(shù)量的語(yǔ)句,返回類(lèi)型默認(rèn)為返回值的類(lèi)型。如果沒(méi)有返回值,返回類(lèi)型為 void。不過(guò)返回值類(lèi)型也是可以根據(jù)需求設(shè)定的,例如下面這種情況:
[](double x) -> int { int y = x; return x-y;}?
?
捕獲子句
lambda 引導(dǎo) [] 不一定是空的,它可以包含捕獲子句,以指定封閉作用域中的變量如何在 lambda 表達(dá)式中訪問(wèn)。如果方括號(hào)為空,則 lambda 表達(dá)式體只能使用 lambda 表達(dá)式中局部定義的實(shí)參和變量。沒(méi)有捕獲子句的 lambda 表達(dá)式稱(chēng)為無(wú)狀態(tài)的 lambda 表達(dá)式,因?yàn)樗荒茉L問(wèn)其封閉作用域中的任何內(nèi)容。默認(rèn)捕獲子句有兩種:= 和 &。捕獲子句只能包含一種默認(rèn)捕獲子句,不能同時(shí)包含兩者。
?
按值捕獲
如果在方括號(hào)中包含 =,lambda 表達(dá)式體就可以按值訪問(wèn)封閉作用域中的所有自動(dòng)變量,即這些變量的值可以在 lambda 表達(dá)式中使用,但不能修改存儲(chǔ)在原始變量中的值。當(dāng)你按值捕獲變量時(shí),又試圖改變它,編譯器就會(huì)報(bào)錯(cuò):
int a = 1,b = 2,c = 3; [ =,&c ](){ a++; b++; c++; cout << a << " " << b << " " << c <<endl; } (); cout << a << " " << b << " " << c <<endl;= 捕獲子句允許在 lambda 表達(dá)式體中按值訪問(wèn) lambda 表達(dá)式定義所在作用域的所有變量。在上面的例子中,在原則上,lambda 表達(dá)式體能夠訪問(wèn) main() 的 3 個(gè)局部變量:a、b 和 c。按值捕獲局部變量的效果與按值傳遞實(shí)參大不相同。
不同點(diǎn)在于:對(duì)于 lambda 表達(dá)式體內(nèi)用到的封閉作用域內(nèi)的每個(gè)局部變量,閉包對(duì)象都有一個(gè)成員。稱(chēng)為 lambda 捕獲了這些變量。至少在概念上,所生成的成員變量的名稱(chēng)與捕獲到的變量的名稱(chēng)相同。這樣一來(lái),lambda 表達(dá)式體看起來(lái)訪問(wèn)的是封閉作用域中的變量,但實(shí)際上訪問(wèn)的是 lambda 閉包內(nèi)存儲(chǔ)的對(duì)應(yīng)的成員變量。
?
按引用捕獲
如果在方括號(hào)中放置 &,封閉作用域中的所有變量就可以按引用訪問(wèn),所以它們的值可以由 lambda 表達(dá)式體中的代碼修改。例如:
int a = 1,b = 2,c = 3; //按引用捕獲 [ & ](){ a++; b++; c++; } (); cout << a << " " << b << " " << c <<endl;外層作用域內(nèi)的所有變量都可按引用訪問(wèn),所以 lambda 可以使用和修改它們的值。雖然 & 捕獲子句是合法的,但是在外層作用域按引用捕獲所有變量并不是一種好方法,因?yàn)檫@將使得變量有可能被無(wú)意修改。類(lèi)似地,使用 = 默認(rèn)捕獲子句則可能引入高開(kāi)銷(xiāo)的復(fù)制操作。因此,更安全的做法是顯式指定如何捕獲自己需要的每個(gè)變量。
?
捕獲特定的變量
通過(guò)在捕獲子句中逐個(gè)列舉,可以指定想要訪問(wèn)的封閉作用域中的特定變量。對(duì)于每個(gè)變量,可以選擇按值還是按引用捕獲。在變量名前面加上 &,就可以按引用捕獲變量。例如:
auto counter { [ &count] (int x, int y) {++count; return x < y;} };count 是封閉作用域中可以在 lambda 表達(dá)式體中訪問(wèn)的唯一變量,&count 規(guī)范使之可以按引用訪問(wèn)。沒(méi)有 &,外層作用域中的 count 變量就按值訪問(wèn)。當(dāng)你想要按值捕獲特定變量時(shí),不能在變量名前面加 = 作為前綴。例如,捕獲子句 [ = numbers ] 是無(wú)效的,正確的語(yǔ)法是 [ numbers ]。
在捕獲子句中放置多個(gè)變量時(shí),就用逗號(hào)分開(kāi)它們。可以自由混合按值捕獲的變量和按引用捕獲的變量。還可以在捕獲子句中,同時(shí)包含默認(rèn)捕獲子句和捕獲的特定變量名稱(chēng)。例如:捕獲子句 [ =, &counter ] 允許按引用訪問(wèn) counter,按值訪問(wèn)封閉作用域中的其他變量。類(lèi)似的,捕獲子句 [ &, numbers ] 的意思是,按值捕獲 numbers,按引用捕獲其他變量。如果指定默認(rèn)捕獲子句(?= 或 &),則它必須是捕獲列表中的第一項(xiàng)。
注意:如果使用 = 默認(rèn)捕獲子句,則不能再按值捕獲任何特定變量;類(lèi)似地,如果使用 &,則不能再按引用捕獲特定變量。例如:[ =, = a ] 或 [ &, & b ]。
?
捕獲this指針
當(dāng)我們?cè)陬?lèi)中的成員函數(shù)中使用 lambda 表達(dá)式時(shí),這個(gè)情況和之前的不一樣,問(wèn)題在于只有局部變量和函數(shù)實(shí)參才能按值或引用捕獲,而類(lèi)的成員變量不能按值或按引用捕獲。以下是在類(lèi)的成員函數(shù)中使用 lambda 的情況:
可以通過(guò)對(duì)捕獲子句添加關(guān)鍵字 this ,讓 lambda 表達(dá)式訪問(wèn)類(lèi)的成員。通過(guò)捕獲 this 指針,實(shí)際上就使得 lambda 表達(dá)式能夠訪問(wèn)包含它的成員函數(shù)所能訪問(wèn)的所有成員,也就是說(shuō),盡管 lambda 閉包不屬于類(lèi),但其函數(shù)調(diào)用運(yùn)算符仍然能夠訪問(wèn)類(lèi)的 protected 和 private 數(shù)據(jù)成員。lambda 表達(dá)式還能夠訪問(wèn)所有成員函數(shù),無(wú)論它們被聲明為 public、protected 還是 private。
class Asd {private:int a, b;public:Asd(){a = 1;b = 2; }int geta(){return a;}int getb(){return b;}void print(){auto asd = [this](){ return getb() - geta();} ;cout << "getb() - geta():" << asd() <<endl;}void print1(){auto asd = [=](){ return b - a;} ;cout << "b-a:" << asd() <<endl;} };注意:= 默認(rèn)捕獲子句已經(jīng)暗示著(按值)捕獲this指針。因此這條語(yǔ)句也是合法的:
auto asd = [=](){ return b - a;} ;但不允許將默認(rèn)捕獲子句 = 與 this 結(jié)合使用 (至少在 C++17 中不允許,C++20 中可能會(huì)有變化)。因此,編譯器將把 [=, this] 這種形式的捕獲子句標(biāo)記為錯(cuò)誤。不過(guò),允許使用[&, this],因?yàn)?& 并不暗示著捕獲 this。
?
捕獲子句小總結(jié):
[] 不捕獲任何變量 [=] 用值的方式捕獲所有變量 [&] 以引用方式捕獲所有變量 [asd] 以值方式捕獲asd; 不捕獲其它變量 [=,&asd] 以引用捕獲asd, 但其余變量都靠值捕獲 [&, asd] 以值捕獲asd, 但其余變量都靠引用捕獲 [this] 捕獲所在類(lèi)的this指針?
完整代碼:
#include <iostream> using namespace std;class Asd {private:int a, b;public:Asd(){a = 1;b = 2; }int geta(){return a;}int getb(){return b;}void print(){auto asd = [this](){ return getb() - geta();} ;cout << "getb() - geta():" << asd() <<endl;}void print1(){auto asd = [=](){ return b - a;} ;cout << "b-a:" << asd() <<endl;} };int main() {//不捕獲任何變量[] { cout << "Study lambda!" <<endl; } ();auto print = [] { cout << "Study lambda!" <<endl; }; print();//根據(jù)需求確定返回值auto asd = [](double x) -> int { int y = x; return x-y;};cout << asd(3.6) <<endl;int a = 1,b = 2,c = 3;//按值捕獲[ = ](){ cout << a << " " << b << " " << c <<endl; } ();//按引用捕獲[ & ](){ a++; b++; c++; } ();cout << a << " " << b << " " << c <<endl;//捕獲特定的變量[ =,&c ](){ c++; cout << a << " " << b << " " << c <<endl; } ();cout << a << " " << b << " " << c <<endl;[ &,a ](){ b++; c++; cout << a << " " << b << " " << c <<endl; } ();cout << a << " " << b << " " << c <<endl;//捕獲this指針Asd asd1, asd2;asd1.print();asd2.print1();return 0; }?
?
結(jié)合 lambda 使用 STL 算法
接下來(lái)將從?距離?和?簡(jiǎn)潔?兩個(gè)方面探討使用 lambda 的優(yōu)勢(shì)。
很多人認(rèn)為,讓定義位于使用的地方附近很有用。這樣,在閱讀源代碼時(shí),就無(wú)需去找該函數(shù)功能的具體代碼。例如,調(diào)用 count_if() 的第三個(gè)參數(shù)時(shí),不需要向前尋找具體實(shí)現(xiàn)。另外,如果需要修改代碼,涉及的內(nèi)容都在附近。從這一角度出發(fā),lambda 是理想的選擇,因?yàn)槠涠x和使用是在同一個(gè)地方進(jìn)行的。而函數(shù)可能存在一種比較糟糕的情況,即其函數(shù)內(nèi)部使用了其他函數(shù),而這些函數(shù)可能在不同的地方,這在閱讀源碼這一點(diǎn)上是非常費(fèi)時(shí)、費(fèi)力的。
從簡(jiǎn)潔的角度看,函數(shù)和 lambda 的簡(jiǎn)潔程度相當(dāng),一個(gè)例外是,需要使用同一個(gè) lambda 兩次:
count1 = count_if ( a.begin(), a.end(), [] (int x) { return x % 3 == 0; }); count2 = count_if ( b.begin(), b.end(), [] (int x) { return x % 3 == 0; });但并不一定要編寫(xiě) lambda 兩次,而是給 lambda 指定一個(gè)名稱(chēng),并使用該名稱(chēng)兩次:
auto mod3 = [] (int x) { return x % 3 == 0; } count1 = count_if ( a.begin(), a.end(), mod3 ); count2 = count_if ( b.begin(), b.end(), mod3 );甚至可以像使用常規(guī)函數(shù)那樣使用有名稱(chēng)的 lambda:
bool result = mod3(z);然而,不同于常規(guī)函數(shù),可在函數(shù)內(nèi)部定義有名稱(chēng)的 lambda 。mod3 的實(shí)際類(lèi)型隨實(shí)現(xiàn)而異,它取決于編譯器使用什么類(lèi)型來(lái)跟蹤 lambda 。
代碼:
#include <iostream> #include <vector> #include <algorithm> using namespace std;bool compare(int a, int b) {return a > b; }int main() { vector<int> asd;for (int i = 0; i < 10; ++i)asd.push_back(i);sort(asd.begin(), asd.end(), [](int &a, int &b)->bool {return a > b;});sort(asd.begin(), asd.end(), compare);for (int i = 0; i < asd.size(); ++i)cout << asd[i] << endl;return 0; }?
代碼:
#include <iostream> #include <numeric> #include <vector>void print_container(const std::vector<char>& c) {for (auto x : c) {std::cout << x << ' ';}std::cout << '\n'; }int main() {std::vector<char> cnt(10);std::iota(cnt.begin(), cnt.end(), '0');std::cout << "Init:\n";print_container(cnt);std::erase(cnt, '3');std::cout << "Erase \'3\':\n";print_container(cnt);auto erased = std::erase_if(cnt, [](char x) { return (x - '0') % 2 == 0; });std::cout << "Erase all even numbers:\n";print_container(cnt);std::cout << "In all " << erased << " even numbers were erased.\n"; }?
總結(jié)
以上是生活随笔為你收集整理的快用一用 lambda 表达式吧,让你的代码更简洁、更漂亮!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C++易被忽略的知识点:移动语义 左值
- 下一篇: C++ string 使用详解(含C++