C++ lambda表達式簡介及作用

2021-02-20 魔蛇學吧
C++ lambda表達式簡介及作用C/C++的可調用對象

在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之前,標準庫提供了bind1stbind2nd用於將一個二元函數裝飾為一元函數,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

相關焦點

  • 【C++基礎】C++11 lambda 表達式解析
    C++11 新增了很多特性,lambda 表達式是其中之一,如果你想了解的 C++11 完整特性,建議去看看C++標準。本文作為 5 月的最後一篇博客,將介紹 C++11 的 lambda 表達式。很多語言都提供了 lambda 表達式,如 Python,Java 8。
  • java8 之 lambda 表達式簡介
    Lambda 表達式的本質只是基於接口的語法糖(語法糖:指在計算機語言中添加的某種語法,這種語法對語言的功能沒有影響,但是更方便程式設計師使用),而 Stream 的引入,使其對集合的操作變的更加方便和強大。lambda表達式格式: (param1, param2, ...) -> expression。
  • Java lambda表達式
    如果lambda表達式匹配參數類型(在這種情況下,是StateChangeListenerinterface),lambda表達式將轉換成實現該參數接口的函數。Java lambda表達式只能在匹配的類型只是單方法接口的時候使用。在上面的示例中,使用lambda表達式作為參數,參數類型為StateChangeListener接口。這個接口只有一個方法。
  • Python匿名函數:Lambda表達式
    【1】何為Lambda表達式?我們以一張圖形進入主題:從圖中我們可以看出lambda表達式幾點特徵:簡潔性,符合了Python的一貫宗旨;起到了函數的作用,但未顯示函數名稱,這就是匿名函數;有形參;有返回值的。
  • Java8 lambda表達式
    如何在正確的場合使用lambda表達式?下面有幾個使用lambda表達式的例子①展示了在沒有參數的情況下如何使用lambda,可以使用一對空的小括號來表示沒有參數,這是一個實現了Runnable的lambda的表達式,該接口只有一個方法run(),該方法不接受任何參數,且返回void.
  • Lambda 表達式的 10 個示例
    作為開發人員,我發現學習和掌握lambda表達式的最佳方法就是勇於嘗試,儘可能多練習流API和lambda表達式,用於對列表(Lists)和集合(Collections)數據進行提取、過濾和排序。本文分享在代碼中最有用的10個lambda表達式的使用方法,這些例子都短小精悍,將幫助你快速學會lambda表達式。
  • Python中的Lambda表達式
    Lambda表達式當我們需要做一些簡單的事情並且更希望快速完成工作而不是正式命名函數時,Lambda表達式是理想的選擇。Lambda表達式也稱為匿名函數。Python中的Lambda表達式是聲明小型匿名函數的一種簡短方式(沒有必要為Lambda函數提供名稱)。
  • 不要在Python中編寫 lambda 表達式了
    我對 lambda 有很多的疑問, 我很猶豫是否要推薦學生接受 Python lambda 表達式. 多年來我一直都很厭惡 lambda 表達式, 自從幾年前我開始頻繁教授 Python 後, 我對它的厭惡與日俱增.我將會說明我對 lambda 表達式的看法, 以及為何我傾向於建議學生們避免使用它.Python 中的 lambda 表達式: 它們是什麼?
  • C++中的lambda函數
    作者:  2019信息與計算科學專業   楊澤天lambda函數是C++11中的匿名函數,又叫lambda表達式,叫lambda表達式更好理解,因為函數是不可以在函數中定義的,表達式可以。lambda表達式,可以簡化編程工作。
  • Python中lambda表達式的常見用法
    lambda表達式只可以包含一個表達式,不允許包含其他複雜的語句,但在表達式中可以調用其他函數,並支持默認值參數和關鍵參數,該表達式的計算結果相當於函數的返回值。下面的代碼演示了不同情況下lambda表達式的應用。
  • Lambda表達式在Python事件中的運用
    本篇筆記內容:Lambda表達式詳解Lambda表達式在事件中的應用【1】Lambda表達式詳解lambda 表達式定義的是一個匿名函數,只適合簡單輸入參數,簡單計算返回結果,不適合功能複雜情況。lambda 定義的匿名函數也有輸入、也有輸出,只是沒有名字。語法格式如下:lambda 參數值列表:表達式其中,參數值列表即為輸入,表達式計算的結構即為輸出。看一個案例:求三個數的和。
  • 「數據清洗」lambda表達式配合使用的四種函數
    標籤:數據清洗、pythonlambda表達式配合使用的四種函數一、什麼是lambda表達式基本特性使用方法filter函數map函數sorted函數reduce函數總結什麼是lambda表達式>lambda 表達式是一個匿名函數,lambda表達式基於數學中的λ演算得名,直接對應於其中的lambda抽象,是一個匿名函數,即沒有函數名的函數。
  • Python每天一分鐘:lambda表達式 (匿名函數)及用法詳解
    lambda表達式介紹python中有一種靈活,便捷的且具有函數功能的表達式:lambda表達式!lambda 表達式可以用來替換局部函數(感興趣的讀者可以自行查閱「局部函數」),下面為大家演示lambda表達式的具體用法。lambda表達式定義首先以一個代碼例子讓大家對lambda表達式有一個直觀的認識:
  • 一文帶你認識 C++ 中的 Lambda 函數
    因為 lambda並不總是函數指針,它一個表達式。但是為了簡單起見,我一直都稱它為函數。那麼在本文中,我將會交替使用lambda函數和表達式來稱呼。Lambda函數是一段簡短的代碼片段,它具有以下特點:(1)不值得命名(無名,匿名,可處理等,無論您如何稱呼)(2)不會重複使用換句話說,它只是語法糖。
  • Java中Lambda表達式的5種不同語法
    表達式的標準語法包括以下內容:1.主體,由單個表達式或語句塊組成。這使代碼更整潔,更像真正的lambda表達式。3. Lambda表達式中的多行代碼如果代碼不能一行編寫,則可以將其括在{}中。現在,該代碼應明確包含return語句。
  • 大家都在說的「Lambda 表達式」到底是什麼?
    表達式就是為了更好的支持函數式編程。2、簡單了解一下lambda的語法格式(參數) ->{語句; }上面就是lambda的語法格式,「->」是它的一個標誌,當你看到「->」你就知道這裡使用了Lambda表達式。
  • Java 8新特性:學習如何使用Lambda表達式(一)
    於是,有了我們第一個lambda表達式!此表達式指定代碼塊和必須傳遞給代碼塊的變量。他使用希臘符號lambda(λ)來標記參數。從那以後,帶有參數變量的表達式被稱為「lambda表達式」。Java lambda略有幾種不同的形式。讓我們更仔細地考慮一下。您剛剛看到其中一個:參數, - >箭頭和表達式。如果代碼包含的計算不適合單個表達式,那麼就像編寫方法一樣編寫它:將代碼放入{}並添加顯式 return語句。
  • 自從學會Java中的lambda表達式和函數式編程技巧,再也不用加班了!
    Lambda和Streams API除了簡化原始碼之外,lambda對於Java中功能導向型接口 Streams API也起了至關重要的作用。它們描述了傳遞給各種API方法的功能單元。深入了解lambda要有效地使用lambda,你必須理解lambda表達式的語法以及目標類型的概念。
  • python入門基礎之lambda匿名函數詳解
    python入門基礎之lambda匿名函數詳解剛開始學習python的時候很多人可能對於lambda函數不了解,感覺和def很混亂,下面我來介紹一下lambda函數我從一下幾個方面來介紹lambda:1、lambda簡介2、lambda與def不同之處3、lambda的使用方法
  • Java 8新特性:學習如何使用Lambda表達式(二)
    可變範圍通常,您希望從lambda表達式中的封閉範圍訪問變量。該 repeatText方法可以在lambda表達式的代碼運行之前返回,而那時參數 count和 text變量將消失,但它們仍然可用於lambda。秘密是什麼?要了解發生了什麼,我們需要提高對lambda表達式的理解。