哪家专门做特卖网站,淘客插件wordpress,800元网站建设,报价单#x1fa90;#x1fa90;#x1fa90;欢迎来到程序员餐厅#x1f4ab;#x1f4ab;#x1f4ab; 主厨#xff1a;邪王真眼
主厨的主页#xff1a;Chef‘s blog
所属专栏#xff1a;c大冒险
总有光环在陨落#xff0c;总有新星在闪烁 lambda表达式
C98中的一个…
欢迎来到程序员餐厅 主厨邪王真眼
主厨的主页Chef‘s blog
所属专栏c大冒险
总有光环在陨落总有新星在闪烁 lambda表达式
C98中的一个例子
在C98中如果想要进行排序可以使用std::sort方法如果待排序元素为自定义类型需要用户定义排序时的比较规则
struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};
struct ComparePriceLess
{bool operator()(const Goods gl, const Goods gr){return gl._price gr._price;}
};
struct ComparePriceGreater
{bool operator()(const Goods gl, const Goods gr){return gl._price gr._price;}
};
int main()
{vectorGoods v { { 苹果, 2.1, 5 }, { 香蕉, 3, 4 }, { 橙子, 2.2,
3 }, { 菠萝, 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess());
sort(v.begin(), v.end(), ComparePriceGreater());
}
随着C语法的发展人们开始觉得上面的写法太复杂了每次为了实现一个algorithm算法
都要重新去写一个类如果每次比较的逻辑不一样还要去实现多个类特别是相同类的命名
这些都给编程者带来了极大的不便。因此在C11语法中出现了Lambda表达式。 lambda表达式语法 lambda表达式书写格式 [capture-list] (parameters) mutable - return-type { statement } [capture-list] : 捕捉列表该列表总是出现在lambda函数的开始位置编译器根据[]来 判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda函数使用。(parameters)参数列表。与普通函数的参数列表一致如果不需要参数传递则可以连同()一起省略mutable默认情况下lambda函数总是一个const函数mutable可以取消其常量性。使用该修饰符时参数列表不可省略(即使参数为空)。-returntype返回值类型。用追踪返回类型形式声明函数的返回值类型没有返回值时此部分可省略。返回值类型明确情况下也可省略由编译器对返回类型进行推导。{statement}函数体。在该函数体内除了可以使用其参数外还可以使用所有捕获 到的变量。 格式省略情况 1.mutable可省略 int a 0, b 0;
auto func [a, b]()-int {return 0; }; 2.返回值类型可省略编译器自动推导 int a 0, b 0;
auto func [a, b]() {return 0; }; 3.没有传参时列表可省略 int a 0, b 0;
auto func [a, b] {return 0; }; 4.而捕捉列表和函数体可以为空。 // 省略了返回值类型无返回值类型auto fun1 [](int c){ };
因此C11中最简单的lambda函数为[]{}; 该lambda函数不能做任何事情。 lambda表达式返回值实际上是仿函数 该仿函数无法直接调用如果想要调用可借助auto将其赋值给一个变量lambda返回的仿函数对象其类名是随机的因此必须用auto来接受这个仿函数对象。 捕获列表说明
捕捉列表描述了父作用域中那些数据可以被lambda使用以及使用的方式传值还是传引用。 1.[var]表示值传递方式捕捉变量var int a 0, b 0;
auto func [a, b] (){return ab};注意此时ab具有常性如果要去掉他们的常性就要加上mutable此时lambda获得ab的方式是传值所以不会影响到父作用域的ab
int a 0, b 0;
auto func [a, b] ()mutable{return ab};
2.[this]表示值传递方式捕捉当前的this指针 3.[]表示值传递方式捕获所有父作用域中的变量(包括this) int a 0, b 0;
auto func []()mutable {return a b; }; 4.[var]表示引用传递捕捉变量var 此时lambda获得ab的方式是传引用在lambda里修改ab会影响父作用域的ab
int a 0, b 0;
auto func [a,b]{return a b; }; 5.[]表示引用传递捕捉所有父作用域中的变量(包括this) int a 0, b 0;
auto func []() {return a b; };
6.我们还可以把传值和传引用混合使用让部分参数传参部分参数传引用 [x, y]:以传值的形式捕获x以传引用的形式捕获y [, x]以传值的形式捕获父作用域所有变量以传引用的形式捕获x [, x]以传值的形式捕获x以传引用的形式捕获父作用域所有变量 注意 父作用域指包含lambda函数的语句块 c捕捉列表不允许变量重复传递否则就会导致编译错误。 比如[, a]已经以值传递方式捕捉了所有变量捕捉a重复在块作用域以外的lambda函数捕捉列表必须为空。在块作用域中的lambda函数仅能捕捉父作用域中局部变量捕捉任何非此作用域或者非局部变量都会导致编译报错。 lambda表达式之间不能相互赋值因为他们的实际类名不同每一个类名都是lamdba随机生成的
有lambda后我们在需要仿函数的地方就无需额外写一个仿函数的类而是直接写lambda表达式
int main()
{vectorGoods v { { 苹果, 2.1, 5 }, { 香蕉, 3, 4 }, { 橙子, 2.2,
3 } };sort(v.begin(), v.end(), [](const Goods g1, const Goods g2){return g1._price g2._price; });sort(v.begin(), v.end(), [](const Goods g1, const Goods g2){return g1._price g2._price; });sort(v.begin(), v.end(), [](const Goods g1, const Goods g2){return g1._evaluate g2._evaluate; });sort(v.begin(), v.end(), [](const Goods g1, const Goods g2){return g1._evaluate g2._evaluate; });
} 函数对象与lambda表达式
函数对象又称为仿函数即可以想函数一样使用的对象就是在类中重载了operator()运算符的
类对象。
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 0.49;Rate r1(rate);r1(10000, 2);
// lamberauto r2 [](double monty, int year)-double{return monty*rate*year;
};r2(10000, 2);return 0;
}
从使用方式上来看函数对象与lambda表达式完全一样。
函数对象将rate作为其成员变量在定义对象时给出初始值即可lambda表达式通过捕获列表可
以直接将该变量捕获到。 实际在底层编译器对于lambda表达式的处理方式完全就是按照函数对象的方式处理的即如
果定义了一个lambda表达式编译器会自动生成一个类在该类中重载了operator(),而这个类的名字是编译器随机产生的所以lambda表达式之间不能相互赋值即使看起来类型相同因为他们的类名是不同的 模板参数中的lambda表达式
我们要给set传一个仿函数 setint, Lessint; 请注意这里我们所传的不是对象而是类但是lambda返回值本身就是对象所以直接传lambda肯定是不可以的
这个时候decltype就登场了
auto func [](int a, int b) {return a b; };
setint, decltype(func); 包装器
function包装器 也叫作适配器。C中的function本质是一个类模板
为什么需要function 如果一个变量f可以按f()的形式调用函数那么称f是一个可调用对象 基于此不难想到可调用对象包括函数、仿函数、lambda
我们来看看他们在下面代码的表现
templateclass F, class T
T useF(F f, T x)
{static int count 0;cout count: count endl;cout count: count endl;return f(x);
}
double f(double i)
{return i / 2;
}
struct Functor
{double operator()(double d){return d / 3;}
};
int main()
{// 函数cout useF(f, 11.11) endl;// 函数对象cout useF(Functor(), 11.11) endl;// lamber表达式cout useF([](double d)-double { return d / 4; }, 11.11) endl;return 0;
}
通过上面的程序验证我们会发现useF函数模板实例化了三份。
然而这里的函数、仿函数、lambda的返回值和参数类型相同可以认为三者极其相似有没有办法让函数模板把他们识别为一种类型从而只需要实例化一份呢
包装器可以很好的解决上面的问题function包含在头文件functional中是一个类模板模板原型如下
template class T function;template class Ret, class... Args
class functionRet(Args...);其语法为function返回值(参数列表)只要是返回值和参数列表相同的可调用对象经过这一层封装都会变成相同的类型。
int f(int a, int b)
{return a b;
}
struct Functor
{
public:int operator() (int a, int b){return a b;}
};
templateclass F
void Function(F f)
{static int count 0;cout countendl;cout count endl;cout endl;
}
int main()
{// 函数名(函数指针)functionint(int, int) func1 f;Function(func1);// 函数对象functionint(int, int) func2 Functor();Function(func1);// lamber表达式functionint(int, int) func3 [](const int a, const int b){return a b; };Function(func1);
}
可以看出func1、func2、func3被认为是一种类型 function接收对象成员函数
class Plus
{
public:static int plusi(int a, int b){return a b;}double plusd(double a, double b){return a b;}
};
int main()
{// 类的成员函数functionint(int, int) func4 Plus::plusi;functiondouble(Plus*, double, double) func5 Plus::plusd;
functiondouble(Plus, double, double) func5 Plus::plusd;
}
注意事项
等号右边的符号别忘了写对于非静态函数必需加上非静态最好加上等号右边的函数要指定类域对于非静态函数左边的第一个参数是类名或类指针 适用场景
比如力扣波兰表达式
对于相邻两数我们要以case语句对-*/进行讨论
class Solution {
public:
int evalRPN(vectorstring tokens) {stackint st;for(auto str : tokens){if(str || str - || str * || str /){int right st.top();st.pop();int left st.top();st.pop();switch(str[0]){case :st.push(leftright);break;case -:st.push(left-right);break;case *:st.push(left*right);break;case /:st.push(left/right);break;}}//..........}return st.top();
}
};
使用包装器以后的玩法
class Solution {
public:
int evalRPN(vectorstring tokens) {stackint st;mapstring, functionint(int, int) opFuncMap {{ , [](int i, int j){return i j; } },{ -, [](int i, int j){return i - j; } },{ *, [](int i, int j){return i * j; } },{ /, [](int i, int j){return i / j; } }};for(auto str : tokens){if(opFuncMap.find(str) ! opFuncMap.end()){int right st.top();st.pop();int left st.top();st.pop();st.push(opFuncMap[str](left, right));}//........}return st.top();
}
}; 冷知识 function 类型相同的对象可以相互赋值
functionint(int) f1 [](int x) { return x * x; };
functionint(int) f2 f1; // f2 现在也是一个 lambda 表达式 function实现了对bool的重载
opearotr bool函数重载方式如下
class A
{
public:operator bool(){return 3_a;}int _a 2;
};int main()
{A a;if (a)cout 666 endl;else{cout 888 endl;}
} function 对象支持 bool 类型转换,可以用于判断 function 对象是否为空(未初始化)。
functionint(int) f;
if (!f) {cout f is empty endl;
} bind
bind音译即是绑定它是C标准库中的一个函数模板用于将函数与其参数进行绑定生成一个新的可调用对象。通过bind我们可以将函数的部分参数固定下来也可以交换参数位置从而得到一个新的函数对象。
// 原型如下
template class Fn, class... Args
/* unspecified */ bind (Fn fn, Args... args);
// with return type (2)
template class Ret, class Fn, class... Args
/* unspecified */ bind (Fn fn, Args... args);
调用bind的一般形式 auto newCallable bind(callable,arg_list); newCallable本身是一个可调用对象arg_list是一个逗号分隔的参数列表对应给定的callable的参数。当我们调用newCallable时newCallable会调用callable,并传给它arg_list中的参数。 交换参数位置
交换了第一个参数和第二个参数的位置
C11后新增一个命名空间域placeholders其内部会存储很多变量这些变量用于函数的传参
placeholders::_n表示原函数中的第n个参数
#include functional
void Mod(int a, int b)
{cout a % bendl;
}
int main()
{std::functionvoid(int, int) func1 std::bind(Mod, placeholders::_2,placeholders::_1);func1(2, 1);Mod(2, 1);return 0;
} 给参数绑定固定值
void Mod(int a, int b)
{cout a % bendl;
}
int main()
{std::functionvoid(int,int) func1 std::bind(Mod, 1, placeholders::_2);func1(2, 10);Mod(1, 10);return 0;
} 可以看出即使我们在func1中给第一个参数传参为2实际也依旧是1
三种写法
functionvoid(int,int) func1 std::bind(Mod, 1, placeholders::_1);
func1(1,10);
functionvoid(int) func2 std::bind(Mod, 1, placeholders::_1);
func2(10);
auto func3 std::bind(Mod, 1, placeholders::_1);
func3(1, 10);
func3(10); 我建议是auto它的适用范围最大 创作不易你的支持对我最大的鼓励
~ 点赞收藏关注 ~