【C++11】lambda表達式及包裝器

一.lambda表達式

1.可調用對象

可調用對象即可以像函數一樣被調用的對象,有以下三種:

  1. 函數(指針)
  2. 仿函數對象
  3. lambda表達式

?tips:調用函數時,既可以用函數名,也可以用函數地址,因為函數名和函數地址是一回事。

2.lambda表達式格式

[捕捉列表](參數列表)mutable->返回值類型?{函數體}?

  1. 捕捉列表不能省略,即使它為空
  2. 參數列表為空時可以省略,但是有mutable時不能省略
  3. mutable用法后面會講
  4. 返回值類型可以省略

例如:<algorithm>中的sort是一個函數模版,第三個參數需要一個可調用對象,我們就可以傳一個lambda表達式過去。如果要傳仿函數對象,需要定義一個類,相比之下,lambda表達式更加輕便。

struct Good
{Good(const string& name, double price, int id):_name(name),_price(price),_id(id){}string _name;double _price;int _id;
};
int main()
{vector<Good> v = { { "蘋果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2 ,3 }, { "菠蘿", 1.5, 4 } };auto priceLess = [](const Good& g1, const Good& g2)->bool{return g1._price < g2._price;};sort(v.begin(), v.end(), priceLess);
}

3.lambda表達式的本質

lambda表達式底層是一個仿函數對象,是一個重載了operator()的類的匿名對象。這個類的名稱是<lambda_uuid>(uuid是一個很長的字符串,通過某種算法得到,同一臺機器上基本不會重復),這個類對用戶也是匿名的,不能直接使用。

?4.詳解捕捉列表

捕捉列表用于捕捉父作用域的局部變量,供函數體中使用。父作用域指的是lambda表達式所在的語句塊。捕捉列表內的東西實質上是該匿名對象的成員。

(1)值捕捉

int main()
{int x = 0, y = 1;//捕捉lambda表達式函數體的父作用域,即main函數作用域內的變量x和變量yauto f = [x, y]()mutable->void{int tmp = x;x = y;y = tmp;};f();cout << "x = " << x << endl;cout << "y = " << y << endl;return 0;
}

lambda表達式是一個仿函數對象,它的operator()函數是const的,也即函數體內不能修改成員,使用mutalbe可以取消const。lambda表達式不建議取消const特性。

可以看出x和y的值并沒有交換,因為捕捉列表中的x和y是main函數中的x,y的拷貝,二者互不影響。

(2)引用捕捉

int main()
{int x = 0, y = 1;auto f = [&x, &y]()->void //不用加mutable,因為引用可以修改{int tmp = x;x = y;y = tmp;};f();cout << "x = " << x << endl;cout << "y = " << y << endl;return 0;
}

(3)全部傳值捕捉

int main()
{int x = 0, y = 1;auto f = [=]()->void{cout << x << endl;cout << y << endl;};f();return 0;
}

如果lambda表達式在成員函數中,那么this指針也會捕捉過來?


class AA
{
public:void func(){auto f1 = [=]()->void{cout << _a;//this->_acout << _b;//this->_b};//這樣寫反而不行,因為_a和_b不在func作用域內/*auto f1 = [_a, _b]()->void{cout << _a;cout << _b;};*/}
private:int _a;int _b;
};

(4)全部引用捕捉

int main()
{int x = 0, y = 1;auto f = [&]()->void{cout << x << endl;cout << y << endl;};f();return 0;
}

同理,如果lambda表達式在成員函數中,this指針也會捕捉過來。?

?(5)混合捕捉


int main()
{int x = 0, y = 1;auto f1 = [&, x]()->void //傳值捕捉x,其余全部引用捕捉,&或=必須在前{cout << x << endl;cout << y << endl;};auto f2 = [x, &y]()->void //傳值捕捉x,引用捕捉y{cout << x << endl;cout << y << endl;};//auto f3 = [=, &]()->void  //不允許重復捕捉,因為使用時會有歧義//{//	cout << x << endl;//	cout << y << endl;//};return 0;
}

前文說到,捕捉列表中的東西實質上是匿名對象的成員,證明如下:

由結果可知,當捕捉父作用域的全部局部變量時,編譯器并非憨憨地全部捕捉,而是看你在函數體內用到了哪些,沒用到的就不捕獲了。?

二.function包裝器

1.function包裝器用法

  1. function是一個類模版,在<functional>中。
  2. 不同的可調用對象雖然類型不同,但是卻可能有相同的調用形式,即返回類型,參數類型相同。
  3. function包裝器作用就是包裝可調用對象,把調用形式相同的可調用對象的類型統一起來,便于書寫它們的類型。
  4. function實際是一個類模版,實例化時的模版參數寫可調用對象的調用形式,即返回值類型(參數類型列表),如void(int, double),這一點和普通模版不一樣。
void swap_func(int& x, int& y)
{int tmp = x;x = y;y = x;cout << "函數指針" << endl;
}class Swap_func
{
public:void operator()(int& x, int& y){int tmp = x;x = y;y = x;cout << "仿函數對象" << endl;}
};
int main()
{int x = 1, y = 0;function<void(int&, int&)> f1 = swap_func;f1(x, y);auto lambda_swap_func = [](int& x, int& y)->void{int tmp = x;x = y;y = x;cout << "lambda表達式" << endl;};f1 = lambda_swap_func;f1(x, y);f1 = Swap_func();f1(x, y);return 0;
}

?

?將具有同種調用形式的可調用對象類型統一起來,下面這段代碼能體現其用處:

map<string, function<void(int&, int&)>> cmdOP = {{"函數指針", swap_func},{"lambda表達式", lambda_swap_func},{"仿函數對象", Swap_func()}};//列表初始化cmdOP["函數指針"](x, y);cmdOP["lambda表達式"](x, y);cmdOP["仿函數對象"](x, y);

如果沒有function包裝器,map的第二個模版參數就只能用函數指針了,并且只能使用函數這一種可調用對象,缺乏靈活性。并且函數指針用起來很麻煩,這也是為什么C++仿函數被廣泛應用的原因。

2.function包裝器包裝成員函數

(1)靜態成員函數

class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};
int main()
{//包裝靜態成員函數function<int(int, int)> f1 = Plus::plusi;int ret = f1(1, 2);return 0;
}

與普通全局函數唯一不同的一點,就是要指明類域。

(2)非靜態成員函數

function<double(double, double)> f2 = Plus::plusd;
//error C3867: “Plus::plusd”: 非標準語法;請使用 "&" 來創建指向成員的指針

非靜態成員函數比較特殊,函數前還需加上取地址符號,一般情況下不管函數取不取地址,都代表函數地址,這里特殊情況記住就行。

function<double(double, double)> f2 = &Plus::plusd;
//error C2440: “初始化”: 無法從“double (__thiscall Plus::* )(double,double)”
//轉換為“std::function<double (double,double)>”

還是有問題,因為成員函數都是隱式傳了this指針的,不然函數體內就無法訪問成員變量。

最終寫法:

int main()
{double x = 1.1;double y = 2.2;function<double(Plus*, double, double)> f2 = &Plus::plusd;double ret = f2(&p, x, y);return 0;
}

還支持這樣寫:

function<double(Plus, double, double)> f2 = &Plus::plusd;
f2(Plus(), x, y);//傳匿名對象

在底層上可以理解為前者是通過對象指針去調用plusd,后者通過對象調用plusd,二者是一樣的。而且后者可以傳匿名對象,更加方便。

但是,每次調用f2都好麻煩啊,需要傳遞對象或者對象指針。如果函數體內部根本不涉及成員變量的操作,也就是說第一個參數傳什么根本不重要。有沒有什么辦法,每次自動傳一個匿名對象,讓我自己少傳一個參數呢?有的,使用bind函數!!!

三.bind包裝器

  1. bind是一個函數模版,在<functional>中
  2. 它的作用是包裝可調用對象,返回一個新的可調用對象,以達到調整調用形式的目的,即參數個數及順序

一般形式:auto newCallable = bind(callable, arg_list)??

callalbe是原來的可調用對象,newCallable是返回的新的可調用對象,arg_list是以逗號分隔的參數列表

?

1.改變參數順序

int Sub(int x, int y)
{return x - y;
}
int main()
{//調整參數順序auto f1 = bind(Sub, placeholders::_2, placeholders::_1);cout << f1(10, 5) << endl;return 0;
}

其中_n(n=1, 2, 3……)是“占位符”,表示新的可調用對象的參數,它定義在命名空間placeholders中。實際上,bind返回的newCallable封裝了Callable,當我們調用newCallable時,newCallable會調用Callable,并傳遞給它arg_list中的參數。

2.改變參數個數

bind意為“綁定”,它真正的作用在于綁定某些參數,從而減少傳參的個數。

int main()
{//調整參數個數auto lambda = [](int a, int b, int c)->void{cout << a << endl;cout << b << endl;cout << c << endl;};function<void(int, int)> f = bind(lambda, placeholders::_1, 10, placeholders::_2);f(1, 100);
}

?

注意觀察,bind返回的是一個可調用對象,當然可以用function包裝。并且function的模版參數就是新的可調用對象的調用形式。?

同理,用function包裝非靜態成員函數時就可以使用bind來減少參數傳遞?

class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};int main()
{function<double(double, double)> f = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);double ret = f(1.1, 2.2);cout << ret << endl;
}

3.bind返回的可調用對象本質

可見,bind返回的可調用對象也是一個類的實例對象,這個類肯定封裝了operator()。其實無論是lambda表達式,還是bind返回的對可調用象,都是用的仿函數技術,這不過外面做了一層精美的包裝,使我們使用起來更加方便。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/214486.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/214486.shtml
英文地址,請注明出處:http://en.pswp.cn/news/214486.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python從入門到精通五:Python數據容器

數據容器入門 為什么學習數據容器 思考一個問題&#xff1a;如果我想要在程序中&#xff0c;記錄5名學生的信息&#xff0c;如姓名。 如何做呢&#xff1f; 學習數據容器&#xff0c;就是為了批量存儲或批量使用多份數據 Python中的數據容器&#xff1a; 一種可以容納多份…

Kalman濾波、擴展Kalman濾波、無跡Kalman濾波和異步濾波的原理及其Matlab代碼

目錄 引言Kalman濾波代碼及其結果展示 擴展Kalman濾波代碼及其結果展示 無跡Kalman濾波無跡變換無跡Kalman濾波代碼及其結果展示 異步無跡Kalman濾波原理代碼及其結果展示 引言 本文給出了Kalman Filter&#xff08;卡爾曼濾波&#xff09;、Extended Kalman Filter&#xff0…

leetcode 98. 驗證二叉搜索樹

leetcode 98. 驗證二叉搜索樹 題目 給你一個二叉樹的根節點 root &#xff0c;判斷其是否是一個有效的二叉搜索樹。 有效 二叉搜索樹定義如下&#xff1a; 節點的左子樹只包含 小于 當前節點的數。 節點的右子樹只包含 大于 當前節點的數。 所有左子樹和右子樹自身必須也是…

vue3 引入 markdown編輯器

參考文檔 安裝依賴 pnpm install mavon-editor // "mavon-editor": "3.0.1",markdown 編輯器 <mavon-editor></mavon-editor>新增文本 <mavon-editor ref"editorRef" v-model"articleModel.text" codeStyle"…

Adams與Abaqus沖突問題

隨著工程仿真軟件的廣泛應用&#xff0c;Adams和Abaqus已成為眾多工程師的首選工具。然而&#xff0c;在使用過程中&#xff0c;一些用戶可能會遇到這兩個軟件之間的沖突問題&#xff0c;導致無法正常進行仿真分析。為了幫助大家解決這一難題&#xff0c;我們推出了一篇關于Ada…

Softmax回歸

一、Softmax回歸關鍵思想 1、回歸問題和分類問題的區別 Softmax回歸雖然叫“回歸”&#xff0c;但是它本質是一個分類問題。回歸是估計一個連續值&#xff0c;而分類是預測一個離散類別。 2、Softmax回歸模型 Softmax回歸跟線性回歸一樣將輸入特征與權重做線性疊加。與線性回歸…

Linux安裝Nginx并部署Vue項目

今天部署了一個Vue項目到阿里云的云服務器上&#xff0c;現記錄該過程。 1. 修改Vue項目配置 我們去項目中發送axios請求的文件里更改一下后端的接口路由&#xff1a; 2. 執行命令打包 npm run build ### 或者 yarn build 打包成功之后&#xff0c;我們會看到一個dist包&a…

[MySQL]SQL優化之索引的使用規則

&#x1f308;鍵盤敲爛&#xff0c;年薪30萬&#x1f308; 目錄 一、索引失效 &#x1f4d5;最左前綴法則 &#x1f4d5;范圍查詢> &#x1f4d5;索引列運算&#xff0c;索引失效 &#x1f4d5;前模糊匹配 &#x1f4d5;or連接的條件 &#x1f4d5;字符串類型不加 …

110. 平衡二叉樹(Java)

給定一個二叉樹&#xff0c;判斷它是否是高度平衡的二叉樹。 本題中&#xff0c;一棵高度平衡二叉樹定義為&#xff1a; 一個二叉樹每個節點 的左右兩個子樹的高度差的絕對值不超過 1 。 示例 1&#xff1a; 輸入&#xff1a;root [3,9,20,null,null,15,7] 輸出&#xff1a;t…

如何通過SPI控制Peregrine的數控衰減器

概要 Peregrine的數控衰減器PE4312是6位射頻數字步進衰減器(DSA,Digital Step Attenuator)工作頻率覆蓋1MHz~4GHz,插入損耗2dB左右,衰減步進0.5dB,最大衰減量為31.5dB,高達59dBm的IIP3提供了良好的動態性能,切換時間0.5微秒,供電電源2.3V~5.5V,邏輯控制兼容1.8V,20…

?如何使用https://www.krea.ai/來實現文生圖,圖生圖,

網址&#xff1a;https://www.krea.ai/apps/image/realtime Krea.ai 是一個強大的人工智能藝術生成器&#xff0c;可用于創建各種創意內容。它可以用來生成文本描述的圖像、將圖像轉換為其他圖像&#xff0c;甚至寫博客文章。 文本描述生成圖像 要使用 Krea.ai 生成文本描述…

設計模式——建造者模式(Java示例)

引言 生成器是一種創建型設計模式&#xff0c; 使你能夠分步驟創建復雜對象。 與其他創建型模式不同&#xff0c; 生成器不要求產品擁有通用接口。 這使得用相同的創建過程生成不同的產品成為可能。 復雜度&#xff1a; 中等 流行度&#xff1a; 流行 使用示例&#xff1a…

【conda】利用Conda創建虛擬環境,Pytorch各版本安裝教程(Ubuntu)

TOC conda 系列&#xff1a; 1. conda指令教程 2. 利用Conda創建虛擬環境&#xff0c;安裝Pytorch各版本教程(Ubuntu) 1. 利用Conda創建虛擬環境 nolonolo:~/sun/SplaTAM$ conda create -n splatam python3.10查看結果&#xff1a; (splatam) nolonolo:~/sun/SplaTAM$ cond…

Java 中的 Deque 接口及其用途

文章目錄 Deque 介紹Deque 使用雙端隊列普通隊列棧 總結 在 Java 中&#xff0c;Deque 接口是一個雙端隊列&#xff08;double-ended queue&#xff09;的數據結構&#xff0c;它支持在兩端插入和移除元素。Deque 是 “Double Ended Queue” 的縮寫&#xff0c;而且它可以同時充…

Linux系統編程(一):基本概念

參考引用 Unix和Linux操作系統有什么區別&#xff1f;一文帶你徹底搞懂posix Linux系統編程&#xff08;文章鏈接匯總&#xff09; 1. Unix 和 Linux 1.1 Unix Unix 操作系統誕生于 1969 年&#xff0c;貝爾實驗室發布了一個用 C 語言編寫的名為「Unix」的操作系統&#xff0…

【基于LSTM的電商評論情感分析:Flask與Sklearn的完美結合】

基于LSTM的電商評論情感分析&#xff1a;Flask與Sklearn的完美結合 引言數據集與爬取數據處理與可視化情感分析模型構建Flask應用搭建詞云展示創新點結論 引言 在當今數字化時代&#xff0c;電商平臺上涌現出大量的用戶評論數據。了解和分析這些評論對于企業改進產品、服務以及…

?expect命令運用于bash?

目錄 ?expect命令運用于bash? expect使用原理 expet使用場景 常用的expect命令選項 Expect腳本的結尾 常用的expect命令選參數 Expect執行方式 單一分支語法 多分支模式語法第一種 多分支模式語法第二種 在shell 中嵌套expect Shell Here Document&#xff08;內…

基于Java實驗室管理系統

基于Java實驗室管理系統 功能需求 1、實驗室設備管理&#xff1a;系統需要提供實驗室設備管理功能&#xff0c;包括設備的查詢、預訂、使用記錄、維護和報廢等。 2、實驗項目管理&#xff1a;系統需要提供實驗項目管理功能&#xff0c;包括項目的創建、審批、執行和驗收等&a…

以太坊:前世今生與未來

一、引言 以太坊&#xff0c;這個在區塊鏈領域大放異彩的名字&#xff0c;似乎已經成為了去中心化應用&#xff08;DApps&#xff09;的代名詞。從初期的萌芽到如今的繁榮發展&#xff0c;以太坊經歷了一段曲折而精彩的旅程。讓我們一起回顧一下以太坊的前世今生&#xff0c;以…

樹實驗代碼

哈夫曼樹 #include <stdio.h> #include <stdlib.h> #define MAXLEN 100typedef struct {int weight;int lchild, rchild, parent; } HTNode;typedef HTNode HT[MAXLEN]; int n;void CreatHFMT(HT T); void InitHFMT(HT T); void InputWeight(HT T); void SelectMi…