在C語言中,可調用對象僅有函數指針。但在C++中,可調用對象增加了兩類,
其一是利用C++操作符重載實現的functor,functor本身是一個struct/class的實例,其特殊的地方在於重載了小括號(調用)操作符其二則是C++2.0引入的lambda表達式,也成為匿名函數,其語法為// lambda
[ <捕獲列表> ] ( <參數列表> ) [options] { <函數體> }
// 返回值即為一個lambda表達式,每個lambda表達式在全局範圍上都屬於特有的一個類別
// 寫出類別很難,所以用auto關鍵字指定收變量的類型
// 譬如
auto add = [] (int lv, int rv) { return lv + rv; };
lambda各部分的格式及作用捕獲列表,捕獲列表是lambda表達式與普通函數的一個較為明顯的區別,主要是用於lambda表達式函數體中使用外層作用域中的變量的情況,捕獲可以以值捕獲或以引用捕獲,效果見名知意。值得注意的是,慎用引用捕獲,因為如果lambda表達式被拷貝到作用域外面可能會由於引用對象不存在而發生錯誤行為。// 例子
// 實現將外層作用域的一個int值輸出
{ // 外層作用域
int val = 10;
auto printVal = [val] { std::cout << val << "\n"; };
printVal();
val = 100;
printVal();
}輸出結果為
10
10為什麼第二行輸出不是100呢?是因為捕獲列表中寫了「val」,默認為值捕獲,如果想要引用捕獲則寫作[&val],輸出則為10 100, 值得注意的是,上述lambda表達式沒有寫小括號,這是因為在不需要參數的時候,小括號可以省略
參數列表,和普通的函數類似,特殊的是沒有參數時小括號可以省略。[options],可選部分,包括mutable關鍵字、異常、返回值。mutable關鍵字用於指定允許修改按值捕獲參數,如果省略則按值捕獲者不可修改;異常和普通函數的格式類似;返回值一般不用寫,但是也可以使用後置返回值指定,類似[] (int s) -> int { return s; }lambda表達式結合STL使用可以接受lambda表達式的STL-algorithm有很多,比如
...凡是能接受一個條件或者操作作為參數傳入的STL算法均可傳入lambda表達式,例子// a vector<int> v 逆序排序
std::sort(v.begin(), v.end(), [] ( int n1, int n2 ) { return n1 > n2; });
將lambda表達式作為函數參數將lambda表達式作為函數參數一般藉助模板,譬如STL的可接受比較標準的sort(取自gcc)template<typename _RandomAccessIterator, typename _Compare>
inline void sort(
_RandomAccessIterator __first, _RandomAccessIterator __last,
_Compare __comp);
// __comp可以接受lambda表達式,並且也可以接受functor或函數指針
將lambda表達式作為函數返回值在C++11中只能使用std::function指定返回值,將欲返回的lambda表達式轉換成function對象,譬如std::function<void(int)> func() {
return [] (int e) { std::cout << e };
}
// in main-function
func()(10); // STDOUT : 10
在C++14中auto關鍵值指定返回值無需像C++11中使用後置返回值指定,編譯器可以推斷,所以可以直接返回一個lambda表達式auto func() {
return [] (int e) { std::cout << e };
}
// in main-function
func()(10); // STDOUT : 10
// g++編譯選項添加 -std=c++14
使用lambda表達式代替bind類函數在C++11之前,標準庫提供了bind1st和bind2nd用於將一個二元函數裝飾為一元函數,C++11後標準庫提供了std::bind函數,將利用佔位符(std::placeholders::_1、_2、...)可以對一個函數靈活裝飾(改變參數列表的順序、個數),利用Lambda表達式也可以實現這種效果。利用Lambda實現bind效果的核心就是利用捕獲列表,譬如
// 假如由一個函數bool greater(int lv, int rv),返回是否lv > rv,有這麼一個場景,
// 有一個有序容器,一群數字,用這一群數字切分這個有序容器。
// 利用std::find_if
std::vector<int> nums = {-3, 1, 1, 3, 3, 4, 4, 5, 6, 8, 8, 10, 12}; // 待切分的容器
std::vector<int> sliceFlag = {1, 4, 8}; // 切分標誌
std::vector<std::vector<int>::iterator> res = { nums.begin() }; // 用來存儲切分點
auto greaterThan = [] (int lv) {
return [lv] (int rv) { return greater(lv, rv); };
}; //
for (auto & f : sliceFlag) {
res.push_back( std::find_if(nums.begin(), nums.end(), greaterThan(f)) );
}
res.push_back(nums.end());
// Test
for (int i = 0; i < res.size() - 1; ++i) {
std::for_each(res[i], res[i + 1], [] (int e) { std::cout << e << ", "; });
std::cout << "\n";
}
/****************************STDOUT********************************/
PS ~~~> .\Test
-3, 1, 1,
3, 3, 4, 4,
5, 6, 8, 8,
10, 12,
利用C++ Lambda優化多分支賦值我們經常需要根據不同的條件分枝來獲取一個變量以供後續使用,
//...
Type res;
switch (val) {
case val0: res = Type(...); break;
case val1: res = Type(...); break;
case val2: res = Type(...); break;
case val3: res = Type(...); break;
..
}
...
...
//...上述代碼需要執行一次默認構造,一次賦值,效率不高,就算有move-assignment的加持,其對默認構造的要求也是一個包袱,對於不支持默認構造的類型很難以此形式解決需求。
那麼,如果把上述代碼寫成一個函數,直接在對應的分支return對應的對象,在結合C++2.0的編譯器對return-by-value的優化,再結合使用inline關鍵字,也可以實現較好的效果,當然要多寫一個函數,如果僅是用一次,或是這樣的需求有很多(要寫很多函數),很麻煩,也有點不利於代碼組織。結合這個利用函數返回值的思路,藉助Lambda表達式便可以靈活的解決這個問題。//...
Type res = [&val] {
switch (val) {
case val0: return Type(...); break;
case val1: return Type(...); break;
case val2: return Type(...); break;
case val3: return Type(...); break;
..
}
}();
...
...
//...
將函數籤名相同的lambda表達式存入容器C風格的可調用對象即函數指針作為元素存入容器時比較簡單的,但是對於Lambda表達式,每個表達式都屬於一個特有的類型,為了將很多lambda存入容器需要藉助std::function,函數籤名同為Type(args..)的Lambda可以轉化為std::function<Type(arg...)>,然後再將其都存入元素類型為std::function的容器即可,例子
int main () {
// input 一個string,
// 格式為num1[+-*/]num2, ( 「1+2」, 「32/20" . . . )
std::map<std::string, std::function<int(int, int)>> opts { // 將Lambda存入容器形成」命令集「
{"+", [] (int lv, int rv) { return lv + rv; }},
{"-", [] (int lv, int rv) { return lv - rv; }},
{"*", [] (int lv, int rv) { return lv * rv; }},
{"/", [] (int lv, int rv) { return lv / rv; }}
};
std::string exp;
while (true) {
std::getline(std::cin, exp);
if (exp == "exit") break;
int lv = atoi(exp.c_str()); // 運算符左值
auto iter = std::find_if(exp.begin(), exp.end(), [] (char ch) -> bool { return !isdigit(ch); } );
int rv = atoi(exp.c_str() + (iter - exp.begin()) + 1); // 運算符右值
std::string op; op.push_back(*iter);
std::printf("--> %d\n", opts.at(op)(lv, rv));
}
return 0;
}
// Test
PS \\\\> ./Test
1+1
--> 2
2/3
--> 0
456+9544
--> 10000
12*12
--> 144
1-90
--> -89
Qt中槽函數直接使用Lambda有時候,connect到的槽函數僅作為槽函數使用,沒有必要封成一個member,使用Lambda是一個較好的選擇,這種情況下,原來形式的connect的第3個參數(接受者)就沒有用了。
// in constructor of class MainWidget(繼承自QWidget)
// 有一個QPushButton的指針, 名字為 pExitButton
connect(pExitButton, &QPushButton::clicked, [this] { close(); }); // 需要注意的是,要捕獲this註: 有時候可能要在QT的.pro文件中的CONFIG += C++11