前言
在Cpp11以前,為了把函數當作對象調用,可以使用C中的函數指針類型,也可以使用Cpp98的仿函數。
但二者都不是很好用,函數指針 return_type (*name)(parameters)的長相就令人望而卻步,仿函數將一個函數重載為一個類的operator()的方式又沉重麻煩。
C++11中做出了(抄Python的)更靈活、輕便的lambda表達式。
lambda表達式
lambda表達式 是一個 匿名函數對象,本質上是一個對象。
語法格式
[capture_list](parameters)->return_type
{};
由捕捉列表、參數列表、返回值、函數體四大部分構成。
捕捉列表用于捕捉此前出現過的變量,后三大部分就是函數正常的定義理解。(如果函數無參且無返回值,可以省略這兩大部分,但是捕捉列表和函數體不可省略)
捕捉列表?
由于lambda本質是一個對象,在寫出lambda表達式時,就在創建一個對象。
捕捉列表中捕捉的值,就是構造函數的參數,用于初始化成員變量,以供函數體中直接使用。
捕捉的規則如下:
【1】捕捉某變量的值
int a = 1, b = 2;
auto afun = [a](){cout << a << endl;
}
auto abfun = [a, b](int x, int y){cout << a + b << endl;
}
此時,捕捉a、b值,相當于拷貝a、b的值,lambda中的a、b和外面的變量a、b是兩個東西。
且lambda內部不能修改捕捉的變量值,會報錯。(硬要修改就要了解mutable關鍵字)
【2】捕捉某變量的引用
要捕捉變量引用,就在捕捉列表中,變量前加上一個&。
int a = 0;
auto af = [&a](){a++;cout<< a << endl;
};
此時的變量a就是內外統一的了,允許修改了。?
【3】捕捉所有變量的值
int a = 0, b = 1, c = 2, d = 3;auto all_fun = [=]{ //捕捉所有要用到的變量值cout << a+b+c+d << endl;
};auto all_fun = [=, &a]{ //在=后指定需要引用的變量a++;cout << a+b+c+d << endl;
};
前者用 = 表示?捕捉所有要用到的變量值;后者在?= 后指定需要引用的變量。
【4】捕捉所有變量的引用
int a = 0, b = 1, c = 2, d = 3;auto r_fun_1 = [&]{a++;b++;c++;
};
auto r_fun_2 = [&, a]{b++;c++;d++;
};
用&表示捕獲所有被使用的變量的引用;后者指定捕獲某變量的值。
補充
【1】全局變量不需要捕捉,也不能。
【2】返回值可以不寫,函數體內部返回值時,編譯器會自己推。但建議寫,為了代碼的可讀性。
【3】使用auto可以推導lambda的類型,這個類型由編譯器決定,MSVC用uuid來保證不同的lambda有不同的類型。這樣匿名函數對象就可以被引用了,延長了生命周期,可以直接作為一個函數使用。(模板也能推)
應用場景
在使用算法庫中的sort排序時,有時想要按照特殊的要求進行排序,就要傳仿函數對象。
現在,我們可以直接傳lambda匿名函數對象即可,輕便易讀。可以替換仿函數的大部分使用。
包裝器
function和bind都在<functional>頭文件中。
function類模板
template <class T> function; // undefinedtemplate <class Ret, class... Args> class function<Ret(Args...)>;
?function包裝器一般用于包裝返回值、參數列表的函數對象。包裝前進行特化即可。
用函數的返回值和參數類型進行特化:Ret是前者類型,可變參數包Args是后者。
int pf(int a, int b)
{return a + b;
}struct Functor
{int operator()(int a, int b){return a - b;}
};int main()
{auto lf = [](int a, int b) { return a * b; };function<int(int, int)> f1 = &pf; //函數名也行,也是函數指針function<int(int, int)> f2 = lf;function<int(int, int)> f3 = Functor();vector<function<int(int, int)>> funcs = { f1, f2, f3 };//非靜態成員函數 function<int(Functor*, int, int)> f4 = &Functor::operator();function<int(Functor&&, int, int)> f5 = &Functor::operator();Functor f1_obj;f4(&f1_obj, 5, 6);f5(Functor(), 5, 6);return 0;
}
通過上例可見,用function包裝了函數指針,lambda函數對象,仿函數對象。?
?這樣統一了他們的類型,最后竟能放在一個容器里。
特殊的地方在于下面的非靜態成員函數的包裝。由于成員函數有一個隱式的參數--this指針,所以其特化類型與上面不同,但既可以傳引用,也可以傳指針。
在調用時,也要手動傳入對象的指針或者對象。
這個就需要回顧類和對象重載運算符 .* 和 ->* 的知識點。但對比一下,可以看出,函數指針真的不方便,不如包裝器好用。
typedef int(Functor::*PF)(int, int);PF pf = &Functor::operator(); //成員函數指針必須加&才能取到//int(Functor::*)(int, int) p = &Functor::operator();//cpp不允許這樣寫和用函數指針類型Functor f;(f.*pf)(5, 6);Functor* p = &f;(p->*pf)(5, 6);
bind函數模板
在了解了function的包裝類型后,bind的作用就不太一樣。
bind主要用于函數的參數個數或位置調整。需要結合placeholders命名空間中的_1、_2、...、_n的占位符使用。
template <class Fn, class... Args>/* unspecified */ bind (Fn&& fn, Args&&... args);template <class Ret, class Fn, class... Args>/* unspecified */ bind (Fn&& fn, Args&&... args);
?使用:把要調整的函數的包裝后的對象傳給bind,使用占位符進行調整。
調整參數位置
using namespace placeholders;
int main()
{auto fun = [](int a, int b) { return a + b*10; };auto newFun = bind(fun, _2, _1);cout << fun(3, 4) << endl; //43cout << newFun(3, 4) << endl; //34return 0;
}
bind中的占位符用來確定實參的位置,根據實參所在位置從左往右確定_1、_2等等。然后bind再將對應占位符的值傳給原本的函數。以實現參數位置的調整。
調整參數個數
通過確定某個位置的值,以達到少傳參數、調整參數個數的目的。
auto newFun_4 = bind(fun, _1, 4);cout << newFun_2(3) << endl; // 3 + 4 * 10;
強調:占位符指的是實參的位置。。。不要搞混了。