单页网站作用是什么,天津建设网查询,廊坊大城网站建设,自己做竞猜网站挣钱吗 #x1f4dd;个人主页#xff1a;Sherry的成长之路 #x1f3e0;学习社区#xff1a;Sherry的成长之路#xff08;个人社区#xff09; #x1f4d6;专栏链接#xff1a;C学习 #x1f3af;长路漫漫浩浩#xff0c;万事皆有期待 上一篇博客#xff1a;【C】C11… 个人主页Sherry的成长之路 学习社区Sherry的成长之路个人社区 专栏链接C学习 长路漫漫浩浩万事皆有期待 上一篇博客【C】C11 ——lambda表达式 文章目录 包装器function包装器function包装器介绍function包装器统一类型function包装器简化代码function包装器的意义 bind包装器bind包装器介绍bind包装器绑定固定参数bind包装器调整传参顺序bind包装器的意义 总结 包装器
function包装器
function包装器介绍
function包装器
function是一种函数包装器也叫做适配器。它可以对可调用对象进行包装C中的function本质就是一个类模板。
function类模板的原型如下
template class T function; // undefined
template class Ret, class... Args
class functionRet(Args...);模板参数说明 Ret被包装的可调用对象的返回值类型。 Args…被包装的可调用对象的形参类型。 包装示例
function包装器可以对可调用对象进行包装包括函数指针函数名、仿函数函数对象、lambda表达式、类的成员函数。比如
int f(int a, int b)
{return a b;
}
struct Functor
{
public:int operator()(int a, int b){return a b;}
};
class Plus
{
public:static int plusi(int a, int b){return a b;}double plusd(double a, double b){return a b;}
};
int main()
{//1、包装函数指针函数名functionint(int, int) func1 f;cout func1(1, 2) endl;//2、包装仿函数函数对象functionint(int, int) func2 Functor();cout func2(1, 2) endl;//3、包装lambda表达式functionint(int, int) func3 [](int a, int b){return a b; };cout func3(1, 2) endl;//4、类的静态成员函数//functionint(int, int) func4 Plus::plusi;functionint(int, int) func4 Plus::plusi; //可省略cout func4(1, 2) endl;//5、类的非静态成员函数functiondouble(Plus, double, double) func5 Plus::plusd; //不可省略cout func5(Plus(), 1.1, 2.2) endl;return 0;
}注意事项 包装时指明返回值类型和各形参类型然后将可调用对象赋值给function包装器即可包装后function对象就可以像普通函数一样使用了。 取静态成员函数的地址可以不用取地址运算符“”但取非静态成员函数的地址必须使用取地址运算符“”。 包装非静态的成员函数时需要注意非静态成员函数的第一个参数是隐藏this指针因此在包装时需要指明第一个形参的类型为类的类型。
function包装器统一类型
对于以下函数模板useF
传入该函数模板的第一个参数可以是任意的可调用对象比如函数指针、仿函数、lambda表达式等。 useF中定义了静态变量count并在每次调用时将count的值和地址进行了打印可判断多次调用时调用的是否是同一个useF函数。
代码如下
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;//lambda表达式cout useF([](double d)-double{return d / 4; }, 11.11) endl;return 0;
}由于函数指针、仿函数、lambda表达式是不同的类型因此useF函数会被实例化出三份三次调用useF函数所打印count的地址也是不同的。
但实际这里根本没有必要实例化出三份useF函数因为三次调用useF函数时传入的可调用对象虽然是不同类型的但这三个可调用对象的返回值和形参类型都是相同的。 这时就可以用包装器分别对着三个可调用对象进行包装然后再用这三个包装后的可调用对象来调用useF函数这时就只会实例化出一份useF函数。 根本原因就是因为包装后这三个可调用对象都是相同的function类型因此最终只会实例化出一份useF函数该函数的第一个模板参数的类型就是function类型的。 代码如下
int main()
{//函数名functiondouble(double) func1 func;cout useF(func1, 11.11) endl;//函数对象functiondouble(double) func2 Functor();cout useF(func2, 11.11) endl;//lambda表达式functiondouble(double) func3 [](double d)-double{return d / 4; };cout useF(func3, 11.11) endl;return 0;
}这时三次调用useF函数所打印count的地址就是相同的并且count在三次调用后会被累加到3表示这一个useF函数被调用了三次。
function包装器简化代码
求解逆波兰表达式的步骤如下 定义一个栈依次遍历所给字符串。 如果遍历到的字符串是数字则直接入栈。 如果遍历到的字符串是加减乘除运算符则从栈定抛出两个数字进行对应的运算并将运算后得到的结果压入栈中。 所给字符串遍历完毕后栈顶的数字就是逆波兰表达式的计算结果。
代码如下
class Solution {
public:int evalRPN(vectorstring tokens) {stackint st;for (const auto str : tokens){int left, right;if (str || str - || str * || str /){right st.top();st.pop();left st.top();st.pop();switch (str[0]){case :st.push(left right);break;case -:st.push(left - right);break;case *:st.push(left * right);break;case /:st.push(left / right);break;default:break;}}else{st.push(stoi(str));}}return st.top();}
};在上述代码中我们通过switch语句来判断本次需要进行哪种运算如果运算类型增加了比如增加了求余、幂、对数等运算那么就需要在switch语句的后面中继续增加case语句。
这种情况可以用包装器来简化代码。
建立各个运算符与其对应需要执行的函数之间的映射关系当需要执行某一运算时就可以直接通过运算符找到对应的函数进行执行。 当运算类型增加时就只需要建立新增运算符与其对应函数之间的映射关系即可。
代码如下
class Solution {
public:int evalRPN(vectorstring tokens) {stackint st;unordered_mapstring, functionint(int, int) opMap {{ , [](int a, int b){return a b; } },{ -, [](int a, int b){return a - b; } },{ *, [](int a, int b){return a * b; } },{ /, [](int a, int b){return a / b; } }};for (const auto str : tokens){int left, right;if (str || str - || str * || str /){right st.top();st.pop();left st.top();st.pop();st.push(opMap[str](left, right));}else{st.push(stoi(str));}}return st.top();}
};需要注意的是这里建立的是运算符与function类型之间的映射关系因此无论是函数指针、仿函数还是lambda表达式都可以在包装后与对应的运算符进行绑定。
function包装器的意义
将可调用对象的类型进行统一便于我们对其进行统一化管理。 包装后明确了可调用对象的返回值和形参类型更加方便使用者使用。
bind包装器
bind包装器介绍 bind包装器 bind也是一种函数包装器也叫做适配器。它可以接受一个可调用对象生成一个新的可调用对象来“适应”原对象的参数列表C中的bind本质是一个函数模板。
bind函数模板的原型如下
template class Fn, class... Args
/* unspecified */ bind(Fn fn, Args... args);
template class Ret, class Fn, class... Args
/* unspecified */ bind(Fn fn, Args... args);模板参数说明
fn可调用对象。 args…要绑定的参数列表值或占位符。 调用bind的一般形式 调用bind的一般形式为auto newCallable bind(callable, arg_list);
解释说明
callable需要包装的可调用对象。 newCallable生成的新的可调用对象。 arg_list逗号分隔的参数列表对应给定的callable的参数。当调用newCallable时newCallable会调用callable并传给它arg_list中的参数。
arg_list中的参数可能包含形如_n的名字其中n是一个整数这些参数是“占位符”表示newCallable的参数它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置比如_1为newCallable的第一个参数_2为第二个参数以此类推。
此外除了用auto接收包装后的可调用对象也可以用function类型指明返回值和形参类型后接收包装后的可调用对象。
bind包装器绑定固定参数 无意义的绑定 下面这种绑定就是无意义的绑定
int Plus(int a, int b)
{return a b;
}
int main()
{//无意义的绑定functionint(int, int) func bind(Plus, placeholders::_1, placeholders::_2);cout func(1, 2) endl; //3return 0;
}绑定时第一个参数传入函数指针这个可调用对象但后续传入的要绑定的参数列表依次是placeholders::_1和placeholders::_2表示后续调用新生成的可调用对象时传入的第一个参数传给placeholders::_1传入的第二个参数传给placeholders::_2。此时绑定后生成的新的可调用对象的传参方式和原来没有绑定的可调用对象是一样的所以说这是一个无意义的绑定。 绑定固定参数 如果想把Plus函数的第二个参数固定绑定为10可以在绑定时将参数列表的placeholders::_2设置为10。比如
int Plus(int a, int b)
{return a b;
}
int main()
{//绑定固定参数functionint(int) func bind(Plus, placeholders::_1, 10);cout func(2) endl; //12return 0;
}此时调用绑定后新生成的可调用对象时就只需要传入一个参数它会将该值与10相加后的结果进行返回。
bind包装器调整传参顺序 调整传参顺序 对于下面Sub类中的sub成员函数sub成员函数的第一个参数是隐藏的this指针如果想要在调用sub成员函数时不用对象进行调用那么可以将sub成员函数的第一个参数固定绑定为一个Sub对象。比如
class Sub
{
public:int sub(int a, int b){return a - b;}
};
int main()
{//绑定固定参数functionint(int, int) func bind(Sub::sub, Sub(), placeholders::_1, placeholders::_2);cout func(1, 2) endl; //-1return 0;
}此时调用绑定后生成的可调用对象时就只需要传入用于相减的两个参数了因为在调用时会固定帮我们传入一个匿名对象给this指针。
如果想要将sub成员函数用于相减的两个参数的顺序交换那么直接在绑定时将placeholders::_1和placeholders::_2的位置交换一下就行了。比如
class Sub
{
public:int sub(int a, int b){return a - b;}
};
int main()
{//调整传参顺序functionint(int, int) func bind(Sub::sub, Sub(), placeholders::_2, placeholders::_1);cout func(1, 2) endl; //1return 0;
}根本原因就是因为后续调用新生成的可调用对象时传入的第一个参数会传给placeholders::_1传入的第二个参数会传给placeholders::_2因此可以在绑定时通过控制placeholders::_n的位置来控制第n个参数的传递位置。
bind包装器的意义
将一个函数的某些参数绑定为固定的值让我们在调用时可以不用传递某些参数。 可以对函数参数的顺序进行灵活调整。
总结
今天我们学习了C11中lambda表达式了解了一些有关的底层原理。接下来我们将继续进行C11的学习。希望我的文章和讲解能对大家的学习提供一些帮助。 当然本文仍有许多不足之处欢迎各位小伙伴们随时私信交流、批评指正我们下期见~