Lambda表達(dá)式概述
Lambda表達(dá)式是現(xiàn)代C++在C ++ 11和更高版本中的一個(gè)新的語(yǔ)法糖 ,在C++11、C++14、C++17和C++20中Lambda表達(dá)的內(nèi)容還在不斷更新。 lambda表達(dá)式(也稱(chēng)為lambda函數(shù))是在調(diào)用或作為函數(shù)參數(shù)傳遞的位置處定義匿名函數(shù)對(duì)象的便捷方法。通常,lambda用于封裝傳遞給算法或異步方法的幾行代碼 。本文主要介紹Lambda的工作原理以及使用方法。
Lambda表達(dá)式定義
Lambda表達(dá)式示例
Lambda有很多叫法,有Lambda表達(dá)式、Lambda函數(shù)、匿名函數(shù),本文中為了方便表述統(tǒng)一用Lambda表達(dá)式進(jìn)行敘述。 ISO C++標(biāo)準(zhǔn)官網(wǎng)展示了一個(gè)簡(jiǎn)單的lambda表示式實(shí)例:
#include <algorithm>
#include <cmath>
void abssort(float* x, unsigned n) {
std::sort(x, x + n,
// Lambda expression begins
[](float a, float b) {
return (std::abs(a) < std::abs(b));
} // end of lambda expression
);
}
在上面的實(shí)例中std::sort 函數(shù)第三個(gè)參數(shù)應(yīng)該是傳遞一個(gè)排序規(guī)則的函數(shù),但是這個(gè)實(shí)例中直接將排序函數(shù)的實(shí)現(xiàn)寫(xiě)在應(yīng)該傳遞函數(shù)的位置,省去了定義排序函數(shù)的過(guò)程,對(duì)于這種不需要復(fù)用,且短小的函數(shù),直接傳遞函數(shù)體可以增加代碼的可讀性。
Lambda表達(dá)式語(yǔ)法定義
- 捕獲列表。在C++規(guī)范中也稱(chēng)為L(zhǎng)ambda導(dǎo)入器, 捕獲列表總是出現(xiàn)在Lambda函數(shù)的開(kāi)始處。實(shí)際上,
[] 是Lambda引出符。編譯器根據(jù)該引出符判斷接下來(lái)的代碼是否是Lambda函數(shù),捕獲列表能夠捕捉上下文中的變量以供Lambda函數(shù)使用。 - 參數(shù)列表。與普通函數(shù)的參數(shù)列表一致。如果不需要參數(shù)傳遞,則可以連同括號(hào)“()”一起省略。
- 可變規(guī)格。
mutable 修飾符, 默認(rèn)情況下Lambda函數(shù)總是一個(gè)const 函數(shù),mutable 可以取消其常量性。在使用該修飾符時(shí),參數(shù)列表不可省略(即使參數(shù)為空)。 - 異常說(shuō)明。用于Lamdba表達(dá)式內(nèi)部函數(shù)拋出異常。
- 返回類(lèi)型。 追蹤返回類(lèi)型形式聲明函數(shù)的返回類(lèi)型。我們可以在不需要返回值的時(shí)候也可以連同符號(hào)”->”一起省略。此外,在返回類(lèi)型明確的情況下,也可以省略該部分,讓編譯器對(duì)返回類(lèi)型進(jìn)行推導(dǎo)。
- lambda函數(shù)體。內(nèi)容與普通函數(shù)一樣,不過(guò)除了可以使用參數(shù)之外,還可以使用所有捕獲的變量。
Lambda表達(dá)式參數(shù)詳解
Lambda捕獲列表
Lambda表達(dá)式與普通函數(shù)最大的區(qū)別是,除了可以使用參數(shù)以外,Lambda函數(shù)還可以通過(guò)捕獲列表訪(fǎng)問(wèn)一些上下文中的數(shù)據(jù)。具體地,捕捉列表描述了上下文中哪些數(shù)據(jù)可以被Lambda使用,以及使用方式(以值傳遞的方式或引用傳遞的方式)。語(yǔ)法上,在“[] ”包括起來(lái)的是捕獲列表,捕獲列表由多個(gè)捕獲項(xiàng)組成,并以逗號(hào)分隔。捕獲列表有以下幾種形式:
auto function = ([]{
std::cout << "Hello World!" << std::endl;
}
);
function();
int num = 100;
auto function = ([num]{
std::cout << num << std::endl;
}
);
function();
[=] 表示值傳遞方式捕獲所有父作用域的變量(包括this )
int index = 1;
int num = 100;
auto function = ([=]{
std::cout << "index: "<< index << ", "
<< "num: "<< num << std::endl;
}
);
function();
int num = 100;
auto function = ([&num]{
num = 1000;
std::cout << "num: " << num << std::endl;
}
);
function();
[&] 表示引用傳遞方式捕捉所有父作用域的變量(包括this )
int index = 1;
int num = 100;
auto function = ([&]{
num = 1000;
index = 2;
std::cout << "index: "<< index << ", "
<< "num: "<< num << std::endl;
}
);
function();
[this] 表示值傳遞方式捕捉當(dāng)前的this 指針
#include <iostream>
using namespace std;
class Lambda
{
public:
void sayHello() {
std::cout << "Hello" << std::endl;
};
void lambda() {
auto function = [this]{
this->sayHello();
};
function();
}
};
int main()
{
Lambda demo;
demo.lambda();
}
[=, &] 拷貝與引用混合
[=, &a, &b] 表示以引用傳遞的方式捕捉變量a 和b ,以值傳遞方式捕捉其它所有變量。
int index = 1;
int num = 100;
auto function = ([=, &index, &num]{
num = 1000;
index = 2;
std::cout << "index: "<< index << ", "
<< "num: "<< num << std::endl;
}
);
function();
[&, a, this] 表示以值傳遞的方式捕捉變量a 和this ,引用傳遞方式捕捉其它所有變量。
不過(guò)值得注意的是,捕捉列表不允許變量重復(fù)傳遞。下面一些例子就是典型的重復(fù),會(huì)導(dǎo)致編譯時(shí)期的錯(cuò)誤。例如:
[=,a] 這里已經(jīng)以值傳遞方式捕捉了所有變量,但是重復(fù)捕捉a 了,會(huì)報(bào)錯(cuò)的;[&,&this] 這里& 已經(jīng)以引用傳遞方式捕捉了所有變量,再捕捉this 也是一種重復(fù)。
如果Lambda主體total 通過(guò)引用訪(fǎng)問(wèn)外部變量,并factor 通過(guò)值訪(fǎng)問(wèn)外部變量,則以下捕獲子句是等效的:
[&total, factor]
[factor, &total]
[&, factor]
[factor, &]
[=, &total]
[&total, =]
Lambda參數(shù)列表
除了捕獲列表之外,Lambda還可以接受輸入?yún)?shù)。參數(shù)列表是可選的,并且在大多數(shù)方面類(lèi)似于函數(shù)的參數(shù)列表。
auto function = [] (int first, int second){
return first + second;
};
function(100, 200);
可變規(guī)格mutable
mutable 修飾符, 默認(rèn)情況下Lambda函數(shù)總是一個(gè)const 函數(shù),mutable 可以取消其常量性。在使用該修飾符時(shí),參數(shù)列表不可省略(即使參數(shù)為空)。
#include <iostream>
using namespace std;
int main()
{
int m = 0;
int n = 0;
[&, n] (int a) mutable { m = ++n + a; }(4);
cout << m << endl << n << endl;
}
異常說(shuō)明
你可以使用 throw() 異常規(guī)范來(lái)指示 Lambda 表達(dá)式不會(huì)引發(fā)任何異常。與普通函數(shù)一樣,如果 Lambda 表達(dá)式聲明 C4297 異常規(guī)范且 Lambda 體引發(fā)異常,Visual C++ 編譯器將生成警告 throw() 。
int main() // C4297 expected
{
[]() throw() { throw 5; }();
}
在MSDN的異常規(guī)范中,明確指出異常規(guī)范是在 C++11 中棄用的 C++ 語(yǔ)言功能。因此這里不建議不建議大家使用。
返回類(lèi)型
Lambda表達(dá)式的返回類(lèi)型會(huì)自動(dòng)推導(dǎo)。除非你指定了返回類(lèi)型,否則不必使用關(guān)鍵字。返回型類(lèi)似于通常的方法或函數(shù)的返回型部分。但是,返回類(lèi)型必須在參數(shù)列表之后,并且必須在返回類(lèi)型->之前包含類(lèi)型關(guān)鍵字。如果Lambda主體僅包含一個(gè)return 語(yǔ)句或該表達(dá)式未返回值,則可以省略L(fǎng)ambda表達(dá)式的return-type 部分。如果Lambda主體包含一個(gè)return 語(yǔ)句,則編譯器將從return 表達(dá)式的類(lèi)型中推斷出return 類(lèi)型。否則,編譯器將返回類(lèi)型推導(dǎo)為void 。
auto x1 = [](int i){ return i; };
Lambda函數(shù)體
Lambda表達(dá)式的Lambda主體(標(biāo)準(zhǔn)語(yǔ)法中的復(fù)合語(yǔ)句)可以包含普通方法或函數(shù)的主體可以包含的任何內(nèi)容。普通函數(shù)和Lambda表達(dá)式的主體都可以訪(fǎng)問(wèn)以下類(lèi)型的變量:
- 捕獲變量
- 形參變量
- 局部聲明的變量
- 類(lèi)數(shù)據(jù)成員,當(dāng)在類(lèi)內(nèi)聲明
this 并被捕獲時(shí) - 具有靜態(tài)存儲(chǔ)持續(xù)時(shí)間的任何變量,例如全局變量
#include <iostream>
using namespace std;
int main()
{
int m = 0;
int n = 0;
[&, n] (int a) mutable { m = ++n + a; }(4);
cout << m << endl << n << endl;
}
Lambda表達(dá)式的優(yōu)缺點(diǎn)
Lambda表達(dá)式的優(yōu)點(diǎn)
- 可以直接在需要調(diào)用函數(shù)的位置定義短小精悍的函數(shù),而不需要預(yù)先定義好函數(shù)
std::find_if(v.begin(), v.end(), [](int& item){return item > 2});
- 使用Lamdba表達(dá)式變得更加緊湊,結(jié)構(gòu)層次更加明顯、代碼可讀性更好
Lambda表達(dá)式的缺點(diǎn)
- Lamdba表達(dá)式語(yǔ)法比較靈活,增加了閱讀代碼的難度
- 對(duì)于函數(shù)復(fù)用無(wú)能為力
Lambda表達(dá)式工作原理
Lambda表達(dá)式工作原理
編譯器會(huì)把一個(gè)Lambda表達(dá)式生成一個(gè)匿名類(lèi)的匿名對(duì)象,并在類(lèi)中重載函數(shù)調(diào)用運(yùn)算符,實(shí)現(xiàn)了一個(gè)operator() 方法。
auto print = []{cout << "Hello World!" << endl; };
編譯器會(huì)把上面這一句翻譯為下面的代碼:
class print_class
{
public:
void operator()(void) const
{
cout << "Hello World!" << endl;
}
};
// 用構(gòu)造的類(lèi)創(chuàng)建對(duì)象,print此時(shí)就是一個(gè)函數(shù)對(duì)象
auto print = print_class();
C++仿函數(shù)
仿函數(shù)(functor)又稱(chēng)為函數(shù)對(duì)象(function object)是一個(gè)能行使函數(shù)功能的類(lèi)。仿函數(shù)的語(yǔ)法幾乎和我們普通的函數(shù)調(diào)用一樣,不過(guò)作為仿函數(shù)的類(lèi),都必須重載operator() 運(yùn)算符,仿函數(shù)與Lamdba表達(dá)式的作用是一致的。舉個(gè)例子:
#include <iostream>
#include <string>
using namespace std;
class Functor
{
public:
void operator() (const string& str) const
{
cout << str << endl;
}
};
int main()
{
Functor myFunctor;
myFunctor("Hello world!");
return 0;
}
Lamdba表達(dá)式適用場(chǎng)景
Lamdba表達(dá)式應(yīng)用于STL算法庫(kù)
for_each 應(yīng)用實(shí)例
int a[4] = {11, 2, 33, 4};
sort(a, a+4, [=](int x, int y) -> bool { return x%10 < y%10; } );
for_each(a, a+4, [=](int x) { cout << x << " ";} );
find_if 應(yīng)用實(shí)例
int x = 5;
int y = 10;
deque<int> coll = { 1, 3, 19, 5, 13, 7, 11, 2, 17 };
auto pos = find_if(coll.cbegin(), coll.cend(), [=](int i) {
return i > x && i < y;
});
remove_if 應(yīng)用實(shí)例
std::vector<int> vec_data = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int x = 5;
vec_data.erase(std::remove_if(vec.date.begin(), vec_data.end(), [](int i) {
return n < x;}), vec_data.end());
std::for_each(vec.date.begin(), vec_data.end(), [](int i) {
std::cout << i << std::endl;});
短小不需要復(fù)用函數(shù)場(chǎng)景
sort 函數(shù)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(void)
{
int data[6] = { 3, 4, 12, 2, 1, 6 };
vector<int> testdata;
testdata.insert(testdata.begin(), data, data + 6);
// 對(duì)于比較大小的邏輯,使用lamdba不需要在重新定義一個(gè)函數(shù)
sort(testdata.begin(), testdata.end(), [](int a, int b){
return a > b; });
return 0;
}
Lamdba表達(dá)式應(yīng)用于多線(xiàn)程場(chǎng)景
#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
int main()
{
// vector 容器存儲(chǔ)線(xiàn)程
std::vector<std::thread> workers;
for (int i = 0; i < 5; i++)
{
workers.push_back(std::thread([]()
{
std::cout << "thread function\n";
}));
}
std::cout << "main thread\n";
// 通過(guò) for_each 循環(huán)每一個(gè)線(xiàn)程
// 第三個(gè)參數(shù)賦值一個(gè)task任務(wù)
// 符號(hào)'[]'會(huì)告訴編譯器我們正在用一個(gè)匿名函數(shù)
// lambda函數(shù)將它的參數(shù)作為線(xiàn)程的引用t
// 然后一個(gè)一個(gè)的join
std::for_each(workers.begin(), workers.end(), [](std::thread &t;)
{
t.join();
});
return 0;
}
std::mutex mutex;
std::condition_variable condition;
std::queue<std::string> queue_data;
std::thread threadBody([&]{
std::unique_lock<std::mutex> lock_log(mutex);
condition.wait(lock_log, [&]{
return !queue_data.front();
});
std::cout << "queue data: " << queue_data.front();
lock_log.unlock();
});
queue_data.push("this is my data");
condition.notity_one();
if(threadBody.joinable())
{
threadBody.join();
}
Lamdba表達(dá)式應(yīng)用于函數(shù)指針與function
#include <iostream>
#include <functional>
using namespace std;
int main(void)
{
int x = 8, y = 9;
auto add = [](int a, int b) { return a + b; };
std::function<int(int, int)> Add = [=](int a, int b) { return a + b; };
cout << "add: " << add(x, y) << endl;
cout << "Add: " << Add(x, y) << endl;
return 0;
}
Lamdba表達(dá)式作為函數(shù)的入?yún)?/h2>
using FuncCallback = std::function<void(void)>;
void DataCallback(FuncCallback callback)
{
std::cout << "Start FuncCallback!" << std::endl;
callback();
std::cout << "End FuncCallback!" << std::endl;
}
auto callback_handler = [&](){
std::cout << "This is callback_handler";
};
DataCallback(callback_handler);
Lamdba表達(dá)式在QT中的應(yīng)用
QTimer *timer=new QTimer;
timer->start(1000);
QObject::connect(timer, &QTimer::timeout, [&](){
qDebug() << "Lambda表達(dá)式";
});
int a = 10;
QString str1 = "漢字博大精深";
connect(pBtn4, &QPushButton::clicked, [=](bool checked){
qDebug() << a <<str1;
qDebug() << checked;
qDebug() << "Hua Windows Lambda Button";
});
總結(jié)
對(duì)于Lambda這種新東西,有的人用的非常爽,而有的人看著都不爽。仁者見(jiàn)仁,智者見(jiàn)智。不管怎么樣,學(xué)了總不會(huì)錯(cuò)!
|