目录
lambda表达式
我们之前如果要对一组数据排序,我们可以调用sort,并且传一个仿函数对象或者函数指针或者调用库中的类模板并实例化成对象,比如:
bool Less(int a, int b) { return a < b; } struct Greater { bool operator()(int a, int b) { return a > b; } }; int main() { vector<int>f = { 5,8,4,1,6,9,3,2,7,0 }; sort(f.begin(), f.end(), Greater());//自己实现的仿函数对象 for (auto e : f) cout << e << ' '; cout << endl; sort(f.begin(), f.end(), Less);//自己实现的函数指针 for (auto e : f) cout << e << ' '; cout << endl; sort(f.begin(), f.end(), greater<int>());//库中的降序 for (auto e : f) cout << e << ' '; cout << endl; sort(f.begin(), f.end(), less<int>());//库中的升序 for (auto e : f) cout << e << ' '; cout << endl; return 0; }
这是我们之前的写法,如果只是单纯的排序一种数据还是比较容易的,但是实际生活中,一个商品往往有很多的属性需要排序,比如一款手机的价格,销量,用户评价等,不仅需要升序还需要降序,所以就导致我们需要写很多的仿函数,这样无疑是非常不方便的,还有可能因为命名问题导致矛盾的产生,所以,为了避免这种情况,我们可以使用lambda表达式
lambda表达式的书写格式如下:
[捕捉列表](参数列表)mutable->返回值类型{函数体}
其中呢,捕捉列表可以不写,但是方括号必须存在;如果没有参数,参数列表可以跟圆括号一起省略;mutable一般不写;返回值类型可以不写,函数体肯定写。可能比较抽象,我们来举个简单的例子
auto add = [](int a, int b)->int {return a + b; }; cout << add(1, 2) << endl; auto swap1 = [](int& a, int& b)->void { int tmp = a; a = b; b = tmp; }; int x = 1, y = 2; swap1(x, y);
lambda所在的是一条语句,所以最后要加分号
那我们再写一个部分可以省略的lambda表达式
auto func1 = [] { cout << "hello world" << endl; }; func1();
有了lambda表达式,那么再处理之前多种数据比较的问题就简单了,比如:
struct fruit { const char* name;//水果名字 int price;//价格 int sales;//销量 }; int main() { vector<fruit>f = { {"苹果",5,10},{"香蕉",4,25},{"西瓜",10,6},{"葡萄",6,12}}; sort(f.begin(), f.end(), [](const fruit& f1, const fruit& f2) {return f1.price < f2.price; }); for (auto& e : f) cout << e.price << ' '; cout << endl; sort(f.begin(), f.end(), [](const fruit& f1, const fruit& f2) {return f1.price > f2.price; }); for (auto& e : f) cout << e.price << ' '; cout << endl; sort(f.begin(), f.end(), [](const fruit& f1, const fruit& f2) {return f1.sales < f2.sales; }); for (auto& e : f) cout << e.sales<< ' '; cout << endl; sort(f.begin(), f.end(), [](const fruit& f1, const fruit& f2) {return f1.sales > f2.sales; }); for (auto& e : f) cout << e.sales << ' '; cout << endl; return 0; }
lambda表达式就是一个局部的匿名函数对象,像上面这样写也确实比写一堆仿函数要清晰明了一些
讲完了lambda表达式的基本使用,我们就来详细的说一下它各个部分具体的作用
首先是捕捉列表,分为传值捕捉和传引用捕捉
比如说传值捕捉,它捕捉到的是当前对象的拷贝,默认这个拷贝是不能修改的,要想修改就要加mutable(可变的),只要加了这个单词,圆括号及里面的内容不能省略
下面是传引用捕捉,就是可以修改原来的变量的值
下面是传值捕捉所有,就是说,当前域中的所有对象都被捕捉进来了
下面是传引用捕捉所有,其实就是一个书写格式的事情
下面是传引用捕捉当前域所有对象,某些对象传值捕捉
其实lambda表达式的底层就是仿函数,我们用这个代码从汇编的角度看一下
class Rate { public: Rate(double rate) :_rate(rate){} double operator()(double money, int year) { return money * _rate * year; } private: double _rate; }; int main() { double rate = 1.35; Rate r1(rate); r1(1000,3); auto func1 = [=](double money, int year)->double {return money * rate * year; }; func1(1000, 3); return 0; }
r1 和func1进行运算的时候汇编分别是这样的:
我们可以看到lambda表达式底层就是调用的仿函数,并且这个仿函数的类型是前面的一大串,这个串这么长就是为了保证它是唯一的,所以lambda表达式间不能相互赋值,因为它们的类型不同
就是因为lambda表达式的类型复杂,所以我们才用auto接受,但是我们知道decltype可以知道类型,所以它们两个之间可以这么用:
class Date { public: bool operator>(const Date& d)const { if (_y != d._y) { return _y > d._y; } else { if (_m != d._m) { return _m > d._m; } else { if (_d != d._d) { return _d > d._d; } else { return false; } } } } private: int _y = 0; int _m = 0; int _d = 0; }; int main() { auto DateGreater = [](const Date& d1, const Date& d2) { return d1>d2; }; cout << typeid(DateGreater).name() << endl; //lambda对象禁掉默认构造,所以这里传一个对象用拷贝构造 priority_queue<Date, vector<Date>, decltype(DateGreater)>p1(DateGreater); return 0; }
function包装器
包装器实际上是一个类模板,可以将可调用对象(函数指针,仿函数,lambda表达式)进行再封装,就是说,将上面的一系列可调用对象在封装一层,这样它们的类型就统一了,这样调用就更好调用了
它也是用的可变模板参数,它写的形式比较特殊,我们后面会写
那我们来比较一下不用function类模板和用的区别:我们建一个函数模板,函数模板对于参数相同,返回值相同的函数指针,仿函数,lambda表达式会形成三个具体的函数,但是function因为只需要形成一个类,所以就只会形成一个函数
template<class F,class V> V useF(F f, V val) { static int count = 0; cout << "count: " << ++count << endl; cout << "&count:" << &count << endl; return f(val); } double f(double i) { return i / 2; } struct Functor { double operator()(double d) { return d / 3; } }; int main() { cout << useF(f, 11.1) << endl;//函数指针 cout << useF(Functor(), 11.1) << endl;//函数对象 cout << useF([](double d)->double {return d / 4; }, 11.1) << endl;//lambda表达式 return 0; }
结果是
证明确实生成了三个不同的函数
下面再用function,要包头文件<functional>
#include<iostream> #include<functional> using namespace std; template<class F,class V> V useF(F f, V val) { static int count = 0; cout << "count: " << ++count << endl; cout << "&count:" << &count << endl; return f(val); } double f(double i) { return i / 2; } struct Functor { double operator()(double d) { return d / 3; } }; int main() { function<double(double)>fc1 = f; cout << useF(fc1, 11.1) << endl; function<double(double)>fc2 = Functor(); cout << useF(fc2, 11.1) << endl; function<double(double)>fc3 = [](double d)->double {return d / 4; }; cout << useF(fc3, 11.1) << endl; return 0; }
这个写的形式就是<返回值类型(参数类型)>,这个的结果是
证明确实只生成了一个函数
它有什么作用呢?我们之前写过逆波兰表达式求值,我们之前都是僵硬的编码,有了function就简单了,我们可以把运算符号和运算方法写到一起放到一个hash表中
class Solution { public: int evalRPN(vector<string>& tokens) { map<string, function<int(int, int)>> hash = { {"+", [](int x, int y) { return x + y; }}, {"-", [](int x, int y) { return x - y; }}, {"*", [](int x, int y) { return x * y; }}, {"/", [](int x, int y) { return x / y; }} }; stack<int> st; for (auto& e : tokens) { if (hash.count(e)) { int right = st.top(); st.pop(); int left = st.top(); st.pop(); int tmp = hash[e](left, right); st.push(tmp); } else { st.push(stoi(e)); } } return st.top(); } };
下面是包装函数,分为普通函数,static静态函数,普通成员函数
class Plus { public: static int plusi(int a, int b) { return a + b; } double plusd(double a, double b) { return a + b; } }; int f(int a, int b) { return a + b; } int main() { Plus plus; function<int(int, int)>fc1 = f;//普通函数 cout << fc1(1, 1) << endl; function<int(int, int)>fc2 = Plus::plusi;//静态成员函数 cout << fc2(1, 1) << endl; function<double(Plus*, double, double)>fc3 = &Plus::plusd;//非静态成员函数 cout << fc3(&plus,1, 1) << endl; function<double(Plus, double, double)>fc4 = &Plus::plusd;//非静态成员函数 cout << fc4(Plus(), 1, 1) << endl; }
bind绑定
bing是一个函数模板,对于可调用对象来说,它可以调整参数的顺序和个数
int Sub(int a, int b) { return a - b; } int main() { int x = 10; int y = 20; cout << Sub(x, y) << endl; auto f1 = bind(Sub, placeholders::_2, placeholders::_1); cout << f1(x, y) << endl; return 0; }
这里的_2和_1分别对应实参中的第2个和第1个参数,就是说:bind中和f1中的参数是按顺序对应的,而_2在Sub里又是第二个参数,这样就调整了参数顺序
auto fc4 = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders:: _2); cout << fc4(2, 3) << endl;; auto fc5 = bind(&Plus::plusd, Plus(), placeholders::_1, 20); cout << fc5(2) << endl;
这样就可以绑定部分参数,改变函数参数的个数