序言
?在上一篇文章中,我們主要介紹了 C++11 中的新增的關鍵詞,以及 范圍for循環
這類語法糖的使用和背后的邏輯。在這篇文章中我們會繼續介紹一個特別重要的新特性分別是 lambda表達式
。
1. lambda表達式
1.1 lambda的定義
?C++11 中的 lambda表達式
是一種定義 匿名函數對象
的方式。它們可以捕獲它們所在作用域中的變量,并且可以在需要函數對象的地方使用,如作為算法的參數。lambda表達式
自 C++11 標準引入后,大大簡化了編碼并增強了代碼的靈活性和可讀性。我們在這里提到 匿名函數對象
,說直白點就是該函數沒有具體名字嘛,那是真的沒有嗎?請大家記住這個疑問。
1.2 lambda表達式的基本語法
lambda表達式
的基本語法如下:
[capture](parameters) mutable -> return_type { body }
- capture:捕獲列表,指定
lambda表達式
體內部可以訪問的外部變量。 - parameters:參數列表,與普通函數的參數列表類似,但
lambda表達式
也可以沒有參數。 - mutable:可選的,表示代碼可以修改以值捕獲的外部變量。默認情況下,這些變量是只讀的。
- return_type:返回類型,可選。編譯器可以根據內容 自動推導返回類型。
- body:表達式的函數體,可以包含任意有效的C++語句。
我們簡單的舉一個示例:
void test_1() {auto func1 = [](int A, int B) ->int { return A + B; };cout << func1(1, 2) << endl;
}
我們返回值在沒有什么特殊需求下完全是可以省略的,編譯器會自動推導,所以我們還可以表示為:
void test_1() {auto func1 = [](int A, int B) { return A + B; };cout << func1(1, 2) << endl;
}
在這里我們的代碼邏輯還是比較簡單的,當我們的代碼邏輯比較復雜時,我們還可以表示為:
void test_1() {auto func1 = [](int A, int B) { ...... //body};
}
1.3 參數詳解
1. capture 捕獲列表
?捕捉列表描述了上下文中那些數據可以被 lambda
使用,以及使用的方式傳值還是傳引用。
舉個栗子:
int A = 1, B = 2, C = 1;
auto func2 = [A]() { cout << A << endl; };
func2();
這里就是告訴編譯器你可以使用 A變量
,當然了,你想讓他用誰就放誰到捕獲列表中:
int A = 1, B = 2;
auto func2 = [A,B]() { cout << A << endl; cout << B << endl;};
func2();
在這里 []
的用法可以簡單總結為 3 種:
- [var]:表示值傳遞方式捕捉變量 var
- [=]:表示值傳遞方式捕獲所有父作用域中的變量(包括this)
- [&]:表示引用傳遞捕捉所有父作用域中的變量(包括this)
注意:父作用域指包含 lambda函數 的語句塊
上述的方法甚至還可以混合使用:
int A = 1, B = 2, C = 3;
// 除了 C 都是值傳遞,而 C 是引用傳遞
auto func2 = [=, &C](){// };// 除了 C 都是引用傳遞,而 C 是值傳遞
auto func3 = [&, C](){// };
2. parameters 參數列表
?參數列表的用法和函數的參數列表都是一致的,沒有什么區別。但是如果你沒有需要傳遞的參數時,甚至可以省去:
auto func3 = [A] { cout << A << endl; };
3. mutable 可變的
?當我們的表達式嘗試改變 以值捕獲 的外部變量時,編譯器會報錯,比如:
auto func3 = [A] () { ++A; };
這是因為編譯器規定這些變量只是可讀的,如果你想要進行相應的修改,需要加上 mutable
:
auto func3 = [A] () mutable { ++A; };
但是這個修改并不會影響 A
本身的的大小,因為是 深拷貝,所以你需要修改 A
本身的大小的話使用引用會更好。
4. return_type 返回類型
?如果你沒什么需要特殊需要的話,完全可以省去,因為編譯器會自動推導,那什么時候是特殊的需求呢,就比如:
double A = 1.5, B = 2.5, C = 3;
auto func2 = [A, B] () -> int{return A * B;};
cout << func2() << endl;
在這里兩個 double
變量相乘推導出肯定是返回 double
,但是我想要返回 int
這就可以指定返回類型。
5. body 函數體
?在函數體中,你可以使用參數列表以及捕獲列表中的變量。
1.4 lambda 背后的邏輯
?我們是怎么使用仿函數的呢?就比如一下比較大小的仿函數:
template<class T>
struct Less{bool operator()(const T& left, const T& right){left < right;}
};Less<int> less;
int A = 1, B = 0;
less(A, B);
我們在使用該仿函數之前,先使用創建了一個相應的對象,然后再使用。
再看看我們的 lambda 表達式
,我們也是先創建一個變量再通過該變量來調用:auto func = [](){};
。 但是我們在前面說過, lambda表達式
是一種定義 匿名函數對象
的方式。所有他是真的沒有名字嗎,不是的,他在背后其實編譯器給他取了個名字的,只是我們不需要知道,我們只需要使用 auto
接受接好了,不需要管他叫什么。
那怎么證明呢?我們看看匯編層的邏輯:
很明顯是由他是有名字的,只是只有編譯器知道而已。
所以,定義了一個 lambda表達式
,編譯器會自動生成一個類,在該類中重載了 operator()
。
2. lambda 的使用場景
2.1 sort 函數
?如果我們需要對自定義類型進行排序,舉個例子:
class Man {
public:Man(string name, int age) {_name = name;_age = age;}string _name;int _age;
};void test_2() {vector<Man> v;v.emplace_back("L", 10);v.emplace_back("M", 11);v.emplace_back("N", 12);sort(v.begin(), v.end());
}
這樣會報錯,因為自定義類型不可以直接比較,我們在以前的解決方案可以是寫一個仿函數,告訴編譯器怎么比較呀:
struct LessForMansAge {bool operator()(const Man& left, const Man& right) {return left._age < right._age;}
};
現在的話,我們可以直接寫一個仿函數:
auto Less = [](const Man& left, const Man& right) { return left._age < right._age; };
sort(v.begin(), v.end(), Less);
2.2 for_each 函數
?for_each
函數是 C++ 標準庫中的一個算法,它定義在頭文件 中。這個函數用于對給定范圍內的每個元素執行一個指定的操作。for_each
算法提供了一個便利的、統一的方法來遍歷容器(或其他支持迭代器的范圍),并對每個元素執行某個操作,而不需要顯式地編寫循環代碼。
舉個例子,我想要讓數組內的元素 * 2 ,并打印結果:
void test_3() {vector<int> v = { 1, 2, 3, 4, 5, 6 };for_each(v.begin(), v.end(), [](int& x) { x *= 2; });for_each(v.begin(), v.end(), [](int& x) { cout << x << endl; });
}
3. 總結
lambda表達式
的語法更加直觀和靈活,特別是在處理簡單函數或回調函數時。它允許開發者直接在表達式中捕獲外部變量,并定義函數體,這使得代碼更加易于編寫和理解。