当前位置: 首页 > ops >正文

【C++详解】C++11(四) 包装器:function、bind、STL中⼀些变化

文章目录

  • 一、STL中⼀些变化
  • 二、包装器
    • function
    • bind


一、STL中⼀些变化

  • 下图圈起来的就是STL中的新容器,但是实际最有⽤的是unordered_map和unordered_set。这两个我们前⾯已经进⾏了⾮常详细的讲解,其他的仅需了解⼀下即可。
  • STL中容器的新接⼝也不少,最重要的就是右值引⽤和移动语义相关的push/insert/emplace系列接⼝和移动构造和移动赋值,还有initializer_list版本的构造等,这些前⾯都讲过了,还有⼀些⽆关痛痒的如cbegin/cend等需要时查查⽂档即可。
  • 容器的范围for遍历,这个在容器部分也讲过了。
  • 还有一点有关swap,我们知道在C++11之前有三种swap函数,一种是算法库的全局swap,一种是针对部分容器实现的全局特化swap,一种是容器的成员函数swap。其中算法库全局swap任何类型都可以交换,但是对于还未实现全局特化版本swap的容器来说调用全局swap就会发生三次深拷贝。所以C++11引入了具有右值引用移动语义特性的全局swap:

在这里插入图片描述

这里接受参数用的左值引用而没用万能引用的原因是交换对象通常可以长期存在并且可以修改,所以用左值引用就足够了。相比C++98直接进行一次拷贝构造和两次拷贝赋值,C++11会先对对象进行move,使其具有右值属性,调用构造和赋值时就会变成调用一次移动构造和两次移动赋值,这也符合交换时逻辑,减少深拷贝代价,大大提升效率。除此之外C++11的swap还支持了数组的交换。

二、包装器

我们之前已经介绍了三种可调用对象:函数指针、仿函数、lambda表达式,但其可调用对象不止这些,例如普通函数、函数指针、类的成员函数(包括静态成员函数)、 类的成员函数指针都属于可调用对象,可调用对象的特征就是能通过 () 执行。
而接下来我们要介绍的包装器是C++11 引入的一种模板类,它可以可统一包装上述所有可调用对象(只要签名匹配),方便存储和传递。例如当map的第二个模板参数是function:

map<string, function<int(int, int)>>

那么map的第二个类型可以传上述可调用对象的任意一种。

function

template <class T>
class function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
  • 以上是 function 的原型,他被定义头⽂件中,语法规定Ret匹配可调用对象的返回值类型,args…匹配可调用对象的参数列表类型。
  • std::function 是⼀个类模板,也是⼀个包装器。 std::function 的实例对象可以包装存 储其他的可以调⽤对象,包括函数指针、仿函数、 lambda 、 bind 表达式等,存储的可调⽤对象被称为 std::function的⽬标。若 std::function 不含⽬标,则称它为空。调⽤空std::function 的⽬标导致抛出std::bad_function_call 异常。
  • C++ 语法规定,普通函数(如示例f)的函数名在赋值时可以隐式转换为函数指针(&可省略),因为它不属于任何类,是全局作用域的符号,但获取成员函数(包括静态成员函数)的地址时,必须使用&加上指定类域。
  • 包装非成员函数时要注意还有⼀个隐含的this指针参数,所以我们还需要"传递"this指针,但是注意这里并不是真正把this指针传递给调用它的对象,因为this指针无法显示传递,而这里"传递"this指针是因为普通成员函数需要一个 this 指针来关联调用它的对象,所以绑定时传对象或者对象的指针过去都可以,这两种传递方式都能为 this 指针提供有效的 “关联对象”。
int f(int a, int b)
{return a + b;
}struct Functor
{
public:int operator() (int a, int b){return a + b;}
};class Plus
{
public:Plus(int n = 10):_n(n){}static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return (a + b) * _n;}private:int _n;
};int main()
{// 包装各种可调⽤对象function<int(int, int)> f1 = f; //包装函数指针function<int(int, int)> f2 = Functor(); //包装仿函数对象function<int(int, int)> f3 = [](int a, int b) {return a + b; }; //包装lambdacout << f1(1, 1) << endl;cout << f2(1, 1) << endl;cout << f3(1, 1) << endl;// 包装静态成员函数// 成员函数要指定类域并且前⾯加&才能获取地址function<int(int, int)> f4 = &Plus::plusi;cout << f4(1, 1) << endl;// 包装普通成员函数// 普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus pd;cout << f5(&pd, 1.1, 1.1) << endl;function<double(Plus&, double, double)> f6 = &Plus::plusd;cout << f6(pd, 1.1, 1.1) << endl;function<double(Plus&&, double, double)> f7 = &Plus::plusd;cout << f7(move(pd), 1.1, 1.1) << endl;cout << f7(Plus(), 1.1, 1.1) << endl;return 0;
}
  • 函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同, std::function 的优势就是统⼀类型,对他们都可以进⾏包装,这样在很多地⽅就⽅便声明可调⽤对象的类型,下⾯以后缀表达式题目样例展⽰了 std::function作为map的参数,实现字符串和可调⽤对象的映射表功能。
class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> 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(left + right);break;case '-':st.push(left - right);break;case '*':st.push(left * right);break;case '/':st.push(left / right);break;}}else{st.push(stoi(str));}}return st.top();}
};
// 使⽤map映射string和function的⽅式实现
// 这种⽅式的最⼤优势之⼀是⽅便扩展,假设还有其他运算,我们增加map中的映射即可
class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;// function作为map的映射可调⽤对象的类型map<string, function<int(int, int)>> opFuncMap = {{"+", [](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; }}};for (auto& str : tokens){if (opFuncMap.count(str)) // 操作符{int right = st.top();st.pop();int left = st.top();st.pop();int ret = opFuncMap[str](left, right);st.push(ret);}else{st.push(stoi(str));}}return st.top();}
};

上面的示例代码涉及两个细节:
第一个是map的count接口,在 C++ 的 std::map 中,count接口的核心功能是统计容器中指定键(key)的出现次数,但由于 std::map 的键是唯一的(每个键最多对应一个值),因此它的返回值只能是 0(键不存在)或 1(键存在)。
第二个是有关这行关键代码的理解:

int ret = opFuncMap[str](left, right);

这行代码首先运用的map的operator[ ],它的功能是通过key返回value,体现在这里就是通过查找str返回对应包装器包装的lambda表达式,然后lambda表达式通过left和right参数计算返回结果。

bind

  • 区别于类模板function,bind是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。bind 可以⽤来调整参数个数和参数顺序。bind 也在< functional >这个头⽂件中。
  • 调⽤bind的⼀般形式: auto newCallable = bind(callable,arg_list); 其中newCallable本⾝是⼀个可调⽤对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable的参数。当我们调⽤newCallable时,newCallable会调⽤callable,并传给它arg_list中的参数。
  • arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表⽰newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表⽰⽣成的可调⽤对象中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3…这些占位符放到placeholders的⼀个命名空间中。
using namespace std;
#include<functional>
#include <iostream>using placeholders::_1;
using placeholders::_2;
using placeholders::_3;int Sub(int a, int b)
{return (a - b) * 10;
}int SubX(int a, int b, int c)
{return (a - b - c) * 10;
}class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){ return a + b;}
};

示例一:调整参数顺序

// bind 本质返回的⼀个仿函数对象// 调整参数顺序(不常⽤)// _1代表第⼀个实参// _2代表第⼆个实参// ...auto sub1 = bind(Sub, _1, _2);cout << sub1(10, 5) << endl;auto sub2 = bind(Sub, _2, _1);cout << sub2(10, 5) << endl;

在这里插入图片描述

示例二:调整参数个数
(要绑死callable的哪个参数,在绑定newCallable时就不用参数,而是直接写死)

	// 调整参数个数 (常⽤)//绑死aauto sub3 = bind(Sub, 100, _1);cout << sub3(5) << endl;//绑死bauto sub4 = bind(Sub, _1, 100);cout << sub4(5) << endl;

在这里插入图片描述

	// 分别绑死第123个参数auto sub5 = bind(SubX, 100, _1, _2);cout << sub5(5, 1) << endl;auto sub6 = bind(SubX, _1, 100, _2);cout << sub6(5, 1) << endl;auto sub7 = bind(SubX, _1, _2, 100);cout << sub7(5, 1) << endl;

调整参数个数的实际运用1:绑死成员函数的this指针,不用自己手动传递

	// 成员函数对象进⾏绑死,就不需要每次都传递了function<double(Plus&&, double, double)> f6 = &Plus::plusd;Plus pd;cout << f6(move(pd), 1.1, 1.1) << endl;cout << f6(Plus(), 1.1, 1.1) << endl;// bind⼀般⽤于,绑死⼀些固定参数function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);cout << f7(1.1, 1.1) << endl;

调整参数个数的实际运用2:计算复利的lambda

	// 计算复利的lambdaauto func1 = [](double rate, double money, int year)->double {double ret = money;for (int i = 0; i < year; i++){ret += ret * rate;}return ret - money;};// 绑死⼀些参数,实现出⽀持不同年华利率,不同⾦额和不同年份计算出复利的结算利息function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);cout << func3_1_5(1000000) << endl;cout << func5_1_5(1000000) << endl;cout << func10_2_5(1000000) << endl;cout << func20_3_5(1000000) << endl;

以上就是小编分享的全部内容了,如果觉得不错还请留下免费的关注和收藏
如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~

在这里插入图片描述

http://www.xdnf.cn/news/19828.html

相关文章:

  • 【AI论文】UI-TARS-2技术报告:借助多轮强化学习推进图形用户界面(GUI)智能体发展
  • 20. 云计算-华为云-云服务
  • Linux Centos7搭建LDAP服务(解决设置密码生成密文添加到配置文件配置后输入密码验证报错)
  • 分享星空投影灯方案
  • 高效菜单管理页面:一键增删改查
  • Word 常用快捷键大全:提升文档处理效率的必备技巧​
  • FastGPT源码解析 Agent工作流编排后端详解
  • Ansible自动化运维:从入门到精通
  • 【面试题】词汇表大小如何选择?
  • React实现点击按钮复制操作【navigator.clipboard与document.execCommand】
  • Elasticsearch面试精讲 Day 6:Query DSL查询语法详解
  • 【JAVA】windows本地跑zookeeper,然后使用代码连接服务获取znode数据
  • 【leetcode】130. 被围绕的区域
  • NLP插曲番外 · 猫猫狐狐问答夜话
  • 分词器详解(一)
  • 信息融智学=信息哲学+信息科学+信息技术+信息系统工程+信息处理之智
  • 组长跟我说,她招人看重的是数据分析能力
  • 计算机视觉(七):膨胀操作
  • 机器学习 - Kaggle项目实践(8)Spooky Author Identification 作者识别
  • awk命令
  • GitHub 上那些值得收藏的英文书籍推荐(计算机 非计算机类)
  • 逻辑回归:从原理到实战的完整指南
  • 刻意练习理论
  • 群晖为家纺企业 500 名员工打造企业网盘,赋能家纺制造效率飞跃
  • Python数据分析与处理(二):将数据写回.mat文件的不同方法【超详细】
  • 第二章 Windows 核心概念通俗解析
  • Linux 的 swap 是什么
  • Vue3 警告:Runtime directive used on component with non-element root node 解决方案
  • 16k+ star! 只需要DDL就能一键生成数据库关系图!
  • 正运动控制卡学习-网络连接